From ebf09f48a2c274d69a5a34f77777860e7aa41387 Mon Sep 17 00:00:00 2001 From: frstrtr Date: Thu, 25 Jun 2026 08:54:08 +0000 Subject: [PATCH] dgb: parity KAT for GBT prevhash formatter u256_be_display_hex Pin coin/hash_format.hpp u256_be_display_hex (the most-significant-limb-first 64-hex render of the LE u256 that both the stratum work source and the embedded work path emit for the GBT "previousblockhash") against a non-circular oracle anchor: the DGB mainnet genesis hash pinned at p2pool-dgb-scrypt bitcoin/networks/digibyte.py:55. The test reverses that external display constant into little-endian storage bytes, feeds u256::from_le_bytes (the path a parsed tip hash takes), and asserts the formatter reproduces the canonical display string byte for byte. Additional cases pin zero/one left-padding, most-significant-limb-first limb ordering, all-ones, and the from_le_bytes byte reversal. Diagnostics / wire-display only: no consensus surface, no payout/subsidy/ version-gate value change. Header-only test (links only GTest); no production call site rewired. Fenced to src/impl/dgb/. Added to both build.yml --target allowlists (#143 NOT_BUILT trap). --- .github/workflows/build.yml | 4 +- src/impl/dgb/test/CMakeLists.txt | 15 ++ src/impl/dgb/test/hash_format_parity_test.cpp | 132 ++++++++++++++++++ 3 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 src/impl/dgb/test/hash_format_parity_test.cpp diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1d58010c7..f74bb16d3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -84,7 +84,7 @@ jobs: 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 \ 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 \ + 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 dgb_hash_format_parity_test v37_test \ -j$(nproc) - name: Run tests @@ -216,7 +216,7 @@ jobs: 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 \ 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 \ + 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 dgb_hash_format_parity_test test_coin_broadcaster test_multiaddress_pplns test_pplns_stress \ v37_test \ -j$(nproc) diff --git a/src/impl/dgb/test/CMakeLists.txt b/src/impl/dgb/test/CMakeLists.txt index 89e2d9736..49d2cdbff 100644 --- a/src/impl/dgb/test/CMakeLists.txt +++ b/src/impl/dgb/test/CMakeLists.txt @@ -760,6 +760,21 @@ if (BUILD_TESTING AND GTest_FOUND) target_link_libraries(dgb_binomial_conf_interval_test PRIVATE GTest::gtest_main GTest::gtest) gtest_add_tests(dgb_binomial_conf_interval_test "" AUTO) + # dgb_hash_format_parity_test: FENCED, additive KAT pinning the pure GBT + # block-hash display formatter coin/hash_format.hpp u256_be_display_hex + # (most-significant-limb-first 64-hex render of the LE u256, the encoding + # both work paths emit for "previousblockhash"). NON-CIRCULAR anchor = the + # DGB mainnet genesis hash pinned at p2pool-dgb-scrypt + # bitcoin/networks/digibyte.py:55; the test reverses that external display + # constant into LE storage bytes, feeds u256::from_le_bytes, and asserts the + # formatter reproduces it byte for byte. DIAGNOSTICS/wire-display only, no + # consensus surface. Pure header (/) -> links only GTest. + # No call site rewired. MUST also be in the build.yml --target allowlist + # (#143 NOT_BUILT trap). + add_executable(dgb_hash_format_parity_test hash_format_parity_test.cpp) + target_link_libraries(dgb_hash_format_parity_test PRIVATE + GTest::gtest_main GTest::gtest) + gtest_add_tests(dgb_hash_format_parity_test "" AUTO) # dgb_desired_version_tally_test: FENCED, additive KAT pinning the # version->work / version->count accumulation core lifted into # coin/desired_version_tally.hpp vs the p2pool-dgb-scrypt data.py:918-922 diff --git a/src/impl/dgb/test/hash_format_parity_test.cpp b/src/impl/dgb/test/hash_format_parity_test.cpp new file mode 100644 index 000000000..8d8a44327 --- /dev/null +++ b/src/impl/dgb/test/hash_format_parity_test.cpp @@ -0,0 +1,132 @@ +// --------------------------------------------------------------------------- +// dgb_hash_format_parity_test -- FENCED, additive KAT pinning the pure GBT +// block-hash display formatter dgb::coin::u256_be_display_hex lifted into +// coin/hash_format.hpp. +// +// u256_be_display_hex renders a 256-bit value (coin/dgb_arith256.hpp u256, a +// little-endian limb array with limb[0] least-significant) as the +// GBT-conventional big-endian display hex: most-significant limb first, 64 +// lowercase hex digits, no 0x prefix -- the encoding the stratum work source +// and the embedded work path both emit for "previousblockhash". A 1-nibble +// divergence in limb/byte ordering there means the two callers of +// build_work_template could emit a prevhash the parent daemon never accepts. +// +// The anchor is NON-CIRCULAR: the DigiByte mainnet genesis block hash +// 7497ea1b465eb39f1c8f507bc877078fe016d6fcb6dfad3a64c98dcc6e1e8496 is the +// external constant the DGB oracle pins at frstrtr/p2pool-dgb-scrypt +// bitcoin/networks/digibyte.py:55 (helper.check_block_header(..., 'genesis')). +// The test parses that literal display hex into 32 MSB-first bytes with an +// independent hex reader, reverses to the on-the-wire little-endian byte order, +// feeds u256::from_le_bytes (the exact storage path a real tip hash takes), and +// asserts u256_be_display_hex reproduces the original oracle string byte for +// byte. The formatter under test is the only code computing the output. +// +// DIAGNOSTICS / WIRE-DISPLAY ONLY: no consensus surface, no payout/subsidy/ +// version-gate value. Pure header (/) -> links only GTest. No +// call site rewired. MUST also be in BOTH build.yml --target allowlists +// (#143 NOT_BUILT trap). +// --------------------------------------------------------------------------- + +#include + +#include +#include +#include + +#include "../coin/hash_format.hpp" + +using dgb::coin::u256; +using dgb::coin::u256_be_display_hex; + +namespace { + +// Independent hex reader (does NOT share the formatter's nibble/limb logic): +// parse a 64-char big-endian display hex string into 32 MSB-first bytes. +std::array be_hex_to_msb_bytes(const std::string& hex) +{ + EXPECT_EQ(hex.size(), 64u); + auto nib = [](char c) -> int { + if (c >= '0' && c <= '9') return c - '0'; + if (c >= 'a' && c <= 'f') return c - 'a' + 10; + if (c >= 'A' && c <= 'F') return c - 'A' + 10; + return -1; + }; + std::array out{}; + for (int i = 0; i < 32; ++i) + out[i] = (unsigned char)((nib(hex[2 * i]) << 4) | nib(hex[2 * i + 1])); + return out; +} + +// Build a u256 from a big-endian display hex string via the production +// from_le_bytes path: reverse MSB-first display bytes into little-endian +// storage order (the byte order a parsed tip hash actually arrives in). +u256 u256_from_display_hex(const std::string& hex) +{ + auto msb = be_hex_to_msb_bytes(hex); + unsigned char le[32]; + for (int i = 0; i < 32; ++i) le[i] = msb[31 - i]; + return u256::from_le_bytes(le); +} + +} // namespace + +// Oracle anchor: the DGB mainnet genesis hash (digibyte.py:55) must round-trip +// through the LE storage path back to its canonical display hex. +TEST(DgbHashFormatParity, GenesisHashRendersOracleDisplayHex) +{ + const std::string genesis = + "7497ea1b465eb39f1c8f507bc877078fe016d6fcb6dfad3a64c98dcc6e1e8496"; + EXPECT_EQ(u256_be_display_hex(u256_from_display_hex(genesis)), genesis); +} + +// Zero renders 64 zero nibbles -- no truncation, full left-padding. +TEST(DgbHashFormatParity, ZeroRendersSixtyFourZeros) +{ + EXPECT_EQ(u256_be_display_hex(u256{}), std::string(64, '0')); +} + +// A tiny value pins the left zero-padding and that limb[0] is the LOW nibble. +TEST(DgbHashFormatParity, OneRendersPaddedToSixtyFourDigits) +{ + u256 v; + v.limb[0] = 1; + EXPECT_EQ(u256_be_display_hex(v), std::string(63, '0') + "1"); +} + +// Distinct per-limb values pin most-significant-limb-FIRST ordering (limb[3] +// is the leading 16 hex digits, limb[0] the trailing 16). +TEST(DgbHashFormatParity, LimbOrderingMostSignificantFirst) +{ + u256 v; + v.limb[3] = 0x0123456789abcdefULL; + v.limb[2] = 0xfedcba9876543210ULL; + v.limb[1] = 0x00000000ffffffffULL; + v.limb[0] = 0xdeadbeefcafef00dULL; + EXPECT_EQ(u256_be_display_hex(v), + "0123456789abcdef" + "fedcba9876543210" + "00000000ffffffff" + "deadbeefcafef00d"); +} + +// All-ones renders 64 'f' nibbles (every limb fully populated). +TEST(DgbHashFormatParity, AllOnesRendersSixtyFourEffs) +{ + u256 v; + for (int i = 0; i < 4; ++i) v.limb[i] = 0xffffffffffffffffULL; + EXPECT_EQ(u256_be_display_hex(v), std::string(64, 'f')); +} + +// from_le_bytes with a fully-distinct 0x00..0x1f pattern pins the byte +// reversal: LE byte 0 (0x00) is the LOW byte -> trailing "00", LE byte 31 +// (0x1f) is the HIGH byte -> leading "1f". +TEST(DgbHashFormatParity, FromLeBytesByteReversalIsBigEndianDisplay) +{ + unsigned char le[32]; + for (int i = 0; i < 32; ++i) le[i] = (unsigned char)i; + EXPECT_EQ(u256_be_display_hex(u256::from_le_bytes(le)), + "1f1e1d1c1b1a1918" + "1716151413121110" + "0f0e0d0c0b0a0908" + "0706050403020100"); +}