Skip to content

Commit

Permalink
test: more template verification tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Sjors committed Mar 4, 2025
1 parent e81b5c0 commit afa0929
Show file tree
Hide file tree
Showing 2 changed files with 275 additions and 0 deletions.
274 changes: 274 additions & 0 deletions test/functional/mining_template_verification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
#!/usr/bin/env python3
# Copyright (c) 2024-Present The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test getblocktemplate RPC in proposal mode
Generate several blocks and test them against the getblocktemplate RPC.
"""

from concurrent.futures import ThreadPoolExecutor

import copy

from test_framework.blocktools import (
create_block,
create_coinbase,
add_witness_commitment,
)

from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
)

from test_framework.messages import (
COutPoint,
CTxIn,
uint256_from_compact,
)

from test_framework.wallet import (
MiniWallet,
)


class MiningTemplateVerificationTest(BitcoinTestFramework):

def set_test_params(self):
self.num_nodes = 1

def run_test(self):
node = self.nodes[0]

block_0_hash = node.getbestblockhash()
block_0_height = node.getblockcount()
self.generate(node, sync_fun=self.no_op, nblocks=1)
block_1 = node.getblock(node.getbestblockhash())
block_2 = create_block(
int(block_1["hash"], 16),
create_coinbase(block_0_height + 2),
block_1["mediantime"] + 1,
)

# Block must build on the current tip
bad_block_2 = copy.deepcopy(block_2)
bad_block_2.hashPrevBlock = int(block_0_hash, 16)
bad_block_2.solve()

assert_equal(
node.getblocktemplate(
template_request={
"data": bad_block_2.serialize().hex(),
"mode": "proposal",
"rules": ["segwit"],
}
),
"inconclusive-not-best-prevblk",
)

self.log.info("Lowering nBits should make the block invalid")
bad_block_2 = copy.deepcopy(block_2)
bad_block_2.nBits = bad_block_2.nBits - 1
bad_block_2.solve()

assert_equal(
node.getblocktemplate(
template_request={
"data": bad_block_2.serialize().hex(),
"mode": "proposal",
"rules": ["segwit"],
}
),
"bad-diffbits",
)

self.log.info("Generate a block")
target = uint256_from_compact(block_2.nBits)
# Ensure that it doesn't meet the target by coincidence
while block_2.sha256 <= target:
block_2.nNonce += 1
block_2.rehash()

self.log.info("A block template doesn't need PoW")
assert_equal(
node.getblocktemplate(
template_request={
"data": block_2.serialize().hex(),
"mode": "proposal",
"rules": ["segwit"],
}
),
None,
)

self.log.info("Add proof of work")
block_2.solve()
assert_equal(
node.getblocktemplate(
template_request={
"data": block_2.serialize().hex(),
"mode": "proposal",
"rules": ["segwit"],
}
),
None,
)

self.log.info("getblocktemplate does not submit the block")
assert_equal(node.getblockcount(), block_0_height + 1)

self.log.info("Submitting this block should succeed")
assert_equal(node.submitblock(block_2.serialize().hex()), None)
node.waitforblockheight(2)

self.log.info("Generate a transaction")
tx = MiniWallet(node).create_self_transfer()
block_3 = create_block(
int(block_2.hash, 16),
create_coinbase(block_0_height + 3),
block_1["mediantime"] + 1,
txlist=[tx["hex"]],
)
assert_equal(len(block_3.vtx), 2)
add_witness_commitment(block_3)
block_3.solve()
assert_equal(
node.getblocktemplate(
template_request={
"data": block_3.serialize().hex(),
"mode": "proposal",
"rules": ["segwit"],
}
),
None,
)

# Call again to ensure the UTXO set wasn't updated
assert_equal(
node.getblocktemplate(
template_request={
"data": block_3.serialize().hex(),
"mode": "proposal",
"rules": ["segwit"],
}
),
None,
)

self.log.info("Add an invalid transaction")
bad_tx = copy.deepcopy(tx)
bad_tx["tx"].vout[0].nValue = 10000000000
bad_tx_hex = bad_tx["tx"].serialize().hex()
assert_equal(
node.testmempoolaccept([bad_tx_hex])[0]["reject-reason"],
"bad-txns-in-belowout",
)
block_3 = create_block(
int(block_2.hash, 16),
create_coinbase(block_0_height + 3),
block_1["mediantime"] + 1,
txlist=[bad_tx_hex],
)
assert_equal(len(block_3.vtx), 2)
add_witness_commitment(block_3)
block_3.solve()

self.log.info("This can't be submitted")
assert_equal(
node.submitblock(block_3.serialize().hex()), "bad-txns-in-belowout"
)

self.log.info("And should also not pass getblocktemplate")
assert_equal(
node.getblocktemplate(
template_request={
"data": block_3.serialize().hex(),
"mode": "proposal",
"rules": ["segwit"],
}
),
"duplicate-invalid",
)

self.log.info("Can't spend coins out of thin air")
bad_tx = copy.deepcopy(tx)
bad_tx["tx"].vin[0] = CTxIn(
outpoint=COutPoint(hash=int("aa" * 32, 16), n=0), scriptSig=b""
)
bad_tx_hex = bad_tx["tx"].serialize().hex()
assert_equal(
node.testmempoolaccept([bad_tx_hex])[0]["reject-reason"], "missing-inputs"
)
block_3 = create_block(
int(block_2.hash, 16),
create_coinbase(block_0_height + 3),
block_1["mediantime"] + 1,
txlist=[bad_tx_hex],
)
assert_equal(len(block_3.vtx), 2)
add_witness_commitment(block_3)
assert_equal(
node.getblocktemplate(
template_request={
"data": block_3.serialize().hex(),
"mode": "proposal",
"rules": ["segwit"],
}
),
"bad-txns-inputs-missingorspent",
)

self.log.info("Can't spend coins twice")
tx_hex = tx["tx"].serialize().hex()
tx_2 = copy.deepcopy(tx)
tx_2_hex = tx_2["tx"].serialize().hex()
# Nothing wrong with these transactions individually
assert_equal(node.testmempoolaccept([tx_hex])[0]["allowed"], True)
assert_equal(node.testmempoolaccept([tx_2_hex])[0]["allowed"], True)
# But can't be combined
assert_equal(
node.testmempoolaccept([tx_hex, tx_2_hex])[0]["package-error"],
"package-contains-duplicates",
)
block_3 = create_block(
int(block_2.hash, 16),
create_coinbase(block_0_height + 3),
block_1["mediantime"] + 1,
txlist=[tx_hex, tx_2_hex],
)
assert_equal(len(block_3.vtx), 3)
add_witness_commitment(block_3)
assert_equal(
node.getblocktemplate(
template_request={
"data": block_3.serialize().hex(),
"mode": "proposal",
"rules": ["segwit"],
}
),
"bad-txns-inputs-missingorspent",
)

# Ensure that getblocktemplate can be called concurrently by many threads.
self.log.info("Check blocks in parallel")
check_50_blocks = lambda n: [
assert_equal(
n.getblocktemplate(
template_request={
"data": block_3.serialize().hex(),
"mode": "proposal",
"rules": ["segwit"],
}
),
"bad-txns-inputs-missingorspent",
)
for _ in range(50)
]
rpcs = [node.cli for _ in range(6)]
with ThreadPoolExecutor(max_workers=len(rpcs)) as threads:
list(threads.map(check_50_blocks, rpcs))


if __name__ == "__main__":
MiningTemplateVerificationTest(__file__).main()
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@
'rpc_decodescript.py',
'rpc_blockchain.py --v1transport',
'rpc_blockchain.py --v2transport',
'mining_template_verification.py',
'rpc_deprecated.py',
'wallet_disable.py',
'wallet_change_address.py --legacy-wallet',
Expand Down

0 comments on commit afa0929

Please sign in to comment.