Files
node/test/fake_chain.hpp
zeekayandHanzo Dev 375c83bf8c A height is decided on Go's rule, and an imported tip is anchored
The proposer was height mod n with no fallback, so one seat down halted the
chain at its turn, and a node that read an export could never build on it.

- windower: Go's proposervm windower (lux-rs/node src/proposer.rs), held to
  windower_test.go's vectors; a proposal is ML-DSA-signed by its slot's
  proposer and checked against it.
- engine: lux-rs/node's step rule, clause for clause — one proposer a window
  from a clock counted off the tip's time, one signature a height through a
  durable journal, the lowest live block that can still reach alpha after a
  settle window, no building beside a live block or f+1 votes, sibling caps,
  one settle deadline a height, the retry, and catch-up on certificates.
- the anchor: a gated validator signs one statement at its imported tip (T, H)
  under its own tag, through the same journal; alpha of them are the
  certificate that lifts the gate, and T+1 is built on it.
- the mesh binds an address, resolves names, accepts on its own thread and is
  swept again, so a seat that restarts is met again.
- tests: windower, statement, engine and live (five seats anchor Zoo
  mainnet's tip and build on it; one down; one that imported another tip; one
  that restarts and catches up).

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-24 16:13:14 -07:00

170 lines
6.5 KiB
C++

// Copyright (C) 2026, Lux Industries, Inc. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause-Eco
//
// fake_chain.hpp — a chain whose blocks are bytes and nothing else, for tests of
// the engine that should prove the engine and not the EVM a second time.
//
// A block is "FAKE" ‖ parent(32) ‖ height(8) ‖ time(8) ‖ nonce(8), named by the
// SHA-256 of those bytes. It verifies when it builds on the accepted tip. The
// state lives in a Store the test holds, so a validator that stops and starts
// again comes back to the chain it left — as a plugin's data directory does —
// and a chain can begin as an import: a tip at a height, with no certificate.
#pragma once
#include "lux/node/vm.hpp"
#include <sha256/cpp/sha256.hpp>
#include <atomic>
#include <chrono>
#include <cstring>
#include <map>
#include <memory>
#include <mutex>
namespace lux::node::test {
struct Store {
std::mutex mu;
Id tip{};
std::uint64_t height = 0;
std::uint64_t stamp = 0;
std::map<Id, std::vector<std::uint8_t>> accepted; // id → bytes, the accepted chain
std::map<std::uint64_t, Id> at; // height → id, the accepted chain
std::uint64_t nonce = 0;
std::atomic<bool> pending{true}; // the pool has something to build
std::atomic<int> builds{0}, parses{0}, verifies{0}, rejects{0};
// An imported history: a tip nothing here decided.
static std::shared_ptr<Store> imported(const Id& tip, std::uint64_t height, std::uint64_t stamp) {
auto s = std::make_shared<Store>();
s->tip = tip;
s->height = height;
s->stamp = stamp;
s->at[height] = tip;
s->accepted[tip] = {'I', 'M', 'P', 'O', 'R', 'T', 'E', 'D'};
return s;
}
};
inline Id fake_id(const std::vector<std::uint8_t>& bytes) {
Id id{};
cevm::crypto::sha256(reinterpret_cast<std::byte*>(id.data()),
reinterpret_cast<const std::byte*>(bytes.data()), bytes.size());
return id;
}
class FakeBlock final : public Block {
public:
static constexpr std::size_t kLen = 4 + 32 + 8 + 8 + 8;
FakeBlock(std::shared_ptr<Store> s, std::vector<std::uint8_t> bytes, Id id, std::uint64_t stamp = 0)
: s_(std::move(s)), bytes_(std::move(bytes)), id_(id), stamp_(stamp) {
if (bytes_.size() == kLen) {
std::memcpy(parent_.data(), bytes_.data() + 4, 32);
height_ = be(bytes_.data() + 36);
stamp_ = be(bytes_.data() + 44);
}
}
Id id() const override { return id_; }
Id parent() const override { return parent_; }
std::uint64_t height() const override { return height_; }
std::uint64_t timestamp() const override { return stamp_; }
std::span<const std::uint8_t> bytes() const override { return bytes_; }
Id root() const override { return id_; }
bool verify() override {
++s_->verifies;
const std::lock_guard<std::mutex> lock(s_->mu);
return parent_ == s_->tip && height_ == s_->height + 1;
}
void accept() override {
const std::lock_guard<std::mutex> lock(s_->mu);
if (s_->tip == id_) return;
if (parent_ != s_->tip || height_ != s_->height + 1) throw std::runtime_error("not on the tip");
s_->tip = id_;
s_->height = height_;
s_->stamp = stamp_;
s_->accepted[id_] = bytes_;
s_->at[height_] = id_;
}
void reject() override { ++s_->rejects; }
static std::uint64_t be(const std::uint8_t* p) {
std::uint64_t v = 0;
for (int i = 0; i < 8; ++i) v = (v << 8) | p[i];
return v;
}
private:
std::shared_ptr<Store> s_;
std::vector<std::uint8_t> bytes_;
Id id_{}, parent_{};
std::uint64_t height_ = 0, stamp_ = 0;
};
class FakeChain final : public VM {
public:
explicit FakeChain(std::shared_ptr<Store> s) : s_(std::move(s)) {}
Id chain_id() const override { return Id{}; }
std::string alias() const override { return "test"; }
std::shared_ptr<Block> build() override {
if (!s_->pending.load()) return nullptr;
++s_->builds;
std::vector<std::uint8_t> b{'F', 'A', 'K', 'E'};
std::uint64_t height = 0, stamp = 0, nonce = 0;
{
const std::lock_guard<std::mutex> lock(s_->mu);
b.insert(b.end(), s_->tip.begin(), s_->tip.end());
height = s_->height + 1;
const auto now =
std::uint64_t(std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()).count());
stamp = std::max(now, s_->stamp);
nonce = ++s_->nonce;
}
put(b, height);
put(b, stamp);
put(b, nonce);
return std::make_shared<FakeBlock>(s_, b, fake_id(b));
}
std::shared_ptr<Block> parse(std::span<const std::uint8_t> bytes) override {
++s_->parses;
if (bytes.size() != FakeBlock::kLen || std::memcmp(bytes.data(), "FAKE", 4) != 0) return nullptr;
std::vector<std::uint8_t> b(bytes.begin(), bytes.end());
return std::make_shared<FakeBlock>(s_, b, fake_id(b));
}
std::shared_ptr<Block> get(const Id& id) const override {
const std::lock_guard<std::mutex> lock(s_->mu);
const auto it = s_->accepted.find(id);
if (it == s_->accepted.end()) return nullptr;
// The imported tip is not one of this chain's blocks: its time is the
// one the store was given.
return std::make_shared<FakeBlock>(s_, it->second, id, s_->stamp);
}
void prefer(const Id&) override {}
Id last_accepted() const override {
const std::lock_guard<std::mutex> lock(s_->mu);
return s_->tip;
}
std::uint64_t last_accepted_height() const override {
const std::lock_guard<std::mutex> lock(s_->mu);
return s_->height;
}
private:
static void put(std::vector<std::uint8_t>& b, std::uint64_t v) {
for (int s = 56; s >= 0; s -= 8) b.push_back(std::uint8_t(v >> s));
}
std::shared_ptr<Store> s_;
};
} // namespace lux::node::test