Skip to content

Commit dfbd013

Browse files
committed
Merge #537: Test loadtxoutset RPC method
913d526 Test loadtxoutset RPC method (Abeeujah) Pull request description: Testing loadtxoutset on regtest requires a deterministic chain that produces a state matching the hardcoded assumeutxo entry in Bitcoin Core’s [chainparams](https://github.com/bitcoin/bitcoin/blob/2b6af628b140dc9d415733e5dc1577f3e54819af/src/kernel/chainparams.cpp#L622-L626) . Without this alignment, a standard dump-and-load cycle on a random regtest chain will always be rejected. Replicate Bitcoin Core's C++ TestChain100Setup deterministic chain (mocktime 1598887952, P2PK coinbase to compressed pubkey of private key 0x01, 110 blocks) so the resulting block hash matches the assumeutxo entry. Dump the UTXO set from the miner node, feed headers to a fresh node via submitheader, then load the snapshot and verify the model. Wire the load_tx_out_set client macro into v27 through v30, which were missing the call. closes #313 ACKs for top commit: tcharding: ACK 913d526 Tree-SHA512: 77271257d4fd4f889b29b387dcaf314970715f45d7fc15da66c32d07ae71d66322f5d767ef5dddff247c6a669b099cb2b8dd543d0c7af4cfab178542d251c50e
2 parents 0b8bea9 + 913d526 commit dfbd013

5 files changed

Lines changed: 100 additions & 0 deletions

File tree

client/src/client_sync/v27/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ crate::impl_client_v17__get_tx_out_proof!();
5454
crate::impl_client_v26__get_tx_out_set_info!();
5555
crate::impl_client_v24__get_tx_spending_prevout!();
5656
crate::impl_client_v26__import_mempool!();
57+
crate::impl_client_v26__load_tx_out_set!();
5758
crate::impl_client_v17__precious_block!();
5859
crate::impl_client_v17__prune_blockchain!();
5960
crate::impl_client_v23__save_mempool!();

client/src/client_sync/v28/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ crate::impl_client_v17__get_tx_out_proof!();
5555
crate::impl_client_v26__get_tx_out_set_info!();
5656
crate::impl_client_v24__get_tx_spending_prevout!();
5757
crate::impl_client_v26__import_mempool!();
58+
crate::impl_client_v26__load_tx_out_set!();
5859
crate::impl_client_v17__precious_block!();
5960
crate::impl_client_v17__prune_blockchain!();
6061
crate::impl_client_v23__save_mempool!();

client/src/client_sync/v29/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ crate::impl_client_v17__get_tx_out_proof!();
5959
crate::impl_client_v26__get_tx_out_set_info!();
6060
crate::impl_client_v24__get_tx_spending_prevout!();
6161
crate::impl_client_v26__import_mempool!();
62+
crate::impl_client_v26__load_tx_out_set!();
6263
crate::impl_client_v17__precious_block!();
6364
crate::impl_client_v17__prune_blockchain!();
6465
crate::impl_client_v23__save_mempool!();

client/src/client_sync/v30/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ crate::impl_client_v17__get_tx_out_proof!();
5757
crate::impl_client_v26__get_tx_out_set_info!();
5858
crate::impl_client_v24__get_tx_spending_prevout!();
5959
crate::impl_client_v26__import_mempool!();
60+
crate::impl_client_v26__load_tx_out_set!();
6061
crate::impl_client_v17__precious_block!();
6162
crate::impl_client_v17__prune_blockchain!();
6263
crate::impl_client_v23__save_mempool!();

integration_test/tests/blockchain.rs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,102 @@ fn blockchain__dump_tx_out_set__modelled() {
3434
assert!(dump.coins_written.to_sat() > 0);
3535
}
3636

37+
#[test]
38+
#[cfg(not(feature = "v25_and_below"))]
39+
fn blockchain__load_tx_out_set__modelled() {
40+
// Regtest `loadtxoutset` requires replicating Bitcoin Core's C++
41+
// TestChain100Setup to produce the exact chain whose height at `110`
42+
// assumeutxo snapshot hash is hardcoded in chainparams.
43+
//
44+
// The C++ test framework uses:
45+
// - SetMockTime(1598887952), incrementing by 1 after each block
46+
// - P2PK coinbase output to compressed pubkey of private key 0x01
47+
// - 110 coinbase-only blocks (no wallet transactions)
48+
//
49+
// Matching these exactly helps us arrive at the expected hash at snapshot
50+
51+
#[cfg(feature = "v29_and_below")]
52+
let expected_block_hash = "696e92821f65549c7ee134edceeeeaaa4105647a3c4fd9f298c0aec0ab50425c";
53+
#[cfg(not(feature = "v29_and_below"))]
54+
let expected_block_hash = "6affe030b7965ab538f820a56ef56c8149b7dc1d1c144af57113be080db7c397";
55+
56+
let snapshot_height = 110;
57+
58+
// Compressed public key for private key 0x01
59+
let coinbase_descriptor =
60+
"pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)";
61+
62+
let exe = node::exe_path().expect("failed to get bitcoind executable");
63+
let mut conf_a = node::Conf::default();
64+
conf_a.p2p = node::P2P::Yes;
65+
let node_a = Node::with_conf(&exe, &conf_a).expect("failed to create miner node");
66+
67+
// TestChain100Setup mocktime matched to the exact
68+
const MOCK_TIME_START: u64 = 1598887952;
69+
for i in 0..snapshot_height {
70+
let mock_time = MOCK_TIME_START + i;
71+
let _: node::serde_json::Value = node_a
72+
.client
73+
.call("setmocktime", &[node::serde_json::json!(mock_time)])
74+
.expect("setmocktime");
75+
node_a.client.generate_to_descriptor(1, coinbase_descriptor).expect("generatetodescriptor");
76+
}
77+
78+
let hash_at_height = node_a
79+
.client
80+
.get_block_hash(snapshot_height)
81+
.expect("getblockhash")
82+
.block_hash()
83+
.expect("parse block hash");
84+
assert_eq!(
85+
hash_at_height.to_string(),
86+
expected_block_hash,
87+
"block hash at height {} does not match hardcoded assumeutxo entry",
88+
snapshot_height
89+
);
90+
91+
let temp_path = integration_test::random_tmp_file();
92+
let dump_path = temp_path.to_str().expect("temp path should be valid UTF-8");
93+
#[cfg(feature = "v28_and_below")]
94+
{
95+
let _: DumpTxOutSet = node_a.client.dump_tx_out_set(dump_path).expect("dumptxoutset");
96+
}
97+
#[cfg(not(feature = "v28_and_below"))]
98+
{
99+
let _: DumpTxOutSet =
100+
node_a.client.dump_tx_out_set(dump_path, "latest").expect("dumptxoutset");
101+
}
102+
let mut conf_b = node::Conf::default();
103+
conf_b.wallet = None;
104+
conf_b.p2p = node::P2P::No;
105+
let node_b = Node::with_conf(&exe, &conf_b).expect("failed to create loader node");
106+
107+
for h in 1..=snapshot_height {
108+
let bh = node_a
109+
.client
110+
.get_block_hash(h)
111+
.expect("getblockhash")
112+
.block_hash()
113+
.expect("parse block hash");
114+
let header = node_a
115+
.client
116+
.get_block_header(&bh)
117+
.expect("getblockheader")
118+
.block_header()
119+
.expect("parse block header");
120+
node_b.client.submit_header(&header).expect("submitheader");
121+
}
122+
123+
let json: LoadTxOutSet =
124+
node_b.client.load_tx_out_set(dump_path).expect("loadtxoutset should succeed");
125+
let model: Result<mtype::LoadTxOutSet, LoadTxOutSetError> = json.into_model();
126+
let model = model.unwrap();
127+
128+
assert_eq!(model.base_height, snapshot_height as u32);
129+
assert_eq!(model.tip_hash, hash_at_height);
130+
assert_eq!(model.coins_loaded, bitcoin::Amount::from_btc(110.0).unwrap());
131+
}
132+
37133
#[test]
38134
fn blockchain__get_best_block_hash__modelled() {
39135
let node = Node::with_wallet(Wallet::None, &[]);

0 commit comments

Comments
 (0)