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:
parent
1f223ebed7
commit
dfefab2f55
|
@ -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__':
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue