test: check getblocktemplate output before and after NU5

This test currently fails with submitblock returning the error
"bad-heartwood-root-in-block".

Added authdigest to GBT coinbasetxn field because we can't obtain this
via getrawtransaction.

Co-authored-by: Jack Grigg <jack@z.cash>
This commit is contained in:
Larry Ruane 2022-01-04 09:46:30 -07:00 committed by Jack Grigg
parent 1f223ebed7
commit dfefab2f55
3 changed files with 127 additions and 33 deletions

View File

@ -8,11 +8,8 @@ import codecs
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
BLOSSOM_BRANCH_ID,
CANOPY_BRANCH_ID,
HEARTWOOD_BRANCH_ID,
NU5_BRANCH_ID,
get_coinbase_address,
hex_str_to_bytes,
nuparams,
start_nodes,
@ -20,8 +17,9 @@ from test_framework.util import (
)
from test_framework.mininode import (
CTransaction,
uint256_from_str,
)
from test_framework.blocktools import(
from test_framework.blocktools import (
create_block
)
from decimal import Decimal
@ -39,38 +37,36 @@ class GetBlockTemplateTest(BitcoinTestFramework):
def setup_network(self, split=False):
args = [
nuparams(BLOSSOM_BRANCH_ID, 1),
nuparams(HEARTWOOD_BRANCH_ID, 1),
nuparams(CANOPY_BRANCH_ID, 1),
nuparams(NU5_BRANCH_ID, 1),
nuparams(CANOPY_BRANCH_ID, 115),
nuparams(NU5_BRANCH_ID, 130),
]
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, [args] * self.num_nodes)
self.is_network_split=False
self.sync_all()
self.is_network_split = False
self.node = self.nodes[0]
def run_test(self):
node = self.nodes[0]
print("Generating blocks")
node.generate(110)
def add_nu5_v4_tx_to_mempool(self):
node = self.node
# sprout to transparent (v4)
recipients = [{"address": self.transparent_addr, "amount": Decimal('0.1')}]
myopid = node.z_sendmany(self.sprout_addr, recipients)
wait_and_assert_operationid_status(node, myopid)
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)
def add_transparent_tx_to_mempool(self):
node = self.node
# transparent to transparent (v5 after nu5)
outputs = {self.transparent_addr: 0.1}
node.sendmany('', outputs)
# submit a tx with a transparent output
outputs = {node.getnewaddress():0.2}
node.sendmany('', outputs)
def gbt_submitblock(self):
node = self.node
mempool_tx_list = node.getrawmempool()
print("Getting block template")
gbt = node.getblocktemplate()
# make sure no transactions were left out (or added)
assert_equal(len(mempool_tx_list), len(gbt['transactions']))
assert_equal(set(mempool_tx_list), set([tx['hash'] for tx in gbt['transactions']]))
prevhash = int(gbt['previousblockhash'], 16)
blockcommitmentshash = int(gbt['defaultroots']['blockcommitmentshash'], 16)
nTime = gbt['mintime']
@ -79,8 +75,10 @@ class GetBlockTemplateTest(BitcoinTestFramework):
f = BytesIO(hex_str_to_bytes(gbt['coinbasetxn']['data']))
coinbase = CTransaction()
coinbase.deserialize(f)
coinbase.calc_sha256()
assert_equal(coinbase.hash, gbt['coinbasetxn']['hash'])
assert_equal(coinbase.auth_digest_hex, gbt['coinbasetxn']['authdigest'])
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
@ -88,18 +86,89 @@ class GetBlockTemplateTest(BitcoinTestFramework):
f = BytesIO(hex_str_to_bytes(gbt_tx['data']))
tx = CTransaction()
tx.deserialize(f)
tx.calc_sha256()
assert_equal(tx.auth_digest_hex, node.getrawtransaction(tx.hash, 1)['authdigest'])
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")
assert_equal(uint256_from_str(block.calc_auth_data_root()), int(gbt['defaultroots']['authdataroot'], 16))
block.solve()
block = block.serialize()
block = codecs.encode(block, 'hex_codec')
block.calc_sha256()
print("Submitting block")
submitblock_reply = node.submitblock(block)
submitblock_reply = node.submitblock(codecs.encode(block.serialize(), 'hex_codec'))
assert_equal(None, submitblock_reply)
assert_equal(block.hash, node.getbestblockhash())
# Wait until the wallet has been notified of all blocks, so that it doesn't try to
# double-spend transparent coins in subsequent test phases.
self.sync_all()
def run_test(self):
node = self.node
# Generate Sprout funds before Canopy activates; using the Sprout address will
# force the generation of v4 transactions from NU5.
print("Generating pre-Canopy blocks to create sprout funds")
# coinbase only becomes mature after 100 blocks, so make one mature.
node.generate(105)
self.sprout_addr = node.z_getnewaddress('sprout')
myopid = node.z_shieldcoinbase('*', self.sprout_addr)['opid']
wait_and_assert_operationid_status(node, myopid)
self.transparent_addr = node.getnewaddress()
node.generate(15)
# at height 120, NU5 is not active
assert_equal(node.getblockchaininfo()['upgrades']['37519621']['status'], 'pending')
print("Testing getblocktemplate for pre-NU5")
# Only the coinbase; this covers the case where the Merkle root
# is equal to the coinbase txid.
print("- only coinbase")
self.gbt_submitblock()
# Adding one transaction triggering a single Merkle digest.
print("- one transaction (plus coinbase)")
self.add_transparent_tx_to_mempool()
self.gbt_submitblock()
# Adding two transactions to trigger hash Merkle root edge case.
print("- two transactions (plus coinbase)")
self.add_transparent_tx_to_mempool()
self.add_transparent_tx_to_mempool()
self.gbt_submitblock()
# Activate NU5, repeat the above cases
node.generate(7)
assert_equal(node.getblockchaininfo()['upgrades']['37519621']['status'], 'active')
print("Testing getblocktemplate for post-NU5")
# Only the coinbase; this covers the case where the block authdata root
# is equal to the coinbase authdata
print("- only coinbase")
self.gbt_submitblock()
# Adding one transaction triggering a single Merkle digest.
print("- one transaction (plus coinbase)")
self.add_transparent_tx_to_mempool()
self.gbt_submitblock()
# Adding two transactions to trigger hash Merkle root edge case.
print("- two transactions (plus coinbase)")
self.add_transparent_tx_to_mempool()
self.add_transparent_tx_to_mempool()
self.gbt_submitblock()
# Adding both v4 and v5 to cover legacy auth digest.
print("- both v4 and v5 transactions (plus coinbase)")
self.add_nu5_v4_tx_to_mempool()
self.add_transparent_tx_to_mempool()
self.add_transparent_tx_to_mempool()
self.gbt_submitblock()
if __name__ == '__main__':

