Skip to content

Commit 672c85c

Browse files
committed
Merge bitcoin/bitcoin#32868: test: refactor: overhaul block hash determination for CBlock{,Header} objects
5fa3495 test: avoid unneeded block header hash -> integer conversions (Sebastian Falbesoner) 2118301 test: rename CBlockHeader `.hash` -> `.hash_hex` for consistency (Sebastian Falbesoner) 23be0ec test: rename CBlockHeader `.rehash()`/`.sha256` -> `.hash_int` for consistency (Sebastian Falbesoner) 8b09cc3 test: remove bare CBlockHeader `.rehash()`/`.calc_sha256()` calls (Sebastian Falbesoner) 0716382 test: remove header hash caching in CBlockHeader class (Sebastian Falbesoner) 0f044e8 test: avoid direct block header modification in feature_block.py (Sebastian Falbesoner) f3c791d test: refactor: dedup `CBlockHeader` serialization (Sebastian Falbesoner) Pull request description: Similar to what #32421 did for `CTransaction` instances, this PR aims to improve the block hash determination of `CBlockHeader`/`CBlock` (the latter is a subclass of the former) instances by removing the block header caching mechanism and introducing consistent naming. Without the statefulness, sneaky testing bugs like #32742 and #32823 are less likely to happen in the future. Note that performance is even less of an issue here compared to `CTransaction`, as we only need to hash 80 bytes, which is less than typical standard transaction sizes [2]. The only instance where the testing logic was relying on caching (i.e. we want to return an outdated value) is tackled in the second commit, the rest should be straight-forward to review, especially for contributors who already reviewed #32421. Summary table showing block hash determaination before/after this PR: | Task | master | PR | |:-----------------------------------|:-------------------------|:-------------| | get block header hash (hex string) | `.hash`[1] | `.hash_hex` | | get block header hash (integer) | `rehash()`, `.sha256`[1] | `.hash_int` | [1] = returned value might be `None` or out-of-date, if rehashing function wasn't called after modification [2] = the only exception I could think of are transaction with pay-to-anchor (P2A) outputs ACKs for top commit: rkrux: re-ACK 5fa3495 modulo failing CI due to silent merge conflict. maflcko: re-ACK 5fa3495 🎩 danielabrozzoni: reACK 5fa3495 Tree-SHA512: 3d13540012654effa063846958a3166d56c1bcb58e1321f52ca4d5c3bcb7abdea72c54d1fb566d04e636d84d06a41d293e16232dbe5d5e78a73c903bb6ffc80d
2 parents 9f713b8 + 5fa3495 commit 672c85c

39 files changed

+241
-278
lines changed

contrib/signet/miner

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ def finish_block(block, signet_solution, grind_cmd):
9595
newheadhex = subprocess.run(cmd, stdout=subprocess.PIPE, input=b"", check=True).stdout.strip()
9696
newhead = from_hex(CBlockHeader(), newheadhex.decode('utf8'))
9797
block.nNonce = newhead.nNonce
98-
block.rehash()
9998
return block
10099

101100
def new_block(tmpl, reward_spk, *, blocktime=None, poolid=None):
@@ -482,15 +481,15 @@ def do_generate(args):
482481
# report
483482
bstr = "block" if gen.is_mine else "backup block"
484483

485-
next_delta = gen.next_block_delta(block.nBits, block.hash)
484+
next_delta = gen.next_block_delta(block.nBits, block.hash_hex)
486485
next_delta += block.nTime - time.time()
487-
next_is_mine = gen.next_block_is_mine(block.hash)
486+
next_is_mine = gen.next_block_is_mine(block.hash_hex)
488487

489-
logging.debug("Block hash %s payout to %s", block.hash, reward_addr)
488+
logging.debug("Block hash %s payout to %s", block.hash_hex, reward_addr)
490489
logging.info("Mined %s at height %d; next in %s (%s)", bstr, tmpl["height"], seconds_to_hms(next_delta), ("mine" if next_is_mine else "backup"))
491490
if r != "":
492-
logging.warning("submitblock returned %s for height %d hash %s", r, tmpl["height"], block.hash)
493-
lastheader = block.hash
491+
logging.warning("submitblock returned %s for height %d hash %s", r, tmpl["height"], block.hash_hex)
492+
lastheader = block.hash_hex
494493

495494
def do_calibrate(args):
496495
if args.nbits is not None and args.seconds is not None:

