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.
542 lines
24 KiB
C++
542 lines
24 KiB
C++
// Copyright (C) 2026, Lux Industries, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: BSD-3-Clause-Eco
|
|
//
|
|
// txs_test.cpp — the transaction wire, byte for byte against the Go reference.
|
|
//
|
|
// Every case builds the SAME fixture the Go generator built (scratchpad/goldgen
|
|
// prints them; golden.hpp pins the output) and asserts the buffers are equal.
|
|
// A round-trip test proves an implementation agrees with itself, which is also
|
|
// what a fork does; only the pinned bytes tell the two apart.
|
|
//
|
|
// The second half re-reads each buffer through the parse dispatch and asserts
|
|
// every accessor returns the value that was written — the wire is one map, and
|
|
// a reader that disagrees with the writer is as much a fork as different bytes.
|
|
|
|
#include "golden.hpp"
|
|
#include "harness.hpp"
|
|
#include "lux/platformvm/txs.hpp"
|
|
|
|
#include <string>
|
|
|
|
using namespace lux::platformvm;
|
|
using namespace lux::platformvm::txs;
|
|
|
|
namespace {
|
|
|
|
Id id_of(std::uint8_t b) {
|
|
Id v{};
|
|
for (std::size_t i = 0; i < kIdLen; ++i) v[i] = static_cast<std::uint8_t>(b + i);
|
|
return v;
|
|
}
|
|
ShortId short_of(std::uint8_t b) {
|
|
ShortId v{};
|
|
for (std::size_t i = 0; i < kShortIdLen; ++i) v[i] = static_cast<std::uint8_t>(b + i);
|
|
return v;
|
|
}
|
|
NodeId node_of(std::uint8_t b) {
|
|
NodeId v{};
|
|
for (std::size_t i = 0; i < kNodeIdLen; ++i) v.b[i] = static_cast<std::uint8_t>(b + i);
|
|
return v;
|
|
}
|
|
|
|
// Go goldgen base(): the same envelope under every transaction below.
|
|
BaseTx base_fixture() {
|
|
const Id asset = id_of(0x10);
|
|
BaseTx b;
|
|
b.network_id = 96369;
|
|
b.blockchain_id = id_of(0x20);
|
|
b.outs = {
|
|
TransferableOutput{asset, 0,
|
|
TransferOutput{1'000'000, OutputOwners{7, 2, {short_of(0x30), short_of(0x40), short_of(0x50)}}}},
|
|
TransferableOutput{asset, 999, TransferOutput{42, OutputOwners{0, 1, {short_of(0x30)}}}},
|
|
};
|
|
b.ins = {
|
|
TransferableInput{UtxoId{id_of(0x60), 1}, asset, 999, TransferInput{1'000'042, {0, 1}}},
|
|
};
|
|
const std::string memo = "native-zap";
|
|
b.memo.assign(memo.begin(), memo.end());
|
|
return b;
|
|
}
|
|
|
|
Owner rewards_owner_fixture() { return Owner{3, 1, {short_of(0x30)}}; }
|
|
|
|
std::vector<TransferableOutput> stake_fixture() {
|
|
return {TransferableOutput{id_of(0x10), 0, TransferOutput{2'000'000, OutputOwners{0, 1, {short_of(0x40)}}}}};
|
|
}
|
|
|
|
signer::ProofOfPossession pop_fixture() {
|
|
signer::ProofOfPossession p{};
|
|
for (std::size_t i = 0; i < signer::kPublicKeyLen; ++i) p.public_key[i] = static_cast<std::uint8_t>(i);
|
|
for (std::size_t i = 0; i < signer::kSignatureLen; ++i)
|
|
p.proof[i] = static_cast<std::uint8_t>(255 - static_cast<int>(i));
|
|
return p;
|
|
}
|
|
|
|
NetworkValidator network_validator_fixture() {
|
|
NetworkValidator nv;
|
|
const NodeId n = node_of(0x96);
|
|
nv.node_id.assign(n.b.begin(), n.b.end());
|
|
nv.weight = 1000;
|
|
nv.balance = 500;
|
|
nv.pop = pop_fixture();
|
|
nv.remaining_balance_owner = PChainOwner{1, {short_of(0x30)}};
|
|
nv.deactivation_owner = PChainOwner{2, {short_of(0x40), short_of(0x50)}};
|
|
return nv;
|
|
}
|
|
|
|
std::vector<std::uint8_t> bytes_of(const std::string& s) { return {s.begin(), s.end()}; }
|
|
|
|
} // namespace
|
|
|
|
// ── the wire: each constructor against the bytes Go printed
|
|
|
|
TEST(BaseTxWire) {
|
|
auto tx = BaseTxUnsigned::create(base_fixture());
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::base_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
TEST(ImportTxWire) {
|
|
std::vector<TransferableInput> imported = {
|
|
TransferableInput{UtxoId{id_of(0x80), 3}, id_of(0x10), 0, TransferInput{500, {0}}}};
|
|
auto tx = ImportTx::create(base_fixture(), id_of(0x70), imported);
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::import_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
TEST(ExportTxWire) {
|
|
std::vector<TransferableOutput> exported = {
|
|
TransferableOutput{id_of(0x10), 0, TransferOutput{900, OutputOwners{0, 1, {short_of(0x30)}}}}};
|
|
auto tx = ExportTx::create(base_fixture(), id_of(0x71), exported);
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::export_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
TEST(CreateChainTxWire) {
|
|
auto tx = CreateChainTx::create(base_fixture(), id_of(0x72), "My Chain 7", id_of(0x73),
|
|
{id_of(0x01), id_of(0x02)}, bytes_of("genesis-bytes"), Auth{0, 2});
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::create_chain_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
TEST(AddValidatorTxWire) {
|
|
auto tx = AddValidatorTx::create(base_fixture(), Validator{node_of(0x90), 100, 200, 2'000'000},
|
|
stake_fixture(), rewards_owner_fixture(), 20000);
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::add_validator_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
TEST(AddDelegatorTxWire) {
|
|
auto tx = AddDelegatorTx::create(base_fixture(), Validator{node_of(0x91), 101, 201, 2'000'000},
|
|
stake_fixture(), rewards_owner_fixture());
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::add_delegator_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
TEST(AddChainValidatorTxWire) {
|
|
auto tx = AddChainValidatorTx::create(base_fixture(), Validator{node_of(0x92), 102, 202, 7}, id_of(0x74),
|
|
Auth{1});
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::add_chain_validator_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
TEST(AddPermissionlessValidatorTxWire) {
|
|
const signer::Signer sig = pop_fixture();
|
|
auto tx = AddPermissionlessValidatorTx::create(base_fixture(), Validator{node_of(0x93), 103, 203, 2'000'000},
|
|
kEmptyId, sig, stake_fixture(), rewards_owner_fixture(),
|
|
rewards_owner_fixture(), 20000);
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::add_permissionless_validator_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
TEST(AddPermissionlessDelegatorTxWire) {
|
|
auto tx = AddPermissionlessDelegatorTx::create(base_fixture(), Validator{node_of(0x94), 104, 204, 2'000'000},
|
|
kEmptyId, stake_fixture(), rewards_owner_fixture());
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::add_permissionless_delegator_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
TEST(RemoveChainValidatorTxWire) {
|
|
auto tx = RemoveChainValidatorTx::create(base_fixture(), node_of(0x95), id_of(0x75), Auth{0});
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::remove_chain_validator_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
TEST(TransferChainOwnershipTxWire) {
|
|
auto tx = TransferChainOwnershipTx::create(base_fixture(), id_of(0x76), Auth{0}, rewards_owner_fixture());
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::transfer_chain_ownership_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
TEST(TransformChainTxWire) {
|
|
TransformChainTx::Params p;
|
|
p.chain = id_of(0x77);
|
|
p.asset_id = id_of(0x78);
|
|
p.initial_supply = 1000;
|
|
p.maximum_supply = 2000;
|
|
p.min_consumption_rate = 10;
|
|
p.max_consumption_rate = 20;
|
|
p.min_validator_stake = 5;
|
|
p.max_validator_stake = 100;
|
|
p.min_stake_duration = 60;
|
|
p.max_stake_duration = 600;
|
|
p.min_delegation_fee = 1000;
|
|
p.min_delegator_stake = 3;
|
|
p.max_validator_weight_factor = 7;
|
|
p.uptime_requirement = 900000;
|
|
auto tx = TransformChainTx::create(base_fixture(), p, Auth{0});
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::transform_chain_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
TEST(RegisterL1ValidatorTxWire) {
|
|
signer::SignatureBytes pop{};
|
|
for (std::size_t i = 0; i < pop.size(); ++i) pop[i] = static_cast<std::uint8_t>(i);
|
|
auto tx = RegisterL1ValidatorTx::create(base_fixture(), 555, pop, bytes_of("warp-message"));
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::register_l1_validator_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
TEST(SetL1ValidatorWeightTxWire) {
|
|
auto tx = SetL1ValidatorWeightTx::create(base_fixture(), bytes_of("weight-message"));
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::set_l1_validator_weight_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
TEST(IncreaseL1ValidatorBalanceTxWire) {
|
|
auto tx = IncreaseL1ValidatorBalanceTx::create(base_fixture(), id_of(0x79), 4242);
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::increase_l1_validator_balance_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
TEST(DisableL1ValidatorTxWire) {
|
|
auto tx = DisableL1ValidatorTx::create(base_fixture(), id_of(0x7a), Auth{3});
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::disable_l1_validator_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
TEST(RewardValidatorTxWire) {
|
|
auto tx = RewardValidatorTx::create(id_of(0x7b));
|
|
REQUIRE_EQ(std::string(pvmgold::reward_validator_tx), hex(tx->bytes()));
|
|
}
|
|
|
|
TEST(CreateNetworkTxWire) {
|
|
const security::Mode sec{false, security::Admission::Open, 2000, security::Manager::Contract};
|
|
auto tx = CreateNetworkTx::create(base_fixture(), kEmptyId, rewards_owner_fixture(), sec,
|
|
{network_validator_fixture()}, id_of(0x7c), bytes_of("mgr"));
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::create_network_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
TEST(ConvertNetworkTxWire) {
|
|
const security::Mode sec{false, security::Admission::Open, 1000, security::Manager::Contract};
|
|
auto tx = ConvertNetworkTx::create(base_fixture(), id_of(0x7d), kEmptyId, id_of(0x7e), sec,
|
|
bytes_of("0xmgr"), {network_validator_fixture()}, Auth{0});
|
|
REQUIRE_OK(tx);
|
|
REQUIRE_EQ(std::string(pvmgold::convert_network_tx), hex(tx.value()->bytes()));
|
|
}
|
|
|
|
// Go: (*Tx).Initialize — signed bytes are unsigned ‖ creds, and the id is
|
|
// sha256 of the whole thing.
|
|
TEST(SignedTxWireAndID) {
|
|
auto unsigned_tx = BaseTxUnsigned::create(base_fixture());
|
|
REQUIRE_OK(unsigned_tx);
|
|
|
|
std::array<std::uint8_t, kSigLen> s0{}, s1{};
|
|
for (std::size_t i = 0; i < kSigLen; ++i) {
|
|
s0[i] = static_cast<std::uint8_t>(i);
|
|
s1[i] = static_cast<std::uint8_t>(255 - static_cast<int>(i));
|
|
}
|
|
Tx tx;
|
|
tx.unsigned_tx = unsigned_tx.value();
|
|
tx.creds = {Credential{{s0, s1}}, Credential{{s1}}};
|
|
REQUIRE_OK(tx.initialize());
|
|
|
|
REQUIRE_EQ(std::string(pvmgold::signed_base_tx), hex(tx.bytes));
|
|
REQUIRE_EQ(std::string(pvmgold::signed_base_tx_id), hex(tx.tx_id));
|
|
|
|
// The unsigned buffer is a genuine byte PREFIX of the signed buffer: that
|
|
// is what removes the "which spelling did we sign" question.
|
|
const auto u = unsigned_tx.value()->bytes();
|
|
REQUIRE(tx.bytes.size() > u.size());
|
|
REQUIRE(std::equal(u.begin(), u.end(), tx.bytes.begin()));
|
|
}
|
|
|
|
// ── the reader: parse each buffer back and check every field
|
|
|
|
TEST(ParseDispatchAndAccessors) {
|
|
// BaseTx envelope, read back through the dispatch.
|
|
{
|
|
auto built = BaseTxUnsigned::create(base_fixture());
|
|
REQUIRE_OK(built);
|
|
auto parsed = parse(built.value()->bytes());
|
|
REQUIRE_OK(parsed);
|
|
const auto& tx = parsed.value();
|
|
REQUIRE(tx.unsigned_tx->kind() == Kind::Base);
|
|
REQUIRE(tx.creds.empty());
|
|
const auto* spend = dynamic_cast<const SpendingTx*>(tx.unsigned_tx.get());
|
|
REQUIRE(spend != nullptr);
|
|
const BaseTx want = base_fixture();
|
|
REQUIRE_EQ_NUM(want.network_id, spend->network_id());
|
|
REQUIRE_EQ(want.blockchain_id, spend->blockchain_id());
|
|
REQUIRE_EQ(want.outs, spend->outputs());
|
|
REQUIRE_EQ(want.ins, spend->inputs());
|
|
REQUIRE_EQ(want.memo, spend->memo());
|
|
}
|
|
// Import: source chain and imported inputs.
|
|
{
|
|
std::vector<TransferableInput> imported = {
|
|
TransferableInput{UtxoId{id_of(0x80), 3}, id_of(0x10), 0, TransferInput{500, {0}}}};
|
|
auto built = ImportTx::create(base_fixture(), id_of(0x70), imported);
|
|
REQUIRE_OK(built);
|
|
auto parsed = parse(built.value()->bytes());
|
|
REQUIRE_OK(parsed);
|
|
const auto* tx = dynamic_cast<const ImportTx*>(parsed.value().unsigned_tx.get());
|
|
REQUIRE(tx != nullptr);
|
|
REQUIRE_EQ(id_of(0x70), tx->source_chain());
|
|
REQUIRE_EQ(imported, tx->imported_inputs());
|
|
}
|
|
// Export: destination chain and exported outputs.
|
|
{
|
|
std::vector<TransferableOutput> exported = {
|
|
TransferableOutput{id_of(0x10), 0, TransferOutput{900, OutputOwners{0, 1, {short_of(0x30)}}}}};
|
|
auto built = ExportTx::create(base_fixture(), id_of(0x71), exported);
|
|
REQUIRE_OK(built);
|
|
auto parsed = parse(built.value()->bytes());
|
|
REQUIRE_OK(parsed);
|
|
const auto* tx = dynamic_cast<const ExportTx*>(parsed.value().unsigned_tx.get());
|
|
REQUIRE(tx != nullptr);
|
|
REQUIRE_EQ(id_of(0x71), tx->destination_chain());
|
|
REQUIRE_EQ(exported, tx->exported_outputs());
|
|
}
|
|
// CreateChain: every delta field.
|
|
{
|
|
auto built = CreateChainTx::create(base_fixture(), id_of(0x72), "My Chain 7", id_of(0x73),
|
|
{id_of(0x01), id_of(0x02)}, bytes_of("genesis-bytes"), Auth{0, 2});
|
|
REQUIRE_OK(built);
|
|
auto parsed = parse(built.value()->bytes());
|
|
REQUIRE_OK(parsed);
|
|
const auto* tx = dynamic_cast<const CreateChainTx*>(parsed.value().unsigned_tx.get());
|
|
REQUIRE(tx != nullptr);
|
|
REQUIRE_EQ(id_of(0x72), tx->chain_id());
|
|
REQUIRE_EQ(id_of(0x73), tx->vm_id());
|
|
REQUIRE_EQ(std::string("My Chain 7"), tx->blockchain_name());
|
|
REQUIRE_EQ(std::vector<Id>({id_of(0x01), id_of(0x02)}), tx->fx_ids());
|
|
REQUIRE_EQ(bytes_of("genesis-bytes"), tx->genesis_data());
|
|
REQUIRE_EQ(Auth({0, 2}), tx->chain_auth());
|
|
}
|
|
// AddPermissionlessValidator: the staking claim, the key, the two owners.
|
|
{
|
|
const signer::Signer sig = pop_fixture();
|
|
auto built = AddPermissionlessValidatorTx::create(base_fixture(),
|
|
Validator{node_of(0x93), 103, 203, 2'000'000},
|
|
kEmptyId, sig, stake_fixture(),
|
|
rewards_owner_fixture(), rewards_owner_fixture(), 20000);
|
|
REQUIRE_OK(built);
|
|
auto parsed = parse(built.value()->bytes());
|
|
REQUIRE_OK(parsed);
|
|
const auto* tx = dynamic_cast<const AddPermissionlessValidatorTx*>(parsed.value().unsigned_tx.get());
|
|
REQUIRE(tx != nullptr);
|
|
REQUIRE_EQ(Validator({node_of(0x93), 103, 203, 2'000'000}), tx->validator());
|
|
REQUIRE_EQ(kEmptyId, tx->chain());
|
|
REQUIRE(tx->signer_value() == sig);
|
|
REQUIRE_EQ(stake_fixture(), tx->stake_outs());
|
|
REQUIRE_EQ(rewards_owner_fixture(), tx->validator_rewards_owner());
|
|
REQUIRE_EQ(rewards_owner_fixture(), tx->delegator_rewards_owner());
|
|
REQUIRE_EQ_NUM(20000u, tx->delegation_shares());
|
|
}
|
|
// TransformChain: fifteen fields, all read back.
|
|
{
|
|
TransformChainTx::Params p;
|
|
p.chain = id_of(0x77);
|
|
p.asset_id = id_of(0x78);
|
|
p.initial_supply = 1000;
|
|
p.maximum_supply = 2000;
|
|
p.min_consumption_rate = 10;
|
|
p.max_consumption_rate = 20;
|
|
p.min_validator_stake = 5;
|
|
p.max_validator_stake = 100;
|
|
p.min_stake_duration = 60;
|
|
p.max_stake_duration = 600;
|
|
p.min_delegation_fee = 1000;
|
|
p.min_delegator_stake = 3;
|
|
p.max_validator_weight_factor = 7;
|
|
p.uptime_requirement = 900000;
|
|
auto built = TransformChainTx::create(base_fixture(), p, Auth{0});
|
|
REQUIRE_OK(built);
|
|
auto parsed = parse(built.value()->bytes());
|
|
REQUIRE_OK(parsed);
|
|
const auto* tx = dynamic_cast<const TransformChainTx*>(parsed.value().unsigned_tx.get());
|
|
REQUIRE(tx != nullptr);
|
|
REQUIRE_EQ(p.chain, tx->chain());
|
|
REQUIRE_EQ(p.asset_id, tx->asset_id());
|
|
REQUIRE_U64(p.initial_supply, tx->initial_supply());
|
|
REQUIRE_U64(p.maximum_supply, tx->maximum_supply());
|
|
REQUIRE_U64(p.min_consumption_rate, tx->min_consumption_rate());
|
|
REQUIRE_U64(p.max_consumption_rate, tx->max_consumption_rate());
|
|
REQUIRE_U64(p.min_validator_stake, tx->min_validator_stake());
|
|
REQUIRE_U64(p.max_validator_stake, tx->max_validator_stake());
|
|
REQUIRE_EQ_NUM(p.min_stake_duration, tx->min_stake_duration());
|
|
REQUIRE_EQ_NUM(p.max_stake_duration, tx->max_stake_duration());
|
|
REQUIRE_EQ_NUM(p.min_delegation_fee, tx->min_delegation_fee());
|
|
REQUIRE_U64(p.min_delegator_stake, tx->min_delegator_stake());
|
|
REQUIRE_EQ_NUM(p.max_validator_weight_factor, tx->max_validator_weight_factor());
|
|
REQUIRE_EQ_NUM(p.uptime_requirement, tx->uptime_requirement());
|
|
REQUIRE_EQ(Auth({0}), tx->chain_auth());
|
|
}
|
|
// CreateNetwork: the security mode and the genesis validator survive.
|
|
{
|
|
const security::Mode sec{false, security::Admission::Open, 2000, security::Manager::Contract};
|
|
auto built = CreateNetworkTx::create(base_fixture(), kEmptyId, rewards_owner_fixture(), sec,
|
|
{network_validator_fixture()}, id_of(0x7c), bytes_of("mgr"));
|
|
REQUIRE_OK(built);
|
|
auto parsed = parse(built.value()->bytes());
|
|
REQUIRE_OK(parsed);
|
|
const auto* tx = dynamic_cast<const CreateNetworkTx*>(parsed.value().unsigned_tx.get());
|
|
REQUIRE(tx != nullptr);
|
|
REQUIRE_EQ(kEmptyId, tx->parent());
|
|
REQUIRE_EQ(rewards_owner_fixture(), tx->owner());
|
|
REQUIRE(tx->security_mode() == sec);
|
|
REQUIRE(tx->sovereign());
|
|
REQUIRE_EQ(std::vector<NetworkValidator>({network_validator_fixture()}), tx->validators());
|
|
REQUIRE_EQ(id_of(0x7c), tx->manager_chain_id());
|
|
REQUIRE_EQ(bytes_of("mgr"), tx->manager_address());
|
|
}
|
|
// ConvertNetwork: the promotion.
|
|
{
|
|
const security::Mode sec{false, security::Admission::Open, 1000, security::Manager::Contract};
|
|
auto built = ConvertNetworkTx::create(base_fixture(), id_of(0x7d), kEmptyId, id_of(0x7e), sec,
|
|
bytes_of("0xmgr"), {network_validator_fixture()}, Auth{0});
|
|
REQUIRE_OK(built);
|
|
auto parsed = parse(built.value()->bytes());
|
|
REQUIRE_OK(parsed);
|
|
const auto* tx = dynamic_cast<const ConvertNetworkTx*>(parsed.value().unsigned_tx.get());
|
|
REQUIRE(tx != nullptr);
|
|
REQUIRE_EQ(id_of(0x7d), tx->network());
|
|
REQUIRE_EQ(kEmptyId, tx->parent());
|
|
REQUIRE_EQ(id_of(0x7e), tx->manager_chain_id());
|
|
REQUIRE_EQ(bytes_of("0xmgr"), tx->manager_address());
|
|
REQUIRE(tx->security_mode() == sec);
|
|
REQUIRE_EQ(std::vector<NetworkValidator>({network_validator_fixture()}), tx->validators());
|
|
REQUIRE_EQ(Auth({0}), tx->auth());
|
|
}
|
|
// The L1 balance transactions and the reward proposal.
|
|
{
|
|
auto built = IncreaseL1ValidatorBalanceTx::create(base_fixture(), id_of(0x79), 4242);
|
|
REQUIRE_OK(built);
|
|
auto parsed = parse(built.value()->bytes());
|
|
REQUIRE_OK(parsed);
|
|
const auto* tx = dynamic_cast<const IncreaseL1ValidatorBalanceTx*>(parsed.value().unsigned_tx.get());
|
|
REQUIRE(tx != nullptr);
|
|
REQUIRE_EQ(id_of(0x79), tx->validation_id());
|
|
REQUIRE_U64(4242u, tx->balance());
|
|
}
|
|
{
|
|
auto built = DisableL1ValidatorTx::create(base_fixture(), id_of(0x7a), Auth{3});
|
|
REQUIRE_OK(built);
|
|
auto parsed = parse(built.value()->bytes());
|
|
REQUIRE_OK(parsed);
|
|
const auto* tx = dynamic_cast<const DisableL1ValidatorTx*>(parsed.value().unsigned_tx.get());
|
|
REQUIRE(tx != nullptr);
|
|
REQUIRE_EQ(id_of(0x7a), tx->validation_id());
|
|
REQUIRE_EQ(Auth({3}), tx->disable_auth());
|
|
}
|
|
{
|
|
auto built = RegisterL1ValidatorTx::create(base_fixture(), 555, pop_fixture().proof,
|
|
bytes_of("warp-message"));
|
|
// goldgen used popSig[i] = i, not the pop fixture's descending proof.
|
|
signer::SignatureBytes want{};
|
|
for (std::size_t i = 0; i < want.size(); ++i) want[i] = static_cast<std::uint8_t>(i);
|
|
auto built2 = RegisterL1ValidatorTx::create(base_fixture(), 555, want, bytes_of("warp-message"));
|
|
REQUIRE_OK(built);
|
|
REQUIRE_OK(built2);
|
|
auto parsed = parse(built2.value()->bytes());
|
|
REQUIRE_OK(parsed);
|
|
const auto* tx = dynamic_cast<const RegisterL1ValidatorTx*>(parsed.value().unsigned_tx.get());
|
|
REQUIRE(tx != nullptr);
|
|
REQUIRE_U64(555u, tx->balance());
|
|
REQUIRE(tx->proof_of_possession() == want);
|
|
REQUIRE_EQ(bytes_of("warp-message"), tx->message());
|
|
}
|
|
{
|
|
auto built = RewardValidatorTx::create(id_of(0x7b));
|
|
auto parsed = parse(built->bytes());
|
|
REQUIRE_OK(parsed);
|
|
const auto* tx = dynamic_cast<const RewardValidatorTx*>(parsed.value().unsigned_tx.get());
|
|
REQUIRE(tx != nullptr);
|
|
REQUIRE_EQ(id_of(0x7b), tx->tx_id());
|
|
}
|
|
}
|
|
|
|
// Go: txs.Parse over a signed buffer — the credentials come back exactly.
|
|
TEST(ParseSignedRoundTrip) {
|
|
auto unsigned_tx = BaseTxUnsigned::create(base_fixture());
|
|
REQUIRE_OK(unsigned_tx);
|
|
std::array<std::uint8_t, kSigLen> s0{}, s1{};
|
|
for (std::size_t i = 0; i < kSigLen; ++i) {
|
|
s0[i] = static_cast<std::uint8_t>(i);
|
|
s1[i] = static_cast<std::uint8_t>(255 - static_cast<int>(i));
|
|
}
|
|
Tx tx;
|
|
tx.unsigned_tx = unsigned_tx.value();
|
|
tx.creds = {Credential{{s0, s1}}, Credential{{s1}}};
|
|
REQUIRE_OK(tx.initialize());
|
|
|
|
auto parsed = parse(tx.bytes);
|
|
REQUIRE_OK(parsed);
|
|
REQUIRE_EQ(tx.tx_id, parsed.value().tx_id);
|
|
REQUIRE_EQ(tx.creds, parsed.value().creds);
|
|
REQUIRE_EQ(hex(tx.unsigned_tx->bytes()), hex(parsed.value().unsigned_tx->bytes()));
|
|
}
|
|
|
|
// Go: txs.parseUnsigned default arm — an unknown kind is refused, not guessed.
|
|
TEST(ParseRejectsUnknownKind) {
|
|
auto built = BaseTxUnsigned::create(base_fixture());
|
|
REQUIRE_OK(built);
|
|
std::vector<std::uint8_t> b(built.value()->bytes().begin(), built.value()->bytes().end());
|
|
// The root object's first byte is the kind; 200 names nothing.
|
|
const std::uint32_t root = static_cast<std::uint32_t>(b[8]) | (static_cast<std::uint32_t>(b[9]) << 8) |
|
|
(static_cast<std::uint32_t>(b[10]) << 16) |
|
|
(static_cast<std::uint32_t>(b[11]) << 24);
|
|
b[root] = 200;
|
|
REQUIRE_ERR(parse(b), Err::UnknownTxKind);
|
|
|
|
// Slot 0 and slot 1 are deliberate holes: a zeroed buffer must not decode.
|
|
b[root] = 0;
|
|
REQUIRE_ERR(parse(b), Err::UnknownTxKind);
|
|
b[root] = 1;
|
|
REQUIRE_ERR(parse(b), Err::UnknownTxKind);
|
|
}
|
|
|
|
// Go: errCredSigsOutOfRange — a credential may not name signatures the buffer
|
|
// does not carry.
|
|
TEST(ParseRejectsCredRangeOutsideSignatureArray) {
|
|
std::array<std::uint8_t, kSigLen> s0{};
|
|
for (std::size_t i = 0; i < kSigLen; ++i) s0[i] = static_cast<std::uint8_t>(i);
|
|
auto creds = write_creds({Credential{{s0}}});
|
|
REQUIRE_OK(creds);
|
|
auto buf = creds.value();
|
|
|
|
// The credential entry is {sigStart u32, sigCount u32}; inflate the count.
|
|
const std::uint32_t root = static_cast<std::uint32_t>(buf[8]) | (static_cast<std::uint32_t>(buf[9]) << 8) |
|
|
(static_cast<std::uint32_t>(buf[10]) << 16) |
|
|
(static_cast<std::uint32_t>(buf[11]) << 24);
|
|
const std::int32_t rel = static_cast<std::int32_t>(
|
|
static_cast<std::uint32_t>(buf[root]) | (static_cast<std::uint32_t>(buf[root + 1]) << 8) |
|
|
(static_cast<std::uint32_t>(buf[root + 2]) << 16) | (static_cast<std::uint32_t>(buf[root + 3]) << 24));
|
|
const std::size_t entry = static_cast<std::size_t>(static_cast<std::int64_t>(root) + rel);
|
|
buf[entry + 4] = 0xff; // sigCount = huge
|
|
buf[entry + 5] = 0xff;
|
|
REQUIRE_ERR(parse_creds(buf), Err::CredSigsOutOfRange);
|
|
}
|
|
|
|
// Go: MarshalOwner / UnmarshalOwner — the one canonical owner encoding.
|
|
TEST(MarshalOwnerRoundTrip) {
|
|
const Owner o{11, 2, {short_of(0x30), short_of(0x40)}};
|
|
const auto b = marshal_owner(o);
|
|
REQUIRE_EQ(std::string(pvmgold::marshal_owner), hex(b));
|
|
auto back = unmarshal_owner(b);
|
|
REQUIRE_OK(back);
|
|
REQUIRE_EQ(o, back.value());
|
|
}
|