Files
zeekay aec9dfebe8 The names, the codec and the store belong to the node, not to one chain
chains/cpp/core is what every C++ chain here shares and shares ONE of: the
fixed-width names and the hashes that derive them, ZAP, and the durable
byte-keyed map the state rests on.

The store is the X-Chain's, moved unchanged — it is the only one in the
matrix that fsyncs, replays a torn tail and rewrites its log. The names are
the two chains' vocabularies reconciled: Id IS lux::consensus::Id, so a
block id crosses the seam with no conversion; NodeId stays a type of its
own because it shares a width with an address and the compiler should
refuse that swap.

The X-Chain now declares no id, no codec and no store of its own. Its
suite passes unchanged, which is the claim that matters: the wire golden
vectors still hold, so the codec that moved is the codec that was there.

And the store is proven the only way that counts. core_durability starts a
second process, has it commit, and has the kernel SIGKILL it — no
destructor, no flush. A third process reopens the file and must find every
committed row byte for byte, nothing written after the last commit, and
the same root folded over the set. A store that only survives a clean
shutdown is a cache with good manners.
2026-09-05 15:04:34 -07:00

120 lines
4.8 KiB
C++

// Copyright (C) 2026, Lux Industries Inc. All rights reserved.
// SPDX-License-Identifier: BSD-3-Clause-Eco
//
// address_test.cpp — bech32, against strings the GO address module produced.
//
// The vectors below were printed by luxfi/address (FormatBech32 over the same
// twenty-byte addresses fixtures.hpp builds, then ParseBech32 back). An address
// that reads differently here than it does in Go is an address that sends value
// somewhere else, so the strings are asserted verbatim rather than round-tripped
// against themselves.
#include "lux/core/check.hpp"
#include "fixtures.hpp"
#include "lux/xvm/address.hpp"
using namespace lux::xvm;
using namespace lux::core::test;
using namespace lux::xvm::test;
namespace {
struct Vector {
std::uint8_t seed;
const char* text;
};
// GENERATED by luxfi/address: FormatBech32("lux", addr(seed)).
constexpr Vector kVectors[] = {
{1, "lux1qypqxpq9qcrsszg2pvxq6rs0zqg3yyc5seedvj"},
{40, "lux19q5j52ev95hz7vp3xgengdfkxuurjw3m9f2n8m"},
{7, "lux1quyqjzstpsxsurcszyfpx9q4zct3sxg68fn9m0"},
{0, "lux1qqqsyqcyq5rqwzqfpg9scrgwpugpzysnvdyfmh"},
{255, "lux1luqqzqsrqszsvpcgpy9qkrqdpc83qygjfml03l"},
};
constexpr const char* kLocalHrp = "local1qypqxpq9qcrsszg2pvxq6rs0zqg3yyc5nnu3uf";
void formats_the_go_string() {
for (const auto& v : kVectors) {
auto s = address::format("lux", view(addr(v.seed)));
check(s.has_value(), std::string("format addr(") + std::to_string(v.seed) + ")");
if (s) check_eq(*s, v.text, std::string("addr(") + std::to_string(v.seed) + ") is Go's");
}
auto s = address::format("local", view(addr(1)));
check(s.has_value(), "format under another hrp");
if (s) check_eq(*s, kLocalHrp, "the hrp is part of the checksum, not a label");
}
void parses_the_go_string() {
for (const auto& v : kVectors) {
auto got = address::parse(v.text);
check(got.has_value(), std::string("parse addr(") + std::to_string(v.seed) + ")");
if (!got) continue;
check_eq(got->first, "lux", "the hrp comes back");
check_eq(hex_of(got->second), hex_of(addr(v.seed)),
std::string("addr(") + std::to_string(v.seed) + ") payload round-trips");
}
}
void parse_short_is_the_whole_journey() {
auto a = address::parse_short(kVectors[0].text);
check(a.has_value(), "parse_short reads an owner");
if (a) check(*a == addr(1), "parse_short gives the twenty bytes");
}
void a_typo_is_refused() {
// One character changed. This is the whole reason the checksum is there.
std::string bad = kVectors[0].text;
bad[bad.size() - 1] = (bad[bad.size() - 1] == 'j') ? 'k' : 'j';
auto got = address::parse(bad);
check(!got.has_value(), "a single mistyped character is refused");
if (!got) check_eq(got.error(), address::kErrBadChecksum, "and refused as a bad checksum");
// The human-readable part is inside the checksum, so an address written for
// one network cannot be read as an address on another.
std::string wrong_hrp = "fuji" + std::string(kVectors[0].text).substr(3);
check(!address::parse(wrong_hrp).has_value(), "the hrp cannot be swapped out");
}
void the_refusals() {
check(!address::parse("").has_value(), "an empty string is not an address");
check_eq(address::parse("lux").error(), address::kErrNoSeparator, "too short to hold one");
check_eq(address::parse("1qypqxpq9qcrsszg2pvxq6rs0zqg3yyc5seedvj").error(),
address::kErrNoSeparator, "an empty hrp is refused");
check_eq(address::parse(std::string("Lux1") + (kVectors[0].text + 4)).error(),
address::kErrMixedCase, "mixed case is refused");
check_eq(address::parse("lux1qypqxpq9qcrsszg2pvxq6rs0zqg3yybc5seedvj").error(),
address::kErrBadCharacter, "'b' is not in the charset");
check_eq(address::parse(std::string(91, 'q')).error(), address::kErrTooLong,
"over ninety characters is refused");
auto too_short = address::short_id(view(Bytes{1, 2, 3}));
check(!too_short.has_value(), "an owner is exactly twenty bytes");
}
void bits_regroup() {
// 5→8 of thirty-two groups is exactly twenty bytes, which is why an address
// needs no padding at all.
Bytes five(32, 0);
auto eight = address::convert_bits(view(five), 5, 8, true);
check(eight.has_value() && eight->size() == 20, "thirty-two five-bit groups are twenty bytes");
auto back = address::convert_bits(view(*eight), 8, 5, true);
check(back.has_value() && back->size() == 32, "and twenty bytes are thirty-two groups");
check(!address::convert_bits(view(five), 0, 8, true).has_value(), "a zero-bit group is refused");
}
} // namespace
int main() {
std::printf("address\n");
formats_the_go_string();
parses_the_go_string();
parse_short_is_the_whole_journey();
a_typo_is_refused();
the_refusals();
bits_regroup();
return report("address");
}