test/functional/example_test.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ def on_block(self, message):
5757
"""Override the standard on_block callback
5858
5959
Store the hash of a received block in the dictionary."""
60-
message.block.calc_sha256()
61-
self.block_receive_map[message.block.sha256] += 1
60+
self.block_receive_map[message.block.hash_int] += 1
6261

6362
def on_inv(self, message):
6463
"""Override the standard on_inv callback"""
@@ -182,7 +181,7 @@ def run_test(self):
182181
block_message = msg_block(block)
183182
# Send message is used to send a P2P message to the node over our P2PInterface
184183
peer_messaging.send_without_ping(block_message)
185-
self.tip = block.sha256
184+
self.tip = block.hash_int
186185
blocks.append(self.tip)
187186
self.block_time += 1
188187
height += 1

test/functional/feature_assumeutxo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ def test_snapshot_not_on_most_work_chain(self, dump_output_path):
272272
block_time = node0.getblock(node0.getbestblockhash())['time'] + 1
273273
fork_block1 = create_block(int(parent_block_hash, 16), create_coinbase(SNAPSHOT_BASE_HEIGHT), block_time)
274274
fork_block1.solve()
275-
fork_block2 = create_block(fork_block1.sha256, create_coinbase(SNAPSHOT_BASE_HEIGHT + 1), block_time + 1)
275+
fork_block2 = create_block(fork_block1.hash_int, create_coinbase(SNAPSHOT_BASE_HEIGHT + 1), block_time + 1)
276276
fork_block2.solve()
277277
node1.submitheader(fork_block1.serialize().hex())
278278
node1.submitheader(fork_block2.serialize().hex())

test/functional/feature_assumevalid.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,15 +103,15 @@ def run_test(self):
103103
block.solve()
104104
# Save the coinbase for later
105105
self.block1 = block
106-
self.tip = block.sha256
106+
self.tip = block.hash_int
107107
height += 1
108108

109109
# Bury the block 100 deep so the coinbase output is spendable
110110
for _ in range(100):
111111
block = create_block(self.tip, create_coinbase(height), self.block_time)
112112
block.solve()
113113
self.blocks.append(block)
114-
self.tip = block.sha256
114+
self.tip = block.hash_int
115115
self.block_time += 1
116116
height += 1
117117

@@ -124,7 +124,7 @@ def run_test(self):
124124
self.block_time += 1
125125
block102.solve()
126126
self.blocks.append(block102)
127-
self.tip = block102.sha256
127+
self.tip = block102.hash_int
128128
self.block_time += 1
129129
height += 1
130130

@@ -133,13 +133,13 @@ def run_test(self):
133133
block = create_block(self.tip, create_coinbase(height), self.block_time)
134134
block.solve()
135135
self.blocks.append(block)
136-
self.tip = block.sha256
136+
self.tip = block.hash_int
137137
self.block_time += 1
138138
height += 1
139139

140140
# Start node1 and node2 with assumevalid so they accept a block with a bad signature.
141-
self.start_node(1, extra_args=["-assumevalid=" + block102.hash])
142-
self.start_node(2, extra_args=["-assumevalid=" + block102.hash])
141+
self.start_node(1, extra_args=["-assumevalid=" + block102.hash_hex])
142+
self.start_node(2, extra_args=["-assumevalid=" + block102.hash_hex])
143143

144144
p2p0 = self.nodes[0].add_p2p_connection(BaseNode())
145145
p2p0.send_header_for_blocks(self.blocks[0:2000])

test/functional/feature_bip68_sequence.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ def test_nonzero_locks(orig_tx, node, relayfee, use_height_lock):
327327
for i in range(2):
328328
block = create_block(tmpl=tmpl, ntime=cur_time)
329329
block.solve()
330-
tip = block.sha256
330+
tip = block.hash_int
331331
assert_equal(None if i == 1 else 'inconclusive', self.nodes[0].submitblock(block.serialize().hex()))
332332
tmpl = self.nodes[0].getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
333333
tmpl['previousblockhash'] = '%x' % tip
@@ -383,7 +383,7 @@ def test_bip68_not_consensus(self):
383383
block.solve()
384384

385385
assert_equal(None, self.nodes[0].submitblock(block.serialize().hex()))
386-
assert_equal(self.nodes[0].getbestblockhash(), block.hash)
386+
assert_equal(self.nodes[0].getbestblockhash(), block.hash_hex)
387387

388388
def activateCSV(self):
389389
# activation should happen at block height 432 (3 periods)

test/functional/feature_block.py

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ def run_test(self):
280280
self.send_blocks([b12, b13, b14], success=False, reject_reason='bad-cb-amount', reconnect=True)
281281