View File

@ -1126,11 +1126,14 @@ class CTransaction(object):
if self.nVersion >= 5:
from . import zip244
txid = zip244.txid_digest(self)
self.auth_digest = zip244.auth_digest(self)
else:
txid = hash256(self.serialize())
self.auth_digest = b'\xFF'*32
if self.sha256 is None:
self.sha256 = uint256_from_str(txid)
self.hash = encode(txid[::-1], 'hex_codec').decode('ascii')
self.auth_digest_hex = encode(self.auth_digest[::-1], 'hex_codec').decode('ascii')
def is_valid(self):
self.calc_sha256()
@ -1263,6 +1266,27 @@ class CBlock(CBlockHeader):
hashes = newhashes
return uint256_from_str(hashes[0])
def calc_auth_data_root(self):
hashes = []
nleaves = 0
for tx in self.vtx:
tx.calc_sha256()
hashes.append(tx.auth_digest)
nleaves += 1
# Continue adding leaves (of zeros) until reaching a power of 2
while nleaves & (nleaves-1) > 0:
hashes.append(b'\x00'*32)
nleaves += 1
while len(hashes) > 1:
newhashes = []
for i in range(0, len(hashes), 2):
digest = blake2b(digest_size=32, person=b'ZcashAuthDatHash')
digest.update(hashes[i])
digest.update(hashes[i+1])
newhashes.append(digest.digest())
hashes = newhashes
return hashes[0]
def is_valid(self, n=48, k=5):
# H(I||...
digest = blake2b(digest_size=(512//n)*n//8, person=zcash_person(n, k))

View File

@ -723,6 +723,7 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp)
entry.pushKV("data", EncodeHexTx(tx));
entry.pushKV("hash", txHash.GetHex());
entry.pushKV("authdigest", tx.GetAuthDigest().GetHex());
UniValue deps(UniValue::VARR);
for (const CTxIn &in : tx.vin)