mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
platformvm had its own thirty-two-byte name wrapped in a struct, its own twenty-byte address, and — under both of them — its own complete SHA-256. Two implementations of the hash a transaction is NAMED by is the sharpest version of this problem: they agree until the day they do not, and the day they do not is a fork. So ids.hpp now declares nothing. It adopts core's names and core's hash, and keeps only the one thing that is a P-chain fact rather than a naming fact: the primary network is the zero id. sha256.hpp is deleted; its FIPS 180-4 vectors moved to core, where the hash now lives, and sha256_test became ids_test, which checks what is left to check here — that a UTXO's name is the reference's ids.ID.Prefix. Six identical local hex() helpers in the test files are gone too. ONE HAZARD, NAMED, BECAUSE IT IS SILENT. FixedId::empty() meant "all zero"; std::array::empty() means "has no elements", which is false for thirty-two bytes and compiles without a word. Five checks were of the first kind and would have become dead: the two empty-asset gates, the empty-VM-id gate, and the reward transaction's own id. They now read `== kEmptyId`, which cannot rot into a tautology. Three of the five were caught by the suite; the reward-id one was not, and nothing tests it. 208 differential vectors still agree across Go, Rust and C++, so the names that changed shape did not change value.
223 lines
7.5 KiB
C++
223 lines
7.5 KiB
C++
// Copyright (C) 2026, Lux Industries, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: BSD-3-Clause-Eco
|
|
//
|
|
// adopt_test.cpp — the register of networks Lux did not create.
|
|
//
|
|
// Ported from Go vms/platformvm/adopt/adopt_test.go, including its fixtures:
|
|
// the real networks, as they actually relate.
|
|
|
|
#include "harness.hpp"
|
|
#include "lux/platformvm/adopt.hpp"
|
|
|
|
using namespace lux::platformvm;
|
|
using namespace lux::platformvm::adopt;
|
|
|
|
namespace {
|
|
|
|
Id id_of(std::uint8_t b) {
|
|
Id v{};
|
|
v[0] = b;
|
|
return v;
|
|
}
|
|
|
|
Record ethereum() {
|
|
return Record{1, id_of(1), kEmptyId, Anchor::Attested, Holding::Gateway, "mchain/eth", {"https://eth"}, 64};
|
|
}
|
|
Record base() {
|
|
return Record{8453, id_of(2), id_of(1), Anchor::Attested, Holding::Gateway,
|
|
"mchain/base", {"https://base"}, 200};
|
|
}
|
|
Record bitcoin() {
|
|
return Record{0x1000, id_of(3), kEmptyId, Anchor::Attested, Holding::Address, "mchain/btc",
|
|
{"https://btc"}, 6};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// A network that takes its security from another may not be adopted before that
|
|
// other one. Adopting Base alone records a belief whose entire basis is a chain
|
|
// the register has never heard of.
|
|
TEST(ANetworkCannotPrecedeTheOneItLeansOn) {
|
|
Registry g;
|
|
REQUIRE_ERR(g.adopt(base()), Err::NoParent);
|
|
REQUIRE_EQ_NUM(0, g.size());
|
|
|
|
REQUIRE_OK(g.adopt(ethereum()));
|
|
REQUIRE_OK(g.adopt(base()));
|
|
REQUIRE_EQ_NUM(2, g.size());
|
|
|
|
// And a network already there is not adopted twice.
|
|
REQUIRE_ERR(g.adopt(ethereum()), Err::AlreadyAdopted);
|
|
}
|
|
|
|
// The same rule backwards: releasing the network underneath would make the one
|
|
// above it unreadable, because its anchor is a claim about the one underneath.
|
|
TEST(ANetworkCannotBeReleasedWhileAnotherLeansOnIt) {
|
|
Registry g;
|
|
REQUIRE_OK(g.adopt(ethereum()));
|
|
REQUIRE_OK(g.adopt(base()));
|
|
|
|
REQUIRE_ERR(g.release(ethereum().key()), Err::ParentHeld);
|
|
|
|
// Release the dependent first and the order opens up.
|
|
REQUIRE_OK(g.release(base().key()));
|
|
REQUIRE_OK(g.release(ethereum().key()));
|
|
REQUIRE_EQ_NUM(0, g.size());
|
|
REQUIRE_ERR(g.release(ethereum().key()), Err::NotAdopted);
|
|
}
|
|
|
|
// The line the permissionless path rests on. Paying buys a record; a record is
|
|
// not a trust decision, and a declared anchor may not hold value.
|
|
TEST(ADeclaredAnchorCannotHoldValue) {
|
|
const Record paid{999, id_of(9), kEmptyId, Anchor::Declared, Holding::Gateway, "",
|
|
{"https://newchain"}, 0};
|
|
|
|
Registry g;
|
|
REQUIRE_OK(g.adopt(paid));
|
|
REQUIRE(!g.may_hold(paid.key()));
|
|
|
|
// And it cannot be smuggled in by attaching a key to it.
|
|
Record bought = paid;
|
|
bought.custody = "mchain/newchain";
|
|
Registry other;
|
|
REQUIRE_ERR(other.adopt(bought), Err::CustodyUnheld);
|
|
}
|
|
|
|
// An anchor that may hold value and names no key promises something it cannot
|
|
// do.
|
|
TEST(AnAnchorThatHoldsValueNeedsAKey) {
|
|
Record r = ethereum();
|
|
r.custody.clear();
|
|
REQUIRE_ERR(r.valid(), Err::NoCustody);
|
|
}
|
|
|
|
// A record has to say which chain it is about, where to reach it, and on what
|
|
// basis it is believed.
|
|
TEST(ARecordSaysWhatItIsAbout) {
|
|
{
|
|
Record r = ethereum();
|
|
r.chain_id = 0;
|
|
REQUIRE_ERR(r.valid(), Err::NoChainId);
|
|
}
|
|
{
|
|
Record r = ethereum();
|
|
r.identity = kEmptyId;
|
|
REQUIRE_ERR(r.valid(), Err::NoIdentity);
|
|
}
|
|
{
|
|
Record r = ethereum();
|
|
r.endpoints.clear();
|
|
REQUIRE_ERR(r.valid(), Err::NoEndpoints);
|
|
}
|
|
{
|
|
Record r = ethereum();
|
|
r.anchor = static_cast<Anchor>(9);
|
|
REQUIRE_ERR(r.valid(), Err::BadAnchor);
|
|
}
|
|
{
|
|
Record r = ethereum();
|
|
r.holding = static_cast<Holding>(9);
|
|
REQUIRE_ERR(r.valid(), Err::BadHolding);
|
|
}
|
|
{
|
|
Record r = ethereum();
|
|
r.parent = r.identity;
|
|
REQUIRE_ERR(r.valid(), Err::SelfParent);
|
|
}
|
|
}
|
|
|
|
// Strengthening is free; weakening is a separate act, because it changes what
|
|
// every downstream consumer is trusting without any of them being asked.
|
|
TEST(AnAnchorStrengthensFreelyAndWeakensDeliberately) {
|
|
Registry g;
|
|
REQUIRE_OK(g.adopt(ethereum()));
|
|
|
|
Record up = ethereum();
|
|
up.anchor = Anchor::Proven;
|
|
REQUIRE_OK(g.revise(up));
|
|
|
|
Record down = up;
|
|
down.anchor = Anchor::Attested;
|
|
REQUIRE_ERR(g.revise(down), Err::WeakerAnchor);
|
|
|
|
REQUIRE_OK(g.weaken(ethereum().key(), Anchor::Attested));
|
|
REQUIRE(g.get(ethereum().key())->anchor == Anchor::Attested);
|
|
// And weakening to what it already is, or to something stronger, is not a
|
|
// weakening.
|
|
REQUIRE_ERR(g.weaken(ethereum().key(), Anchor::Attested), Err::NotWeaker);
|
|
REQUIRE_ERR(g.weaken(ethereum().key(), Anchor::Proven), Err::NotWeaker);
|
|
|
|
// Dropping below the custodial line drops the key with it, or the record
|
|
// would contradict itself.
|
|
REQUIRE_OK(g.weaken(ethereum().key(), Anchor::Declared));
|
|
REQUIRE(g.get(ethereum().key())->custody.empty());
|
|
REQUIRE(!g.may_hold(ethereum().key()));
|
|
|
|
// A network nobody adopted cannot be revised or weakened.
|
|
REQUIRE_ERR(g.revise(bitcoin()), Err::NotAdopted);
|
|
REQUIRE_ERR(g.weaken(bitcoin().key(), Anchor::Declared), Err::NotAdopted);
|
|
}
|
|
|
|
// A fork inherits the chain id of the chain it left, so the identity
|
|
// commitment is what keeps the two apart.
|
|
TEST(AForkDoesNotOverwriteTheChainItLeft) {
|
|
Registry g;
|
|
REQUIRE_OK(g.adopt(ethereum()));
|
|
|
|
Record fork = ethereum();
|
|
fork.identity = id_of(0xFF); // the same chain id, a different chain
|
|
REQUIRE_OK(g.adopt(fork));
|
|
REQUIRE_EQ_NUM(2, g.size());
|
|
REQUIRE_U64(ethereum().chain_id, g.get(fork.key())->chain_id);
|
|
}
|
|
|
|
// Where security comes from is not revisable. A network that changed it would
|
|
// have become something else.
|
|
TEST(ASecuritySourceIsNotRevisable) {
|
|
Registry g;
|
|
REQUIRE_OK(g.adopt(ethereum()));
|
|
REQUIRE_OK(g.adopt(bitcoin()));
|
|
REQUIRE_OK(g.adopt(base()));
|
|
|
|
Record moved = base();
|
|
moved.parent = bitcoin().key();
|
|
REQUIRE_ERR(g.revise(moved), Err::SourceNotRevisable);
|
|
}
|
|
|
|
// An unadopted network is not bridgeable — the difference this register makes.
|
|
// Before it, a bridge trusted a file on disk and nothing said the estate had
|
|
// sanctioned it.
|
|
TEST(AnUnadoptedNetworkMayNotHoldValue) {
|
|
Registry g;
|
|
REQUIRE(!g.may_hold(id_of(42)));
|
|
REQUIRE(!g.get(id_of(42)).has_value());
|
|
|
|
// The positive control: an adopted, attested one may.
|
|
REQUIRE_OK(g.adopt(ethereum()));
|
|
REQUIRE(g.may_hold(ethereum().key()));
|
|
}
|
|
|
|
// Bitcoin has nowhere to put a gateway, so the custody address is the gateway.
|
|
// Adoption does not care; it is the same record read by a different client.
|
|
TEST(AChainWithNoContractsIsStillAdoptable) {
|
|
Registry g;
|
|
REQUIRE_OK(g.adopt(bitcoin()));
|
|
REQUIRE(g.get(bitcoin().key())->holding == Holding::Address);
|
|
REQUIRE(g.may_hold(bitcoin().key()));
|
|
REQUIRE(g.get(bitcoin().key())->sovereign());
|
|
REQUIRE(!base().sovereign());
|
|
}
|
|
|
|
// The names are part of the record: a log that cannot say what an anchor was
|
|
// cannot show anyone what changed.
|
|
TEST(TheAnchorsAndHoldingsAreNamed) {
|
|
REQUIRE_EQ(std::string("declared"), std::string(anchor_name(Anchor::Declared)));
|
|
REQUIRE_EQ(std::string("attested"), std::string(anchor_name(Anchor::Attested)));
|
|
REQUIRE_EQ(std::string("proven"), std::string(anchor_name(Anchor::Proven)));
|
|
REQUIRE_EQ(std::string("gateway"), std::string(holding_name(Holding::Gateway)));
|
|
REQUIRE_EQ(std::string("address"), std::string(holding_name(Holding::Address)));
|
|
REQUIRE(!custodial(Anchor::Declared));
|
|
REQUIRE(custodial(Anchor::Attested));
|
|
REQUIRE(custodial(Anchor::Proven));
|
|
}
|