282282
# New tip should be b13.
283-
assert_equal(node.getbestblockhash(), b13.hash)
283+
assert_equal(node.getbestblockhash(), b13.hash_hex)
284284

285285
# Add a block with MAX_BLOCK_SIGOPS and one with one more sigop
286286
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3)
@@ -609,32 +609,32 @@ def run_test(self):
609609
# The next few blocks are going to be created "by hand" since they'll do funky things, such as having
610610
# the first transaction be non-coinbase, etc. The purpose of b44 is to make sure this works.
611611
self.log.info("Build block 44 manually")
612-
height = self.block_heights[self.tip.sha256] + 1
612+
height = self.block_heights[self.tip.hash_int] + 1
613613
coinbase = create_coinbase(height, self.coinbase_pubkey)
614614
b44 = CBlock()
615615
b44.nTime = self.tip.nTime + 1
616-
b44.hashPrevBlock = self.tip.sha256
616+
b44.hashPrevBlock = self.tip.hash_int
617617
b44.nBits = REGTEST_N_BITS
618618
b44.vtx.append(coinbase)
619619
tx = self.create_and_sign_transaction(out[14], 1)
620620
b44.vtx.append(tx)
621621
b44.hashMerkleRoot = b44.calc_merkle_root()
622622
b44.solve()
623623
self.tip = b44
624-
self.block_heights[b44.sha256] = height
624+
self.block_heights[b44.hash_int] = height
625625
self.blocks[44] = b44
626626
self.send_blocks([b44], True)
627627

628628
self.log.info("Reject a block with a non-coinbase as the first tx")
629629
non_coinbase = self.create_tx(out[15], 0, 1)
630630
b45 = CBlock()
631631
b45.nTime = self.tip.nTime + 1
632-
b45.hashPrevBlock = self.tip.sha256
632+
b45.hashPrevBlock = self.tip.hash_int
633633
b45.nBits = REGTEST_N_BITS
634634
b45.vtx.append(non_coinbase)
635635
b45.hashMerkleRoot = b45.calc_merkle_root()
636636
b45.solve()
637-
self.block_heights[b45.sha256] = self.block_heights[self.tip.sha256] + 1
637+
self.block_heights[b45.hash_int] = self.block_heights[self.tip.hash_int] + 1
638638
self.tip = b45
639639
self.blocks[45] = b45
640640
self.send_blocks([b45], success=False, reject_reason='bad-cb-missing', reconnect=True)
@@ -643,12 +643,12 @@ def run_test(self):
643643
self.move_tip(44)
644644
b46 = CBlock()
645645
b46.nTime = b44.nTime + 1
646-
b46.hashPrevBlock = b44.sha256
646+
b46.hashPrevBlock = b44.hash_int
647647
b46.nBits = REGTEST_N_BITS
648648
b46.vtx = []
649649
b46.hashMerkleRoot = 0
650650
b46.solve()
651-
self.block_heights[b46.sha256] = self.block_heights[b44.sha256] + 1
651+
self.block_heights[b46.hash_int] = self.block_heights[b44.hash_int] + 1
652652
self.tip = b46
653653
assert 46 not in self.blocks
654654
self.blocks[46] = b46
@@ -658,10 +658,9 @@ def run_test(self):
658658
self.move_tip(44)
659659
b47 = self.next_block(47)
660660
target = uint256_from_compact(b47.nBits)
661-
while b47.sha256 <= target:
661+
while b47.hash_int <= target:
662662
# Rehash nonces until an invalid too-high-hash block is found.
663663
b47.nNonce += 1
664-
b47.rehash()
665664
self.send_blocks([b47], False, force_send=True, reject_reason='high-hash', reconnect=True)
666665

667666
self.log.info("Reject a block with a timestamp >2 hours in the future")
@@ -719,8 +718,7 @@ def run_test(self):
719718
# valid timestamp
720719
self.move_tip(53)
721720
b55 = self.next_block(55, spend=out[15])
722-
b55.nTime = b35.nTime
723-
self.update_block(55, [])
721+
self.update_block(55, [], nTime=b35.nTime)
724722
self.send_blocks([b55], True)
725723
self.save_spendable_output()
726724

