Skip to content

Commit 5ef41ff

Browse files
authored
Merge pull request #478 from frstrtr/dgb/hash-format-parity-kat
dgb: parity KAT for GBT prevhash formatter u256_be_display_hex
2 parents 5107ce3 + ebf09f4 commit 5ef41ff

3 files changed

Lines changed: 149 additions & 2 deletions

File tree

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ jobs:
8484
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 \
8585
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 \
8686
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
87-
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 \
87+
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 \
8888
-j$(nproc)
8989
9090
- name: Run tests
@@ -216,7 +216,7 @@ jobs:
216216
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 \
217217
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 \
218218
rpc_request_test softfork_check_test genesis_check_test algo_select_test digishield_walk_test header_chain_test \
219-
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 \
219+
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 \
220220
v37_test \
221221
-j$(nproc)
222222

src/impl/dgb/test/CMakeLists.txt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,21 @@ if (BUILD_TESTING AND GTest_FOUND)
760760
target_link_libraries(dgb_binomial_conf_interval_test PRIVATE
761761
GTest::gtest_main GTest::gtest)
762762
gtest_add_tests(dgb_binomial_conf_interval_test "" AUTO)
763+
# dgb_hash_format_parity_test: FENCED, additive KAT pinning the pure GBT
764+
# block-hash display formatter coin/hash_format.hpp u256_be_display_hex
765+
# (most-significant-limb-first 64-hex render of the LE u256, the encoding
766+
# both work paths emit for "previousblockhash"). NON-CIRCULAR anchor = the
767+
# DGB mainnet genesis hash pinned at p2pool-dgb-scrypt
768+
# bitcoin/networks/digibyte.py:55; the test reverses that external display
769+
# constant into LE storage bytes, feeds u256::from_le_bytes, and asserts the
770+
# formatter reproduces it byte for byte. DIAGNOSTICS/wire-display only, no
771+
# consensus surface. Pure header (<cstdint>/<string>) -> links only GTest.
772+
# No call site rewired. MUST also be in the build.yml --target allowlist
773+
# (#143 NOT_BUILT trap).
774+
add_executable(dgb_hash_format_parity_test hash_format_parity_test.cpp)
775+
target_link_libraries(dgb_hash_format_parity_test PRIVATE
776+
GTest::gtest_main GTest::gtest)
777+
gtest_add_tests(dgb_hash_format_parity_test "" AUTO)
763778
# dgb_desired_version_tally_test: FENCED, additive KAT pinning the
764779
# version->work / version->count accumulation core lifted into
765780
# coin/desired_version_tally.hpp vs the p2pool-dgb-scrypt data.py:918-922
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// ---------------------------------------------------------------------------
2+
// dgb_hash_format_parity_test -- FENCED, additive KAT pinning the pure GBT
3+
// block-hash display formatter dgb::coin::u256_be_display_hex lifted into
4+
// coin/hash_format.hpp.
5+
//
6+
// u256_be_display_hex renders a 256-bit value (coin/dgb_arith256.hpp u256, a
7+
// little-endian limb array with limb[0] least-significant) as the
8+
// GBT-conventional big-endian display hex: most-significant limb first, 64
9+
// lowercase hex digits, no 0x prefix -- the encoding the stratum work source
10+
// and the embedded work path both emit for "previousblockhash". A 1-nibble
11+
// divergence in limb/byte ordering there means the two callers of
12+
// build_work_template could emit a prevhash the parent daemon never accepts.
13+
//
14+
// The anchor is NON-CIRCULAR: the DigiByte mainnet genesis block hash
15+
// 7497ea1b465eb39f1c8f507bc877078fe016d6fcb6dfad3a64c98dcc6e1e8496 is the
16+
// external constant the DGB oracle pins at frstrtr/p2pool-dgb-scrypt
17+
// bitcoin/networks/digibyte.py:55 (helper.check_block_header(..., 'genesis')).
18+
// The test parses that literal display hex into 32 MSB-first bytes with an
19+
// independent hex reader, reverses to the on-the-wire little-endian byte order,
20+
// feeds u256::from_le_bytes (the exact storage path a real tip hash takes), and
21+
// asserts u256_be_display_hex reproduces the original oracle string byte for
22+
// byte. The formatter under test is the only code computing the output.
23+
//
24+
// DIAGNOSTICS / WIRE-DISPLAY ONLY: no consensus surface, no payout/subsidy/
25+
// version-gate value. Pure header (<cstdint>/<string>) -> links only GTest. No
26+
// call site rewired. MUST also be in BOTH build.yml --target allowlists
27+
// (#143 NOT_BUILT trap).
28+
// ---------------------------------------------------------------------------
29+
30+
#include <gtest/gtest.h>
31+
32+
#include <array>
33+
#include <cstdint>
34+
#include <string>
35+
36+
#include "../coin/hash_format.hpp"
37+
38+
using dgb::coin::u256;
39+
using dgb::coin::u256_be_display_hex;
40+
41+
namespace {
42+
43+
// Independent hex reader (does NOT share the formatter's nibble/limb logic):
44+
// parse a 64-char big-endian display hex string into 32 MSB-first bytes.
45+
std::array<unsigned char, 32> be_hex_to_msb_bytes(const std::string& hex)
46+
{
47+
EXPECT_EQ(hex.size(), 64u);
48+
auto nib = [](char c) -> int {
49+
if (c >= '0' && c <= '9') return c - '0';
50+
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
51+
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
52+
return -1;
53+
};
54+
std::array<unsigned char, 32> out{};
55+
for (int i = 0; i < 32; ++i)
56+
out[i] = (unsigned char)((nib(hex[2 * i]) << 4) | nib(hex[2 * i + 1]));
57+
return out;
58+
}
59+
60+
// Build a u256 from a big-endian display hex string via the production
61+
// from_le_bytes path: reverse MSB-first display bytes into little-endian
62+
// storage order (the byte order a parsed tip hash actually arrives in).
63+
u256 u256_from_display_hex(const std::string& hex)
64+
{
65+
auto msb = be_hex_to_msb_bytes(hex);
66+
unsigned char le[32];
67+
for (int i = 0; i < 32; ++i) le[i] = msb[31 - i];
68+
return u256::from_le_bytes(le);
69+
}
70+
71+
} // namespace
72+
73+
// Oracle anchor: the DGB mainnet genesis hash (digibyte.py:55) must round-trip
74+
// through the LE storage path back to its canonical display hex.
75+
TEST(DgbHashFormatParity, GenesisHashRendersOracleDisplayHex)
76+
{
77+
const std::string genesis =
78+
"7497ea1b465eb39f1c8f507bc877078fe016d6fcb6dfad3a64c98dcc6e1e8496";
79+
EXPECT_EQ(u256_be_display_hex(u256_from_display_hex(genesis)), genesis);
80+
}
81+
82+
// Zero renders 64 zero nibbles -- no truncation, full left-padding.
83+
TEST(DgbHashFormatParity, ZeroRendersSixtyFourZeros)
84+
{
85+
EXPECT_EQ(u256_be_display_hex(u256{}), std::string(64, '0'));
86+
}
87+
88+
// A tiny value pins the left zero-padding and that limb[0] is the LOW nibble.
89+
TEST(DgbHashFormatParity, OneRendersPaddedToSixtyFourDigits)
90+
{
91+
u256 v;
92+
v.limb[0] = 1;
93+
EXPECT_EQ(u256_be_display_hex(v), std::string(63, '0') + "1");
94+
}
95+
96+
// Distinct per-limb values pin most-significant-limb-FIRST ordering (limb[3]
97+
// is the leading 16 hex digits, limb[0] the trailing 16).
98+
TEST(DgbHashFormatParity, LimbOrderingMostSignificantFirst)
99+
{
100+
u256 v;
101+
v.limb[3] = 0x0123456789abcdefULL;
102+
v.limb[2] = 0xfedcba9876543210ULL;
103+
v.limb[1] = 0x00000000ffffffffULL;
104+
v.limb[0] = 0xdeadbeefcafef00dULL;
105+
EXPECT_EQ(u256_be_display_hex(v),
106+
"0123456789abcdef"
107+
"fedcba9876543210"
108+
"00000000ffffffff"
109+
"deadbeefcafef00d");
110+
}
111+
112+
// All-ones renders 64 'f' nibbles (every limb fully populated).
113+
TEST(DgbHashFormatParity, AllOnesRendersSixtyFourEffs)
114+
{
115+
u256 v;
116+
for (int i = 0; i < 4; ++i) v.limb[i] = 0xffffffffffffffffULL;
117+
EXPECT_EQ(u256_be_display_hex(v), std::string(64, 'f'));
118+
}
119+
120+
// from_le_bytes with a fully-distinct 0x00..0x1f pattern pins the byte
121+
// reversal: LE byte 0 (0x00) is the LOW byte -> trailing "00", LE byte 31
122+
// (0x1f) is the HIGH byte -> leading "1f".
123+
TEST(DgbHashFormatParity, FromLeBytesByteReversalIsBigEndianDisplay)
124+
{
125+
unsigned char le[32];
126+
for (int i = 0; i < 32; ++i) le[i] = (unsigned char)i;
127+
EXPECT_EQ(u256_be_display_hex(u256::from_le_bytes(le)),
128+
"1f1e1d1c1b1a1918"
129+
"1716151413121110"
130+
"0f0e0d0c0b0a0908"
131+
"0706050403020100");
132+
}

0 commit comments

Comments
 (0)