test: Use result of getblocktemplate to submitblock

This will ensure that miners can use the values returned by
getblocktemplate (in particular, the block commitment hash)
to submit a valid block using the submitblock RPC.
This commit is contained in:
Larry Ruane 2021-12-19 13:49:38 -07:00
parent 8c0177a507
commit b538577359
2 changed files with 66 additions and 30 deletions

View File

@ -3,6 +3,8 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
from io import BytesIO
import codecs
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
@ -10,13 +12,24 @@ from test_framework.util import (
CANOPY_BRANCH_ID,
HEARTWOOD_BRANCH_ID,
NU5_BRANCH_ID,
get_coinbase_address,
hex_str_to_bytes,
nuparams,
start_nodes,
wait_and_assert_operationid_status,
)
from test_framework.mininode import (
CTransaction,
)
from test_framework.blocktools import(
create_block
)
from decimal import Decimal
class GetBlockTemplateTest(BitcoinTestFramework):
'''
Test getblocktemplate.
Test getblocktemplate, ensure that a block created from its result
can be submitted and accepted.
'''
def __init__(self):
@ -25,7 +38,8 @@ class GetBlockTemplateTest(BitcoinTestFramework):
self.setup_clean_chain = True
def setup_network(self, split=False):
args = [nuparams(BLOSSOM_BRANCH_ID, 1),
args = [
nuparams(BLOSSOM_BRANCH_ID, 1),
nuparams(HEARTWOOD_BRANCH_ID, 1),
nuparams(CANOPY_BRANCH_ID, 1),
nuparams(NU5_BRANCH_ID, 1),
@ -36,40 +50,56 @@ class GetBlockTemplateTest(BitcoinTestFramework):
def run_test(self):
node = self.nodes[0]
node.generate(1) # Mine a block to leave initial block download
print("Generating blocks")
node.generate(110)
# Test 1: Default to coinbasetxn
tmpl = node.getblocktemplate()
assert('coinbasetxn' in tmpl)
assert('coinbasevalue' not in tmpl)
print("Add transactions to the mempool so they will be in the template")
# This part of the test should be improved, submit some V4 transactions
# and varying combinations of shielded and transparent
for _ in range(5):
# submit a tx with a shielded output
taddr0 = get_coinbase_address(node)
zaddr = node.z_getnewaddress('sapling')
recipients = [{"address": zaddr, "amount": Decimal('0.1')}]
myopid = node.z_sendmany(taddr0, recipients, 1, 0)
wait_and_assert_operationid_status(node, myopid)
# Test 2: Get coinbasetxn if requested
tmpl = node.getblocktemplate({'capabilities': ['coinbasetxn']})
assert('coinbasetxn' in tmpl)
assert('coinbasevalue' not in tmpl)
# submit a tx with a transparent output
outputs = {node.getnewaddress():0.2}
node.sendmany('', outputs)
# Test 3: coinbasevalue not supported if requested
tmpl = node.getblocktemplate({'capabilities': ['coinbasevalue']})
assert('coinbasetxn' in tmpl)
assert('coinbasevalue' not in tmpl)
print("Getting block template")
gbt = node.getblocktemplate()
# Test 4: coinbasevalue not supported if both requested
tmpl = node.getblocktemplate({'capabilities': ['coinbasetxn', 'coinbasevalue']})
assert('coinbasetxn' in tmpl)
assert('coinbasevalue' not in tmpl)
prevhash = int(gbt['previousblockhash'], 16)
blockcommitmentshash = int(gbt['defaultroots']['blockcommitmentshash'], 16)
nTime = gbt['mintime']
nBits = int(gbt['bits'], 16)
# Test 5: General checks
tmpl = node.getblocktemplate()
assert_equal(16, len(tmpl['noncerange']))
# should be proposing height 2, since current tip is height 1
assert_equal(2, tmpl['height'])
f = BytesIO(hex_str_to_bytes(gbt['coinbasetxn']['data']))
coinbase = CTransaction()
coinbase.deserialize(f)
# Test 6: coinbasetxn checks
assert(tmpl['coinbasetxn']['required'])
print("Creating_block from template (simulating a miner)")
block = create_block(prevhash, coinbase, nTime, nBits, blockcommitmentshash)
# copy the non-coinbase transactions from the block template to the block
for gbt_tx in gbt['transactions']:
f = BytesIO(hex_str_to_bytes(gbt_tx['data']))
tx = CTransaction()
tx.deserialize(f)
block.vtx.append(tx)
block.hashMerkleRoot = int(gbt['defaultroots']['merkleroot'], 16)
assert_equal(block.hashMerkleRoot, block.calc_merkle_root(), "merkleroot")
assert_equal(len(block.vtx), len(gbt['transactions']) + 1, "number of transactions")
assert_equal(block.hashPrevBlock, int(gbt['previousblockhash'], 16), "prevhash")
block.solve()
block = block.serialize()
block = codecs.encode(block, 'hex_codec')
print("Submitting block")
node.submitblock(block)
# Test 7: blockcommitmentshash checks
assert('blockcommitmentshash' in tmpl)
assert('00' * 32 != tmpl['finalsaplingroothash'])
if __name__ == '__main__':
GetBlockTemplateTest().main()

View File

@ -449,9 +449,15 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp)
"{\n"
" \"version\" : n, (numeric) The block version\n"
" \"previousblockhash\" : \"xxxx\", (string) The hash of current highest block\n"
" \"blockcommitmentshash\" : \"xxxx\", (string) The hash of the block commitments field in the block header\n"
" \"blockcommitmentshash\" : \"xxxx\", (string) (DEPRECATED) The hash of the block commitments field in the block header\n"
" \"lightclientroothash\" : \"xxxx\", (string) (DEPRECATED) The hash of the light client root field in the block header\n"
" \"finalsaplingroothash\" : \"xxxx\", (string) (DEPRECATED) The hash of the light client root field in the block header\n"
" \"defaultroots\" : { (json object) root hashes that need to be recomputed if the transaction set is modified\n"
" \"merkleroot\" : \"xxxx\" (string) The hash of the transactions in the block header\n"
" \"authdataroot\" : \"xxxx\" (string) The hash of the authorizing data merkel tree\n"
" \"chainhistoryroot\" : \"xxxx\" (string) The hash of the chain history\n"
" \"blockcommitmentshash\" : \"xxxx\" (string) The hash of the block commitments field in the block header\n"
" }\n"
" \"transactions\" : [ (array) contents of non-coinbase transactions that should be included in the next block\n"
" {\n"
" \"data\" : \"xxxx\", (string) transaction data encoded in hexadecimal (byte-for-byte)\n"