Skip to content

Commit ab68693

Browse files
committed
test: more template verification tests
1 parent bc3c839 commit ab68693

File tree

2 files changed

+275
-0
lines changed

2 files changed

+275
-0
lines changed
Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
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+
add_witness_commitment,
18+
)
19+
20+
from test_framework.test_framework import BitcoinTestFramework
21+
from test_framework.util import (
22+
assert_equal,
23+
)
24+
25+
from test_framework.messages import (
26+
COutPoint,
27+
CTxIn,
28+
uint256_from_compact,
29+
)
30+
31+
from test_framework.wallet import (
32+
MiniWallet,
33+
)
34+
35+
36+
class MiningTemplateVerificationTest(BitcoinTestFramework):
37+
38+
def set_test_params(self):
39+
self.num_nodes = 1
40+
41+
def run_test(self):
42+
node = self.nodes[0]
43+
44+
block_0_hash = node.getbestblockhash()
45+
block_0_height = node.getblockcount()
46+
self.generate(node, sync_fun=self.no_op, nblocks=1)
47+
block_1 = node.getblock(node.getbestblockhash())
48+
block_2 = create_block(
49+
int(block_1["hash"], 16),
50+
create_coinbase(block_0_height + 2),
51+
block_1["mediantime"] + 1,
52+
)
53+
54+
# Block must build on the current tip
55+
bad_block_2 = copy.deepcopy(block_2)
56+
bad_block_2.hashPrevBlock = int(block_0_hash, 16)
57+
bad_block_2.solve()
58+
59+
assert_equal(
60+
node.getblocktemplate(
61+
template_request={
62+
"data": bad_block_2.serialize().hex(),
63+
"mode": "proposal",
64+
"rules": ["segwit"],
65+
}
66+
),
67+
"inconclusive-not-best-prevblk",
68+
)
69+
70+
self.log.info("Lowering nBits should make the block invalid")
71+
bad_block_2 = copy.deepcopy(block_2)
72+
bad_block_2.nBits = bad_block_2.nBits - 1
73+
bad_block_2.solve()
74+
75+
assert_equal(
76+
node.getblocktemplate(
77+
template_request={
78+
"data": bad_block_2.serialize().hex(),
79+
"mode": "proposal",
80+
"rules": ["segwit"],
81+
}
82+
),
83+
"bad-diffbits",
84+
)
85+
86+
self.log.info("Generate a block")
87+
target = uint256_from_compact(block_2.nBits)
88+
# Ensure that it doesn't meet the target by coincidence
89+
while block_2.sha256 <= target:
90+
block_2.nNonce += 1
91+
block_2.rehash()
92+
93+
self.log.info("A block template doesn't need PoW")
94+
assert_equal(
95+
node.getblocktemplate(
96+
template_request={
97+
"data": block_2.serialize().hex(),
98+
"mode": "proposal",
99+
"rules": ["segwit"],
100+
}
101+
),
102+
None,
103+
)
104+
105+
self.log.info("Add proof of work")
106+
block_2.solve()
107+
assert_equal(
108+
node.getblocktemplate(
109+
template_request={
110+
"data": block_2.serialize().hex(),
111+
"mode": "proposal",
112+
"rules": ["segwit"],
113+
}
114+
),
115+
None,
116+
)
117+
118+
self.log.info("getblocktemplate does not submit the block")
119+
assert_equal(node.getblockcount(), block_0_height + 1)
120+
121+
self.log.info("Submitting this block should succeed")
122+
assert_equal(node.submitblock(block_2.serialize().hex()), None)
123+
node.waitforblockheight(2)
124+
125+
self.log.info("Generate a transaction")
126+
tx = MiniWallet(node).create_self_transfer()
127+
block_3 = create_block(
128+
int(block_2.hash, 16),
129+
create_coinbase(block_0_height + 3),
130+
block_1["mediantime"] + 1,
131+
txlist=[tx["hex"]],
132+
)
133+
assert_equal(len(block_3.vtx), 2)
134+
add_witness_commitment(block_3)
135+
block_3.solve()
136+
assert_equal(
137+
node.getblocktemplate(
138+
template_request={
139+
"data": block_3.serialize().hex(),
140+
"mode": "proposal",
141+
"rules": ["segwit"],
142+
}
143+
),
144+
None,
145+
)
146+
147+
# Call again to ensure the UTXO set wasn't updated
148+
assert_equal(
149+
node.getblocktemplate(
150+
template_request={
151+
"data": block_3.serialize().hex(),
152+
"mode": "proposal",
153+
"rules": ["segwit"],
154+
}
155+
),
156+
None,
157+
)
158+
159+
self.log.info("Add an invalid transaction")
160+
bad_tx = copy.deepcopy(tx)
161+
bad_tx["tx"].vout[0].nValue = 10000000000
162+
bad_tx_hex = bad_tx["tx"].serialize().hex()
163+
assert_equal(
164+
node.testmempoolaccept([bad_tx_hex])[0]["reject-reason"],
165+
"bad-txns-in-belowout",
166+
)
167+
block_3 = create_block(
168+
int(block_2.hash, 16),
169+
create_coinbase(block_0_height + 3),
170+
block_1["mediantime"] + 1,
171+
txlist=[bad_tx_hex],
172+
)
173+
assert_equal(len(block_3.vtx), 2)
174+
add_witness_commitment(block_3)
175+
block_3.solve()
176+
177+
self.log.info("This can't be submitted")
178+
assert_equal(
179+
node.submitblock(block_3.serialize().hex()), "bad-txns-in-belowout"
180+
)
181+
182+
self.log.info("And should also not pass getblocktemplate")
183+
assert_equal(
184+
node.getblocktemplate(
185+
template_request={
186+
"data": block_3.serialize().hex(),
187+
"mode": "proposal",
188+
"rules": ["segwit"],
189+
}
190+
),
191+
"duplicate-invalid",
192+
)
193+
194+
self.log.info("Can't spend coins out of thin air")
195+
bad_tx = copy.deepcopy(tx)
196+
bad_tx["tx"].vin[0] = CTxIn(
197+
outpoint=COutPoint(hash=int("aa" * 32, 16), n=0), scriptSig=b""
198+
)
199+
bad_tx_hex = bad_tx["tx"].serialize().hex()
200+
assert_equal(
201+
node.testmempoolaccept([bad_tx_hex])[0]["reject-reason"], "missing-inputs"
202+
)
203+
block_3 = create_block(
204+
int(block_2.hash, 16),
205+
create_coinbase(block_0_height + 3),
206+
block_1["mediantime"] + 1,
207+
txlist=[bad_tx_hex],
208+
)
209+
assert_equal(len(block_3.vtx), 2)
210+
add_witness_commitment(block_3)
211+
assert_equal(
212+
node.getblocktemplate(
213+
template_request={
214+
"data": block_3.serialize().hex(),
215+
"mode": "proposal",
216+
"rules": ["segwit"],
217+
}
218+
),
219+
"bad-txns-inputs-missingorspent",
220+
)
221+
222+
self.log.info("Can't spend coins twice")
223+
tx_hex = tx["tx"].serialize().hex()
224+
tx_2 = copy.deepcopy(tx)
225+
tx_2_hex = tx_2["tx"].serialize().hex()
226+
# Nothing wrong with these transactions individually
227+
assert_equal(node.testmempoolaccept([tx_hex])[0]["allowed"], True)
228+
assert_equal(node.testmempoolaccept([tx_2_hex])[0]["allowed"], True)
229+
# But can't be combined
230+
assert_equal(
231+
node.testmempoolaccept([tx_hex, tx_2_hex])[0]["package-error"],
232+
"package-contains-duplicates",
233+
)
234+
block_3 = create_block(
235+
int(block_2.hash, 16),
236+
create_coinbase(block_0_height + 3),
237+
block_1["mediantime"] + 1,
238+
txlist=[tx_hex, tx_2_hex],
239+
)
240+
assert_equal(len(block_3.vtx), 3)
241+
add_witness_commitment(block_3)
242+
assert_equal(
243+
node.getblocktemplate(
244+
template_request={
245+
"data": block_3.serialize().hex(),
246+
"mode": "proposal",
247+
"rules": ["segwit"],
248+
}
249+
),
250+
"bad-txns-inputs-missingorspent",
251+
)
252+
253+
# Ensure that getblocktemplate can be called concurrently by many threads.
254+
self.log.info("Check blocks in parallel")
255+
check_50_blocks = lambda n: [
256+
assert_equal(
257+
n.getblocktemplate(
258+
template_request={
259+
"data": block_3.serialize().hex(),
260+
"mode": "proposal",
261+
"rules": ["segwit"],
262+
}
263+
),
264+
"bad-txns-inputs-missingorspent",
265+
)
266+
for _ in range(50)
267+
]
268+
rpcs = [node.cli for _ in range(6)]
269+
with ThreadPoolExecutor(max_workers=len(rpcs)) as threads:
270+
list(threads.map(check_50_blocks, rpcs))
271+
272+
273+
if __name__ == "__main__":
274+
MiningTemplateVerificationTest(__file__).main()

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@
244244
'rpc_decodescript.py',
245245
'rpc_blockchain.py --v1transport',
246246
'rpc_blockchain.py --v2transport',
247+
'mining_template_verification.py',
247248
'rpc_deprecated.py',
248249
'wallet_disable.py',
249250
'wallet_change_address.py --legacy-wallet',

0 commit comments

Comments
 (0)