Skip to content

Commit 10c9088

Browse files
committed
test: move gbt proposal mode tests to new file
Additionally this commit gives each test its own function. The assert_submitblock helper is absorbed into assert_template. Review hint: git show --color-moved=dimmed-zebra
1 parent 94959b8 commit 10c9088

File tree

3 files changed

+185
-105
lines changed

3 files changed

+185
-105
lines changed

test/functional/mining_basic.py

Lines changed: 15 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
"""Test mining RPCs
66
77
- getmininginfo
8-
- getblocktemplate proposal mode
9-
- submitblock"""
8+
- getblocktemplate
9+
- submitblock
10+
11+
mining_template_verification.py tests getblocktemplate in proposal mode"""
1012

1113
import copy
1214
from decimal import Decimal
@@ -54,18 +56,6 @@
5456
VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 28
5557
DEFAULT_BLOCK_MIN_TX_FEE = 1000 # default `-blockmintxfee` setting [sat/kvB]
5658

57-
58-
def assert_template(node, block, expect, rehash=True):
59-
if rehash:
60-
block.hashMerkleRoot = block.calc_merkle_root()
61-
rsp = node.getblocktemplate(template_request={
62-
'data': block.serialize().hex(),
63-
'mode': 'proposal',
64-
'rules': ['segwit'],
65-
})
66-
assert_equal(rsp, expect)
67-
68-
6959
class MiningTest(BitcoinTestFramework):
7060
def set_test_params(self):
7161
self.num_nodes = 3
@@ -225,8 +215,13 @@ def test_timewarp(self):
225215
block.nBits = int(tmpl["bits"], 16)
226216
block.nNonce = 0
227217
block.vtx = [create_coinbase(height=int(tmpl["height"]))]
218+
block.hashMerkleRoot = block.calc_merkle_root()
228219
block.solve()
229-
assert_template(node, block, None)
220+
assert_equal(node.getblocktemplate(template_request={
221+
'data': block.serialize().hex(),
222+
'mode': 'proposal',
223+
'rules': ['segwit'],
224+
}), None)
230225

231226
bad_block = copy.deepcopy(block)
232227
bad_block.nTime = t
@@ -376,12 +371,6 @@ def run_test(self):
376371
self.wallet = MiniWallet(node)
377372
self.mine_chain()
378373

379-
def assert_submitblock(block, result_str_1, result_str_2=None):
380-
block.solve()
381-
result_str_2 = result_str_2 or 'duplicate-invalid'
382-
assert_equal(result_str_1, node.submitblock(hexdata=block.serialize().hex()))
383-
assert_equal(result_str_2, node.submitblock(hexdata=block.serialize().hex()))
384-
385374
self.log.info('getmininginfo')
386375
mining_info = node.getmininginfo()
387376
assert_equal(mining_info['blocks'], 200)
@@ -433,103 +422,24 @@ def assert_submitblock(block, result_str_1, result_str_2=None):
433422
block.nBits = int(tmpl["bits"], 16)
434423
block.nNonce = 0
435424
block.vtx = [coinbase_tx]
425+
block.hashMerkleRoot = block.calc_merkle_root()
436426

437427
self.log.info("getblocktemplate: segwit rule must be set")
438428
assert_raises_rpc_error(-8, "getblocktemplate must be called with the segwit rule set", node.getblocktemplate, {})
439429

440-
self.log.info("getblocktemplate: Test valid block")
441-
assert_template(node, block, None)
442-
443430
self.log.info("submitblock: Test block decode failure")
444431
assert_raises_rpc_error(-22, "Block decode failed", node.submitblock, block.serialize()[:-15].hex())
445432

446-
self.log.info("getblocktemplate: Test bad input hash for coinbase transaction")
447-
bad_block = copy.deepcopy(block)
448-
bad_block.vtx[0].vin[0].prevout.hash += 1
449-
assert_template(node, bad_block, 'bad-cb-missing')
450-
451-
self.log.info("submitblock: Test bad input hash for coinbase transaction")
452-
bad_block.solve()
453-
assert_equal("bad-cb-missing", node.submitblock(hexdata=bad_block.serialize().hex()))
454-
455-
self.log.info("submitblock: Test block with no transactions")
456-
no_tx_block = copy.deepcopy(block)
457-
no_tx_block.vtx.clear()
458-
no_tx_block.hashMerkleRoot = 0
459-
no_tx_block.solve()
460-
assert_equal("bad-blk-length", node.submitblock(hexdata=no_tx_block.serialize().hex()))
461-
462433
self.log.info("submitblock: Test empty block")
463434
assert_equal('high-hash', node.submitblock(hexdata=CBlock().serialize().hex()))
464435

465-
self.log.info("getblocktemplate: Test truncated final transaction")
466-
assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {
467-
'data': block.serialize()[:-1].hex(),
468-
'mode': 'proposal',
469-
'rules': ['segwit'],
470-
})
471-
472-
self.log.info("getblocktemplate: Test duplicate transaction")
473-
bad_block = copy.deepcopy(block)
474-
bad_block.vtx.append(bad_block.vtx[0])
475-
assert_template(node, bad_block, 'bad-txns-duplicate')
476-
assert_submitblock(bad_block, 'bad-txns-duplicate', 'bad-txns-duplicate')
477-
478-
self.log.info("getblocktemplate: Test invalid transaction")
479-
bad_block = copy.deepcopy(block)
480-
bad_tx = copy.deepcopy(bad_block.vtx[0])
481-
bad_tx.vin[0].prevout.hash = 255
482-
bad_block.vtx.append(bad_tx)
483-
assert_template(node, bad_block, 'bad-txns-inputs-missingorspent')
484-
assert_submitblock(bad_block, 'bad-txns-inputs-missingorspent')
485-
486-
self.log.info("getblocktemplate: Test nonfinal transaction")
487-
bad_block = copy.deepcopy(block)
488-
bad_block.vtx[0].nLockTime = 2**32 - 1
489-
assert_template(node, bad_block, 'bad-txns-nonfinal')
490-
assert_submitblock(bad_block, 'bad-txns-nonfinal')
491-
492-
self.log.info("getblocktemplate: Test bad tx count")
493-
# The tx count is immediately after the block header
494-
bad_block_sn = bytearray(block.serialize())
495-
assert_equal(bad_block_sn[BLOCK_HEADER_SIZE], 1)
496-
bad_block_sn[BLOCK_HEADER_SIZE] += 1
497-
assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {
498-
'data': bad_block_sn.hex(),
499-
'mode': 'proposal',
500-
'rules': ['segwit'],
501-
})
502-
503-
self.log.info("getblocktemplate: Test bad bits")
504-
bad_block = copy.deepcopy(block)
505-
bad_block.nBits = 469762303 # impossible in the real world
506-
assert_template(node, bad_block, 'bad-diffbits')
507-
508-
self.log.info("getblocktemplate: Test bad merkle root")
509-
bad_block = copy.deepcopy(block)
510-
bad_block.hashMerkleRoot += 1
511-
assert_template(node, bad_block, 'bad-txnmrklroot', False)
512-
assert_submitblock(bad_block, 'bad-txnmrklroot', 'bad-txnmrklroot')
513-
514-
self.log.info("getblocktemplate: Test bad timestamps")
515-
bad_block = copy.deepcopy(block)
516-
bad_block.nTime = 2**32 - 1
517-
assert_template(node, bad_block, 'time-too-new')
518-
assert_submitblock(bad_block, 'time-too-new', 'time-too-new')
519-
bad_block.nTime = 0
520-
assert_template(node, bad_block, 'time-too-old')
521-
assert_submitblock(bad_block, 'time-too-old', 'time-too-old')
522-
523-
self.log.info("getblocktemplate: Test not best block")
524-
bad_block = copy.deepcopy(block)
525-
bad_block.hashPrevBlock = 123
526-
assert_template(node, bad_block, 'inconclusive-not-best-prevblk')
527-
assert_submitblock(bad_block, 'prev-blk-not-found', 'prev-blk-not-found')
528-
529436
self.log.info('submitheader tests')
530437
assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='xx' * BLOCK_HEADER_SIZE))
531438
assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='ff' * (BLOCK_HEADER_SIZE-2)))
532-
assert_raises_rpc_error(-25, 'Must submit previous header', lambda: node.submitheader(hexdata=super(CBlock, bad_block).serialize().hex()))
439+
440+
missing_ancestor_block = copy.deepcopy(block)
441+
missing_ancestor_block.hashPrevBlock = 123
442+
assert_raises_rpc_error(-25, 'Must submit previous header', lambda: node.submitheader(hexdata=super(CBlock, missing_ancestor_block).serialize().hex()))
533443

534444
block.nTime += 1
535445
block.solve()
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2024-Present The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""Test getblocktemplate RPC in proposal mode
6+
7+
Generate several blocks and test them against the getblocktemplate RPC.
8+
"""
9+
10+
from concurrent.futures import ThreadPoolExecutor
11+
12+
import copy
13+
14+
from test_framework.blocktools import (
15+
create_block,
16+
create_coinbase,
17+
)
18+
19+
from test_framework.test_framework import BitcoinTestFramework
20+
from test_framework.util import (
21+
assert_equal,
22+
assert_raises_rpc_error,
23+
)
24+
25+
from test_framework.messages import (
26+
BLOCK_HEADER_SIZE,
27+
)
28+
29+
def assert_template(node, block, expect, *, rehash=True, submit=True, solve=True, expect_submit=None):
30+
if rehash:
31+
block.hashMerkleRoot = block.calc_merkle_root()
32+
33+
rsp = node.getblocktemplate(template_request={
34+
'data': block.serialize().hex(),
35+
'mode': 'proposal',
36+
'rules': ['segwit'],
37+
})
38+
assert_equal(rsp, expect)
39+
# Only attempt to submit invalid templates
40+
if submit and expect is not None:
41+
# submitblock runs checks in a different order, so may not return
42+
# the same error
43+
if expect_submit is None:
44+
expect_submit = expect
45+
if solve:
46+
block.solve()
47+
assert_equal(node.submitblock(block.serialize().hex()), expect_submit)
48+
49+
class MiningTemplateVerificationTest(BitcoinTestFramework):
50+
51+
def set_test_params(self):
52+
self.num_nodes = 1
53+
54+
def valid_block_test(self, node, block):
55+
self.log.info("Valid block")
56+
assert_template(node, block, None)
57+
58+
def cb_missing_test(self, node, block):
59+
self.log.info("Bad input hash for coinbase transaction")
60+
bad_block = copy.deepcopy(block)
61+
bad_block.vtx[0].vin[0].prevout.hash += 1
62+
assert_template(node, bad_block, 'bad-cb-missing')
63+
64+
def block_without_transactions_test(self, node, block):
65+
self.log.info("Block with no transactions")
66+
67+
no_tx_block = copy.deepcopy(block)
68+
no_tx_block.vtx.clear()
69+
no_tx_block.hashMerkleRoot = 0
70+
no_tx_block.solve()
71+
assert_template(node, no_tx_block, 'bad-blk-length', rehash=False)
72+
73+
def truncated_final_transaction_test(self, node, block):
74+
self.log.info("Truncated final transaction")
75+
assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate,
76+
template_request={
77+
"data": block.serialize()[:-1].hex(),
78+
"mode": "proposal",
79+
"rules": ["segwit"],
80+
}
81+
)
82+
83+
def duplicate_transaction_test(self, node, block):
84+
self.log.info("Duplicate transaction")
85+
bad_block = copy.deepcopy(block)
86+
bad_block.vtx.append(bad_block.vtx[0])
87+
assert_template(node, bad_block, 'bad-txns-duplicate')
88+
89+
def thin_air_spending_test(self, node, block):
90+
self.log.info("Transaction that spends from thin air")
91+
bad_block = copy.deepcopy(block)
92+
bad_tx = copy.deepcopy(bad_block.vtx[0])
93+
bad_tx.vin[0].prevout.hash = 255
94+
bad_block.vtx.append(bad_tx)
95+
assert_template(node, bad_block, 'bad-txns-inputs-missingorspent')
96+
97+
def non_final_transaction_test(self, node, block):
98+
self.log.info("Non-final transaction")
99+
bad_block = copy.deepcopy(block)
100+
bad_block.vtx[0].nLockTime = 2**32 - 1
101+
assert_template(node, bad_block, 'bad-txns-nonfinal')
102+
103+
def bad_tx_count_test(self, node, block):
104+
self.log.info("Bad tx count")
105+
# The tx count is immediately after the block header
106+
bad_block_sn = bytearray(block.serialize())
107+
assert_equal(bad_block_sn[BLOCK_HEADER_SIZE], 1)
108+
bad_block_sn[BLOCK_HEADER_SIZE] += 1
109+
assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {
110+
'data': bad_block_sn.hex(),
111+
'mode': 'proposal',
112+
'rules': ['segwit'],
113+
})
114+
115+
def nbits_test(self, node, block):
116+
self.log.info("Extremely high nBits")
117+
bad_block = copy.deepcopy(block)
118+
bad_block.nBits = 469762303 # impossible in the real world
119+
assert_template(node, bad_block, "bad-diffbits", solve=False, expect_submit="high-hash")
120+
121+
def merkle_root_test(self, node, block):
122+
self.log.info("Bad merkle root")
123+
bad_block = copy.deepcopy(block)
124+
bad_block.hashMerkleRoot += 1
125+
assert_template(node, bad_block, 'bad-txnmrklroot', rehash=False)
126+
127+
def bad_timestamp_test(self, node, block):
128+
self.log.info("Bad timestamps")
129+
bad_block = copy.deepcopy(block)
130+
bad_block.nTime = 2**32 - 1
131+
assert_template(node, bad_block, 'time-too-new')
132+
bad_block.nTime = 0
133+
assert_template(node, bad_block, 'time-too-old')
134+
135+
def current_tip_test(self, node, block):
136+
self.log.info("Block must build on the current tip")
137+
bad_block = copy.deepcopy(block)
138+
bad_block.hashPrevBlock = 123
139+
bad_block.solve()
140+
141+
assert_template(node, bad_block, "inconclusive-not-best-prevblk", expect_submit="prev-blk-not-found")
142+
143+
def run_test(self):
144+
node = self.nodes[0]
145+
146+
block_0_height = node.getblockcount()
147+
self.generate(node, sync_fun=self.no_op, nblocks=1)
148+
block_1 = node.getblock(node.getbestblockhash())
149+
block_2 = create_block(
150+
int(block_1["hash"], 16),
151+
create_coinbase(block_0_height + 2),
152+
block_1["mediantime"] + 1,
153+
)
154+
155+
self.valid_block_test(node, block_2)
156+
self.cb_missing_test(node, block_2)
157+
self.block_without_transactions_test(node, block_2)
158+
self.truncated_final_transaction_test(node, block_2)
159+
self.duplicate_transaction_test(node, block_2)
160+
self.thin_air_spending_test(node, block_2)
161+
self.non_final_transaction_test(node, block_2)
162+
self.bad_tx_count_test(node, block_2)
163+
self.nbits_test(node, block_2)
164+
self.merkle_root_test(node, block_2)
165+
self.bad_timestamp_test(node, block_2)
166+
self.current_tip_test(node, block_2)
167+
168+
if __name__ == "__main__":
169+
MiningTemplateVerificationTest(__file__).main()

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@
209209
'rpc_decodescript.py',
210210
'rpc_blockchain.py --v1transport',
211211
'rpc_blockchain.py --v2transport',
212+
'mining_template_verification.py',
212213
'rpc_deprecated.py',
213214
'wallet_disable.py',
214215
'wallet_change_address.py',

0 commit comments

Comments
 (0)