diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1d58010c7..980d3e8c8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -82,7 +82,7 @@ jobs: test_address_resolution test_compute_share_target \ test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_redistribute_test dgb_block_assembly_test dgb_header_sample_build_test dgb_header_ingest_test dgb_mempool_ingest_test \ dgb_gentx_coinbase_test dgb_connection_coinbase_test dgb_pplns_payout_split_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test nmc_mempool_name_test nmc_block_broadcast_test dgb_gentx_share_path_test dgb_conn_pplns_producer_test dgb_other_tx_resolver_test \ - dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test \ + dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test dgb_compact_blocks_bip152_parity_test \ rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \ dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_binomial_conf_interval_test dgb_desired_version_tally_test dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_share_weight_decay_test dgb_naughty_propagation_test v37_test \ -j$(nproc) @@ -214,7 +214,7 @@ jobs: test_address_resolution test_compute_share_target \ test_utxo test_dgb_subsidy test_dgb_coinbase_value dgb_share_test dgb_redistribute_test dgb_block_assembly_test dgb_header_sample_build_test dgb_header_ingest_test dgb_mempool_ingest_test \ dgb_gentx_coinbase_test dgb_connection_coinbase_test dgb_pplns_payout_split_test nmc_auxpow_merkle_test nmc_template_builder_test nmc_auxpow_wire_test nmc_reconstruct_won_block_test nmc_mempool_name_test nmc_block_broadcast_test dgb_gentx_share_path_test dgb_conn_pplns_producer_test dgb_other_tx_resolver_test \ - dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test \ + dgb_other_tx_assembler_test dgb_reconstruct_won_block_test dgb_reconstruct_closure_test dgb_gentx_unpack_test dgb_work_source_test dgb_template_builder_test dgb_embedded_coin_node_test dgb_embedded_tx_select_test dgb_template_other_txs_test dgb_coinbase_value_parity_test dgb_submit_classify_test dgb_aux_parent_coinbase_parity_test dgb_template_capture_test dgb_aux_doge_db_commitment_bind_test dgb_aux_doge_dc_proof_test dgb_aux_doge_bind_parsers_test dgb_compact_blocks_bip152_parity_test \ rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \ dgb_coin_node_seam_test dgb_block_broadcast_test dgb_won_block_dispatch_test dgb_forced_won_share_dualpath_test dgb_scrypt_pow_test dgb_nonce_grinder_test dgb_regrind_block_test dgb_won_block_finalize_test dgb_share_target_genesis_test dgb_pool_msg_wire_test dgb_get_shares_walk_test dgb_download_stops_test dgb_think_p1_walk_bounds_test dgb_think_p1_desired_emit_test dgb_think_p6_desired_cutoff_test dgb_think_p4_head_keys_test dgb_think_p3_best_head_test dgb_g1_oracle_byte_parity_test dgb_think_p2_walk_bounds_test dgb_expected_time_to_block_test dgb_tail_score_endpoints_test dgb_pool_attempts_per_second_test dgb_pool_efficiency_test dgb_think_p5_best_share_punish_test dgb_auto_ratchet_tail_guard_test dgb_binomial_conf_interval_test dgb_desired_version_tally_test dgb_get_height_and_last_endpoints_test dgb_chain_walk_window_test dgb_redistribute_delegate_ghal_test dgb_share_weight_decay_test dgb_naughty_propagation_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \ v37_test \ diff --git a/src/impl/dgb/test/CMakeLists.txt b/src/impl/dgb/test/CMakeLists.txt index 89e2d9736..1b316c3d0 100644 --- a/src/impl/dgb/test/CMakeLists.txt +++ b/src/impl/dgb/test/CMakeLists.txt @@ -919,4 +919,22 @@ if (BUILD_TESTING AND GTest_FOUND) dgb_coin pool sharechain) gtest_add_tests(dgb_think_p5_best_share_punish_test "" AUTO) + # dgb_compact_blocks_bip152_parity_test: FENCED, additive KAT pinning the + # BIP 152 compact-block RELAY wire primitives in coin/compact_blocks.hpp -- + # ShortTxID 6-byte little-endian layout + to_uint64() inverse, and the + # BlockTransactionsRequest (getblocktxn) differential transaction-index + # encode/decode. RELAY-ONLY (reconstructed block is validated normally): no + # consensus / PoW / share-format / payout surface. NON-CIRCULAR: short-ID + # bytes and the {0,2,6,10,11}->{0,1,3,3,0} diff sequence are hand-derived + # from the BIP 152 spec, asserted against the on-the-wire serialiser output. + # No call site rewired. MUST also be in BOTH build.yml --target allowlists + # (#143 NOT_BUILT trap). + add_executable(dgb_compact_blocks_bip152_parity_test compact_blocks_bip152_parity_test.cpp) + target_link_libraries(dgb_compact_blocks_bip152_parity_test PRIVATE + GTest::gtest_main GTest::gtest + core dgb + c2pool_payout c2pool_merged_mining c2pool_hashrate c2pool_storage + dgb_coin pool sharechain) + gtest_add_tests(dgb_compact_blocks_bip152_parity_test "" AUTO) + endif() diff --git a/src/impl/dgb/test/compact_blocks_bip152_parity_test.cpp b/src/impl/dgb/test/compact_blocks_bip152_parity_test.cpp new file mode 100644 index 000000000..6e2ec2a0a --- /dev/null +++ b/src/impl/dgb/test/compact_blocks_bip152_parity_test.cpp @@ -0,0 +1,164 @@ +// --------------------------------------------------------------------------- +// dgb_compact_blocks_bip152_parity_test -- FENCED, additive KAT pinning the +// BIP 152 compact-block RELAY wire primitives in coin/compact_blocks.hpp: +// * ShortTxID 6-byte little-endian byte layout + to_uint64() inverse, and +// * the differential transaction-index encoding/decoding on +// BlockTransactionsRequest (the "getblocktxn" message). +// +// Both are RELAY-ONLY (the embedded P2P compact-block path reconstructs a full +// block which is then validated normally): NO consensus surface, no PoW, no +// share format, no payout/subsidy/version-gate value. A 1-byte divergence in +// either primitive only changes bandwidth/reconstruction, never block validity, +// but a silent drift there desyncs us from any BIP 152 peer (DigiByte Core / +// the btc-family relay this header 1:1 mirrors). +// +// The anchors are NON-CIRCULAR. BIP 152 fixes both encodings by spec: +// * short IDs are 6 bytes, least-significant byte first (the spec's +// "6-byte integer" little-endian wire form) -- pinned here with hand-written +// byte goldens, not by re-deriving them from the code under test. +// * differential indexes: "The first index ... is the absolute index. Each +// subsequent index is the difference minus one from the previous" -- so the +// absolute list {0,2,6,10,11} MUST serialise its index diffs as +// {0,1,3,3,0} (2-0-1, 6-2-1, 10-6-1, 11-10-1). The expected diff bytes are +// computed by hand from the spec and asserted against the on-the-wire bytes +// the BlockTransactionsRequest serialiser emits. +// +// DIAGNOSTICS / WIRE-RELAY ONLY. No call site rewired; pins existing behaviour. +// MUST also appear in BOTH build.yml --target allowlists (#143 NOT_BUILT trap). +// --------------------------------------------------------------------------- + +#include + +#include +#include + +#include +#include + +#include "../coin/compact_blocks.hpp" + +using dgb::coin::ShortTxID; +using dgb::coin::BlockTransactionsRequest; + +namespace { + +// Pull the bytes a PackStream currently holds into a plain vector so +// the wire encoding can be asserted byte for byte. +std::vector drain(PackStream& ps) +{ + std::vector out; + out.reserve(ps.size()); + const std::byte* p = ps.data(); + for (size_t i = 0; i < ps.size(); ++i) + out.push_back(static_cast(p[i])); + return out; +} + +} // namespace + +// A short ID is the low 48 bits of a uint64, stored least-significant byte +// first. The byte goldens are written by hand from the BIP 152 6-byte LE form. +TEST(DgbCompactBlocksBip152Parity, ShortTxIDLittleEndianByteLayout) +{ + ShortTxID s(0x123456789abcULL); + const uint8_t expect[6] = {0xbc, 0x9a, 0x78, 0x56, 0x34, 0x12}; + for (int i = 0; i < 6; ++i) + EXPECT_EQ(s.data[i], expect[i]) << "byte " << i; + EXPECT_EQ(s.to_uint64(), 0x123456789abcULL); +} + +// Boundary values round-trip through the 6-byte form: zero, one, and the +// 48-bit maximum (everything above 2^48 is masked off by construction). +TEST(DgbCompactBlocksBip152Parity, ShortTxIDRoundTripBoundaries) +{ + for (uint64_t v : {uint64_t(0), uint64_t(1), uint64_t(0xffffffffffffULL)}) + EXPECT_EQ(ShortTxID(v).to_uint64(), v); + + // The top byte of a 6-byte ID survives (pins that it is byte index 5). + ShortTxID hi(0xff0000000000ULL); + EXPECT_EQ(hi.data[5], 0xff); + EXPECT_EQ(hi.data[0], 0x00); +} + +// getblocktxn differential index encoding: absolute {0,2,6,10,11} must emit +// the spec diff sequence {0,1,3,3,0}. Blockhash left null so the 32 leading +// wire bytes are zero and the index region is unambiguous. +TEST(DgbCompactBlocksBip152Parity, GetBlockTxnDifferentialIndexWireBytes) +{ + BlockTransactionsRequest req; + req.blockhash = uint256::ZERO; + req.indexes = {0u, 2u, 6u, 10u, 11u}; + + PackStream ps; + req.Serialize(ps); + const std::vector wire = drain(ps); + + // 32 (blockhash) + 1 (count) + 5 (single-byte diffs) = 38 bytes. + ASSERT_EQ(wire.size(), 38u); + for (int i = 0; i < 32; ++i) + EXPECT_EQ(wire[i], 0x00) << "blockhash byte " << i; + EXPECT_EQ(wire[32], 0x05); // CompactSize(5) + const uint8_t expect_diffs[5] = {0x00, 0x01, 0x03, 0x03, 0x00}; + for (int i = 0; i < 5; ++i) + EXPECT_EQ(wire[33 + i], expect_diffs[i]) << "diff " << i; +} + +// Decoding the differential stream reconstructs the ABSOLUTE index list and the +// blockhash unchanged -- the inverse of the encoding above. +TEST(DgbCompactBlocksBip152Parity, GetBlockTxnDifferentialIndexRoundTrip) +{ + BlockTransactionsRequest req; + req.blockhash = uint256S( + "00000000000000000000000000000000000000000000000000000000deadbeef"); + req.indexes = {0u, 2u, 6u, 10u, 11u}; + + PackStream ps; + req.Serialize(ps); + std::vector bytes; + { + const std::byte* p = ps.data(); + bytes.assign(reinterpret_cast(p), + reinterpret_cast(p) + ps.size()); + } + + BlockTransactionsRequest got; + PackStream rs(bytes); + got.Unserialize(rs); + + EXPECT_EQ(got.blockhash, req.blockhash); + EXPECT_EQ(got.indexes, req.indexes); +} + +// Edge cases: the first index is absolute (not delta-encoded), and an empty +// index list serialises to a bare CompactSize(0). +TEST(DgbCompactBlocksBip152Parity, GetBlockTxnFirstAbsoluteAndEmpty) +{ + { + BlockTransactionsRequest req; + req.blockhash = uint256::ZERO; + req.indexes = {7u}; + PackStream ps; + req.Serialize(ps); + const std::vector wire = drain(ps); + ASSERT_EQ(wire.size(), 34u); // 32 + count(1) + diff(1) + EXPECT_EQ(wire[32], 0x01); // CompactSize(1) + EXPECT_EQ(wire[33], 0x07); // first index is absolute -> 7, not 6 + } + { + BlockTransactionsRequest req; + req.blockhash = uint256::ZERO; + PackStream ps; + req.Serialize(ps); + const std::vector wire = drain(ps); + ASSERT_EQ(wire.size(), 33u); // 32 + count(0) + EXPECT_EQ(wire[32], 0x00); + + BlockTransactionsRequest got; + std::vector bytes( + reinterpret_cast(ps.data()), + reinterpret_cast(ps.data()) + ps.size()); + PackStream rs(bytes); + got.Unserialize(rs); + EXPECT_TRUE(got.indexes.empty()); + } +}