@@ -733,10 +731,10 @@ def run_test(self):
733731
self.log.info("Accept a previously rejected future block at a later time")
734732
node.setmocktime(int(time.time()) + 2*60*60)
735733
self.move_tip(48)
736-
self.block_heights[b48.sha256] = self.block_heights[b44.sha256] + 1 # b48 is a parent of b44
734+
self.block_heights[b48.hash_int] = self.block_heights[b44.hash_int] + 1 # b48 is a parent of b44
737735
b48p = self.next_block("48p")
738736
self.send_blocks([b48, b48p], success=True) # Reorg to the longer chain
739-
node.invalidateblock(b48p.hash) # mark b48p as invalid
737+
node.invalidateblock(b48p.hash_hex) # mark b48p as invalid
740738
node.setmocktime(0)
741739

742740
# Test Merkle tree malleability
@@ -780,7 +778,7 @@ def run_test(self):
780778
self.blocks[56] = b56
781779
assert_equal(len(b56.vtx), 3)
782780
b56 = self.update_block(56, [tx1])
783-
assert_equal(b56.hash, b57.hash)
781+
assert_equal(b56.hash_hex, b57.hash_hex)
784782
self.send_blocks([b56], success=False, reject_reason='bad-txns-duplicate', reconnect=True)
785783

786784
# b57p2 - a good block with 6 tx'es, don't submit until end
@@ -798,7 +796,7 @@ def run_test(self):
798796
self.move_tip(55)
799797
b56p2 = copy.deepcopy(b57p2)
800798
self.blocks["b56p2"] = b56p2
801-
assert_equal(b56p2.hash, b57p2.hash)
799+
assert_equal(b56p2.hash_hex, b57p2.hash_hex)
802800
assert_equal(len(b56p2.vtx), 6)
803801
b56p2 = self.update_block("b56p2", [tx3, tx4])
804802
self.send_blocks([b56p2], success=False, reject_reason='bad-txns-duplicate', reconnect=True)
@@ -956,7 +954,7 @@ def run_test(self):
956954
self.move_tip('dup_2')
957955
b64 = CBlock(b64a)
958956
b64.vtx = copy.deepcopy(b64a.vtx)
959-
assert_equal(b64.hash, b64a.hash)
957+
assert_equal(b64.hash_hex, b64a.hash_hex)
960958
assert_equal(b64.get_weight(), MAX_BLOCK_WEIGHT)
961959
self.blocks[64] = b64
962960
b64 = self.update_block(64, [])
@@ -1060,12 +1058,12 @@ def run_test(self):
10601058
b72 = self.update_block(72, [tx1, tx2]) # now tip is 72
10611059
b71 = copy.deepcopy(b72)
10621060
b71.vtx.append(tx2) # add duplicate tx2
1063-
self.block_heights[b71.sha256] = self.block_heights[b69.sha256] + 1 # b71 builds off b69
1061+
self.block_heights[b71.hash_int] = self.block_heights[b69.hash_int] + 1 # b71 builds off b69
10641062
self.blocks[71] = b71
10651063

10661064
assert_equal(len(b71.vtx), 4)
10671065
assert_equal(len(b72.vtx), 3)
1068-
assert_equal(b72.sha256, b71.sha256)
1066+
assert_equal(b72.hash_int, b71.hash_int)
10691067

10701068
self.move_tip(71)
10711069
self.send_blocks([b71], success=False, reject_reason='bad-txns-duplicate', reconnect=True)
@@ -1370,7 +1368,7 @@ def next_block(self, number, spend=None, additional_coinbase_value=0, *, script=
13701368
base_block_hash = self.genesis_hash
13711369
block_time = int(time.time()) + 1
13721370
else:
1373-
base_block_hash = self.tip.sha256
1371+
base_block_hash = self.tip.hash_int
13741372
block_time = self.tip.nTime + 1
13751373
# First create the coinbase
13761374
height = self.block_heights[base_block_hash] + 1
@@ -1388,7 +1386,7 @@ def next_block(self, number, spend=None, additional_coinbase_value=0, *, script=
13881386
# Block is created. Find a valid nonce.
13891387
block.solve()
13901388
self.tip = block
1391-
self.block_heights[block.sha256] = height
1389+
self.block_heights[block.hash_int] = height
13921390
assert number not in self.blocks
13931391
self.blocks[number] = block
13941392
return block
@@ -1408,17 +1406,19 @@ def move_tip(self, number):
14081406
self.tip = self.blocks[number]
14091407

14101408
# adds transactions to the block and updates state
1411-
def update_block(self, block_number, new_transactions):
1409+
def update_block(self, block_number, new_transactions, *, nTime=None):
14121410
block = self.blocks[block_number]
14131411
self.add_transactions_to_block(block, new_transactions)
1414-
old_sha256 = block.sha256
1412+
old_hash_int = block.hash_int
1413+
if nTime is not None:
1414+
block.nTime = nTime
14151415
block.hashMerkleRoot = block.calc_merkle_root()
14161416
block.solve()
14171417
# Update the internal state just like in next_block
14181418
self.tip = block
1419-
if block.sha256 != old_sha256:
1420-
self.block_heights[block.sha256] = self.block_heights[old_sha256]
1421-
del self.block_heights[old_sha256]
1419+
if block.hash_int != old_hash_int:
1420+
self.block_heights[block.hash_int] = self.block_heights[old_hash_int]
1421+
del self.block_heights[old_hash_int]
14221422
self.blocks[block_number] = block
14231423
return block
14241424

test/functional/feature_cltv.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,15 +127,15 @@ def run_test(self):
127127
self.test_cltv_info(is_active=False) # Not active as of current tip and next block does not need to obey rules
128128
peer.send_and_ping(msg_block(block))
129129
self.test_cltv_info(is_active=True) # Not active as of current tip, but next block must obey rules
130-
assert_equal(self.nodes[0].getbestblockhash(), block.hash)
130+
assert_equal(self.nodes[0].getbestblockhash(), block.hash_hex)
131131

132132
self.log.info("Test that blocks must now be at least version 4")
133-
tip = block.sha256
133+
tip = block.hash_int
134134
block_time += 1
135135
block = create_block(tip, create_coinbase(CLTV_HEIGHT), block_time, version=3)
136136
block.solve()
137137

138-
with self.nodes[0].assert_debug_log(expected_msgs=[f'{block.hash}, bad-version(0x00000003)']):
138+
with self.nodes[0].assert_debug_log(expected_msgs=[f'{block.hash_hex}, bad-version(0x00000003)']):
139139
peer.send_and_ping(msg_block(block))
140140
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)
141141
peer.sync_with_ping()
@@ -196,7 +196,7 @@ def run_test(self):
196196
self.test_cltv_info(is_active=True) # Not active as of current tip, but next block must obey rules
197197
peer.send_and_ping(msg_block(block))
198198
self.test_cltv_info(is_active=True) # Active as of current tip
199-
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256)
199+
assert_equal(self.nodes[0].getbestblockhash(), block.hash_hex)
200200

201201

202202
if __name__ == '__main__':

test/functional/feature_csv_activation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ def generate_blocks(self, number):
165165
block = self.create_test_block([])
166166
test_blocks.append(block)
167167
self.last_block_time += 600
168-
self.tip = block.sha256
168+
self.tip = block.hash_int
169169
self.tipheight += 1
170170
return test_blocks
171171

test/functional/feature_dersig.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,15 @@ def run_test(self):
9292
peer.send_and_ping(msg_block(block))
9393
assert_equal(self.nodes[0].getblockcount(), DERSIG_HEIGHT - 1)
9494
self.test_dersig_info(is_active=True) # Not active as of current tip, but next block must obey rules
95-
assert_equal(self.nodes[0].getbestblockhash(), block.hash)
95+
assert_equal(self.nodes[0].getbestblockhash(), block.hash_hex)
9696

9797
self.log.info("Test that blocks must now be at least version 3")
98-
tip = block.sha256
98+
tip = block.hash_int
9999
block_time += 1
100100
block = create_block(tip, create_coinbase(DERSIG_HEIGHT), block_time, version=2)
101101
block.solve()
102102

103-
with self.nodes[0].assert_debug_log(expected_msgs=[f'{block.hash}, bad-version(0x00000002)']):
103+
with self.nodes[0].assert_debug_log(expected_msgs=[f'{block.hash_hex}, bad-version(0x00000002)']):
104104
peer.send_and_ping(msg_block(block))
105105
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)
106106
peer.sync_with_ping()
@@ -146,7 +146,7 @@ def run_test(self):
146146
self.test_dersig_info(is_active=True) # Not active as of current tip, but next block must obey rules
147147
peer.send_and_ping(msg_block(block))
148148
self.test_dersig_info(is_active=True) # Active as of current tip
149-
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256)
149+
assert_equal(self.nodes[0].getbestblockhash(), block.hash_hex)
150150

151151

152152
if __name__ == '__main__':

test/functional/feature_maxuploadtarget.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,7 @@ def on_inv(self, message):
4141
pass
4242

4343
def on_block(self, message):
44-
message.block.calc_sha256()
45-
self.block_receive_map[message.block.sha256] += 1
44+
self.block_receive_map[message.block.hash_int] += 1
4645

4746
class MaxUploadTest(BitcoinTestFramework):
4847

0 commit comments

Comments
 (0)