def run_test (self): node = self.nodes[0] p2pStore = node.add_p2p_connection (P2PDataStore ()) p2pGetter = node.add_p2p_connection (P2PBlockGetter ()) self.log.info ("Adding a block with non-zero hash in the auxpow...") blk, blkHash = self.createBlock () blk.auxpow.hashBlock = 12345678 blkHex = blk.serialize ().hex () assert_equal (node.submitblock (blkHex), None) assert_equal (node.getbestblockhash (), blkHash) self.log.info ("Retrieving block through RPC...") gotHex = node.getblock (blkHash, 0) assert gotHex != blkHex gotBlk = CBlock () gotBlk.deserialize (BytesIO (hex_str_to_bytes (gotHex))) assert_equal (gotBlk.auxpow.hashBlock, 0) self.log.info ("Retrieving block through P2P...") gotBlk = p2pGetter.getBlock (blkHash) assert_equal (gotBlk.auxpow.hashBlock, 0) self.log.info ("Sending zero-hash auxpow through RPC...") blk, blkHash = self.createBlock () blk.auxpow.hashBlock = 0 assert_equal (node.submitblock (blk.serialize ().hex ()), None) assert_equal (node.getbestblockhash (), blkHash) self.log.info ("Sending zero-hash auxpow through P2P...") blk, blkHash = self.createBlock () blk.auxpow.hashBlock = 0 p2pStore.send_blocks_and_test ([blk], node, success=True) assert_equal (node.getbestblockhash (), blkHash) self.log.info ("Sending non-zero nIndex auxpow through RPC...") blk, blkHash = self.createBlock () blk.auxpow.nIndex = 42 assert_equal (node.submitblock (blk.serialize ().hex ()), None) assert_equal (node.getbestblockhash (), blkHash) self.log.info ("Sending non-zero nIndex auxpow through P2P...") blk, blkHash = self.createBlock () blk.auxpow.nIndex = 42 p2pStore.send_blocks_and_test ([blk], node, success=True) assert_equal (node.getbestblockhash (), blkHash)
def test_transaction_serialization(self): legacy_addr = self.nodes[0].getnewaddress("", "legacy") p2sh_addr = self.nodes[0].getnewaddress("", "p2sh-segwit") bech32_addr = self.nodes[0].getnewaddress("", "bech32") self.unknown_addr = self.nodes[1].getnewaddress() # directly seed types of utxos required self.nodes[0].generatetoaddress(1, legacy_addr) self.nodes[0].generatetoaddress(1, p2sh_addr) self.nodes[0].generatetoaddress(1, bech32_addr) self.nodes[0].generatetoaddress(101, self.unknown_addr) # grab utxos filtering by age legacy_utxo = self.nodes[0].listunspent(104, 104)[0] p2sh_utxo = self.nodes[0].listunspent(103, 103)[0] bech32_utxo = self.nodes[0].listunspent(102, 102)[0] submitted_txids = [] self.log.info("Testing legacy UTXO") submitted_txids.append(self.assert_tx_format_also_signed(legacy_utxo, segwit=False)) self.log.info("Testing p2sh UTXO") submitted_txids.append(self.assert_tx_format_also_signed(p2sh_utxo, segwit=True)) self.log.info("Testing bech32 UTXO") submitted_txids.append(self.assert_tx_format_also_signed(bech32_utxo, segwit=True)) blockhash = self.nodes[0].generate(1)[0] hexblock = self.nodes[0].getblock(blockhash, 0) block_details = self.nodes[0].getblock(blockhash, 2) block = CBlock() block.deserialize(BytesIO(hex_str_to_bytes(hexblock))) assert(len(block.vtx) == len(submitted_txids) + 1) assert_equal(len(block_details["tx"]), len(block.vtx)) for tx1, tx2 in zip(block.vtx[1:], block_details["tx"][1:]): # no tuple wildcard, just re-used tx2 on first one assert((tx1.rehash(), tx2["wtxid"]) in submitted_txids) assert((tx2["txid"], tx2["hash"]) in submitted_txids) assert((tx2["txid"], tx2["wtxid"]) in submitted_txids) block.rehash() assert_equal(block.hash, self.nodes[0].getbestblockhash())
def run_test(self): node = self.nodes[0] self.log.info('getmininginfo') mining_info = node.getmininginfo() assert_equal(mining_info['blocks'], 200) assert_equal(mining_info['chain'], 'regtest') assert_equal(mining_info['currentblocktx'], 0) assert_equal(mining_info['currentblockweight'], 0) assert_equal(mining_info['difficulty'], Decimal('4.656542373906925E-10')) assert_equal(mining_info['networkhashps'], Decimal('0.003333333333333334')) assert_equal(mining_info['pooledtx'], 0) # Mine a block to leave initial block download node.generate(1) tmpl = node.getblocktemplate() self.log.info("getblocktemplate: Test capability advertised") assert 'proposal' in tmpl['capabilities'] assert 'coinbasetxn' not in tmpl coinbase_tx = create_coinbase(height=int(tmpl["height"]) + 1) # sequence numbers must not be max for nLockTime to have effect coinbase_tx.vin[0].nSequence = 2**32 - 2 coinbase_tx.rehash() block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) block.nTime = tmpl["curtime"] block.nBits = int(tmpl["bits"], 16) block.nNonce = 0 block.vtx = [coinbase_tx] self.log.info("getblocktemplate: Test valid block") assert_template(node, block, None) self.log.info("submitblock: Test block decode failure") assert_raises_rpc_error(-22, "Block decode failed", node.submitblock, b2x(block.serialize()[:-15])) self.log.info( "getblocktemplate: Test bad input hash for coinbase transaction") bad_block = copy.deepcopy(block) bad_block.vtx[0].vin[0].prevout.hash += 1 bad_block.vtx[0].rehash() assert_template(node, bad_block, 'bad-cb-missing') self.log.info("submitblock: Test invalid coinbase transaction") assert_raises_rpc_error(-22, "Block does not start with a coinbase", node.submitblock, b2x(bad_block.serialize())) self.log.info("getblocktemplate: Test truncated final transaction") assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, { 'data': b2x(block.serialize()[:-1]), 'mode': 'proposal' }) self.log.info("getblocktemplate: Test duplicate transaction") bad_block = copy.deepcopy(block) bad_block.vtx.append(bad_block.vtx[0]) assert_template(node, bad_block, 'bad-txns-duplicate') self.log.info("getblocktemplate: Test invalid transaction") bad_block = copy.deepcopy(block) bad_tx = copy.deepcopy(bad_block.vtx[0]) bad_tx.vin[0].prevout.hash = 255 bad_tx.rehash() bad_block.vtx.append(bad_tx) assert_template(node, bad_block, 'bad-txns-inputs-missingorspent') self.log.info("getblocktemplate: Test nonfinal transaction") bad_block = copy.deepcopy(block) bad_block.vtx[0].nLockTime = 2**32 - 1 bad_block.vtx[0].rehash() assert_template(node, bad_block, 'bad-txns-nonfinal') self.log.info("getblocktemplate: Test bad tx count") # The tx count is immediately after the block header TX_COUNT_OFFSET = 80 bad_block_sn = bytearray(block.serialize()) assert_equal(bad_block_sn[TX_COUNT_OFFSET], 1) bad_block_sn[TX_COUNT_OFFSET] += 1 assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, { 'data': b2x(bad_block_sn), 'mode': 'proposal' }) self.log.info("getblocktemplate: Test bad bits") bad_block = copy.deepcopy(block) bad_block.nBits = 469762303 # impossible in the real world assert_template(node, bad_block, 'bad-diffbits') self.log.info("getblocktemplate: Test bad merkle root") bad_block = copy.deepcopy(block) bad_block.hashMerkleRoot += 1 assert_template(node, bad_block, 'bad-txnmrklroot', False) self.log.info("getblocktemplate: Test bad timestamps") bad_block = copy.deepcopy(block) bad_block.nTime = 2**31 - 1 assert_template(node, bad_block, 'time-too-new') bad_block.nTime = 0 assert_template(node, bad_block, 'time-too-old') self.log.info("getblocktemplate: Test not best block") bad_block = copy.deepcopy(block) bad_block.hashPrevBlock = 123 assert_template(node, bad_block, 'inconclusive-not-best-prevblk')
def mine_large_blocks(node, n): # Make a large scriptPubKey for the coinbase transaction. This is OP_RETURN # followed by 950k of OP_NOP. This would be non-standard in a non-coinbase # transaction but is consensus valid. # Set the nTime if this is the first time this function has been called. # A static variable ensures that time is monotonicly increasing and is therefore # different for each block created => blockhash is unique. if "nTimes" not in mine_large_blocks.__dict__: mine_large_blocks.nTime = 0 # Get the block parameters for the first block big_script = CScript([OP_RETURN] + [OP_NOP] * 950000) best_block = node.getblock(node.getbestblockhash()) height = int(best_block["height"]) + 1 mine_large_blocks.nTime = max(mine_large_blocks.nTime, int(best_block["time"])) + 1 previousblockhash = int(best_block["hash"], 16) for _ in range(n): # Build the coinbase transaction (with large scriptPubKey) coinbase_tx = create_coinbase(height) coinbase_tx.vin[0].nSequence = 2**32 - 1 coinbase_tx.vout[0].scriptPubKey = big_script coinbase_tx.rehash() # Build the block block = CBlock() block.nVersion = best_block["version"] block.hashPrevBlock = previousblockhash block.nTime = mine_large_blocks.nTime block.nBits = int('207fffff', 16) block.nNonce = 0 block.vtx = [coinbase_tx] block.hashMerkleRoot = block.calc_merkle_root() block.solve() # Submit to the node node.submitblock(ToHex(block)) previousblockhash = block.sha256 height += 1 mine_large_blocks.nTime += 1
def build_block_on_tip(self): blockhash = self.generate(self.nodes[2], 1)[0] block_hex = self.nodes[2].getblock(blockhash=blockhash, verbosity=0) block = from_hex(CBlock(), block_hex) block.rehash() return block
def run_test(self): node = self.nodes[0] self.wallet = MiniWallet(node) self.mine_chain() def assert_submitblock(block, result_str_1, result_str_2=None): block.solve() result_str_2 = result_str_2 or 'duplicate-invalid' assert_equal(result_str_1, node.submitblock(hexdata=block.serialize().hex())) assert_equal(result_str_2, node.submitblock(hexdata=block.serialize().hex())) self.log.info('getmininginfo') mining_info = node.getmininginfo() assert_equal(mining_info['blocks'], 200) assert_equal(mining_info['chain'], self.chain) assert 'currentblocktx' not in mining_info assert 'currentblockweight' not in mining_info assert 'currentblocksize' not in mining_info assert_equal(mining_info['difficulty'], Decimal('4.656542373906925E-10')) assert_equal(mining_info['networkhashps'], Decimal('0.003333333333333334')) assert_equal(mining_info['pooledtx'], 0) self.log.info("getblocktemplate: Test default witness commitment") txid = int(self.wallet.send_self_transfer(from_node=node)['wtxid'], 16) tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) # Check that default_witness_commitment is present. assert 'default_witness_commitment' in tmpl witness_commitment = tmpl['default_witness_commitment'] # Check that default_witness_commitment is correct. witness_root = CBlock.get_merkle_root( [ser_uint256(0), ser_uint256(txid)]) script = get_witness_script(witness_root, 0) assert_equal(witness_commitment, script.hex()) # Mine a block to leave initial block download and clear the mempool self.generatetoaddress(node, 1, node.get_deterministic_priv_key().address) tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) self.log.info("getblocktemplate: Test capability advertised") assert 'proposal' in tmpl['capabilities'] assert 'coinbasetxn' not in tmpl next_height = int(tmpl["height"]) coinbase_tx = create_coinbase(height=next_height) # sequence numbers must not be max for nLockTime to have effect coinbase_tx.vin[0].nSequence = 2**32 - 2 coinbase_tx.rehash() block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) block.nTime = tmpl["curtime"] block.nBits = int(tmpl["bits"], 16) block.nNonce = 0 block.vtx = [coinbase_tx] self.log.info("getblocktemplate: segwit rule must be set") assert_raises_rpc_error( -8, "getblocktemplate must be called with the segwit rule set", node.getblocktemplate) self.log.info("getblocktemplate: Test valid block") assert_template(node, block, None) self.log.info("submitblock: Test block decode failure") assert_raises_rpc_error(-22, "Block decode failed", node.submitblock, block.serialize()[:-15].hex()) self.log.info( "getblocktemplate: Test bad input hash for coinbase transaction") bad_block = copy.deepcopy(block) bad_block.vtx[0].vin[0].prevout.hash += 1 bad_block.vtx[0].rehash() assert_template(node, bad_block, 'bad-cb-missing') self.log.info("submitblock: Test invalid coinbase transaction") assert_raises_rpc_error(-22, "Block does not start with a coinbase", node.submitblock, bad_block.serialize().hex()) self.log.info("getblocktemplate: Test truncated final transaction") assert_raises_rpc_error( -22, "Block decode failed", node.getblocktemplate, { 'data': block.serialize()[:-1].hex(), 'mode': 'proposal', 'rules': ['segwit'], }) self.log.info("getblocktemplate: Test duplicate transaction") bad_block = copy.deepcopy(block) bad_block.vtx.append(bad_block.vtx[0]) assert_template(node, bad_block, 'bad-txns-duplicate') assert_submitblock(bad_block, 'bad-txns-duplicate', 'bad-txns-duplicate') self.log.info("getblocktemplate: Test invalid transaction") bad_block = copy.deepcopy(block) bad_tx = copy.deepcopy(bad_block.vtx[0]) bad_tx.vin[0].prevout.hash = 255 bad_tx.rehash() bad_block.vtx.append(bad_tx) assert_template(node, bad_block, 'bad-txns-inputs-missingorspent') assert_submitblock(bad_block, 'bad-txns-inputs-missingorspent') self.log.info("getblocktemplate: Test nonfinal transaction") bad_block = copy.deepcopy(block) bad_block.vtx[0].nLockTime = 2**32 - 1 bad_block.vtx[0].rehash() assert_template(node, bad_block, 'bad-txns-nonfinal') assert_submitblock(bad_block, 'bad-txns-nonfinal') self.log.info("getblocktemplate: Test bad tx count") # The tx count is immediately after the block header bad_block_sn = bytearray(block.serialize()) assert_equal(bad_block_sn[BLOCK_HEADER_SIZE], 1) bad_block_sn[BLOCK_HEADER_SIZE] += 1 assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, { 'data': bad_block_sn.hex(), 'mode': 'proposal', 'rules': ['segwit'], }) self.log.info("getblocktemplate: Test bad bits") bad_block = copy.deepcopy(block) bad_block.nBits = 469762303 # impossible in the real world assert_template(node, bad_block, 'bad-diffbits') self.log.info("getblocktemplate: Test bad merkle root") bad_block = copy.deepcopy(block) bad_block.hashMerkleRoot += 1 assert_template(node, bad_block, 'bad-txnmrklroot', False) assert_submitblock(bad_block, 'bad-txnmrklroot', 'bad-txnmrklroot') self.log.info("getblocktemplate: Test bad timestamps") bad_block = copy.deepcopy(block) bad_block.nTime = 2**31 - 1 assert_template(node, bad_block, 'time-too-new') assert_submitblock(bad_block, 'time-too-new', 'time-too-new') bad_block.nTime = 0 assert_template(node, bad_block, 'time-too-old') assert_submitblock(bad_block, 'time-too-old', 'time-too-old') self.log.info("getblocktemplate: Test not best block") bad_block = copy.deepcopy(block) bad_block.hashPrevBlock = 123 assert_template(node, bad_block, 'inconclusive-not-best-prevblk') assert_submitblock(bad_block, 'prev-blk-not-found', 'prev-blk-not-found') self.log.info('submitheader tests') assert_raises_rpc_error( -22, 'Block header decode failed', lambda: node.submitheader(hexdata='xx' * BLOCK_HEADER_SIZE)) assert_raises_rpc_error( -22, 'Block header decode failed', lambda: node.submitheader(hexdata='ff' * (BLOCK_HEADER_SIZE - 2))) assert_raises_rpc_error( -25, 'Must submit previous header', lambda: node.submitheader( hexdata=super(CBlock, bad_block).serialize().hex())) block.nTime += 1 block.solve() def chain_tip(b_hash, *, status='headers-only', branchlen=1): return { 'hash': b_hash, 'height': 202, 'branchlen': branchlen, 'status': status } assert chain_tip(block.hash) not in node.getchaintips() node.submitheader(hexdata=block.serialize().hex()) assert chain_tip(block.hash) in node.getchaintips() node.submitheader( hexdata=CBlockHeader(block).serialize().hex()) # Noop assert chain_tip(block.hash) in node.getchaintips() bad_block_root = copy.deepcopy(block) bad_block_root.hashMerkleRoot += 2 bad_block_root.solve() assert chain_tip(bad_block_root.hash) not in node.getchaintips() node.submitheader( hexdata=CBlockHeader(bad_block_root).serialize().hex()) assert chain_tip(bad_block_root.hash) in node.getchaintips() # Should still reject invalid blocks, even if we have the header: assert_equal( node.submitblock(hexdata=bad_block_root.serialize().hex()), 'bad-txnmrklroot') assert_equal( node.submitblock(hexdata=bad_block_root.serialize().hex()), 'bad-txnmrklroot') assert chain_tip(bad_block_root.hash) in node.getchaintips() # We know the header for this invalid block, so should just return early without error: node.submitheader( hexdata=CBlockHeader(bad_block_root).serialize().hex()) assert chain_tip(bad_block_root.hash) in node.getchaintips() bad_block_lock = copy.deepcopy(block) bad_block_lock.vtx[0].nLockTime = 2**32 - 1 bad_block_lock.vtx[0].rehash() bad_block_lock.hashMerkleRoot = bad_block_lock.calc_merkle_root() bad_block_lock.solve() assert_equal( node.submitblock(hexdata=bad_block_lock.serialize().hex()), 'bad-txns-nonfinal') assert_equal( node.submitblock(hexdata=bad_block_lock.serialize().hex()), 'duplicate-invalid') # Build a "good" block on top of the submitted bad block bad_block2 = copy.deepcopy(block) bad_block2.hashPrevBlock = bad_block_lock.sha256 bad_block2.solve() assert_raises_rpc_error( -25, 'bad-prevblk', lambda: node.submitheader(hexdata=CBlockHeader( bad_block2).serialize().hex())) # Should reject invalid header right away bad_block_time = copy.deepcopy(block) bad_block_time.nTime = 1 bad_block_time.solve() assert_raises_rpc_error( -25, 'time-too-old', lambda: node.submitheader( hexdata=CBlockHeader(bad_block_time).serialize().hex())) # Should ask for the block from a p2p node, if they announce the header as well: peer = node.add_p2p_connection(P2PDataStore()) peer.wait_for_getheaders(timeout=5) # Drop the first getheaders peer.send_blocks_and_test(blocks=[block], node=node) # Must be active now: assert chain_tip(block.hash, status='active', branchlen=0) in node.getchaintips() # Building a few blocks should give the same results self.generatetoaddress(node, 10, node.get_deterministic_priv_key().address) assert_raises_rpc_error( -25, 'time-too-old', lambda: node.submitheader( hexdata=CBlockHeader(bad_block_time).serialize().hex())) assert_raises_rpc_error( -25, 'bad-prevblk', lambda: node.submitheader(hexdata=CBlockHeader( bad_block2).serialize().hex())) node.submitheader(hexdata=CBlockHeader(block).serialize().hex()) node.submitheader( hexdata=CBlockHeader(bad_block_root).serialize().hex()) assert_equal(node.submitblock(hexdata=block.serialize().hex()), 'duplicate') # valid
def run_test(self): parent = self.nodes[0] #parent2 = self.nodes[1] sidechain = self.nodes[2] sidechain2 = self.nodes[3] # If we're testing post-transition, force a fedpegscript transition and # getting rid of old fedpegscript by making at least another epoch pass by WSH_OP_TRUE = self.nodes[0].decodescript("51")["segwit"]["hex"] # We just randomize the keys a bit to get another valid fedpegscript new_fedpegscript = sidechain.tweakfedpegscript("f00dbabe")["script"] if self.options.post_transition: print("Running test post-transition") for _ in range(30): block_hex = sidechain.getnewblockhex( 0, { "signblockscript": WSH_OP_TRUE, "max_block_witness": 10, "fedpegscript": new_fedpegscript, "extension_space": [] }) sidechain.submitblock(block_hex) assert_equal(sidechain.getsidechaininfo()["current_fedpegscripts"], [new_fedpegscript] * 2) if self.options.pre_transition: print( "Running test pre-transition, dynafed activated from first block" ) for node in self.nodes: node.importprivkey(privkey=node.get_deterministic_priv_key().key, label="mining") util.node_fastmerkle = sidechain parent.generate(101) sidechain.generate(101) self.log.info("sidechain info: {}".format( sidechain.getsidechaininfo())) addrs = sidechain.getpeginaddress() addr = addrs["mainchain_address"] assert_equal( sidechain.decodescript(addrs["claim_script"])["type"], "witness_v0_keyhash") txid1 = parent.sendtoaddress(addr, 24) vout = find_vout_for_address(parent, txid1, addr) # 10+2 confirms required to get into mempool and confirm assert_equal(sidechain.getsidechaininfo()["pegin_confirmation_depth"], 10) parent.generate(1) time.sleep(2) proof = parent.gettxoutproof([txid1]) raw = parent.gettransaction(txid1)["hex"] print("Attempting peg-ins") # First attempt fails the consensus check but gives useful result try: pegtxid = sidechain.claimpegin(raw, proof) raise Exception( "Peg-in should not be mature enough yet, need another block.") except JSONRPCException as e: assert ( "Peg-in Bitcoin transaction needs more confirmations to be sent." in e.error["message"]) # Second attempt simply doesn't hit mempool bar parent.generate(10) try: pegtxid = sidechain.claimpegin(raw, proof) raise Exception( "Peg-in should not be mature enough yet, need another block.") except JSONRPCException as e: assert ( "Peg-in Bitcoin transaction needs more confirmations to be sent." in e.error["message"]) try: pegtxid = sidechain.createrawpegin(raw, proof, 'AEIOU') raise Exception("Peg-in with non-hex claim_script should fail.") except JSONRPCException as e: assert ("Given claim_script is not hex." in e.error["message"]) # Should fail due to non-matching wallet address try: scriptpubkey = sidechain.getaddressinfo( get_new_unconfidential_address(sidechain))["scriptPubKey"] pegtxid = sidechain.claimpegin(raw, proof, scriptpubkey) raise Exception( "Peg-in with non-matching claim_script should fail.") except JSONRPCException as e: assert ( "Given claim_script does not match the given Bitcoin transaction." in e.error["message"]) # 12 confirms allows in mempool parent.generate(1) # Make sure that a tx with a duplicate pegin claim input gets rejected. raw_pegin = sidechain.createrawpegin(raw, proof)["hex"] raw_pegin = FromHex(CTransaction(), raw_pegin) raw_pegin.vin.append(raw_pegin.vin[0]) # duplicate the pegin input raw_pegin = sidechain.signrawtransactionwithwallet( bytes_to_hex_str(raw_pegin.serialize()))["hex"] assert_raises_rpc_error(-26, "bad-txns-inputs-duplicate", sidechain.sendrawtransaction, raw_pegin) # Also try including this tx in a block manually and submitting it. doublespendblock = FromHex(CBlock(), sidechain.getnewblockhex()) doublespendblock.vtx.append(FromHex(CTransaction(), raw_pegin)) doublespendblock.hashMerkleRoot = doublespendblock.calc_merkle_root() add_witness_commitment(doublespendblock) doublespendblock.solve() block_hex = bytes_to_hex_str(doublespendblock.serialize(True)) assert_raises_rpc_error(-25, "bad-txns-inputs-duplicate", sidechain.testproposedblock, block_hex, True) # Should succeed via wallet lookup for address match, and when given raw_pegin = sidechain.createrawpegin(raw, proof)['hex'] signed_pegin = sidechain.signrawtransactionwithwallet(raw_pegin) # Find the address that the peg-in used outputs = [] for pegin_vout in sidechain.decoderawtransaction(raw_pegin)['vout']: if pegin_vout['scriptPubKey']['type'] == 'witness_v0_keyhash': outputs.append({ pegin_vout['scriptPubKey']['addresses'][0]: pegin_vout['value'] }) elif pegin_vout['scriptPubKey']['type'] == 'fee': outputs.append({"fee": pegin_vout['value']}) # Check the createrawtransaction makes the same unsigned peg-in transaction raw_pegin2 = sidechain.createrawtransaction( [{ "txid": txid1, "vout": vout, "pegin_bitcoin_tx": raw, "pegin_txout_proof": proof, "pegin_claim_script": addrs["claim_script"] }], outputs) assert_equal(raw_pegin, raw_pegin2) # Check that createpsbt makes the correct unsigned peg-in pegin_psbt = sidechain.createpsbt( [{ "txid": txid1, "vout": vout, "pegin_bitcoin_tx": raw, "pegin_txout_proof": proof, "pegin_claim_script": addrs["claim_script"] }], outputs) decoded_psbt = sidechain.decodepsbt(pegin_psbt) # Check that pegin_bitcoin_tx == raw, but due to stripping witnesses, we need to compare their txids txid1 = parent.decoderawtransaction( decoded_psbt['inputs'][0]['pegin_bitcoin_tx'])['txid'] txid2 = parent.decoderawtransaction(raw)['txid'] assert_equal(txid1, txid2) # Check the rest assert_equal(decoded_psbt['inputs'][0]['pegin_claim_script'], addrs["claim_script"]) assert_equal(decoded_psbt['inputs'][0]['pegin_txout_proof'], proof) assert_equal(decoded_psbt['inputs'][0]['pegin_genesis_hash'], parent.getblockhash(0)) # Make a psbt without those peg-in data and merge them merge_pegin_psbt = sidechain.createpsbt([{ "txid": txid1, "vout": vout }], outputs) decoded_psbt = sidechain.decodepsbt(merge_pegin_psbt) assert 'pegin_bitcoin_tx' not in decoded_psbt['inputs'][0] assert 'pegin_claim_script' not in decoded_psbt['inputs'][0] assert 'pegin_txout_proof' not in decoded_psbt['inputs'][0] assert 'pegin_genesis_hash' not in decoded_psbt['inputs'][0] merged_pegin_psbt = sidechain.combinepsbt( [pegin_psbt, merge_pegin_psbt]) assert_equal(pegin_psbt, merged_pegin_psbt) # Now sign the psbt signed_psbt = sidechain.walletsignpsbt(pegin_psbt) # Finalize and extract and compare fin_psbt = sidechain.finalizepsbt(signed_psbt['psbt']) assert_equal(fin_psbt, signed_pegin) # Try funding a psbt with the peg-in assert_equal(sidechain.getbalance()['bitcoin'], 50) out_bal = 0 outputs.append({sidechain.getnewaddress(): 49.999}) for out in outputs: for val in out.values(): out_bal += Decimal(val) assert_greater_than(out_bal, 50) pegin_psbt = sidechain.walletcreatefundedpsbt( [{ "txid": txid1, "vout": vout, "pegin_bitcoin_tx": raw, "pegin_txout_proof": proof, "pegin_claim_script": addrs["claim_script"] }], outputs) signed_psbt = sidechain.walletsignpsbt(pegin_psbt['psbt']) fin_psbt = sidechain.finalizepsbt(signed_psbt['psbt']) assert fin_psbt['complete'] sample_pegin_struct = FromHex(CTransaction(), signed_pegin["hex"]) # Round-trip peg-in transaction using python serialization assert_equal(signed_pegin["hex"], bytes_to_hex_str(sample_pegin_struct.serialize())) # Store this for later (evil laugh) sample_pegin_witness = sample_pegin_struct.wit.vtxinwit[0].peginWitness pegtxid1 = sidechain.claimpegin(raw, proof) # Make sure a second pegin claim does not get accepted in the mempool when # another mempool tx already claims that pegin. assert_raises_rpc_error(-4, "txn-mempool-conflict", sidechain.claimpegin, raw, proof) # Will invalidate the block that confirms this transaction later self.sync_all(self.node_groups) blockhash = sidechain2.generate(1) self.sync_all(self.node_groups) sidechain.generate(5) tx1 = sidechain.gettransaction(pegtxid1) if "confirmations" in tx1 and tx1["confirmations"] == 6: print("Peg-in is confirmed: Success!") else: raise Exception("Peg-in confirmation has failed.") # Look at pegin fields decoded = sidechain.decoderawtransaction(tx1["hex"]) assert decoded["vin"][0]["is_pegin"] == True assert len(decoded["vin"][0]["pegin_witness"]) > 0 # Check that there's sufficient fee for the peg-in vsize = decoded["vsize"] fee_output = decoded["vout"][1] fallbackfee_pervbyte = Decimal("0.00001") / Decimal("1000") assert fee_output["scriptPubKey"]["type"] == "fee" assert fee_output["value"] >= fallbackfee_pervbyte * vsize # Quick reorg checks of pegs sidechain.invalidateblock(blockhash[0]) if sidechain.gettransaction(pegtxid1)["confirmations"] != 0: raise Exception( "Peg-in didn't unconfirm after invalidateblock call.") # Re-org causes peg-ins to get booted(wallet will resubmit in 10 minutes) assert_equal(sidechain.getrawmempool(), []) sidechain.sendrawtransaction(tx1["hex"]) # Create duplicate claim, put it in block along with current one in mempool # to test duplicate-in-block claims between two txs that are in the same block. raw_pegin = sidechain.createrawpegin(raw, proof)["hex"] raw_pegin = sidechain.signrawtransactionwithwallet(raw_pegin)["hex"] raw_pegin = FromHex(CTransaction(), raw_pegin) doublespendblock = FromHex(CBlock(), sidechain.getnewblockhex()) assert (len(doublespendblock.vtx) == 2) # coinbase and pegin doublespendblock.vtx.append(raw_pegin) doublespendblock.hashMerkleRoot = doublespendblock.calc_merkle_root() add_witness_commitment(doublespendblock) doublespendblock.solve() block_hex = bytes_to_hex_str(doublespendblock.serialize(True)) assert_raises_rpc_error(-25, "bad-txns-double-pegin", sidechain.testproposedblock, block_hex, True) # Re-enters block sidechain.generate(1) if sidechain.gettransaction(pegtxid1)["confirmations"] != 1: raise Exception("Peg-in should have one confirm on side block.") sidechain.reconsiderblock(blockhash[0]) if sidechain.gettransaction(pegtxid1)["confirmations"] != 6: raise Exception("Peg-in should be back to 6 confirms.") # Now the pegin is already claimed in a confirmed tx. # In that case, a duplicate claim should (1) not be accepted in the mempool # and (2) not be accepted in a block. assert_raises_rpc_error(-4, "pegin-already-claimed", sidechain.claimpegin, raw, proof) # For case (2), manually craft a block and include the tx. doublespendblock = FromHex(CBlock(), sidechain.getnewblockhex()) doublespendblock.vtx.append(raw_pegin) doublespendblock.hashMerkleRoot = doublespendblock.calc_merkle_root() add_witness_commitment(doublespendblock) doublespendblock.solve() block_hex = bytes_to_hex_str(doublespendblock.serialize(True)) assert_raises_rpc_error(-25, "bad-txns-double-pegin", sidechain.testproposedblock, block_hex, True) # Do multiple claims in mempool n_claims = 6 print("Flooding mempool with a few claims") pegtxs = [] sidechain.generate(101) # Do mixture of raw peg-in and automatic peg-in tx construction # where raw creation is done on another node for i in range(n_claims): addrs = sidechain.getpeginaddress() txid = parent.sendtoaddress(addrs["mainchain_address"], 1) parent.generate(1) proof = parent.gettxoutproof([txid]) raw = parent.gettransaction(txid)["hex"] if i % 2 == 0: parent.generate(11) pegtxs += [sidechain.claimpegin(raw, proof)] else: # The raw API doesn't check for the additional 2 confirmation buffer # So we only get 10 confirms then send off. Miners will add to block anyways. # Don't mature whole way yet to test signing immature peg-in input parent.generate(8) # Wallet in sidechain2 gets funds instead of sidechain raw_pegin = sidechain2.createrawpegin( raw, proof, addrs["claim_script"])["hex"] # First node should also be able to make a valid transaction with or without 3rd arg # since this wallet originated the claim_script itself sidechain.createrawpegin(raw, proof, addrs["claim_script"]) sidechain.createrawpegin(raw, proof) signed_pegin = sidechain.signrawtransactionwithwallet( raw_pegin) assert (signed_pegin["complete"]) assert ("warning" in signed_pegin) # warning for immature peg-in # fully mature them now parent.generate(1) pegtxs += [sidechain.sendrawtransaction(signed_pegin["hex"])] self.sync_all(self.node_groups) sidechain2.generate(1) for i, pegtxid in enumerate(pegtxs): if i % 2 == 0: tx = sidechain.gettransaction(pegtxid) else: tx = sidechain2.gettransaction(pegtxid) if "confirmations" not in tx or tx["confirmations"] == 0: raise Exception("Peg-in confirmation has failed.") print("Test pegouts") self.test_pegout(get_new_unconfidential_address(parent, "legacy"), sidechain) self.test_pegout(get_new_unconfidential_address(parent, "p2sh-segwit"), sidechain) self.test_pegout(get_new_unconfidential_address(parent, "bech32"), sidechain) print("Test pegout P2SH") parent_chain_addr = get_new_unconfidential_address(parent) parent_pubkey = parent.getaddressinfo(parent_chain_addr)["pubkey"] parent_chain_p2sh_addr = parent.createmultisig( 1, [parent_pubkey])["address"] self.test_pegout(parent_chain_p2sh_addr, sidechain) print("Test pegout Garbage") parent_chain_addr = "garbage" try: self.test_pegout(parent_chain_addr, sidechain) raise Exception("A garbage address should fail.") except JSONRPCException as e: assert ("Invalid Bitcoin address" in e.error["message"]) print("Test pegout Garbage valid") prev_txid = sidechain.sendtoaddress(sidechain.getnewaddress(), 1) sidechain.generate(1) pegout_chain = 'a' * 64 pegout_hex = 'b' * 500 inputs = [{"txid": prev_txid, "vout": 0}] outputs = {"vdata": [pegout_chain, pegout_hex]} rawtx = sidechain.createrawtransaction(inputs, outputs) raw_pegout = sidechain.decoderawtransaction(rawtx) assert 'vout' in raw_pegout and len(raw_pegout['vout']) > 0 pegout_tested = False for output in raw_pegout['vout']: scriptPubKey = output['scriptPubKey'] if 'type' in scriptPubKey and scriptPubKey['type'] == 'nulldata': assert ('pegout_hex' in scriptPubKey and 'pegout_asm' in scriptPubKey and 'pegout_type' in scriptPubKey) assert ('pegout_chain' in scriptPubKey and 'pegout_reqSigs' not in scriptPubKey and 'pegout_addresses' not in scriptPubKey) assert scriptPubKey['pegout_type'] == 'nonstandard' assert scriptPubKey['pegout_chain'] == pegout_chain assert scriptPubKey['pegout_hex'] == pegout_hex pegout_tested = True break assert pegout_tested print( "Now test failure to validate peg-ins based on intermittent bitcoind rpc failure" ) self.stop_node(1) txid = parent.sendtoaddress(addr, 1) parent.generate(12) proof = parent.gettxoutproof([txid]) raw = parent.gettransaction(txid)["hex"] sidechain.claimpegin(raw, proof) # stuck peg sidechain.generate(1) print("Waiting to ensure block is being rejected by sidechain2") time.sleep(5) assert (sidechain.getblockcount() != sidechain2.getblockcount()) print("Restarting parent2") self.start_node(1) connect_nodes_bi(self.nodes, 0, 1) # Don't make a block, race condition when pegin-invalid block # is awaiting further validation, nodes reject subsequent blocks # even ones they create print( "Now waiting for node to re-evaluate peg-in witness failed block... should take a few seconds" ) self.sync_all(self.node_groups) print("Completed!\n") print("Now send funds out in two stages, partial, and full") some_btc_addr = get_new_unconfidential_address(parent) bal_1 = sidechain.getwalletinfo()["balance"]['bitcoin'] try: sidechain.sendtomainchain(some_btc_addr, bal_1 + 1) raise Exception("Sending out too much; should have failed") except JSONRPCException as e: assert ("Insufficient funds" in e.error["message"]) assert (sidechain.getwalletinfo()["balance"]["bitcoin"] == bal_1) try: sidechain.sendtomainchain(some_btc_addr + "b", bal_1 - 1) raise Exception("Sending to invalid address; should have failed") except JSONRPCException as e: assert ("Invalid Bitcoin address" in e.error["message"]) assert (sidechain.getwalletinfo()["balance"]["bitcoin"] == bal_1) try: sidechain.sendtomainchain("1Nro9WkpaKm9axmcfPVp79dAJU1Gx7VmMZ", bal_1 - 1) raise Exception( "Sending to mainchain address when should have been testnet; should have failed" ) except JSONRPCException as e: assert ("Invalid Bitcoin address" in e.error["message"]) assert (sidechain.getwalletinfo()["balance"]["bitcoin"] == bal_1) # Test superfluous peg-in witness data on regular spend before we have no funds raw_spend = sidechain.createrawtransaction( [], {sidechain.getnewaddress(): 1}) fund_spend = sidechain.fundrawtransaction(raw_spend) sign_spend = sidechain.signrawtransactionwithwallet(fund_spend["hex"]) signed_struct = FromHex(CTransaction(), sign_spend["hex"]) # Non-witness tx has no witness serialized yet if len(signed_struct.wit.vtxinwit) == 0: signed_struct.wit.vtxinwit = [CTxInWitness()] signed_struct.wit.vtxinwit[ 0].peginWitness.stack = sample_pegin_witness.stack assert_equal( sidechain.testmempoolaccept( [bytes_to_hex_str(signed_struct.serialize())])[0]["allowed"], False) assert_equal( sidechain.testmempoolaccept([ bytes_to_hex_str(signed_struct.serialize()) ])[0]["reject-reason"], "68: extra-pegin-witness") signed_struct.wit.vtxinwit[0].peginWitness.stack = [b'\x00' * 100000 ] # lol assert_equal( sidechain.testmempoolaccept( [bytes_to_hex_str(signed_struct.serialize())])[0]["allowed"], False) assert_equal( sidechain.testmempoolaccept([ bytes_to_hex_str(signed_struct.serialize()) ])[0]["reject-reason"], "68: extra-pegin-witness") peg_out_txid = sidechain.sendtomainchain(some_btc_addr, 1) peg_out_details = sidechain.decoderawtransaction( sidechain.getrawtransaction(peg_out_txid)) # peg-out, change, fee assert (len(peg_out_details["vout"]) == 3) found_pegout_value = False for output in peg_out_details["vout"]: if "value" in output and output["value"] == 1: found_pegout_value = True assert (found_pegout_value) bal_2 = sidechain.getwalletinfo()["balance"]["bitcoin"] # Make sure balance went down assert (bal_2 + 1 < bal_1) # Send rest of coins using subtractfee from output arg sidechain.sendtomainchain(some_btc_addr, bal_2, True) assert (sidechain.getwalletinfo()["balance"]['bitcoin'] == 0) print('Test coinbase peg-in maturity rules') # Have bitcoin output go directly into a claim output pegin_info = sidechain.getpeginaddress() mainchain_addr = pegin_info["mainchain_address"] # Watch the address so we can get tx without txindex parent.importaddress(mainchain_addr) claim_block = parent.generatetoaddress(50, mainchain_addr)[0] self.sync_all(self.node_groups) block_coinbase = parent.getblock(claim_block, 2)["tx"][0] claim_txid = block_coinbase["txid"] claim_tx = block_coinbase["hex"] claim_proof = parent.gettxoutproof([claim_txid], claim_block) # Can't claim something even though it has 50 confirms since it's coinbase assert_raises_rpc_error( -8, "Peg-in Bitcoin transaction needs more confirmations to be sent.", sidechain.claimpegin, claim_tx, claim_proof) # If done via raw API, still doesn't work coinbase_pegin = sidechain.createrawpegin(claim_tx, claim_proof) assert_equal(coinbase_pegin["mature"], False) signed_pegin = sidechain.signrawtransactionwithwallet( coinbase_pegin["hex"])["hex"] assert_raises_rpc_error( -26, "bad-pegin-witness, Needs more confirmations.", sidechain.sendrawtransaction, signed_pegin) # 50 more blocks to allow wallet to make it succeed by relay and consensus parent.generatetoaddress(50, parent.getnewaddress()) self.sync_all(self.node_groups) # Wallet still doesn't want to for 2 more confirms assert_equal( sidechain.createrawpegin(claim_tx, claim_proof)["mature"], False) # But we can just shoot it off claim_txid = sidechain.sendrawtransaction(signed_pegin) sidechain.generatetoaddress(1, sidechain.getnewaddress()) self.sync_all(self.node_groups) assert_equal(sidechain.gettransaction(claim_txid)["confirmations"], 1) # Test a confidential pegin. print("Performing a confidential pegin.") # start pegin pegin_addrs = sidechain.getpeginaddress() assert_equal( sidechain.decodescript(pegin_addrs["claim_script"])["type"], "witness_v0_keyhash") pegin_addr = addrs["mainchain_address"] txid_fund = parent.sendtoaddress(pegin_addr, 10) # 10+2 confirms required to get into mempool and confirm parent.generate(11) self.sync_all(self.node_groups) proof = parent.gettxoutproof([txid_fund]) raw = parent.gettransaction(txid_fund)["hex"] raw_pegin = sidechain.createrawpegin(raw, proof)['hex'] pegin = FromHex(CTransaction(), raw_pegin) # add new blinding pubkey for the pegin output pegin.vout[0].nNonce = CTxOutNonce( hex_str_to_bytes( sidechain.getaddressinfo(sidechain.getnewaddress( "", "blech32"))["confidential_key"])) # now add an extra input and output from listunspent; we need a blinded output for this blind_addr = sidechain.getnewaddress("", "blech32") sidechain.sendtoaddress(blind_addr, 15) sidechain.generate(6) # Make sure sidechain2 knows about the same input self.sync_all(self.node_groups) unspent = [ u for u in sidechain.listunspent(6, 6) if u["amount"] == 15 ][0] assert (unspent["spendable"]) assert ("amountcommitment" in unspent) pegin.vin.append( CTxIn(COutPoint(int(unspent["txid"], 16), unspent["vout"]))) # insert corresponding output before fee output new_destination = sidechain.getaddressinfo( sidechain.getnewaddress("", "blech32")) new_dest_script_pk = hex_str_to_bytes(new_destination["scriptPubKey"]) new_dest_nonce = CTxOutNonce( hex_str_to_bytes(new_destination["confidential_key"])) new_dest_asset = pegin.vout[0].nAsset pegin.vout.insert( 1, CTxOut( int(unspent["amount"] * COIN) - 10000, new_dest_script_pk, new_dest_asset, new_dest_nonce)) # add the 10 ksat fee pegin.vout[2].nValue.setToAmount(pegin.vout[2].nValue.getAmount() + 10000) pegin_hex = ToHex(pegin) # test with both blindraw and rawblindraw raw_pegin_blinded1 = sidechain.blindrawtransaction(pegin_hex) raw_pegin_blinded2 = sidechain.rawblindrawtransaction( pegin_hex, ["", unspent["amountblinder"]], [10, 15], [unspent["asset"]] * 2, ["", unspent["assetblinder"]], "", False) pegin_signed1 = sidechain.signrawtransactionwithwallet( raw_pegin_blinded1) pegin_signed2 = sidechain.signrawtransactionwithwallet( raw_pegin_blinded2) for pegin_signed in [pegin_signed1, pegin_signed2]: final_decoded = sidechain.decoderawtransaction(pegin_signed["hex"]) assert (final_decoded["vin"][0]["is_pegin"]) assert (not final_decoded["vin"][1]["is_pegin"]) assert ("assetcommitment" in final_decoded["vout"][0]) assert ("valuecommitment" in final_decoded["vout"][0]) assert ("commitmentnonce" in final_decoded["vout"][0]) assert ("value" not in final_decoded["vout"][0]) assert ("asset" not in final_decoded["vout"][0]) assert (final_decoded["vout"][0]["commitmentnonce_fully_valid"]) assert ("assetcommitment" in final_decoded["vout"][1]) assert ("valuecommitment" in final_decoded["vout"][1]) assert ("commitmentnonce" in final_decoded["vout"][1]) assert ("value" not in final_decoded["vout"][1]) assert ("asset" not in final_decoded["vout"][1]) assert (final_decoded["vout"][1]["commitmentnonce_fully_valid"]) assert ("value" in final_decoded["vout"][2]) assert ("asset" in final_decoded["vout"][2]) # check that it is accepted in either mempool accepted = sidechain.testmempoolaccept([pegin_signed["hex"]])[0] if not accepted["allowed"]: raise Exception(accepted["reject-reason"]) accepted = sidechain2.testmempoolaccept([pegin_signed["hex"]])[0] if not accepted["allowed"]: raise Exception(accepted["reject-reason"]) print("Blinded transaction looks ok!" ) # need this print to distinguish failures in for loop print('Success!') # Manually stop sidechains first, then the parent chains. self.stop_node(2) self.stop_node(3) self.stop_node(0) self.stop_node(1)
def run_test(self): self.stop_node(0) shutil.rmtree(self.nodes[0].datadir) initialize_datadir(self.options.tmpdir, 0) self.log.info("Test with no genesis file") self.nodes[0].assert_start_raises_init_error([], 'ReadGenesisBlock: unable to read genesis file', match=ErrorMatch.PARTIAL_REGEX) self.log.info("Phase 1: Tests using genesis block") self.log.info("Test correct genesis file") self.writeGenesisBlockToFile(self.nodes[0].datadir) self.start_node(0) self.stop_node(0) self.log.info("Restart with correct genesis file") self.start_node(0) self.stop_node(0) self.log.info("Test incorrect genesis block - No Coinbase") genesis_coinbase = createGenesisCoinbase(self.signblockpubkey) genesis_coinbase.vin[0].prevout.hash = 111111 genesis = createIncorectGenesisBlock(genesis_coinbase, self.signblockprivkey, self.signblockpubkey) writeIncorrectGenesisBlockToFile(self.nodes[0].datadir, genesis) self.nodes[0].assert_start_raises_init_error([], 'ReadGenesisBlock: invalid genesis block', match=ErrorMatch.PARTIAL_REGEX) self.log.info("Test incorrect genesis block - Incorrect height") genesis_coinbase_height = createGenesisCoinbase(self.signblockpubkey) genesis_coinbase_height.vin[0].prevout.n = 10 genesis = createIncorectGenesisBlock(genesis_coinbase_height, self.signblockprivkey, self.signblockpubkey) writeIncorrectGenesisBlockToFile(self.nodes[0].datadir, genesis) self.nodes[0].assert_start_raises_init_error([], 'ReadGenesisBlock: invalid height in genesis block', match=ErrorMatch.PARTIAL_REGEX) self.log.info("Test incorrect genesis block - Multiple transactions") genesis_coinbase = createGenesisCoinbase(self.signblockpubkey) genesis = createIncorectGenesisBlock(genesis_coinbase, self.signblockprivkey, self.signblockpubkey) genesis.vtx.append(CTransaction()) genesis.hashMerkleRoot = genesis.calc_merkle_root() genesis.hashImMerkleRoot = genesis.calc_immutable_merkle_root() genesis.solve(self.signblockprivkey) writeIncorrectGenesisBlockToFile(self.nodes[0].datadir, genesis) self.nodes[0].assert_start_raises_init_error([], 'ReadGenesisBlock: invalid genesis block', match=ErrorMatch.PARTIAL_REGEX) self.log.info("Test incorrect genesis block - No proof") genesis = createIncorectGenesisBlock(genesis_coinbase, self.signblockprivkey, self.signblockpubkey) genesis.proof.clear() writeIncorrectGenesisBlockToFile(self.nodes[0].datadir, genesis) self.nodes[0].assert_start_raises_init_error([], 'ReadGenesisBlock: invalid genesis block', match=ErrorMatch.PARTIAL_REGEX) self.log.info("Test incorrect genesis block - Insufficient Proof") genesis = createIncorectGenesisBlock(genesis_coinbase, self.signblockprivkey, self.signblockpubkey) genesis.proof = genesis.proof[:-1] writeIncorrectGenesisBlockToFile(self.nodes[0].datadir, genesis) self.nodes[0].assert_start_raises_init_error([], 'ReadGenesisBlock: invalid genesis block', match=ErrorMatch.PARTIAL_REGEX) self.log.info("Test incorrect genesis block - Incorrect xfieldType") genesis_coinbase = createGenesisCoinbase(self.signblockpubkey) genesis = CBlock() genesis.nTime = int(time.time() + 600) genesis.hashPrevBlock = 0 genesis.vtx.append(genesis_coinbase) genesis.hashMerkleRoot = genesis.calc_merkle_root() genesis.hashImMerkleRoot = genesis.calc_immutable_merkle_root() genesis.xfieldType = 0 genesis.xfield = hex_str_to_bytes(self.signblockpubkey) genesis.solve(self.signblockprivkey) writeIncorrectGenesisBlockToFile(self.nodes[0].datadir, genesis) self.nodes[0].assert_start_raises_init_error([], 'ReadGenesisBlock: invalid xfieldType in genesis block', match=ErrorMatch.PARTIAL_REGEX) self.log.info("Test incorrect genesis block - Incorrect xfield") genesis_coinbase = createGenesisCoinbase(self.signblockpubkey) genesis = CBlock() genesis.nTime = int(time.time() + 600) genesis.hashPrevBlock = 0 genesis.vtx.append(genesis_coinbase) genesis.hashMerkleRoot = genesis.calc_merkle_root() genesis.hashImMerkleRoot = genesis.calc_immutable_merkle_root() genesis.xfieldType = 1 genesis.xfield = hex_str_to_bytes(self.signblockpubkey[:32]) genesis.solve(self.signblockprivkey) writeIncorrectGenesisBlockToFile(self.nodes[0].datadir, genesis) self.nodes[0].assert_start_raises_init_error([], 'Aggregate Public Key for Signed Block is invalid', match=ErrorMatch.PARTIAL_REGEX) self.log.info("Test incorrect genesis block - No hashMerkleRoot") genesis_coinbase = createGenesisCoinbase(self.signblockpubkey) genesis = CBlock() genesis.nTime = int(time.time() + 600) genesis.hashPrevBlock = 0 genesis.vtx.append(genesis_coinbase) genesis.xfieldType = 1 genesis.xfield = hex_str_to_bytes(self.signblockpubkey) # not populating hashMerkleRoot and hashImMerkleRoot genesis.solve(self.signblockprivkey) writeIncorrectGenesisBlockToFile(self.nodes[0].datadir, genesis) self.nodes[0].assert_start_raises_init_error([], 'ReadGenesisBlock: invalid MerkleRoot in genesis block', match=ErrorMatch.PARTIAL_REGEX) self.log.info("Test incorrect genesis block - No hashImMerkleRoot") genesis_coinbase = createGenesisCoinbase(self.signblockpubkey) genesis = CBlock() genesis.nTime = int(time.time() + 600) genesis.hashPrevBlock = 0 genesis.vtx.append(genesis_coinbase) genesis.hashMerkleRoot = genesis.calc_merkle_root() genesis.xfieldType = 1 genesis.xfield = hex_str_to_bytes(self.signblockpubkey) # not populating hashImMerkleRoot genesis.solve(self.signblockprivkey) writeIncorrectGenesisBlockToFile(self.nodes[0].datadir, genesis) self.nodes[0].assert_start_raises_init_error([], 'ReadGenesisBlock: invalid MerkleRoot in genesis block', match=ErrorMatch.PARTIAL_REGEX) self.log.info("Phase 2: Tests using genesis.dat file") self.log.info("Test new genesis file") self.genesisBlock = None self.writeGenesisBlockToFile(self.nodes[0].datadir, nTime=int(time.time())) #different genesis file self.nodes[0].assert_start_raises_init_error([], 'Error: Incorrect or no genesis block found.', match=ErrorMatch.PARTIAL_REGEX) datadir = self.nodes[0].datadir genesisFile = os.path.join(datadir, "genesis.dat") self.log.info("Test incorrect genesis file - append 2 bytes") self.writeGenesisBlockToFile(self.nodes[0].datadir) with open(genesisFile, 'a', encoding='utf8') as f: f.write("abcd") self.nodes[0].assert_start_raises_init_error([], 'ReadGenesisBlock: invalid genesis file', match=ErrorMatch.PARTIAL_REGEX) os.remove(genesisFile) self.log.info("Test incorrect genesis file - append many bytes") self.writeGenesisBlockToFile(self.nodes[0].datadir) with open(genesisFile, 'a', encoding='utf8') as f: s = "".join([str(i) for i in range(0,16) for j in range(0, 100)]) f.write(s) self.nodes[0].assert_start_raises_init_error([], 'ReadGenesisBlock: invalid genesis file', match=ErrorMatch.PARTIAL_REGEX) os.remove(genesisFile) self.log.info("Test incorrect genesis file - replace 2 bytes") self.writeGenesisBlockToFile(self.nodes[0].datadir) with open(genesisFile, 'r+', encoding='utf8') as f: content = f.readline() clen = len(content) content = content[:500] + "0000" + content[504:] assert(len(content) == clen) f.write(content) self.nodes[0].assert_start_raises_init_error([], 'ReadGenesisBlock: invalid genesis file', match=ErrorMatch.PARTIAL_REGEX) os.remove(genesisFile) self.log.info("Test incorrect genesis file - insert 2 bytes") content = "" self.writeGenesisBlockToFile(self.nodes[0].datadir) with open(genesisFile, 'r+', encoding='utf8') as f: content = f.readline() clen = len(content) content = content[:550] + "1111" + content[550:] assert(len(content) == clen + 4) f.write(content) self.nodes[0].assert_start_raises_init_error([], 'ReadGenesisBlock: invalid genesis file', match=ErrorMatch.PARTIAL_REGEX) os.remove(genesisFile) self.log.info("Test incorrect genesis file - remove 2 bytes") content = "" self.writeGenesisBlockToFile(self.nodes[0].datadir) with open(genesisFile, 'r+', encoding='utf8') as f: content = f.readline() clen = len(content) content = content[:100] + content[104:] assert(len(content) == clen - 4) f.write(content) self.nodes[0].assert_start_raises_init_error([], 'ReadGenesisBlock: invalid genesis file', match=ErrorMatch.PARTIAL_REGEX) os.remove(genesisFile) self.log.info("Test incorrect genesis file - truncate file") self.writeGenesisBlockToFile(self.nodes[0].datadir) with open(genesisFile, 'r+', encoding='utf8') as f: f.truncate(500) self.nodes[0].assert_start_raises_init_error([], 'CDataStream::read().*end of data', match=ErrorMatch.PARTIAL_REGEX) self.log.info("Phase 3: Edit genesis file after sarting the blockchain") self.stop_node(0) shutil.rmtree(self.nodes[0].datadir) initialize_datadir(self.options.tmpdir, 0) self.log.info("Starting node") self.writeGenesisBlockToFile(self.nodes[0].datadir) self.start_node(0) self.nodes[0].add_p2p_connection(P2PInterface()) self.log.info("Generating 10 blocks") blocks = self.nodes[0].generate(10, self.signblockprivkey_wif) self.sync_all([self.nodes[0:1]]) assert_equal(self.nodes[0].getbestblockhash(), blocks[-1]) self.stop_node(0) shutil.copytree(self.nodes[0].datadir, os.path.join(self.options.tmpdir, "backup")) self.log.info("Creating corrupt genesis file") with open(genesisFile, 'r+', encoding='utf8') as f: content = f.readline() clen = len(content) content = content[:500] + "0000" + content[504:] assert(len(content) == clen) f.write(content) self.nodes[0].assert_start_raises_init_error([]) self.log.info("Starting node again") self.genesisBlock = None self.writeGenesisBlockToFile(self.nodes[0].datadir) self.nodes[0].assert_start_raises_init_error([], 'Error: Incorrect or no genesis block found.', match=ErrorMatch.PARTIAL_REGEX) self.log.info("Recovering original blockchain") shutil.rmtree(self.nodes[0].datadir) shutil.copytree(os.path.join(self.options.tmpdir, "backup"), self.nodes[0].datadir) self.start_node(0) self.nodes[0].add_p2p_connection(P2PInterface()) self.sync_all([self.nodes[0:1]]) assert_equal(self.nodes[0].getbestblockhash(), blocks[-1]) self.log.info("Blockchain intact!")
def test_compactblock_construction(self, node, test_node, use_witness_address): # Generate a bunch of transactions. node.generate(101) num_transactions = 25 address = node.getnewaddress() segwit_tx_generated = False if use_witness_address: # Want at least one segwit spend, so move some funds to # a witness address. address = node.addwitnessaddress(address) value_to_send = 1000 segwit_txid = node.sendtoaddress(address, satoshi_round(value_to_send)) segwit_tx = node.getrawtransaction(segwit_txid, 1) vout = next( filter(lambda vout: int(vout['value']) == 1000, segwit_tx['vout'])) node.generate(1) segwit_spend_txid = node.sendtypeto( '', '', [{ 'address': address, 'amount': 0.1 }], '', '', False, {'inputs': [{ 'tx': segwit_txid, 'n': vout['n'] }]}) segwit_spend_tx = node.gettransaction(segwit_spend_txid) segwit_spend = FromHex(CTransaction(), segwit_spend_tx["hex"]) if not segwit_spend.wit.is_null(): segwit_tx_generated = True num_transactions -= 1 for i in range(num_transactions): node.sendtoaddress(address, 0.1) if use_witness_address: assert segwit_tx_generated # check that our test is not broken # Wait until we've seen the block announcement for the resulting tip tip = int(node.getbestblockhash(), 16) test_node.wait_for_block_announcement(tip) # Make sure we will receive a fast-announce compact block self.request_cb_announcements(test_node, node) # Now mine a block, and look at the resulting compact block. test_node.clear_block_announcement() block_hash = int(node.generate(1)[0], 16) # Store the raw block in our internal format. block = FromHex(CBlock(), node.getblock("%02x" % block_hash, False)) for tx in block.vtx: tx.calc_sha256() block.rehash() # Wait until the block was announced (via compact blocks) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock) # Now fetch and check the compact block header_and_shortids = None with mininode_lock: assert_in("cmpctblock", test_node.last_message) # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs( test_node.last_message["cmpctblock"].header_and_shortids) self.check_compactblock_construction_from_block( header_and_shortids, block_hash, block) # Now fetch the compact block using a normal non-announce getdata with mininode_lock: test_node.clear_block_announcement() inv = CInv(4, block_hash) # 4 == "CompactBlock" test_node.send_message(msg_getdata([inv])) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock) # Now fetch and check the compact block header_and_shortids = None with mininode_lock: assert "cmpctblock" in test_node.last_message # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs( test_node.last_message["cmpctblock"].header_and_shortids) self.check_compactblock_construction_from_block( header_and_shortids, block_hash, block)
def run_test(self): node = self.nodes[0] def assert_submitblock(block, result_str_1, result_str_2=None): block.solve() result_str_2 = result_str_2 or 'duplicate-invalid' assert_equal(result_str_1, node.submitblock(hexdata=block.serialize().hex())) assert_equal(result_str_2, node.submitblock(hexdata=block.serialize().hex())) self.log.info('getmininginfo') mining_info = node.getmininginfo() assert_equal(mining_info['blocks'], 200) assert_equal(mining_info['chain'], 'regtest') assert_equal(mining_info['currentblocksize'], 0) assert_equal(mining_info['currentblocktx'], 0) assert_equal(mining_info['difficulty'], Decimal('4.656542373906925E-10')) assert_equal(mining_info['networkhashps'], Decimal('0.003333333333333334')) assert_equal(mining_info['pooledtx'], 0) # Mine a block to leave initial block download node.generatetoaddress(1, node.get_deterministic_priv_key().address) tmpl = node.getblocktemplate() self.log.info("getblocktemplate: Test capability advertised") assert 'proposal' in tmpl['capabilities'] assert 'coinbasetxn' not in tmpl next_height = int(tmpl["height"]) coinbase_tx = create_coinbase(height=next_height) # sequence numbers must not be max for nLockTime to have effect coinbase_tx.vin[0].nSequence = 2**32 - 2 coinbase_tx.rehash() # round-trip the encoded bip34 block height commitment assert_equal(CScriptNum.decode(coinbase_tx.vin[0].scriptSig), next_height) # round-trip negative and multi-byte CScriptNums to catch python # regression assert_equal(CScriptNum.decode(CScriptNum.encode(CScriptNum(1500))), 1500) assert_equal(CScriptNum.decode(CScriptNum.encode(CScriptNum(-1500))), -1500) assert_equal(CScriptNum.decode(CScriptNum.encode(CScriptNum(-1))), -1) block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) block.nTime = tmpl["curtime"] block.nBits = int(tmpl["bits"], 16) block.nNonce = 0 block.vtx = [coinbase_tx] self.log.info("getblocktemplate: Test valid block") assert_template(node, block, None) self.log.info("submitblock: Test block decode failure") assert_raises_rpc_error(-22, "Block decode failed", node.submitblock, block.serialize()[:-15].hex()) self.log.info( "getblocktemplate: Test bad input hash for coinbase transaction") bad_block = copy.deepcopy(block) bad_block.vtx[0].vin[0].prevout.hash += 1 bad_block.vtx[0].rehash() assert_template(node, bad_block, 'bad-cb-missing') self.log.info("submitblock: Test invalid coinbase transaction") assert_raises_rpc_error(-22, "Block does not start with a coinbase", node.submitblock, bad_block.serialize().hex()) self.log.info("getblocktemplate: Test truncated final transaction") assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, { 'data': block.serialize()[:-1].hex(), 'mode': 'proposal' }) self.log.info("getblocktemplate: Test duplicate transaction") bad_block = copy.deepcopy(block) bad_block.vtx.append(bad_block.vtx[0]) assert_template(node, bad_block, 'bad-txns-duplicate') assert_submitblock(bad_block, 'bad-txns-duplicate', 'bad-txns-duplicate') self.log.info("getblocktemplate: Test invalid transaction") bad_block = copy.deepcopy(block) bad_tx = copy.deepcopy(bad_block.vtx[0]) bad_tx.vin[0].prevout.hash = 255 bad_tx.rehash() bad_block.vtx.append(bad_tx) assert_template(node, bad_block, 'bad-txns-inputs-missingorspent') assert_submitblock(bad_block, 'bad-txns-inputs-missingorspent') self.log.info("getblocktemplate: Test nonfinal transaction") bad_block = copy.deepcopy(block) bad_block.vtx[0].nLockTime = 2**32 - 1 bad_block.vtx[0].rehash() assert_template(node, bad_block, 'bad-txns-nonfinal') assert_submitblock(bad_block, 'bad-txns-nonfinal') self.log.info("getblocktemplate: Test bad tx count") # The tx count is immediately after the block header TX_COUNT_OFFSET = 80 bad_block_sn = bytearray(block.serialize()) assert_equal(bad_block_sn[TX_COUNT_OFFSET], 1) bad_block_sn[TX_COUNT_OFFSET] += 1 assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, { 'data': bad_block_sn.hex(), 'mode': 'proposal' }) self.log.info("getblocktemplate: Test bad bits") bad_block = copy.deepcopy(block) bad_block.nBits = 469762303 # impossible in the real world assert_template(node, bad_block, 'bad-diffbits') self.log.info("getblocktemplate: Test bad merkle root") bad_block = copy.deepcopy(block) bad_block.hashMerkleRoot += 1 assert_template(node, bad_block, 'bad-txnmrklroot', False) assert_submitblock(bad_block, 'bad-txnmrklroot', 'bad-txnmrklroot') self.log.info("getblocktemplate: Test bad timestamps") bad_block = copy.deepcopy(block) bad_block.nTime = 2**31 - 1 assert_template(node, bad_block, 'time-too-new') assert_submitblock(bad_block, 'time-too-new', 'time-too-new') bad_block.nTime = 0 assert_template(node, bad_block, 'time-too-old') assert_submitblock(bad_block, 'time-too-old', 'time-too-old') self.log.info("getblocktemplate: Test not best block") bad_block = copy.deepcopy(block) bad_block.hashPrevBlock = 123 assert_template(node, bad_block, 'inconclusive-not-best-prevblk') assert_submitblock(bad_block, 'prev-blk-not-found', 'prev-blk-not-found') self.log.info('submitheader tests') assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='xx' * 80)) assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='ff' * 78)) assert_raises_rpc_error(-25, 'Must submit previous header', lambda: node.submitheader(hexdata='ff' * 80)) block.nTime += 1 block.solve() def chain_tip(b_hash, *, status='headers-only', branchlen=1): return { 'hash': b_hash, 'height': 202, 'branchlen': branchlen, 'status': status } assert chain_tip(block.hash) not in node.getchaintips() node.submitheader(hexdata=block.serialize().hex()) assert chain_tip(block.hash) in node.getchaintips() # Noop node.submitheader(hexdata=CBlockHeader(block).serialize().hex()) assert chain_tip(block.hash) in node.getchaintips() bad_block_root = copy.deepcopy(block) bad_block_root.hashMerkleRoot += 2 bad_block_root.solve() assert chain_tip(bad_block_root.hash) not in node.getchaintips() node.submitheader( hexdata=CBlockHeader(bad_block_root).serialize().hex()) assert chain_tip(bad_block_root.hash) in node.getchaintips() # Should still reject invalid blocks, even if we have the header: assert_equal( node.submitblock(hexdata=bad_block_root.serialize().hex()), 'bad-txnmrklroot') assert_equal( node.submitblock(hexdata=bad_block_root.serialize().hex()), 'bad-txnmrklroot') assert chain_tip(bad_block_root.hash) in node.getchaintips() # We know the header for this invalid block, so should just return # early without error: node.submitheader( hexdata=CBlockHeader(bad_block_root).serialize().hex()) assert chain_tip(bad_block_root.hash) in node.getchaintips() bad_block_lock = copy.deepcopy(block) bad_block_lock.vtx[0].nLockTime = 2**32 - 1 bad_block_lock.vtx[0].rehash() bad_block_lock.hashMerkleRoot = bad_block_lock.calc_merkle_root() bad_block_lock.solve() assert_equal( node.submitblock(hexdata=bad_block_lock.serialize().hex()), 'bad-txns-nonfinal') assert_equal( node.submitblock(hexdata=bad_block_lock.serialize().hex()), 'duplicate-invalid') # Build a "good" block on top of the submitted bad block bad_block2 = copy.deepcopy(block) bad_block2.hashPrevBlock = bad_block_lock.sha256 bad_block2.solve() assert_raises_rpc_error( -25, 'bad-prevblk', lambda: node.submitheader(hexdata=CBlockHeader( bad_block2).serialize().hex())) # Should reject invalid header right away bad_block_time = copy.deepcopy(block) bad_block_time.nTime = 1 bad_block_time.solve() assert_raises_rpc_error( -25, 'time-too-old', lambda: node.submitheader( hexdata=CBlockHeader(bad_block_time).serialize().hex())) # Should ask for the block from a p2p node, if they announce the header # as well: node.add_p2p_connection(P2PDataStore()) # Drop the first getheaders node.p2p.wait_for_getheaders(timeout=5) node.p2p.send_blocks_and_test(blocks=[block], node=node) # Must be active now: assert chain_tip(block.hash, status='active', branchlen=0) in node.getchaintips() # Building a few blocks should give the same results node.generatetoaddress(10, node.get_deterministic_priv_key().address) assert_raises_rpc_error( -25, 'time-too-old', lambda: node.submitheader( hexdata=CBlockHeader(bad_block_time).serialize().hex())) assert_raises_rpc_error( -25, 'bad-prevblk', lambda: node.submitheader(hexdata=CBlockHeader( bad_block2).serialize().hex())) node.submitheader(hexdata=CBlockHeader(block).serialize().hex()) node.submitheader( hexdata=CBlockHeader(bad_block_root).serialize().hex()) # valid assert_equal(node.submitblock(hexdata=block.serialize().hex()), 'duplicate') # Ensure that "parameters" and "dummy" second arg are interchangeable self.log.info("Ensuring that the \"dummy\" and \"parameters\" args to " "submitblock are aliases of each other") # Ensure that unknown arg raises the right error assert_raises_rpc_error(-8, "Unknown named parameter foobar", node.submitblock, hexdata=block.serialize().hex(), foobar={}) # Now check that dummy={} is accepted as a valid call assert_equal( node.submitblock(hexdata=block.serialize().hex(), dummy={}), 'duplicate') # And that parameters={} is accepted as a valid call assert_equal( node.submitblock(hexdata=block.serialize().hex(), parameters={}), 'duplicate') # Test for RPC_CLIENT_NOT_CONNECTED error self.nodes[0].disconnect_p2ps() self.nodes[1].disconnect_p2ps() self.stop_node(1) assert_raises_rpc_error( -9, # RPC_CLIENT_NOT_CONNECTED "Bitcoin is not connected!", node.getblocktemplate)
def run_test(self): node = self.nodes[0] self.log.info('getmininginfo') mining_info = node.getmininginfo() assert_equal(mining_info['blocks'], 200) assert_equal(mining_info['chain'], 'regtest') assert_equal(mining_info['currentblocktx'], 0) assert_equal(mining_info['currentblockweight'], 0) assert_equal(mining_info['difficulty'], Decimal('4.656542373906925E-10')) assert_equal(mining_info['networkhashps'], Decimal('0.003333333333333334')) assert_equal(mining_info['pooledtx'], 0) # Mine a block to leave initial block download node.generate(1) tmpl = node.getblocktemplate() self.log.info("getblocktemplate: Test capability advertised") assert 'proposal' in tmpl['capabilities'] assert 'coinbasetxn' not in tmpl coinbase_tx = create_coinbase(height=int(tmpl["height"]) + 1) # sequence numbers must not be max for nLockTime to have effect coinbase_tx.vin[0].nSequence = 2 ** 32 - 2 coinbase_tx.rehash() block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) block.nTime = tmpl["curtime"] block.nBits = int(tmpl["bits"], 16) block.nNonce = 0 block.vtx = [coinbase_tx] self.log.info("getblocktemplate: Test valid block") assert_template(node, block, None) self.log.info("submitblock: Test block decode failure") assert_raises_rpc_error(-22, "Block decode failed", node.submitblock, b2x(block.serialize()[:-15])) self.log.info("getblocktemplate: Test bad input hash for coinbase transaction") bad_block = copy.deepcopy(block) bad_block.vtx[0].vin[0].prevout.hash += 1 bad_block.vtx[0].rehash() assert_template(node, bad_block, 'bad-cb-missing') self.log.info("submitblock: Test invalid coinbase transaction") assert_raises_rpc_error(-22, "Block does not start with a coinbase", node.submitblock, b2x(bad_block.serialize())) self.log.info("getblocktemplate: Test truncated final transaction") assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {'data': b2x(block.serialize()[:-1]), 'mode': 'proposal'}) self.log.info("getblocktemplate: Test duplicate transaction") bad_block = copy.deepcopy(block) bad_block.vtx.append(bad_block.vtx[0]) assert_template(node, bad_block, 'bad-txns-duplicate') self.log.info("getblocktemplate: Test invalid transaction") bad_block = copy.deepcopy(block) bad_tx = copy.deepcopy(bad_block.vtx[0]) bad_tx.vin[0].prevout.hash = 255 bad_tx.rehash() bad_block.vtx.append(bad_tx) assert_template(node, bad_block, 'bad-txns-inputs-missingorspent') self.log.info("getblocktemplate: Test nonfinal transaction") bad_block = copy.deepcopy(block) bad_block.vtx[0].nLockTime = 2 ** 32 - 1 bad_block.vtx[0].rehash() assert_template(node, bad_block, 'bad-txns-nonfinal') self.log.info("getblocktemplate: Test bad tx count") # The tx count is immediately after the block header TX_COUNT_OFFSET = 80 bad_block_sn = bytearray(block.serialize()) assert_equal(bad_block_sn[TX_COUNT_OFFSET], 1) bad_block_sn[TX_COUNT_OFFSET] += 1 assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {'data': b2x(bad_block_sn), 'mode': 'proposal'}) self.log.info("getblocktemplate: Test bad bits") bad_block = copy.deepcopy(block) bad_block.nBits = 469762303 # impossible in the real world assert_template(node, bad_block, 'bad-diffbits') self.log.info("getblocktemplate: Test bad merkle root") bad_block = copy.deepcopy(block) bad_block.hashMerkleRoot += 1 assert_template(node, bad_block, 'bad-txnmrklroot', False) self.log.info("getblocktemplate: Test bad timestamps") bad_block = copy.deepcopy(block) bad_block.nTime = 2 ** 31 - 1 assert_template(node, bad_block, 'time-too-new') bad_block.nTime = 0 assert_template(node, bad_block, 'time-too-old') self.log.info("getblocktemplate: Test not best block") bad_block = copy.deepcopy(block) bad_block.hashPrevBlock = 123 assert_template(node, bad_block, 'inconclusive-not-best-prevblk')
def run_test(self): parent = self.nodes[0] #parent2 = self.nodes[1] sidechain = self.nodes[2] sidechain2 = self.nodes[3] for node in self.nodes: node.importprivkey(privkey=node.get_deterministic_priv_key().key, label="mining") util.node_fastmerkle = sidechain parent.generate(101) sidechain.generate(101) self.log.info("sidechain info: {}".format(sidechain.getsidechaininfo())) addrs = sidechain.getpeginaddress() addr = addrs["mainchain_address"] assert_equal(sidechain.decodescript(addrs["claim_script"])["type"], "witness_v0_keyhash") txid1 = parent.sendtoaddress(addr, 24) # 10+2 confirms required to get into mempool and confirm parent.generate(1) time.sleep(2) proof = parent.gettxoutproof([txid1]) raw = parent.gettransaction(txid1)["hex"] print("Attempting peg-ins") # First attempt fails the consensus check but gives useful result try: pegtxid = sidechain.claimpegin(raw, proof) raise Exception("Peg-in should not be mature enough yet, need another block.") except JSONRPCException as e: assert("Peg-in Bitcoin transaction needs more confirmations to be sent." in e.error["message"]) # Second attempt simply doesn't hit mempool bar parent.generate(10) try: pegtxid = sidechain.claimpegin(raw, proof) raise Exception("Peg-in should not be mature enough yet, need another block.") except JSONRPCException as e: assert("Peg-in Bitcoin transaction needs more confirmations to be sent." in e.error["message"]) try: pegtxid = sidechain.createrawpegin(raw, proof, 'AEIOU') raise Exception("Peg-in with non-hex claim_script should fail.") except JSONRPCException as e: assert("Given claim_script is not hex." in e.error["message"]) # Should fail due to non-matching wallet address try: scriptpubkey = sidechain.getaddressinfo(get_new_unconfidential_address(sidechain))["scriptPubKey"] pegtxid = sidechain.claimpegin(raw, proof, scriptpubkey) raise Exception("Peg-in with non-matching claim_script should fail.") except JSONRPCException as e: assert("Given claim_script does not match the given Bitcoin transaction." in e.error["message"]) # 12 confirms allows in mempool parent.generate(1) # Make sure that a tx with a duplicate pegin claim input gets rejected. raw_pegin = sidechain.createrawpegin(raw, proof)["hex"] raw_pegin = FromHex(CTransaction(), raw_pegin) raw_pegin.vin.append(raw_pegin.vin[0]) # duplicate the pegin input raw_pegin = sidechain.signrawtransactionwithwallet(raw_pegin.serialize().hex())["hex"] assert_raises_rpc_error(-26, "bad-txns-inputs-duplicate", sidechain.sendrawtransaction, raw_pegin) # Also try including this tx in a block manually and submitting it. doublespendblock = FromHex(CBlock(), sidechain.getnewblockhex()) doublespendblock.vtx.append(FromHex(CTransaction(), raw_pegin)) doublespendblock.hashMerkleRoot = doublespendblock.calc_merkle_root() add_witness_commitment(doublespendblock) doublespendblock.solve() block_hex = bytes_to_hex_str(doublespendblock.serialize(True)) assert_raises_rpc_error(-25, "bad-txns-inputs-duplicate", sidechain.testproposedblock, block_hex, True) # Should succeed via wallet lookup for address match, and when given raw_pegin = sidechain.createrawpegin(raw, proof)['hex'] signed_pegin = sidechain.signrawtransactionwithwallet(raw_pegin) sample_pegin_struct = FromHex(CTransaction(), signed_pegin["hex"]) # Round-trip peg-in transaction using python serialization assert_equal(signed_pegin["hex"], sample_pegin_struct.serialize().hex()) # Store this for later (evil laugh) sample_pegin_witness = sample_pegin_struct.wit.vtxinwit[0].peginWitness pegtxid1 = sidechain.claimpegin(raw, proof) # Make sure a second pegin claim does not get accepted in the mempool when # another mempool tx already claims that pegin. assert_raises_rpc_error(-4, "txn-mempool-conflict", sidechain.claimpegin, raw, proof) # Will invalidate the block that confirms this transaction later self.sync_all(self.node_groups) blockhash = sidechain2.generate(1) self.sync_all(self.node_groups) sidechain.generate(5) tx1 = sidechain.gettransaction(pegtxid1) if "confirmations" in tx1 and tx1["confirmations"] == 6: print("Peg-in is confirmed: Success!") else: raise Exception("Peg-in confirmation has failed.") # Look at pegin fields decoded = sidechain.decoderawtransaction(tx1["hex"]) assert decoded["vin"][0]["is_pegin"] == True assert len(decoded["vin"][0]["pegin_witness"]) > 0 # Check that there's sufficient fee for the peg-in vsize = decoded["vsize"] fee_output = decoded["vout"][1] fallbackfee_pervbyte = Decimal("0.00001")/Decimal("1000") assert fee_output["scriptPubKey"]["type"] == "fee" assert fee_output["value"] >= fallbackfee_pervbyte*vsize # Quick reorg checks of pegs sidechain.invalidateblock(blockhash[0]) if sidechain.gettransaction(pegtxid1)["confirmations"] != 0: raise Exception("Peg-in didn't unconfirm after invalidateblock call.") # Create duplicate claim, put it in block along with current one in mempool # to test duplicate-in-block claims between two txs that are in the same block. raw_pegin = sidechain.createrawpegin(raw, proof)["hex"] raw_pegin = sidechain.signrawtransactionwithwallet(raw_pegin)["hex"] raw_pegin = FromHex(CTransaction(), raw_pegin) doublespendblock = FromHex(CBlock(), sidechain.getnewblockhex()) assert(len(doublespendblock.vtx) == 2) # coinbase and pegin doublespendblock.vtx.append(raw_pegin) doublespendblock.hashMerkleRoot = doublespendblock.calc_merkle_root() add_witness_commitment(doublespendblock) doublespendblock.solve() block_hex = bytes_to_hex_str(doublespendblock.serialize(True)) assert_raises_rpc_error(-25, "bad-txns-double-pegin", sidechain.testproposedblock, block_hex, True) # Re-enters block sidechain.generate(1) if sidechain.gettransaction(pegtxid1)["confirmations"] != 1: raise Exception("Peg-in should have one confirm on side block.") sidechain.reconsiderblock(blockhash[0]) if sidechain.gettransaction(pegtxid1)["confirmations"] != 6: raise Exception("Peg-in should be back to 6 confirms.") # Now the pegin is already claimed in a confirmed tx. # In that case, a duplicate claim should (1) not be accepted in the mempool # and (2) not be accepted in a block. assert_raises_rpc_error(-4, "pegin-already-claimed", sidechain.claimpegin, raw, proof) # For case (2), manually craft a block and include the tx. doublespendblock = FromHex(CBlock(), sidechain.getnewblockhex()) doublespendblock.vtx.append(raw_pegin) doublespendblock.hashMerkleRoot = doublespendblock.calc_merkle_root() add_witness_commitment(doublespendblock) doublespendblock.solve() block_hex = bytes_to_hex_str(doublespendblock.serialize(True)) assert_raises_rpc_error(-25, "bad-txns-double-pegin", sidechain.testproposedblock, block_hex, True) # Do multiple claims in mempool n_claims = 6 print("Flooding mempool with a few claims") pegtxs = [] sidechain.generate(101) # Do mixture of raw peg-in and automatic peg-in tx construction # where raw creation is done on another node for i in range(n_claims): addrs = sidechain.getpeginaddress() txid = parent.sendtoaddress(addrs["mainchain_address"], 1) parent.generate(1) proof = parent.gettxoutproof([txid]) raw = parent.gettransaction(txid)["hex"] if i % 2 == 0: parent.generate(11) pegtxs += [sidechain.claimpegin(raw, proof)] else: # The raw API doesn't check for the additional 2 confirmation buffer # So we only get 10 confirms then send off. Miners will add to block anyways. # Don't mature whole way yet to test signing immature peg-in input parent.generate(8) # Wallet in sidechain2 gets funds instead of sidechain raw_pegin = sidechain2.createrawpegin(raw, proof, addrs["claim_script"])["hex"] # First node should also be able to make a valid transaction with or without 3rd arg # since this wallet originated the claim_script itself sidechain.createrawpegin(raw, proof, addrs["claim_script"]) sidechain.createrawpegin(raw, proof) signed_pegin = sidechain.signrawtransactionwithwallet(raw_pegin) assert(signed_pegin["complete"]) assert("warning" in signed_pegin) # warning for immature peg-in # fully mature them now parent.generate(1) pegtxs += [sidechain.sendrawtransaction(signed_pegin["hex"])] self.sync_all(self.node_groups) sidechain2.generate(1) for i, pegtxid in enumerate(pegtxs): if i % 2 == 0: tx = sidechain.gettransaction(pegtxid) else: tx = sidechain2.gettransaction(pegtxid) if "confirmations" not in tx or tx["confirmations"] == 0: raise Exception("Peg-in confirmation has failed.") print("Test pegouts") self.test_pegout(get_new_unconfidential_address(parent, "legacy"), sidechain) self.test_pegout(get_new_unconfidential_address(parent, "p2sh-segwit"), sidechain) self.test_pegout(get_new_unconfidential_address(parent, "bech32"), sidechain) print("Test pegout P2SH") parent_chain_addr = get_new_unconfidential_address(parent) parent_pubkey = parent.getaddressinfo(parent_chain_addr)["pubkey"] parent_chain_p2sh_addr = parent.createmultisig(1, [parent_pubkey])["address"] self.test_pegout(parent_chain_p2sh_addr, sidechain) print("Test pegout Garbage") parent_chain_addr = "garbage" try: self.test_pegout(parent_chain_addr, sidechain) raise Exception("A garbage address should fail.") except JSONRPCException as e: assert("Invalid Bitcoin address" in e.error["message"]) print("Test pegout Garbage valid") prev_txid = sidechain.sendtoaddress(sidechain.getnewaddress(), 1) sidechain.generate(1) pegout_chain = 'a' * 64 pegout_hex = 'b' * 500 inputs = [{"txid": prev_txid, "vout": 0}] outputs = {"vdata": [pegout_chain, pegout_hex]} rawtx = sidechain.createrawtransaction(inputs, outputs) raw_pegout = sidechain.decoderawtransaction(rawtx) assert 'vout' in raw_pegout and len(raw_pegout['vout']) > 0 pegout_tested = False for output in raw_pegout['vout']: scriptPubKey = output['scriptPubKey'] if 'type' in scriptPubKey and scriptPubKey['type'] == 'nulldata': assert ('pegout_hex' in scriptPubKey and 'pegout_asm' in scriptPubKey and 'pegout_type' in scriptPubKey) assert ('pegout_chain' in scriptPubKey and 'pegout_reqSigs' not in scriptPubKey and 'pegout_addresses' not in scriptPubKey) assert scriptPubKey['pegout_type'] == 'nonstandard' assert scriptPubKey['pegout_chain'] == pegout_chain assert scriptPubKey['pegout_hex'] == pegout_hex pegout_tested = True break assert pegout_tested print("Now test failure to validate peg-ins based on intermittent bitcoind rpc failure") self.stop_node(1) txid = parent.sendtoaddress(addr, 1) parent.generate(12) proof = parent.gettxoutproof([txid]) raw = parent.gettransaction(txid)["hex"] sidechain.claimpegin(raw, proof) # stuck peg sidechain.generate(1) print("Waiting to ensure block is being rejected by sidechain2") time.sleep(5) assert(sidechain.getblockcount() != sidechain2.getblockcount()) print("Restarting parent2") self.start_node(1) connect_nodes_bi(self.nodes, 0, 1) # Don't make a block, race condition when pegin-invalid block # is awaiting further validation, nodes reject subsequent blocks # even ones they create print("Now waiting for node to re-evaluate peg-in witness failed block... should take a few seconds") self.sync_all(self.node_groups) print("Completed!\n") print("Now send funds out in two stages, partial, and full") some_btc_addr = get_new_unconfidential_address(parent) bal_1 = sidechain.getwalletinfo()["balance"]['bitcoin'] try: sidechain.sendtomainchain(some_btc_addr, bal_1 + 1) raise Exception("Sending out too much; should have failed") except JSONRPCException as e: assert("Insufficient funds" in e.error["message"]) assert(sidechain.getwalletinfo()["balance"]["bitcoin"] == bal_1) try: sidechain.sendtomainchain(some_btc_addr+"b", bal_1 - 1) raise Exception("Sending to invalid address; should have failed") except JSONRPCException as e: assert("Invalid Bitcoin address" in e.error["message"]) assert(sidechain.getwalletinfo()["balance"]["bitcoin"] == bal_1) try: sidechain.sendtomainchain("1Nro9WkpaKm9axmcfPVp79dAJU1Gx7VmMZ", bal_1 - 1) raise Exception("Sending to mainchain address when should have been testnet; should have failed") except JSONRPCException as e: assert("Invalid Bitcoin address" in e.error["message"]) assert(sidechain.getwalletinfo()["balance"]["bitcoin"] == bal_1) # Test superfluous peg-in witness data on regular spend before we have no funds raw_spend = sidechain.createrawtransaction([], {sidechain.getnewaddress():1}) fund_spend = sidechain.fundrawtransaction(raw_spend) sign_spend = sidechain.signrawtransactionwithwallet(fund_spend["hex"]) signed_struct = FromHex(CTransaction(), sign_spend["hex"]) # Non-witness tx has no witness serialized yet if len(signed_struct.wit.vtxinwit) == 0: signed_struct.wit.vtxinwit = [CTxInWitness()] signed_struct.wit.vtxinwit[0].peginWitness.stack = sample_pegin_witness.stack assert_equal(sidechain.testmempoolaccept([signed_struct.serialize().hex()])[0]["allowed"], False) assert_equal(sidechain.testmempoolaccept([signed_struct.serialize().hex()])[0]["reject-reason"], "68: extra-pegin-witness") signed_struct.wit.vtxinwit[0].peginWitness.stack = [b'\x00'*100000] # lol assert_equal(sidechain.testmempoolaccept([signed_struct.serialize().hex()])[0]["allowed"], False) assert_equal(sidechain.testmempoolaccept([signed_struct.serialize().hex()])[0]["reject-reason"], "68: extra-pegin-witness") peg_out_txid = sidechain.sendtomainchain(some_btc_addr, 1) peg_out_details = sidechain.decoderawtransaction(sidechain.getrawtransaction(peg_out_txid)) # peg-out, change, fee assert(len(peg_out_details["vout"]) == 3) found_pegout_value = False for output in peg_out_details["vout"]: if "value" in output and output["value"] == 1: found_pegout_value = True assert(found_pegout_value) bal_2 = sidechain.getwalletinfo()["balance"]["bitcoin"] # Make sure balance went down assert(bal_2 + 1 < bal_1) # Send rest of coins using subtractfee from output arg sidechain.sendtomainchain(some_btc_addr, bal_2, True) assert(sidechain.getwalletinfo()["balance"]['bitcoin'] == 0) print('Test coinbase peg-in maturity rules') # Have bitcoin output go directly into a claim output pegin_info = sidechain.getpeginaddress() mainchain_addr = pegin_info["mainchain_address"] # Watch the address so we can get tx without txindex parent.importaddress(mainchain_addr) claim_block = parent.generatetoaddress(50, mainchain_addr)[0] block_coinbase = parent.getblock(claim_block, 2)["tx"][0] claim_txid = block_coinbase["txid"] claim_tx = block_coinbase["hex"] claim_proof = parent.gettxoutproof([claim_txid], claim_block) # Can't claim something even though it has 50 confirms since it's coinbase assert_raises_rpc_error(-8, "Peg-in Bitcoin transaction needs more confirmations to be sent.", sidechain.claimpegin, claim_tx, claim_proof) # If done via raw API, still doesn't work coinbase_pegin = sidechain.createrawpegin(claim_tx, claim_proof) assert_equal(coinbase_pegin["mature"], False) signed_pegin = sidechain.signrawtransactionwithwallet(coinbase_pegin["hex"])["hex"] assert_raises_rpc_error(-26, "bad-pegin-witness, Needs more confirmations.", sidechain.sendrawtransaction, signed_pegin) # 50 more blocks to allow wallet to make it succeed by relay and consensus parent.generatetoaddress(50, parent.getnewaddress()) # Wallet still doesn't want to for 2 more confirms assert_equal(sidechain.createrawpegin(claim_tx, claim_proof)["mature"], False) # But we can just shoot it off claim_txid = sidechain.sendrawtransaction(signed_pegin) sidechain.generatetoaddress(1, sidechain.getnewaddress()) assert_equal(sidechain.gettransaction(claim_txid)["confirmations"], 1) print('Success!') # Manually stop sidechains first, then the parent chains. self.stop_node(2) self.stop_node(3) self.stop_node(0) self.stop_node(1)
def mine_large_blocks(node, n): # Make a large scriptPubKey for the coinbase transaction. This is OP_RETURN # followed by 950k of OP_NOP. This would be non-standard in a non-coinbase # transaction but is consensus valid. # Get the block parameters for the first block big_script = CScript([OP_RETURN] + [OP_NOP] * 950000) best_block = node.getblock(node.getbestblockhash()) height = int(best_block["height"]) + 1 try: # Static variable ensures that time is monotonicly increasing and is therefore # different for each block created => blockhash is unique. mine_large_blocks.nTime = min(mine_large_blocks.nTime, int(best_block["time"])) + 1 except AttributeError: mine_large_blocks.nTime = int(best_block["time"]) + 1 previousblockhash = int(best_block["hash"], 16) for _ in range(n): # Build the coinbase transaction (with large scriptPubKey) coinbase_tx = create_coinbase(height) coinbase_tx.vin[0].nSequence = 2 ** 32 - 1 coinbase_tx.vout[0].scriptPubKey = big_script coinbase_tx.rehash() # Build the block block = CBlock() block.nVersion = best_block["version"] block.hashPrevBlock = previousblockhash block.nTime = mine_large_blocks.nTime block.nBits = int('207fffff', 16) block.nNonce = 0 block.vtx = [coinbase_tx] block.hashMerkleRoot = block.calc_merkle_root() block.solve() # Submit to the node node.submitblock(ToHex(block)) previousblockhash = block.sha256 height += 1 mine_large_blocks.nTime += 1
def test_muhash_implementation(self): self.log.info("Test MuHash implementation consistency") node = self.nodes[0] wallet = MiniWallet(node) mocktime = node.getblockheader(node.getblockhash(0))['time'] + 1 node.setmocktime(mocktime) # Generate 100 blocks and remove the first since we plan to spend its # coinbase block_hashes = wallet.generate(1) + node.generate(99) blocks = list( map(lambda block: FromHex(CBlock(), node.getblock(block, False)), block_hashes)) blocks.pop(0) # Create a spending transaction and mine a block which includes it txid = wallet.send_self_transfer(from_node=node)['txid'] tx_block = node.generateblock(output=wallet.get_address(), transactions=[txid]) blocks.append(FromHex(CBlock(), node.getblock(tx_block['hash'], False))) # Unlike upstream, Xaya allows spending the genesis block's coinbase, # so we have to include that into the UTXO set. genesis = FromHex(CBlock(), node.getblock(node.getblockhash(0), False)) blocks = [genesis] + blocks # Serialize the outputs that should be in the UTXO set and add them to # a MuHash object muhash = MuHash3072() for height, block in enumerate(blocks): # We spent the first mined block (after the genesis block). if height > 0: height += 1 for tx in block.vtx: for n, tx_out in enumerate(tx.vout): coinbase = 1 if not tx.vin[0].prevout.hash else 0 # Skip witness commitment if (coinbase and n > 0): continue data = COutPoint(int(tx.rehash(), 16), n).serialize() data += struct.pack("<i", height * 2 + coinbase) data += tx_out.serialize() muhash.insert(data) finalized = muhash.digest() node_muhash = node.gettxoutsetinfo("muhash")['muhash'] assert_equal(finalized[::-1].hex(), node_muhash) # The values differ from upstream since in Xaya the genesis block's coinbase # is part of the UTXO set. self.log.info("Test deterministic UTXO set hash results") assert_equal( node.gettxoutsetinfo()['hash_serialized_2'], "450cb0874edb935d7243d3e83ea2dfe463729a7f08bbe701ab830f3927ce88da") assert_equal( node.gettxoutsetinfo("muhash")['muhash'], "5de773dfb84089156402f41bbfddf27652a3cf136e2bed2986a7ce6bc6db4a80")
def test_compactblock_construction(self, node, test_node): # Generate a bunch of transactions. node.generate(101) num_transactions = 25 address = node.getnewaddress() for i in range(num_transactions): txid = node.sendtoaddress(address, 0.1) hex_tx = node.gettransaction(txid)["hex"] tx = FromHex(CTransaction(), hex_tx) # Wait until we've seen the block announcement for the resulting tip tip = int(node.getbestblockhash(), 16) test_node.wait_for_block_announcement(tip) # Make sure we will receive a fast-announce compact block self.request_cb_announcements(test_node, node) # Now mine a block, and look at the resulting compact block. test_node.clear_block_announcement() block_hash = int(node.generate(1)[0], 16) # Store the raw block in our internal format. block = FromHex(CBlock(), node.getblock("{:064x}".format(block_hash), False)) for tx in block.vtx: tx.calc_sha256() block.rehash() # Wait until the block was announced (via compact blocks) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock) # Now fetch and check the compact block header_and_shortids = None with mininode_lock: assert "cmpctblock" in test_node.last_message # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs( test_node.last_message["cmpctblock"].header_and_shortids) self.check_compactblock_construction_from_block( header_and_shortids, block_hash, block) # Now fetch the compact block using a normal non-announce getdata test_node.clear_block_announcement() inv = CInv(MSG_CMPCT_BLOCK, block_hash) test_node.send_message(msg_getdata([inv])) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock) # Now fetch and check the compact block header_and_shortids = None with mininode_lock: assert "cmpctblock" in test_node.last_message # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs( test_node.last_message["cmpctblock"].header_and_shortids) self.check_compactblock_construction_from_block( header_and_shortids, block_hash, block)
def run_test(self): self.description = "Covers the reorg with a zc public spend in vtx" self.init_test() DENOM_TO_USE = 10 # zc denomination INITAL_MINED_BLOCKS = 321 # First mined blocks (rewards collected to mint) MORE_MINED_BLOCKS = 105 # More blocks mined before spending zerocoins # 1) Starting mining blocks self.log.info("Mining %d blocks.." % INITAL_MINED_BLOCKS) self.node.generate(INITAL_MINED_BLOCKS) # 2) Mint 2 zerocoins self.node.mintzerocoin(DENOM_TO_USE) self.node.generate(1) self.node.mintzerocoin(DENOM_TO_USE) self.node.generate(1) # 3) Mine additional blocks and collect the mints self.log.info("Mining %d blocks.." % MORE_MINED_BLOCKS) self.node.generate(MORE_MINED_BLOCKS) self.log.info("Collecting mints...") mints = self.node.listmintedzerocoins(True, False) assert len(mints) == 2, "mints list has len %d (!= 2)" % len(mints) # 4) Get unspent coins and chain tip self.unspent = self.node.listunspent() block_count = self.node.getblockcount() pastBlockHash = self.node.getblockhash(block_count) self.log.info("Block count: %d - Current best: %s..." % (self.node.getblockcount(), self.node.getbestblockhash()[:5])) pastBlock = CBlock() pastBlock.deserialize(BytesIO(hex_str_to_bytes(self.node.getblock(pastBlockHash, False)))) checkpoint = pastBlock.nAccumulatorCheckpoint # 5) get the raw zerocoin spend txes self.log.info("Getting the raw zerocoin public spends...") public_spend_A = self.node.createrawzerocoinpublicspend(mints[0].get("serial hash")) tx_A = CTransaction() tx_A.deserialize(BytesIO(hex_str_to_bytes(public_spend_A))) tx_A.rehash() public_spend_B = self.node.createrawzerocoinpublicspend(mints[1].get("serial hash")) tx_B = CTransaction() tx_B.deserialize(BytesIO(hex_str_to_bytes(public_spend_B))) tx_B.rehash() # Spending same coins to different recipients to get different txids my_addy = "yAVWM5urwaTyhiuFQHP2aP47rdZsLUG5PH" public_spend_A2 = self.node.createrawzerocoinpublicspend(mints[0].get("serial hash"), my_addy) tx_A2 = CTransaction() tx_A2.deserialize(BytesIO(hex_str_to_bytes(public_spend_A2))) tx_A2.rehash() public_spend_B2 = self.node.createrawzerocoinpublicspend(mints[1].get("serial hash"), my_addy) tx_B2 = CTransaction() tx_B2.deserialize(BytesIO(hex_str_to_bytes(public_spend_B2))) tx_B2.rehash() self.log.info("tx_A id: %s" % str(tx_A.hash)) self.log.info("tx_B id: %s" % str(tx_B.hash)) self.log.info("tx_A2 id: %s" % str(tx_A2.hash)) self.log.info("tx_B2 id: %s" % str(tx_B2.hash)) self.test_nodes[0].handle_connect() # 6) create block_A --> main chain self.log.info("") self.log.info("*** block_A ***") self.log.info("Creating block_A [%d] with public spend tx_A in it." % (block_count + 1)) block_A = self.new_block(block_count, pastBlock, checkpoint, tx_A) self.log.info("Hash of block_A: %s..." % block_A.hash[:5]) self.log.info("sending block_A...") var = self.node.submitblock(bytes_to_hex_str(block_A.serialize())) if var is not None: self.log.info("result: %s" % str(var)) raise Exception("block_A not accepted") time.sleep(2) assert_equal(self.node.getblockcount(), block_count+1) assert_equal(self.node.getbestblockhash(), block_A.hash) self.log.info(" >> block_A connected <<") self.log.info("Current chain: ... --> block_0 [%d] --> block_A [%d]\n" % (block_count, block_count+1)) # 7) create block_B --> forked chain self.log.info("*** block_B ***") self.log.info("Creating block_B [%d] with public spend tx_B in it." % (block_count + 1)) block_B = self.new_block(block_count, pastBlock, checkpoint, tx_B) self.log.info("Hash of block_B: %s..." % block_B.hash[:5]) self.log.info("sending block_B...") var = self.node.submitblock(bytes_to_hex_str(block_B.serialize())) self.log.info("result of block_B submission: %s" % str(var)) time.sleep(2) assert_equal(self.node.getblockcount(), block_count+1) assert_equal(self.node.getbestblockhash(), block_A.hash) # block_B is not added. Chain remains the same self.log.info(" >> block_B not connected <<") self.log.info("Current chain: ... --> block_0 [%d] --> block_A [%d]\n" % (block_count, block_count+1)) # 8) Create new block block_C on the forked chain (block_B) block_count += 1 self.log.info("*** block_C ***") self.log.info("Creating block_C [%d] on top of block_B triggering the reorg" % (block_count + 1)) block_C = self.new_block(block_count, block_B, checkpoint) self.log.info("Hash of block_C: %s..." % block_C.hash[:5]) self.log.info("sending block_C...") var = self.node.submitblock(bytes_to_hex_str(block_C.serialize())) if var is not None: self.log.info("result: %s" % str(var)) raise Exception("block_C not accepted") time.sleep(2) assert_equal(self.node.getblockcount(), block_count+1) assert_equal(self.node.getbestblockhash(), block_C.hash) self.log.info(" >> block_A disconnected / block_B and block_C connected <<") self.log.info("Current chain: ... --> block_0 [%d] --> block_B [%d] --> block_C [%d]\n" % ( block_count - 1, block_count, block_count+1 )) # 7) Now create block_D which tries to spend same coin of tx_B again on the (new) main chain # (this block will be rejected) block_count += 1 self.log.info("*** block_D ***") self.log.info("Creating block_D [%d] trying to double spend the coin of tx_B" % (block_count + 1)) block_D = self.new_block(block_count, block_C, checkpoint, tx_B2) self.log.info("Hash of block_D: %s..." % block_D.hash[:5]) self.log.info("sending block_D...") var = self.node.submitblock(bytes_to_hex_str(block_D.serialize())) self.log.info("result of block_D submission: %s" % str(var)) time.sleep(2) assert_equal(self.node.getblockcount(), block_count) assert_equal(self.node.getbestblockhash(), block_C.hash) # block_D is not added. Chain remains the same self.log.info(" >> block_D rejected <<") self.log.info("Current chain: ... --> block_0 [%d] --> block_B [%d] --> block_C [%d]\n" % ( block_count - 2, block_count - 1, block_count )) # 8) Now create block_E which spends tx_A again on main chain # (this block will be accepted and connected since tx_A was spent on block_A now disconnected) self.log.info("*** block_E ***") self.log.info("Creating block_E [%d] trying spend tx_A on main chain" % (block_count + 1)) block_E = self.new_block(block_count, block_C, checkpoint, tx_A) self.log.info("Hash of block_E: %s..." % block_E.hash[:5]) self.log.info("sending block_E...") var = self.node.submitblock(bytes_to_hex_str(block_E.serialize())) if var is not None: self.log.info("result: %s" % str(var)) raise Exception("block_E not accepted") time.sleep(2) assert_equal(self.node.getblockcount(), block_count+1) assert_equal(self.node.getbestblockhash(), block_E.hash) self.log.info(" >> block_E connected <<") self.log.info("Current chain: ... --> block_0 [%d] --> block_B [%d] --> block_C [%d] --> block_E [%d]\n" % ( block_count - 2, block_count - 1, block_count, block_count+1 )) # 9) Now create block_F which tries to double spend the coin in tx_A # # (this block will be rejected) block_count += 1 self.log.info("*** block_F ***") self.log.info("Creating block_F [%d] trying to double spend the coin in tx_A" % (block_count + 1)) block_F = self.new_block(block_count, block_E, checkpoint, tx_A2) self.log.info("Hash of block_F: %s..." % block_F.hash[:5]) self.log.info("sending block_F...") var = self.node.submitblock(bytes_to_hex_str(block_F.serialize())) self.log.info("result of block_F submission: %s" % str(var)) time.sleep(2) assert_equal(self.node.getblockcount(), block_count) assert_equal(self.node.getbestblockhash(), block_E.hash) self.log.info(" >> block_F rejected <<") self.log.info("Current chain: ... --> block_0 [%d] --> block_B [%d] --> block_C [%d] --> block_E [%d]\n" % ( block_count - 3, block_count - 2, block_count - 1, block_count )) self.log.info("All good.")
def test_compactblock_construction(self, test_node): node = self.nodes[0] # Generate a bunch of transactions. self.generate(node, COINBASE_MATURITY + 1) num_transactions = 25 segwit_tx_generated = False for _ in range(num_transactions): hex_tx = self.wallet.send_self_transfer( from_node=self.nodes[0])['hex'] tx = tx_from_hex(hex_tx) if not tx.wit.is_null(): segwit_tx_generated = True assert segwit_tx_generated # check that our test is not broken # Wait until we've seen the block announcement for the resulting tip tip = int(node.getbestblockhash(), 16) test_node.wait_for_block_announcement(tip) # Make sure we will receive a fast-announce compact block self.request_cb_announcements(test_node) # Now mine a block, and look at the resulting compact block. test_node.clear_block_announcement() block_hash = int(self.generate(node, 1)[0], 16) # Store the raw block in our internal format. block = from_hex(CBlock(), node.getblock("%064x" % block_hash, False)) for tx in block.vtx: tx.calc_sha256() block.rehash() # Wait until the block was announced (via compact blocks) test_node.wait_until(lambda: "cmpctblock" in test_node.last_message, timeout=30) # Now fetch and check the compact block header_and_shortids = None with p2p_lock: # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs( test_node.last_message["cmpctblock"].header_and_shortids) self.check_compactblock_construction_from_block( header_and_shortids, block_hash, block) # Now fetch the compact block using a normal non-announce getdata test_node.clear_block_announcement() inv = CInv(MSG_CMPCT_BLOCK, block_hash) test_node.send_message(msg_getdata([inv])) test_node.wait_until(lambda: "cmpctblock" in test_node.last_message, timeout=30) # Now fetch and check the compact block header_and_shortids = None with p2p_lock: # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs( test_node.last_message["cmpctblock"].header_and_shortids) self.check_compactblock_construction_from_block( header_and_shortids, block_hash, block)
def run_test(self): def create_justification(fork, finalizer, after_blocks): fork.generatetoaddress(after_blocks - 1, fork.getnewaddress('', 'bech32')) self.wait_for_vote_and_disconnect(finalizer=finalizer, node=fork) fork.generatetoaddress(1, fork.getnewaddress('', 'bech32')) assert_equal(len(fork.getrawmempool()), 0) def sync_node_to_fork(node, fork): connect_nodes(node, fork.index) block_hash = fork.getblockhash(fork.getblockcount()) node.waitforblock(block_hash, 5000) assert_equal(node.getblockhash(node.getblockcount()), block_hash) disconnect_nodes(node, fork.index) def wait_for_reject(p2p, err, block): wait_until(lambda: p2p.has_reject(err, block), timeout=5) # Two validators (but actually having the same key) produce parallel justifications # node must always follow the longest justified fork # finalizer1 -> fork1 # / # node # \ # finalizer2 -> fork2 node = self.nodes[0] fork1 = self.nodes[1] fork2 = self.nodes[2] finalizer1 = self.nodes[3] finalizer2 = self.nodes[4] node.importmasterkey(regtest_mnemonics[0]['mnemonics']) finalizer1.importmasterkey(regtest_mnemonics[1]['mnemonics']) finalizer2.importmasterkey(regtest_mnemonics[1]['mnemonics']) fork1.importmasterkey(regtest_mnemonics[2]['mnemonics']) fork2.importmasterkey(regtest_mnemonics[2]['mnemonics']) # create network topology connect_nodes(node, fork1.index) connect_nodes(node, fork2.index) connect_nodes(finalizer1, fork1.index) connect_nodes(finalizer2, fork2.index) # leave IBD node.generatetoaddress(2, node.getnewaddress('', 'bech32')) sync_blocks([node, fork1, fork2, finalizer1, finalizer2]) # Do not let finalizer2 to see deposit from finalizer1 disconnect_nodes(node, fork2.index) payto = finalizer1.getnewaddress('', 'legacy') txid1 = finalizer1.deposit(payto, 1500) finalizer2.setaccount(payto, '') txid2 = finalizer2.deposit(payto, 1500) if txid1 != txid2: # improve log message tx1 = FromHex(CTransaction(), finalizer1.getrawtransaction(txid1)) tx2 = FromHex(CTransaction(), finalizer2.getrawtransaction(txid2)) print(tx1) print(tx2) assert_equal(txid1, txid2) # Connect back connect_nodes(node, fork2.index) self.wait_for_transaction(txid1, timeout=150) node.generatetoaddress(1, node.getnewaddress('', 'bech32')) sync_blocks([node, fork1, fork2]) disconnect_nodes(node, fork1.index) disconnect_nodes(node, fork2.index) disconnect_nodes(finalizer1, fork1.index) disconnect_nodes(finalizer2, fork2.index) # create common 5 epochs to leave instant finalization # fork1 # F F F F J / # e0 - e1 - e2 - e3 - e4 - e5 node # \ # fork2 node.generatetoaddress(22, node.getnewaddress('', 'bech32')) assert_equal(node.getblockcount(), 25) assert_finalizationstate( node, { 'currentDynasty': 2, 'currentEpoch': 5, 'lastJustifiedEpoch': 4, 'lastFinalizedEpoch': 3, 'validators': 0 }) connect_nodes(node, fork1.index) connect_nodes(node, fork2.index) sync_blocks([node, fork1, fork2]) disconnect_nodes(node, fork1.index) disconnect_nodes(node, fork2.index) # create fist justified epoch on fork1 # node must follow this fork # # - e6 fork1, node # F F F F J * / # e0 - e1 - e2 - e3 - e4 - e5 # \ # fork2 # e4 is finalized for fork1 # e5 is justified for fork1 create_justification(fork=fork1, finalizer=finalizer1, after_blocks=2) assert_equal(fork1.getblockcount(), 27) assert_finalizationstate( fork1, { 'currentDynasty': 3, 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4, 'validators': 1 }) sync_node_to_fork(node, fork1) assert_finalizationstate( node, { 'currentDynasty': 3, 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4, 'validators': 1 }) self.log.info('node successfully switched to the justified fork') # create longer justified epoch on fork2 # node must switch ("zig") to this fork # # - e6 fork1 # F F F F F J / # e0 - e1 - e2 - e3 - e4 - e5 # \ J # - e6 - e7 - e8 fork2, node create_justification(fork=fork2, finalizer=finalizer2, after_blocks=2) assert_equal(fork2.getblockcount(), 27) assert_finalizationstate( fork2, { 'currentDynasty': 3, 'currentEpoch': 6, 'lastJustifiedEpoch': 5, 'lastFinalizedEpoch': 4, 'validators': 1 }) create_justification(fork=fork2, finalizer=finalizer2, after_blocks=10) assert_equal(fork2.getblockcount(), 37) assert_finalizationstate( fork2, { 'currentDynasty': 4, 'currentEpoch': 8, 'lastJustifiedEpoch': 7, 'lastFinalizedEpoch': 4, 'validators': 1 }) sync_node_to_fork(node, fork2) assert_finalizationstate( node, { 'currentDynasty': 4, 'currentEpoch': 8, 'lastJustifiedEpoch': 7, 'lastFinalizedEpoch': 4, 'validators': 1 }) self.log.info( 'node successfully switched to the longest justified fork') # create longer justified epoch on the previous fork1 # node must switch ("zag") to this fork # J # - e6 - e7 - e8 - e9 fork1, node # F F F F F J / # e0 - e1 - e2 - e3 - e4 - e5 # \ J # - e6 - e7 - e8 fork2 create_justification(fork=fork1, finalizer=finalizer1, after_blocks=16) assert_equal(fork1.getblockcount(), 43) assert_finalizationstate( fork1, { 'currentDynasty': 4, 'currentEpoch': 9, 'lastJustifiedEpoch': 8, 'lastFinalizedEpoch': 4, 'validators': 1 }) sync_node_to_fork(node, fork1) assert_finalizationstate( node, { 'currentDynasty': 4, 'currentEpoch': 9, 'lastJustifiedEpoch': 8, 'lastFinalizedEpoch': 4, 'validators': 1 }) self.log.info( 'node successfully switched back to the longest justified fork') # test that re-org before finalization is not possible # J J* # - e6 - e7 - e8 - e9 - e10 - e11 - e12[56, 57] fork1 # F F F F F J / | # e0 - e1 - e2 - e3 - e4 - e5 56] node # \ J # - e6 - e7 - e8 fork2 # e11 is not justified for node known_fork1_height = fork1.getblockcount() assert_equal(node.getblockcount(), known_fork1_height) known_fork1_hash = fork1.getblockhash(known_fork1_height) assert_equal(node.getblockhash(known_fork1_height), known_fork1_hash) create_justification(fork=fork1, finalizer=finalizer1, after_blocks=14) assert_equal(fork1.getblockcount(), 57) assert_finalizationstate( fork1, { 'currentDynasty': 4, 'currentEpoch': 12, 'lastJustifiedEpoch': 11, 'lastFinalizedEpoch': 4, 'validators': 1 }) attacker = node.add_p2p_connection(BaseNode()) network_thread_start() attacker.wait_for_verack() # send blocks without the last one that has a justified vote node_blocks = node.getblockcount() for h in range(known_fork1_height + 1, fork1.getblockcount()): block_hash = fork1.getblockhash(h) block = FromHex(CBlock(), fork1.getblock(block_hash, 0)) attacker.send_message(msg_witness_block(block)) node_blocks += 1 wait_until(lambda: node.getblockcount() == node_blocks, timeout=15) assert_equal(node.getblockcount(), 56) assert_finalizationstate( node, { 'currentDynasty': 4, 'currentEpoch': 12, 'lastJustifiedEpoch': 8, 'lastFinalizedEpoch': 4, 'validators': 1 }) # create finalization # J J # - e6 - e7 - e8 - e9 - e10 - e11 - e12[56, 57] fork1 # F F F F F J / | # e0 - e1 - e2 - e3 - e4 - e5 56] node # \ J F J # - e6 - e7 - e8 - e9 - e10 - e11 - e12[56, 57] fork2 create_justification(fork=fork2, finalizer=finalizer2, after_blocks=11) assert_equal(fork2.getblockcount(), 48) assert_finalizationstate( fork2, { 'currentDynasty': 4, 'currentEpoch': 10, 'lastJustifiedEpoch': 9, 'lastFinalizedEpoch': 4, 'validators': 1 }) create_justification(fork=fork2, finalizer=finalizer2, after_blocks=6) assert_equal(fork2.getblockcount(), 54) assert_finalizationstate( fork2, { 'currentDynasty': 4, 'currentEpoch': 11, 'lastJustifiedEpoch': 10, 'lastFinalizedEpoch': 9, 'validators': 1 }) fork2.generatetoaddress(3, fork2.getnewaddress('', 'bech32')) assert_equal(fork2.getblockcount(), 57) assert_finalizationstate( fork2, { 'currentDynasty': 5, 'currentEpoch': 12, 'lastJustifiedEpoch': 10, 'lastFinalizedEpoch': 9, 'validators': 1 }) # node follows longer finalization # J J # - e6 - e7 - e8 - e9 - e10 - e11 - e12[56, 57] fork1 # F F F F F J / # e0 - e1 - e2 - e3 - e4 - e5 # \ J F J # - e6 - e7 - e8 - e9 - e10 - e11 - e12[56, 57] fork2, node tip = fork2.getblockhash(57) sync_node_to_fork(node, fork2) assert_equal(node.getblockcount(), 57) assert_finalizationstate( node, { 'currentDynasty': 5, 'currentEpoch': 12, 'lastJustifiedEpoch': 10, 'lastFinalizedEpoch': 9, 'validators': 1 }) # send block with surrounded vote that justifies longer fork # node's view: # J J # - e6 - e7 - e8 - e9 - e10 - e11 - e12[56, 57] fork1 # F F F F F J / # e0 - e1 - e2 - e3 - e4 - e5 # \ J F J # - e6 - e7 - e8 - e9 - e10 - e11 - e12[56, 57] fork2, node block_hash = fork1.getblockhash(fork1.getblockcount()) block = FromHex(CBlock(), fork1.getblock(block_hash, 0)) block.calc_sha256() attacker.send_message(msg_witness_block(block)) # node should't re-org to malicious fork wait_for_reject(attacker, b'bad-fork-before-last-finalized-epoch', block.sha256) assert_equal(node.getblockcount(), 57) assert_equal(node.getblockhash(57), tip) assert_finalizationstate( node, { 'currentDynasty': 5, 'currentEpoch': 12, 'lastJustifiedEpoch': 10, 'lastFinalizedEpoch': 9, 'validators': 1 }) self.log.info('node did not re-org before finalization')
def commits_test(self, node): def check_headers(number): wait_until(lambda: node.getblockchaininfo()['headers'] == number, timeout=5) def check_reject(err, block): wait_until(lambda: node.p2p.has_reject(err, block), timeout=5) def getbestblockhash(): return int(node.getbestblockhash(), 16) def make_block(prev=None, secret=None): if secret is None: secret = "default" coinbase_key = CECKey() coinbase_key.set_secretbytes(bytes(secret, "utf-8")) coinbase_pubkey = coinbase_key.get_pubkey() if prev is None: block_base_hash = getbestblockhash() block_time = int(time.time()) + 1 else: block_base_hash = prev.sha256 block_time = prev.nTime + 1 height = prev.height + 1 if prev else 1 snapshot_hash = 0 stake = self.nodes[0].listunspent()[0] coinbase = create_coinbase(height, stake, snapshot_hash, coinbase_pubkey) coinbase.rehash() b = create_block(block_base_hash, coinbase, block_time) b.solve() b.height = height return b def make_commits_msg(blocks): msg = msg_commits(0) for b in blocks: hc = HeaderAndCommits() hc.header = CBlockHeader(b) msg.data += [hc] return msg def send_commits(blocks): node.p2p.reset_messages() node.p2p.send_message(make_commits_msg(blocks)) chain = [] def generate(n): tip = chain[-1] if len(chain) > 0 else None for i in range(0, n): tip = make_block(tip) chain.append(tip) check_headers(0) # initial state of the node # generate 10 blocks and send commits generate(10) send_commits(chain) check_headers(10) # node accepted 10 headers # send same commits again send_commits(chain) check_headers(10) # send last 5 commits send_commits(chain[-5:]) check_headers(10) # generate next 10 blocks, try to send commits starting from 2nd block generate(10) send_commits(chain[11:]) check_reject(b'prev-blk-not-found', 0) # node rejected orphan headers check_headers(10) # must keep old amount of headers # send correct commits send_commits(chain[10:]) check_headers(20) # node accepted headers # generate next 10 blocks, send whole chain generate(10) send_commits(chain) check_headers(30) # node accepted headers # generate next 10 blocks, fool commit in one of them, send them generate(10) msg = make_commits_msg(chain[-10:]) malicious_block = copy.deepcopy(chain[-1]) msg.data[ -1].commits = malicious_block.vtx # fool commits with coinbase tx tx = malicious_block.vtx[0] tx.calc_sha256() hashes = [ser_uint256(tx.sha256)] malicious_block.hash_finalizer_commits_merkle_root = CBlock.get_merkle_root( hashes) malicious_block.rehash() msg.data[ -1].header.hash_finalizer_commits_merkle_root = malicious_block.hash_finalizer_commits_merkle_root node.p2p.send_message(msg) check_reject( b'bad-non-commit', malicious_block.sha256 ) # node rejected commits because of non-commit transaction check_headers(30) # must keep old amount of headers # send commits with bad merkle root msg = make_commits_msg(chain[-10:]) malicious_block = copy.deepcopy(chain[-2]) malicious_block.hash_finalizer_commits_merkle_root = 42 malicious_block.rehash() msg.data[ -2].header.hash_finalizer_commits_merkle_root = malicious_block.hash_finalizer_commits_merkle_root node.p2p.send_message(msg) check_reject( b'bad-finalizer-commits-merkle-root', malicious_block.sha256 ) # node rejected commits because of bad commits merkle root check_headers(30) # must keep old amount of headers
if use_witness_address: assert(segwit_tx_generated) # check that our test is not broken # Wait until we've seen the block announcement for the resulting tip tip = int(node.getbestblockhash(), 16) test_node.wait_for_block_announcement(tip) # Make sure we will receive a fast-announce compact block self.request_cb_announcements(test_node) # Now mine a block, and look at the resulting compact block. test_node.clear_block_announcement() block_hash = int(node.generate(1)[0], 16) # Store the raw block in our internal format. block = FromHex(CBlock(), node.getblock("%02x" % block_hash, False)) for tx in block.vtx: tx.calc_sha256() block.rehash() # Wait until the block was announced (via compact blocks) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock) # Now fetch and check the compact block header_and_shortids = None with mininode_lock: assert "cmpctblock" in test_node.last_message # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs(test_node.last_message["cmpctblock"].header_and_shortids) self.check_compactblock_construction_from_block(version, header_and_shortids, block_hash, block)
def run_test(self): node = self.nodes[0] def assert_submitblock(block, result_str_1, result_str_2=None): block.solve() result_str_2 = result_str_2 or 'duplicate-invalid' assert_equal(result_str_1, node.submitblock(hexdata=b2x(block.serialize()))) assert_equal(result_str_2, node.submitblock(hexdata=b2x(block.serialize()))) self.log.info('getmininginfo') mining_info = node.getmininginfo() assert_equal(mining_info['blocks'], 200) assert_equal(mining_info['chain'], self.chain) assert_equal(mining_info['currentblocktx'], 0) assert_equal(mining_info['currentblockweight'], 0) assert_equal(mining_info['pooledtx'], 0) # Mine a block to leave initial block download node.generate(1) tmpl = node.getblocktemplate() self.log.info("getblocktemplate: Test capability advertised") assert 'proposal' in tmpl['capabilities'] assert 'coinbasetxn' not in tmpl coinbase_tx = create_coinbase(height=int(tmpl["height"]) + 1) # sequence numbers must not be max for nLockTime to have effect coinbase_tx.vin[0].nSequence = 2 ** 32 - 2 coinbase_tx.rehash() block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) block.nTime = tmpl["curtime"] block.block_height = node.getblockcount()+1 block.nBits = 0 block.nNonce = 0 block.vtx = [coinbase_tx] self.log.info("getblocktemplate: Test valid block") assert_template(node, block, None) self.log.info("submitblock: Test block decode failure") assert_raises_rpc_error(-22, "Block decode failed", node.submitblock, b2x(block.serialize()[:-15])) self.log.info("getblocktemplate: Test bad input hash for coinbase transaction") bad_block = copy.deepcopy(block) bad_block.vtx[0].vin[0].prevout.hash += 1 bad_block.vtx[0].rehash() assert_template(node, bad_block, 'bad-cb-missing') self.log.info("submitblock: Test invalid coinbase transaction") assert_raises_rpc_error(-22, "Block does not start with a coinbase", node.submitblock, b2x(bad_block.serialize())) self.log.info("getblocktemplate: Test truncated final transaction") assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {'data': b2x(block.serialize()[:-1]), 'mode': 'proposal'}) self.log.info("getblocktemplate: Test duplicate transaction") bad_block = copy.deepcopy(block) bad_block.vtx.append(bad_block.vtx[0]) assert_template(node, bad_block, 'bad-txns-duplicate') assert_submitblock(bad_block, 'bad-txns-duplicate', 'bad-txns-duplicate') self.log.info("getblocktemplate: Test invalid transaction") bad_block = copy.deepcopy(block) bad_tx = copy.deepcopy(bad_block.vtx[0]) bad_tx.vin[0].prevout.hash = 255 bad_tx.rehash() bad_block.vtx.append(bad_tx) assert_template(node, bad_block, 'bad-txns-inputs-missingorspent') assert_submitblock(bad_block, 'bad-txns-inputs-missingorspent') self.log.info("getblocktemplate: Test nonfinal transaction") bad_block = copy.deepcopy(block) bad_block.vtx[0].nLockTime = 2 ** 32 - 1 bad_block.vtx[0].rehash() assert_template(node, bad_block, 'bad-txns-nonfinal') assert_submitblock(bad_block, 'bad-txns-nonfinal') self.log.info("getblocktemplate: Test bad tx count") # The tx count is immediately after the block header TX_COUNT_OFFSET = len(super(CBlock, block).serialize()) bad_block_sn = bytearray(block.serialize()) assert_equal(bad_block_sn[TX_COUNT_OFFSET], 1) bad_block_sn[TX_COUNT_OFFSET] += 1 assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {'data': b2x(bad_block_sn), 'mode': 'proposal'}) # No PoW test # self.log.info("getblocktemplate: Test bad bits") # bad_block = copy.deepcopy(block) # bad_block.nBits = 469762303 # impossible in the real world # assert_template(node, bad_block, 'bad-diffbits') self.log.info("getblocktemplate: Test bad merkle root") bad_block = copy.deepcopy(block) bad_block.hashMerkleRoot += 1 assert_template(node, bad_block, 'bad-txnmrklroot', False) assert_submitblock(bad_block, 'bad-txnmrklroot', 'bad-txnmrklroot') self.log.info("getblocktemplate: Test bad timestamps") bad_block = copy.deepcopy(block) bad_block.nTime = 2 ** 31 - 1 assert_template(node, bad_block, 'time-too-new') assert_submitblock(bad_block, 'time-too-new', 'time-too-new') bad_block.nTime = 0 assert_template(node, bad_block, 'time-too-old') assert_submitblock(bad_block, 'time-too-old', 'time-too-old') self.log.info("getblocktemplate: Test not best block") bad_block = copy.deepcopy(block) bad_block.hashPrevBlock = 123 assert_template(node, bad_block, 'inconclusive-not-best-prevblk') assert_submitblock(bad_block, 'prev-blk-not-found', 'prev-blk-not-found') self.log.info('submitheader tests') assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='xx' * TX_COUNT_OFFSET)) assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='ff' * (TX_COUNT_OFFSET-2))) assert_raises_rpc_error(-25, 'Must submit previous header', lambda: node.submitheader(hexdata=super(CBlock, bad_block).serialize().hex())) block.nTime += 1 block.solve() def chain_tip(b_hash, *, status='headers-only', branchlen=1): return {'hash': b_hash, 'height': 202, 'branchlen': branchlen, 'status': status} assert chain_tip(block.hash) not in node.getchaintips() node.submitheader(hexdata=b2x(block.serialize())) assert chain_tip(block.hash) in node.getchaintips() node.submitheader(hexdata=b2x(CBlockHeader(block).serialize())) # Noop assert chain_tip(block.hash) in node.getchaintips() bad_block_root = copy.deepcopy(block) bad_block_root.hashMerkleRoot += 2 bad_block_root.solve() assert chain_tip(bad_block_root.hash) not in node.getchaintips() node.submitheader(hexdata=b2x(CBlockHeader(bad_block_root).serialize())) assert chain_tip(bad_block_root.hash) in node.getchaintips() # Should still reject invalid blocks, even if we have the header: assert_equal(node.submitblock(hexdata=b2x(bad_block_root.serialize())), 'bad-txnmrklroot') assert_equal(node.submitblock(hexdata=b2x(bad_block_root.serialize())), 'bad-txnmrklroot') assert chain_tip(bad_block_root.hash) in node.getchaintips() # We know the header for this invalid block, so should just return early without error: node.submitheader(hexdata=b2x(CBlockHeader(bad_block_root).serialize())) assert chain_tip(bad_block_root.hash) in node.getchaintips() bad_block_lock = copy.deepcopy(block) bad_block_lock.vtx[0].nLockTime = 2**32 - 1 bad_block_lock.vtx[0].rehash() bad_block_lock.hashMerkleRoot = bad_block_lock.calc_merkle_root() bad_block_lock.solve() assert_equal(node.submitblock(hexdata=b2x(bad_block_lock.serialize())), 'bad-txns-nonfinal') assert_equal(node.submitblock(hexdata=b2x(bad_block_lock.serialize())), 'duplicate-invalid') # Build a "good" block on top of the submitted bad block bad_block2 = copy.deepcopy(block) bad_block2.hashPrevBlock = bad_block_lock.sha256 bad_block2.solve() assert_raises_rpc_error(-25, 'bad-prevblk', lambda: node.submitheader(hexdata=b2x(CBlockHeader(bad_block2).serialize()))) # Should reject invalid header right away bad_block_time = copy.deepcopy(block) bad_block_time.nTime = 1 bad_block_time.solve() assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader(hexdata=b2x(CBlockHeader(bad_block_time).serialize()))) # Should ask for the block from a p2p node, if they announce the header as well: node.add_p2p_connection(P2PDataStore()) node.p2p.wait_for_getheaders(timeout=5) # Drop the first getheaders node.p2p.send_blocks_and_test(blocks=[block], node=node) # Must be active now: assert chain_tip(block.hash, status='active', branchlen=0) in node.getchaintips() # Building a few blocks should give the same results node.generate(10) assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader(hexdata=b2x(CBlockHeader(bad_block_time).serialize()))) assert_raises_rpc_error(-25, 'bad-prevblk', lambda: node.submitheader(hexdata=b2x(CBlockHeader(bad_block2).serialize()))) node.submitheader(hexdata=b2x(CBlockHeader(block).serialize())) node.submitheader(hexdata=b2x(CBlockHeader(bad_block_root).serialize())) assert_equal(node.submitblock(hexdata=b2x(block.serialize())), 'duplicate') # valid
def test_getblocktxn_handler(self, test_node): node = self.nodes[0] # bitcoind will not send blocktxn responses for blocks whose height is # more than 10 blocks deep. MAX_GETBLOCKTXN_DEPTH = 10 chain_height = node.getblockcount() current_height = chain_height while (current_height >= chain_height - MAX_GETBLOCKTXN_DEPTH): block_hash = node.getblockhash(current_height) block = from_hex(CBlock(), node.getblock(block_hash, False)) msg = msg_getblocktxn() msg.block_txn_request = BlockTransactionsRequest( int(block_hash, 16), []) num_to_request = random.randint(1, len(block.vtx)) msg.block_txn_request.from_absolute( sorted(random.sample(range(len(block.vtx)), num_to_request))) test_node.send_message(msg) test_node.wait_until(lambda: "blocktxn" in test_node.last_message, timeout=10) [tx.calc_sha256() for tx in block.vtx] with p2p_lock: assert_equal( test_node.last_message["blocktxn"].block_transactions. blockhash, int(block_hash, 16)) all_indices = msg.block_txn_request.to_absolute() for index in all_indices: tx = test_node.last_message[ "blocktxn"].block_transactions.transactions.pop(0) tx.calc_sha256() assert_equal(tx.sha256, block.vtx[index].sha256) # Check that the witness matches assert_equal(tx.calc_sha256(True), block.vtx[index].calc_sha256(True)) test_node.last_message.pop("blocktxn", None) current_height -= 1 # Next request should send a full block response, as we're past the # allowed depth for a blocktxn response. block_hash = node.getblockhash(current_height) msg.block_txn_request = BlockTransactionsRequest( int(block_hash, 16), [0]) with p2p_lock: test_node.last_message.pop("block", None) test_node.last_message.pop("blocktxn", None) test_node.send_and_ping(msg) with p2p_lock: test_node.last_message["block"].block.calc_sha256() assert_equal(test_node.last_message["block"].block.sha256, int(block_hash, 16)) assert "blocktxn" not in test_node.last_message # Request with out-of-bounds tx index results in disconnect bad_peer = self.nodes[0].add_p2p_connection(TestP2PConn()) block_hash = node.getblockhash(chain_height) block = from_hex(CBlock(), node.getblock(block_hash, False)) msg.block_txn_request = BlockTransactionsRequest( int(block_hash, 16), [len(block.vtx)]) with node.assert_debug_log( ['getblocktxn with out-of-bounds tx indices']): bad_peer.send_message(msg) bad_peer.wait_for_disconnect()
assert_equal(mining_info['networkhashps'], Decimal('0.003333333333333334')) assert_equal(mining_info['pooledtx'], 0) # Mine a block to leave initial block download node.generate(1) tmpl = node.getblocktemplate() self.log.info("getblocktemplate: Test capability advertised") assert 'proposal' in tmpl['capabilities'] assert 'coinbasetxn' not in tmpl coinbase_tx = create_coinbase(height=int(tmpl["height"]) + 1) # sequence numbers must not be max for nLockTime to have effect coinbase_tx.vin[0].nSequence = 2 ** 32 - 2 coinbase_tx.rehash() block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) block.nTime = tmpl["curtime"] block.nBits = int(tmpl["bits"], 16) block.nNonce = 0 block.vtx = [coinbase_tx] self.log.info("getblocktemplate: Test valid block") assert_template(node, block, None) self.log.info("submitblock: Test block decode failure") assert_raises_rpc_error(-22, "Block decode failed", node.submitblock, block.serialize()[:-15].hex()) self.log.info("getblocktemplate: Test bad input hash for coinbase transaction") bad_block = copy.deepcopy(block)
def createIncorectGenesisBlock(genesis_coinbase, signblockprivkey, signblockpubkey): genesis = CBlock() genesis.nTime = int(time.time() + 600) genesis.hashPrevBlock = 0 genesis.vtx.append(genesis_coinbase) genesis.hashMerkleRoot = genesis.calc_merkle_root() genesis.hashImMerkleRoot = genesis.calc_immutable_merkle_root() genesis.xfieldType = 1 genesis.xfield = hex_str_to_bytes(signblockpubkey) genesis.solve(signblockprivkey) return genesis
def run_test(self): node = self.nodes[0] # Mine a block to leave initial block download node.generate(1) tmpl = node.getblocktemplate() self.log.info("getblocktemplate: Test capability advertised") assert 'proposal' in tmpl['capabilities'] assert 'coinbasetxn' not in tmpl coinbase_tx = create_coinbase(height=int(tmpl["height"]) + 1) # sequence numbers must not be max for nLockTime to have effect coinbase_tx.vin[0].nSequence = 2**32 - 2 coinbase_tx.rehash() block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) block.nTime = tmpl["curtime"] block.nBits = int(tmpl["bits"], 16) block.nNonce = 0 block.vtx = [coinbase_tx] self.log.info("getblocktemplate: Test valid block") assert_template(node, block, None) self.log.info("submitblock: Test block decode failure") assert_raises_rpc_error(-22, "Block decode failed", node.submitblock, b2x(block.serialize()[:-15])) self.log.info( "getblocktemplate: Test bad input hash for coinbase transaction") bad_block = copy.deepcopy(block) bad_block.vtx[0].vin[0].prevout.hash += 1 bad_block.vtx[0].rehash() assert_template(node, bad_block, 'bad-cb-missing') self.log.info("submitblock: Test invalid coinbase transaction") assert_raises_rpc_error(-22, "Block does not start with a coinbase", node.submitblock, b2x(bad_block.serialize())) self.log.info("getblocktemplate: Test truncated final transaction") assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, { 'data': b2x(block.serialize()[:-1]), 'mode': 'proposal' }) self.log.info("getblocktemplate: Test duplicate transaction") bad_block = copy.deepcopy(block) bad_block.vtx.append(bad_block.vtx[0]) assert_template(node, bad_block, 'bad-txns-duplicate') self.log.info("getblocktemplate: Test invalid transaction") bad_block = copy.deepcopy(block) bad_tx = copy.deepcopy(bad_block.vtx[0]) bad_tx.vin[0].prevout.hash = 255 bad_tx.rehash() bad_block.vtx.append(bad_tx) assert_template(node, bad_block, 'bad-txns-inputs-missingorspent') self.log.info("getblocktemplate: Test nonfinal transaction") bad_block = copy.deepcopy(block) bad_block.vtx[0].nLockTime = 2**32 - 1 bad_block.vtx[0].rehash() assert_template(node, bad_block, 'bad-txns-nonfinal') self.log.info("getblocktemplate: Test bad tx count") # The tx count is immediately after the block header TX_COUNT_OFFSET = 80 bad_block_sn = bytearray(block.serialize()) assert_equal(bad_block_sn[TX_COUNT_OFFSET], 1) bad_block_sn[TX_COUNT_OFFSET] += 1 assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, { 'data': b2x(bad_block_sn), 'mode': 'proposal' }) self.log.info("getblocktemplate: Test bad bits") bad_block = copy.deepcopy(block) bad_block.nBits = 469762303 # impossible in the real world assert_template(node, bad_block, 'bad-diffbits') self.log.info("getblocktemplate: Test bad merkle root") bad_block = copy.deepcopy(block) bad_block.hashMerkleRoot += 1 assert_template(node, bad_block, 'bad-txnmrklroot', False) self.log.info("getblocktemplate: Test bad timestamps") bad_block = copy.deepcopy(block) bad_block.nTime = 2**31 - 1 assert_template(node, bad_block, 'time-too-new') bad_block.nTime = 0 assert_template(node, bad_block, 'time-too-old') self.log.info("getblocktemplate: Test not best block") bad_block = copy.deepcopy(block) bad_block.hashPrevBlock = 123 assert_template(node, bad_block, 'inconclusive-not-best-prevblk') self.log.info('submitheader tests') assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='xx' * 80)) assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='ff' * 78)) assert_raises_rpc_error(-25, 'Must submit previous header', lambda: node.submitheader(hexdata='ff' * 80)) block.solve() def chain_tip(b_hash, *, status='headers-only', branchlen=1): return { 'hash': b_hash, 'height': 202, 'branchlen': branchlen, 'status': status } assert chain_tip(block.hash) not in node.getchaintips() node.submitheader(hexdata=b2x(block.serialize())) assert chain_tip(block.hash) in node.getchaintips() # Noop node.submitheader(hexdata=b2x(CBlockHeader(block).serialize())) assert chain_tip(block.hash) in node.getchaintips() bad_block_root = copy.deepcopy(block) bad_block_root.hashMerkleRoot += 2 bad_block_root.solve() assert chain_tip(bad_block_root.hash) not in node.getchaintips() node.submitheader( hexdata=b2x(CBlockHeader(bad_block_root).serialize())) assert chain_tip(bad_block_root.hash) in node.getchaintips() # Should still reject invalid blocks, even if we have the header: assert_equal(node.submitblock(hexdata=b2x(bad_block_root.serialize())), 'invalid') assert chain_tip(bad_block_root.hash) in node.getchaintips() # We know the header for this invalid block, so should just return early without error: node.submitheader( hexdata=b2x(CBlockHeader(bad_block_root).serialize())) assert chain_tip(bad_block_root.hash) in node.getchaintips() bad_block_lock = copy.deepcopy(block) bad_block_lock.vtx[0].nLockTime = 2**32 - 1 bad_block_lock.vtx[0].rehash() bad_block_lock.hashMerkleRoot = bad_block_lock.calc_merkle_root() bad_block_lock.solve() assert_equal(node.submitblock(hexdata=b2x(bad_block_lock.serialize())), 'invalid') # Build a "good" block on top of the submitted bad block bad_block2 = copy.deepcopy(block) bad_block2.hashPrevBlock = bad_block_lock.sha256 bad_block2.solve() assert_raises_rpc_error( -25, 'bad-prevblk', lambda: node.submitheader(hexdata=b2x( CBlockHeader(bad_block2).serialize()))) # Should reject invalid header right away bad_block_time = copy.deepcopy(block) bad_block_time.nTime = 1 bad_block_time.solve() assert_raises_rpc_error( -25, 'time-too-old', lambda: node.submitheader(hexdata=b2x( CBlockHeader(bad_block_time).serialize()))) # Should ask for the block from a p2p node, if they announce the header as well: node.add_p2p_connection(P2PDataStore()) # Drop the first getheaders node.p2p.wait_for_getheaders(timeout=5) node.p2p.send_blocks_and_test(blocks=[block], node=node) # Must be active now: assert chain_tip(block.hash, status='active', branchlen=0) in node.getchaintips() # Building a few blocks should give the same results node.generate(10) assert_raises_rpc_error( -25, 'time-too-old', lambda: node.submitheader(hexdata=b2x( CBlockHeader(bad_block_time).serialize()))) assert_raises_rpc_error( -25, 'bad-prevblk', lambda: node.submitheader(hexdata=b2x( CBlockHeader(bad_block2).serialize()))) node.submitheader(hexdata=b2x(CBlockHeader(block).serialize())) node.submitheader( hexdata=b2x(CBlockHeader(bad_block_root).serialize()))
def run_test(self): node = self.nodes[0] # convenience reference to the node self.bootstrap_p2p() # Add one p2p connection to the node self.block_heights = {} self.coinbase_key = ECKey() self.coinbase_key.generate() self.coinbase_pubkey = self.coinbase_key.get_pubkey().get_bytes() self.tip = None self.blocks = {} self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) self.block_heights[self.genesis_hash] = 0 self.spendable_outputs = [] # Create a new block b0 = self.next_block(0) self.save_spendable_output() self.send_blocks([b0]) # Allow the block to mature blocks = [] for i in range(99): blocks.append(self.next_block(5000 + i)) self.save_spendable_output() self.send_blocks(blocks) # collect spendable outputs now to avoid cluttering the code later on out = [] for i in range(33): out.append(self.get_spendable_output()) # Start by building a couple of blocks on top (which output is spent is # in parentheses): # genesis -> b1 (0) -> b2 (1) b1 = self.next_block(1, spend=out[0]) self.save_spendable_output() b2 = self.next_block(2, spend=out[1]) self.save_spendable_output() self.send_blocks([b1, b2]) # Fork like this: # # genesis -> b1 (0) -> b2 (1) # \-> b3 (1) # # Nothing should happen at this point. We saw b2 first so it takes # priority. self.log.info("Don't reorg to a chain of the same length") self.move_tip(1) b3 = self.next_block(3, spend=out[1]) txout_b3 = b3.vtx[1] self.send_blocks([b3], False) # Now we add another block to make the alternative chain longer. # # genesis -> b1 (0) -> b2 (1) # \-> b3 (1) -> b4 (2) self.log.info("Reorg to a longer chain") b4 = self.next_block(4, spend=out[2]) self.send_blocks([b4]) # ... and back to the first chain. # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b3 (1) -> b4 (2) self.move_tip(2) b5 = self.next_block(5, spend=out[2]) self.save_spendable_output() self.send_blocks([b5], False) self.log.info("Reorg back to the original chain") b6 = self.next_block(6, spend=out[3]) self.send_blocks([b6], True) # Try to create a fork that double-spends # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b7 (2) -> b8 (4) # \-> b3 (1) -> b4 (2) self.log.info( "Reject a chain with a double spend, even if it is longer") self.move_tip(5) b7 = self.next_block(7, spend=out[2]) self.send_blocks([b7], False) b8 = self.next_block(8, spend=out[4]) self.send_blocks([b8], False, reconnect=True) # Try to create a block that has too much fee # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b9 (4) # \-> b3 (1) -> b4 (2) self.log.info( "Reject a block where the miner creates too much coinbase reward") self.move_tip(6) b9 = self.next_block(9, spend=out[4], additional_coinbase_value=1) self.send_blocks([b9], success=False, reject_reason='bad-cb-amount', reconnect=True) # Create a fork that ends in a block with too much fee (the one that causes the reorg) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b10 (3) -> b11 (4) # \-> b3 (1) -> b4 (2) self.log.info( "Reject a chain where the miner creates too much coinbase reward, even if the chain is longer") self.move_tip(5) b10 = self.next_block(10, spend=out[3]) self.send_blocks([b10], False) b11 = self.next_block(11, spend=out[4], additional_coinbase_value=1) self.send_blocks([b11], success=False, reject_reason='bad-cb-amount', reconnect=True) # Try again, but with a valid fork first # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b14 (5) # \-> b3 (1) -> b4 (2) self.log.info( "Reject a chain where the miner creates too much coinbase reward, even if the chain is longer (on a forked chain)") self.move_tip(5) b12 = self.next_block(12, spend=out[3]) self.save_spendable_output() b13 = self.next_block(13, spend=out[4]) self.save_spendable_output() b14 = self.next_block(14, spend=out[5], additional_coinbase_value=1) self.send_blocks([b12, b13, b14], success=False, reject_reason='bad-cb-amount', reconnect=True) # New tip should be b13. assert_equal(node.getbestblockhash(), b13.hash) self.log.info("Skipped sigops tests") # tests were moved to feature_block_sigops.py self.move_tip(13) b15 = self.next_block(15) self.save_spendable_output() self.send_blocks([b15], True) # Attempt to spend a transaction created on a different fork # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b17 (b3.vtx[1]) # \-> b3 (1) -> b4 (2) self.log.info("Reject a block with a spend from a re-org'ed out tx") self.move_tip(15) b17 = self.next_block(17, spend=txout_b3) self.send_blocks([b17], success=False, reject_reason='bad-txns-inputs-missingorspent', reconnect=True) # Attempt to spend a transaction created on a different fork (on a fork this time) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) # \-> b18 (b3.vtx[1]) -> b19 (6) # \-> b3 (1) -> b4 (2) self.log.info( "Reject a block with a spend from a re-org'ed out tx (on a forked chain)") self.move_tip(13) b18 = self.next_block(18, spend=txout_b3) self.send_blocks([b18], False) b19 = self.next_block(19, spend=out[6]) self.send_blocks([b19], success=False, reject_reason='bad-txns-inputs-missingorspent', reconnect=True) # Attempt to spend a coinbase at depth too low # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b20 (7) # \-> b3 (1) -> b4 (2) self.log.info("Reject a block spending an immature coinbase.") self.move_tip(15) b20 = self.next_block(20, spend=out[7]) self.send_blocks([b20], success=False, reject_reason='bad-txns-premature-spend-of-coinbase') # Attempt to spend a coinbase at depth too low (on a fork this time) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) # \-> b21 (6) -> b22 (5) # \-> b3 (1) -> b4 (2) self.log.info( "Reject a block spending an immature coinbase (on a forked chain)") self.move_tip(13) b21 = self.next_block(21, spend=out[6]) self.send_blocks([b21], False) b22 = self.next_block(22, spend=out[5]) self.send_blocks([b22], success=False, reject_reason='bad-txns-premature-spend-of-coinbase') # Create a block on either side of LEGACY_MAX_BLOCK_SIZE and make sure its accepted/rejected # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) # \-> b24 (6) -> b25 (7) # \-> b3 (1) -> b4 (2) self.log.info("Accept a block of size LEGACY_MAX_BLOCK_SIZE") self.move_tip(15) b23 = self.next_block(23, spend=out[6]) tx = CTransaction() script_length = LEGACY_MAX_BLOCK_SIZE - len(b23.serialize()) - 69 script_output = CScript([b'\x00' * script_length]) tx.vout.append(CTxOut(0, script_output)) tx.vin.append(CTxIn(COutPoint(b23.vtx[1].sha256, 0))) b23 = self.update_block(23, [tx]) # Make sure the math above worked out to produce a max-sized block assert_equal(len(b23.serialize()), LEGACY_MAX_BLOCK_SIZE) self.send_blocks([b23], True) self.save_spendable_output() # Create blocks with a coinbase input script size out of range # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) # \-> ... (6) -> ... (7) # \-> b3 (1) -> b4 (2) self.log.info( "Reject a block with coinbase input script size out of range") self.move_tip(15) b26 = self.next_block(26, spend=out[6]) b26.vtx[0].vin[0].scriptSig = b'\x00' b26.vtx[0].rehash() # update_block causes the merkle root to get updated, even with no new # transactions, and updates the required state. b26 = self.update_block(26, []) self.send_blocks([b26], success=False, reject_reason='bad-cb-length', reconnect=True) # Extend the b26 chain to make sure bitcoind isn't accepting b26 b27 = self.next_block(27, spend=out[7]) self.send_blocks([b27], False) # Now try a too-large-coinbase script self.move_tip(15) b28 = self.next_block(28, spend=out[6]) b28.vtx[0].vin[0].scriptSig = b'\x00' * 101 b28.vtx[0].rehash() b28 = self.update_block(28, []) self.send_blocks([b28], success=False, reject_reason='bad-cb-length', reconnect=True) # Extend the b28 chain to make sure bitcoind isn't accepting b28 b29 = self.next_block(29, spend=out[7]) self.send_blocks([b29], False) # b30 has a max-sized coinbase scriptSig. self.move_tip(23) b30 = self.next_block(30) b30.vtx[0].vin[0].scriptSig = b'\x00' * 100 b30.vtx[0].rehash() b30 = self.update_block(30, []) self.send_blocks([b30], True) self.save_spendable_output() self.log.info("Skipped sigops tests") # tests were moved to feature_block_sigops.py b31 = self.next_block(31) self.save_spendable_output() b33 = self.next_block(33) self.save_spendable_output() b35 = self.next_block(35) self.save_spendable_output() self.send_blocks([b31, b33, b35], True) # Check spending of a transaction in a block which failed to connect # # b6 (3) # b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) # \-> b37 (11) # \-> b38 (11/37) # # save 37's spendable output, but then double-spend out11 to invalidate # the block self.log.info( "Reject a block spending transaction from a block which failed to connect") self.move_tip(35) b37 = self.next_block(37, spend=out[11]) txout_b37 = b37.vtx[1] tx = self.create_and_sign_transaction(out[11], 0) b37 = self.update_block(37, [tx]) self.send_blocks([b37], success=False, reject_reason='bad-txns-inputs-missingorspent', reconnect=True) # attempt to spend b37's first non-coinbase tx, at which point b37 was # still considered valid self.move_tip(35) b38 = self.next_block(38, spend=txout_b37) self.send_blocks([b38], success=False, reject_reason='bad-txns-inputs-missingorspent', reconnect=True) self.log.info("Skipped sigops tests") # tests were moved to feature_block_sigops.py self.move_tip(35) b39 = self.next_block(39) self.save_spendable_output() b41 = self.next_block(41) self.send_blocks([b39, b41], True) # Fork off of b39 to create a constant base again # # b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) # \-> b41 (12) # self.move_tip(39) b42 = self.next_block(42, spend=out[12]) self.save_spendable_output() b43 = self.next_block(43, spend=out[13]) self.save_spendable_output() self.send_blocks([b42, b43], True) # Test a number of really invalid scenarios # # -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b44 (14) # \-> ??? (15) # The next few blocks are going to be created "by hand" since they'll do funky things, such as having # the first transaction be non-coinbase, etc. The purpose of b44 is to # make sure this works. self.log.info("Build block 44 manually") height = self.block_heights[self.tip.sha256] + 1 coinbase = create_coinbase(height, self.coinbase_pubkey) b44 = CBlock() b44.nTime = self.tip.nTime + 1 b44.hashPrevBlock = self.tip.sha256 b44.nBits = 0x207fffff b44.vtx.append(coinbase) b44.hashMerkleRoot = b44.calc_merkle_root() b44.solve() self.tip = b44 self.block_heights[b44.sha256] = height self.blocks[44] = b44 self.send_blocks([b44], True) self.log.info("Reject a block with a non-coinbase as the first tx") non_coinbase = self.create_tx(out[15], 0, 1) b45 = CBlock() b45.nTime = self.tip.nTime + 1 b45.hashPrevBlock = self.tip.sha256 b45.nBits = 0x207fffff b45.vtx.append(non_coinbase) b45.hashMerkleRoot = b45.calc_merkle_root() b45.calc_sha256() b45.solve() self.block_heights[b45.sha256] = self.block_heights[ self.tip.sha256] + 1 self.tip = b45 self.blocks[45] = b45 self.send_blocks([b45], success=False, reject_reason='bad-cb-missing', reconnect=True) self.log.info("Reject a block with no transactions") self.move_tip(44) b46 = CBlock() b46.nTime = b44.nTime + 1 b46.hashPrevBlock = b44.sha256 b46.nBits = 0x207fffff b46.vtx = [] b46.hashMerkleRoot = 0 b46.solve() self.block_heights[b46.sha256] = self.block_heights[b44.sha256] + 1 self.tip = b46 assert 46 not in self.blocks self.blocks[46] = b46 self.send_blocks([b46], success=False, reject_reason='bad-cb-missing', reconnect=True) self.log.info("Reject a block with invalid work") self.move_tip(44) b47 = self.next_block(47, solve=False) target = uint256_from_compact(b47.nBits) while b47.sha256 < target: b47.nNonce += 1 b47.rehash() self.send_blocks([b47], False, request_block=False) self.log.info("Reject a block with a timestamp >2 hours in the future") self.move_tip(44) b48 = self.next_block(48, solve=False) b48.nTime = int(time.time()) + 60 * 60 * 3 b48.solve() self.send_blocks([b48], False, request_block=False) self.log.info("Reject a block with invalid merkle hash") self.move_tip(44) b49 = self.next_block(49) b49.hashMerkleRoot += 1 b49.solve() self.send_blocks([b49], success=False, reject_reason='bad-txnmrklroot', reconnect=True) self.log.info("Reject a block with incorrect POW limit") self.move_tip(44) b50 = self.next_block(50) b50.nBits = b50.nBits - 1 b50.solve() self.send_blocks([b50], False, request_block=False, reconnect=True) self.log.info("Reject a block with two coinbase transactions") self.move_tip(44) b51 = self.next_block(51) cb2 = create_coinbase(51, self.coinbase_pubkey) b51 = self.update_block(51, [cb2]) self.send_blocks([b51], success=False, reject_reason='bad-tx-coinbase', reconnect=True) self.log.info("Reject a block with duplicate transactions") self.move_tip(44) b52 = self.next_block(52, spend=out[15]) b52 = self.update_block(52, [b52.vtx[1]]) self.send_blocks([b52], success=False, reject_reason='tx-duplicate', reconnect=True) # Test block timestamps # -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) # \-> b54 (15) # self.move_tip(43) b53 = self.next_block(53, spend=out[14]) self.send_blocks([b53], False) self.save_spendable_output() self.log.info("Reject a block with timestamp before MedianTimePast") b54 = self.next_block(54, spend=out[15]) b54.nTime = b35.nTime - 1 b54.solve() self.send_blocks([b54], False, request_block=False) # valid timestamp self.move_tip(53) b55 = self.next_block(55, spend=out[15]) b55.nTime = b35.nTime self.update_block(55, []) self.send_blocks([b55], True) self.save_spendable_output() # Test Merkle tree malleability # # -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57p2 (16) # \-> b57 (16) # \-> b56p2 (16) # \-> b56 (16) # # Merkle tree malleability (CVE-2012-2459): repeating sequences of transactions in a block without # affecting the merkle root of a block, while still invalidating it. # See: src/consensus/merkle.h # # b57 has three txns: coinbase, tx, tx1. The merkle root computation will duplicate tx. # Result: OK # # b56 copies b57 but duplicates tx1 and does not recalculate the block hash. So it has a valid merkle # root but duplicate transactions. # Result: Fails # # b57p2 has six transactions in its merkle tree: # - coinbase, tx, tx1, tx2, tx3, tx4 # Merkle root calculation will duplicate as necessary. # Result: OK. # # b56p2 copies b57p2 but adds both tx3 and tx4. The purpose of the test is to make sure the code catches # duplicate txns that are not next to one another with the "bad-txns-duplicate" error (which indicates # that the error was caught early, avoiding a DOS vulnerability.) # b57 - a good block with 2 txs, don't submit until end self.move_tip(55) b57 = self.next_block(57) tx = self.create_and_sign_transaction(out[16], 1) tx1 = self.create_tx(tx, 0, 1) b57 = self.update_block(57, [tx, tx1]) # b56 - copy b57, add a duplicate tx self.log.info( "Reject a block with a duplicate transaction in the Merkle Tree (but with a valid Merkle Root)") self.move_tip(55) b56 = copy.deepcopy(b57) self.blocks[56] = b56 assert_equal(len(b56.vtx), 3) b56 = self.update_block(56, [b57.vtx[2]]) assert_equal(b56.hash, b57.hash) self.send_blocks([b56], success=False, reject_reason='bad-txns-duplicate', reconnect=True) # b57p2 - a good block with 6 tx'es, don't submit until end self.move_tip(55) b57p2 = self.next_block("57p2") tx = self.create_and_sign_transaction(out[16], 1) tx1 = self.create_tx(tx, 0, 1) tx2 = self.create_tx(tx1, 0, 1) tx3 = self.create_tx(tx2, 0, 1) tx4 = self.create_tx(tx3, 0, 1) b57p2 = self.update_block("57p2", [tx, tx1, tx2, tx3, tx4]) # b56p2 - copy b57p2, duplicate two non-consecutive tx's self.log.info( "Reject a block with two duplicate transactions in the Merkle Tree (but with a valid Merkle Root)") self.move_tip(55) b56p2 = copy.deepcopy(b57p2) self.blocks["b56p2"] = b56p2 assert_equal(len(b56p2.vtx), 6) b56p2 = self.update_block("b56p2", b56p2.vtx[4:6], reorder=False) assert_equal(b56p2.hash, b57p2.hash) self.send_blocks([b56p2], success=False, reject_reason='bad-txns-duplicate', reconnect=True) self.move_tip("57p2") self.send_blocks([b57p2], True) self.move_tip(57) # The tip is not updated because 57p2 seen first self.send_blocks([b57], False) self.save_spendable_output() # Test a few invalid tx types # # -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) # \-> ??? (17) # # tx with prevout.n out of range self.log.info( "Reject a block with a transaction with prevout.n out of range") self.move_tip(57) b58 = self.next_block(58, spend=out[17]) tx = CTransaction() assert(len(out[17].vout) < 42) tx.vin.append( CTxIn(COutPoint(out[17].sha256, 42), CScript([OP_TRUE]), 0xffffffff)) tx.vout.append(CTxOut(0, b"")) pad_tx(tx) tx.calc_sha256() b58 = self.update_block(58, [tx]) self.send_blocks([b58], success=False, reject_reason='bad-txns-inputs-missingorspent', reconnect=True) # tx with output value > input value self.log.info( "Reject a block with a transaction with outputs > inputs") self.move_tip(57) b59 = self.next_block(59) tx = self.create_and_sign_transaction(out[17], 51 * COIN) b59 = self.update_block(59, [tx]) self.send_blocks([b59], success=False, reject_reason='bad-txns-in-belowout', reconnect=True) # reset to good chain self.move_tip(57) b60 = self.next_block(60, spend=out[17]) self.send_blocks([b60], True) self.save_spendable_output() # Test BIP30 # # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) # \-> b61 (18) # # Blocks are not allowed to contain a transaction whose id matches that of an earlier, # not-fully-spent transaction in the same chain. To test, make identical coinbases; # the second one should be rejected. # self.log.info( "Reject a block with a transaction with a duplicate hash of a previous transaction (BIP30)") self.move_tip(60) b61 = self.next_block(61, spend=out[18]) # Equalize the coinbases b61.vtx[0].vin[0].scriptSig = b60.vtx[0].vin[0].scriptSig b61.vtx[0].rehash() b61 = self.update_block(61, []) assert_equal(b60.vtx[0].serialize(), b61.vtx[0].serialize()) self.send_blocks([b61], success=False, reject_reason='bad-txns-BIP30', reconnect=True) # Test tx.isFinal is properly rejected (not an exhaustive tx.isFinal test, that should be in data-driven transaction tests) # # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) # \-> b62 (18) # self.log.info( "Reject a block with a transaction with a nonfinal locktime") self.move_tip(60) b62 = self.next_block(62) tx = CTransaction() tx.nLockTime = 0xffffffff # this locktime is non-final # don't set nSequence tx.vin.append(CTxIn(COutPoint(out[18].sha256, 0))) tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) assert tx.vin[0].nSequence < 0xffffffff tx.calc_sha256() b62 = self.update_block(62, [tx]) self.send_blocks([b62], success=False, reject_reason='bad-txns-nonfinal') # Test a non-final coinbase is also rejected # # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) # \-> b63 (-) # self.log.info( "Reject a block with a coinbase transaction with a nonfinal locktime") self.move_tip(60) b63 = self.next_block(63) b63.vtx[0].nLockTime = 0xffffffff b63.vtx[0].vin[0].nSequence = 0xDEADBEEF b63.vtx[0].rehash() b63 = self.update_block(63, []) self.send_blocks([b63], success=False, reject_reason='bad-txns-nonfinal') # This checks that a block with a bloated VARINT between the block_header and the array of tx such that # the block is > LEGACY_MAX_BLOCK_SIZE with the bloated varint, but <= LEGACY_MAX_BLOCK_SIZE without the bloated varint, # does not cause a subsequent, identical block with canonical encoding to be rejected. The test does not # care whether the bloated block is accepted or rejected; it only cares that the second block is accepted. # # What matters is that the receiving node should not reject the bloated block, and then reject the canonical # block on the basis that it's the same as an already-rejected block (which would be a consensus failure.) # # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) # \ # b64a (18) # b64a is a bloated block (non-canonical varint) # b64 is a good block (same as b64 but w/ canonical varint) # self.log.info( "Accept a valid block even if a bloated version of the block has previously been sent") self.move_tip(60) regular_block = self.next_block("64a", spend=out[18]) # make it a "broken_block," with non-canonical serialization b64a = CBrokenBlock(regular_block) b64a.initialize(regular_block) self.blocks["64a"] = b64a self.tip = b64a tx = CTransaction() # use canonical serialization to calculate size script_length = LEGACY_MAX_BLOCK_SIZE - \ len(b64a.normal_serialize()) - 69 script_output = CScript([b'\x00' * script_length]) tx.vout.append(CTxOut(0, script_output)) tx.vin.append(CTxIn(COutPoint(b64a.vtx[1].sha256, 0))) b64a = self.update_block("64a", [tx]) assert_equal(len(b64a.serialize()), LEGACY_MAX_BLOCK_SIZE + 8) self.send_blocks([b64a], success=False, reject_reason='non-canonical ReadCompactSize()') # bitcoind doesn't disconnect us for sending a bloated block, but if we subsequently # resend the header message, it won't send us the getdata message again. Just # disconnect and reconnect and then call sync_blocks. # TODO: improve this test to be less dependent on P2P DOS behaviour. node.disconnect_p2ps() self.reconnect_p2p() self.move_tip(60) b64 = CBlock(b64a) b64.vtx = copy.deepcopy(b64a.vtx) assert_equal(b64.hash, b64a.hash) assert_equal(len(b64.serialize()), LEGACY_MAX_BLOCK_SIZE) self.blocks[64] = b64 b64 = self.update_block(64, []) self.send_blocks([b64], True) self.save_spendable_output() # Spend an output created in the block itself # # -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) # self.log.info( "Accept a block with a transaction spending an output created in the same block") self.move_tip(64) b65 = self.next_block(65) tx1 = self.create_and_sign_transaction(out[19], out[19].vout[0].nValue) tx2 = self.create_and_sign_transaction(tx1, 0) b65 = self.update_block(65, [tx1, tx2]) self.send_blocks([b65], True) self.save_spendable_output() # Attempt to double-spend a transaction created in a block # # -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) # \-> b67 (20) # # self.log.info( "Reject a block with a transaction double spending a transaction created in the same block") self.move_tip(65) b67 = self.next_block(67) tx1 = self.create_and_sign_transaction(out[20], out[20].vout[0].nValue) tx2 = self.create_and_sign_transaction(tx1, 1) tx3 = self.create_and_sign_transaction(tx1, 2) b67 = self.update_block(67, [tx1, tx2, tx3]) self.send_blocks([b67], success=False, reject_reason='bad-txns-inputs-missingorspent', reconnect=True) # More tests of block subsidy # # -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) # \-> b68 (20) # # b68 - coinbase with an extra 10 satoshis, # creates a tx that has 9 satoshis from out[20] go to fees # this fails because the coinbase is trying to claim 1 satoshi too much in fees # # b69 - coinbase with extra 10 satoshis, and a tx that gives a 10 satoshi fee # this succeeds # self.log.info( "Reject a block trying to claim too much subsidy in the coinbase transaction") self.move_tip(65) b68 = self.next_block(68, additional_coinbase_value=10) tx = self.create_and_sign_transaction( out[20], out[20].vout[0].nValue - 9) b68 = self.update_block(68, [tx]) self.send_blocks([b68], success=False, reject_reason='bad-cb-amount', reconnect=True) self.log.info( "Accept a block claiming the correct subsidy in the coinbase transaction") self.move_tip(65) b69 = self.next_block(69, additional_coinbase_value=10) tx = self.create_and_sign_transaction( out[20], out[20].vout[0].nValue - 10) self.update_block(69, [tx]) self.send_blocks([b69], True) self.save_spendable_output() # Test spending the outpoint of a non-existent transaction # # -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) # \-> b70 (21) # self.log.info( "Reject a block containing a transaction spending from a non-existent input") self.move_tip(69) b70 = self.next_block(70, spend=out[21]) bogus_tx = CTransaction() bogus_tx.sha256 = uint256_from_str( b"23c70ed7c0506e9178fc1a987f40a33946d4ad4c962b5ae3a52546da53af0c5c") tx = CTransaction() tx.vin.append(CTxIn(COutPoint(bogus_tx.sha256, 0), b"", 0xffffffff)) tx.vout.append(CTxOut(1, b"")) pad_tx(tx) b70 = self.update_block(70, [tx]) self.send_blocks([b70], success=False, reject_reason='bad-txns-inputs-missingorspent', reconnect=True) # Test accepting an invalid block which has the same hash as a valid one (via merkle tree tricks) # # -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) -> b72 (21) # \-> b71 (21) # # b72 is a good block. # b71 is a copy of 72, but re-adds one of its transactions. However, # it has the same hash as b72. self.log.info( "Reject a block containing a duplicate transaction but with the same Merkle root (Merkle tree malleability") self.move_tip(69) b72 = self.next_block(72) tx1 = self.create_and_sign_transaction(out[21], 2) tx2 = self.create_and_sign_transaction(tx1, 1) b72 = self.update_block(72, [tx1, tx2]) # now tip is 72 b71 = copy.deepcopy(b72) # add duplicate last transaction b71.vtx.append(b72.vtx[-1]) # b71 builds off b69 self.block_heights[b71.sha256] = self.block_heights[b69.sha256] + 1 self.blocks[71] = b71 assert_equal(len(b71.vtx), 4) assert_equal(len(b72.vtx), 3) assert_equal(b72.sha256, b71.sha256) self.move_tip(71) self.send_blocks([b71], success=False, reject_reason='bad-txns-duplicate', reconnect=True) self.move_tip(72) self.send_blocks([b72], True) self.save_spendable_output() self.log.info("Skipped sigops tests") # tests were moved to feature_block_sigops.py b75 = self.next_block(75) self.save_spendable_output() b76 = self.next_block(76) self.save_spendable_output() self.send_blocks([b75, b76], True) # Test transaction resurrection # # -> b77 (24) -> b78 (25) -> b79 (26) # \-> b80 (25) -> b81 (26) -> b82 (27) # # b78 creates a tx, which is spent in b79. After b82, both should be in mempool # # The tx'es must be unsigned and pass the node's mempool policy. It is unsigned for the # rather obscure reason that the Python signature code does not distinguish between # Low-S and High-S values (whereas the bitcoin code has custom code which does so); # as a result of which, the odds are 50% that the python code will use the right # value and the transaction will be accepted into the mempool. Until we modify the # test framework to support low-S signing, we are out of luck. # # To get around this issue, we construct transactions which are not signed and which # spend to OP_TRUE. If the standard-ness rules change, this test would need to be # updated. (Perhaps to spend to a P2SH OP_TRUE script) self.log.info("Test transaction resurrection during a re-org") self.move_tip(76) b77 = self.next_block(77) tx77 = self.create_and_sign_transaction(out[24], 10 * COIN) b77 = self.update_block(77, [tx77]) self.send_blocks([b77], True) self.save_spendable_output() b78 = self.next_block(78) tx78 = self.create_tx(tx77, 0, 9 * COIN) b78 = self.update_block(78, [tx78]) self.send_blocks([b78], True) b79 = self.next_block(79) tx79 = self.create_tx(tx78, 0, 8 * COIN) b79 = self.update_block(79, [tx79]) self.send_blocks([b79], True) # mempool should be empty assert_equal(len(self.nodes[0].getrawmempool()), 0) self.move_tip(77) b80 = self.next_block(80, spend=out[25]) self.send_blocks([b80], False, request_block=False) self.save_spendable_output() b81 = self.next_block(81, spend=out[26]) # other chain is same length self.send_blocks([b81], False, request_block=False) self.save_spendable_output() b82 = self.next_block(82, spend=out[27]) # now this chain is longer, triggers re-org self.send_blocks([b82], True) self.save_spendable_output() # now check that tx78 and tx79 have been put back into the peer's # mempool mempool = self.nodes[0].getrawmempool() assert_equal(len(mempool), 2) assert tx78.hash in mempool assert tx79.hash in mempool # Test invalid opcodes in dead execution paths. # # -> b81 (26) -> b82 (27) -> b83 (28) # self.log.info( "Accept a block with invalid opcodes in dead execution paths") b83 = self.next_block(83) op_codes = [OP_IF, INVALIDOPCODE, OP_ELSE, OP_TRUE, OP_ENDIF] script = CScript(op_codes) tx1 = self.create_and_sign_transaction( out[28], out[28].vout[0].nValue, script) tx2 = self.create_and_sign_transaction(tx1, 0, CScript([OP_TRUE])) tx2.vin[0].scriptSig = CScript([OP_FALSE]) tx2.rehash() b83 = self.update_block(83, [tx1, tx2]) self.send_blocks([b83], True) self.save_spendable_output() # Reorg on/off blocks that have OP_RETURN in them (and try to spend them) # # -> b81 (26) -> b82 (27) -> b83 (28) -> b84 (29) -> b87 (30) -> b88 (31) # \-> b85 (29) -> b86 (30) \-> b89a (32) # self.log.info("Test re-orging blocks with OP_RETURN in them") b84 = self.next_block(84) tx1 = self.create_tx(out[29], 0, 0, CScript([OP_RETURN])) vout_offset = len(tx1.vout) tx1.vout.append(CTxOut(0, CScript([OP_TRUE]))) tx1.vout.append(CTxOut(0, CScript([OP_TRUE]))) tx1.vout.append(CTxOut(0, CScript([OP_TRUE]))) tx1.vout.append(CTxOut(0, CScript([OP_TRUE]))) tx1.calc_sha256() self.sign_tx(tx1, out[29]) tx1.rehash() tx2 = self.create_tx(tx1, vout_offset, 0, CScript([OP_RETURN])) tx2.vout.append(CTxOut(0, CScript([OP_RETURN]))) tx3 = self.create_tx(tx1, vout_offset + 1, 0, CScript([OP_RETURN])) tx3.vout.append(CTxOut(0, CScript([OP_TRUE]))) tx4 = self.create_tx(tx1, vout_offset + 2, 0, CScript([OP_TRUE])) tx4.vout.append(CTxOut(0, CScript([OP_RETURN]))) tx5 = self.create_tx(tx1, vout_offset + 3, 0, CScript([OP_RETURN])) b84 = self.update_block(84, [tx1, tx2, tx3, tx4, tx5]) self.send_blocks([b84], True) self.save_spendable_output() self.move_tip(83) b85 = self.next_block(85, spend=out[29]) self.send_blocks([b85], False) # other chain is same length b86 = self.next_block(86, spend=out[30]) self.send_blocks([b86], True) self.move_tip(84) b87 = self.next_block(87, spend=out[30]) self.send_blocks([b87], False) # other chain is same length self.save_spendable_output() b88 = self.next_block(88, spend=out[31]) self.send_blocks([b88], True) self.save_spendable_output() # trying to spend the OP_RETURN output is rejected b89a = self.next_block("89a", spend=out[32]) tx = self.create_tx(tx1, 0, 0, CScript([OP_TRUE])) b89a = self.update_block("89a", [tx]) self.send_blocks([b89a], success=False, reject_reason='bad-txns-inputs-missingorspent', reconnect=True) self.log.info( "Test a re-org of one week's worth of blocks (1088 blocks)") self.move_tip(88) LARGE_REORG_SIZE = 1088 blocks = [] spend = out[32] for i in range(89, LARGE_REORG_SIZE + 89): b = self.next_block(i, spend) tx = CTransaction() script_length = LEGACY_MAX_BLOCK_SIZE - len(b.serialize()) - 69 script_output = CScript([b'\x00' * script_length]) tx.vout.append(CTxOut(0, script_output)) tx.vin.append(CTxIn(COutPoint(b.vtx[1].sha256, 0))) b = self.update_block(i, [tx]) assert_equal(len(b.serialize()), LEGACY_MAX_BLOCK_SIZE) blocks.append(b) self.save_spendable_output() spend = self.get_spendable_output() self.send_blocks(blocks, True, timeout=960) chain1_tip = i # now create alt chain of same length self.move_tip(88) blocks2 = [] for i in range(89, LARGE_REORG_SIZE + 89): blocks2.append(self.next_block("alt" + str(i))) self.send_blocks(blocks2, False, request_block=False) # extend alt chain to trigger re-org block = self.next_block("alt" + str(chain1_tip + 1)) self.send_blocks([block], True, timeout=960) # ... and re-org back to the first chain self.move_tip(chain1_tip) block = self.next_block(chain1_tip + 1) self.send_blocks([block], False, request_block=False) block = self.next_block(chain1_tip + 2) self.send_blocks([block], True, timeout=960)
def _zmq_test(self): num_blocks = 5 self.log.info("Generate %(n)d blocks (and %(n)d coinbase txes)" % {"n": num_blocks}) genhashes = self.nodes[0].generatetoaddress(num_blocks, ADDRESS_BCRT1_UNSPENDABLE) self.sync_all() for x in range(num_blocks): # Should receive the coinbase txid. txid = self.hashtx.receive() # Should receive the coinbase raw transaction. hex = self.rawtx.receive() tx = CTransaction() tx.deserialize(BytesIO(hex)) tx.calc_sha256() assert_equal(tx.hash, bytes_to_hex_str(txid)) # Should receive the generated block hash. hash = bytes_to_hex_str(self.hashblock.receive()) assert_equal(genhashes[x], hash) # The block should only have the coinbase txid. assert_equal([bytes_to_hex_str(txid)], self.nodes[1].getblock(hash)["tx"]) # Should receive the generated raw block. raw_block = self.rawblock.receive() block = CBlock() f = BytesIO(raw_block) block.deserialize(f) block.calc_sha256() assert_equal(genhashes[x], block.hash) if self.is_wallet_compiled(): self.log.info("Wait for tx from second node") payment_txid = self.nodes[1].sendtoaddress( self.nodes[0].getnewaddress(), 1.0) self.sync_all() # Should receive the broadcasted txid. txid = self.hashtx.receive() assert_equal(payment_txid, bytes_to_hex_str(txid)) # Should receive the broadcasted raw transaction. hex = self.rawtx.receive() assert_equal(payment_txid, bytes_to_hex_str(hash256(hex))) self.log.info("Test the getzmqnotifications RPC") assert_equal(self.nodes[0].getzmqnotifications(), [ { "type": "pubhashblock", "address": ADDRESS, "hwm": 1000 }, { "type": "pubhashtx", "address": ADDRESS, "hwm": 1000 }, { "type": "pubrawblock", "address": ADDRESS, "hwm": 1000 }, { "type": "pubrawtx", "address": ADDRESS, "hwm": 1000 }, ]) assert_equal(self.nodes[1].getzmqnotifications(), [])
def run_test(self): node0 = self.nodes[0] node1 = self.nodes[1] node0.importprivkey(mandatory_privkey) self.log.info( "generatetoaddress: Making blocks of various kinds, checking for rejection" ) # Create valid blocks to get out of IBD and get some funds (subsidy goes to permitted addr) node0.generatetoaddress(101, mandatory_address) # Generating for another address will not work assert_raises_rpc_error( -1, "CreateNewBlock: TestBlockValidity failed: bad-coinbase-txos", node0.generatetoaddress, 1, node0.getnewaddress()) # Have non-mandatory node make a template self.sync_all() tmpl = node1.getblocktemplate() # We make a block with OP_TRUE coinbase output that will fail on node0 coinbase_tx = create_coinbase(height=int(tmpl["height"])) # sequence numbers must not be max for nLockTime to have effect coinbase_tx.vin[0].nSequence = 2**32 - 2 coinbase_tx.rehash() block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) block.nTime = tmpl["curtime"] block.nBits = int(tmpl["bits"], 16) block.nNonce = 0 block.proof = CProof(bytearray.fromhex('51')) block.vtx = [coinbase_tx] block.block_height = int(tmpl["height"]) block.hashMerkleRoot = block.calc_merkle_root() self.log.info("getblocktemplate: Test block on both nodes") assert_template(node1, block, None) assert_template(node0, block, 'bad-coinbase-txos') self.log.info("getblocktemplate: Test non-subsidy block on both nodes") # Without block reward anything goes, this allows commitment outputs like segwit coinbase_tx.vout[0].nValue = CTxOutValue(0) coinbase_tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff']) coinbase_tx.rehash() block.vtx = [coinbase_tx] assert_template(node0, block, None) assert_template(node1, block, None) # # Also test that coinbases can't have fees. self.sync_all() tmpl = node1.getblocktemplate() coinbase_tx = create_coinbase(height=int(tmpl["height"])) # sequence numbers must not be max for nLockTime to have effect coinbase_tx.vin[0].nSequence = 2**32 - 2 # Add fee output. coinbase_tx.vout[0].nValue.setToAmount( coinbase_tx.vout[0].nValue.getAmount() - 1) coinbase_tx.vout.append(CTxOut(1)) coinbase_tx.rehash() block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) block.nTime = tmpl["curtime"] block.nBits = int(tmpl["bits"], 16) block.nNonce = 0 block.proof = CProof(bytearray.fromhex('51')) block.vtx = [coinbase_tx] block.block_height = int(tmpl["height"]) block.hashMerkleRoot = block.calc_merkle_root() # should not be accepted assert_template(node0, block, "bad-cb-fee") assert_template(node1, block, "bad-cb-fee")
def blockhashstr(block_data): b = CBlock() b.deserialize(BytesIO(block_data), legacy=False) b.calc_sha256() return b.hash
def run_test(self): # Block will have 10 satoshi output, node 1 will ban addr = self.nodes[0].getnewaddress() sub_block = self.nodes[0].generatetoaddress(1, addr) raw_coinbase = self.nodes[0].getrawtransaction( self.nodes[0].getblock(sub_block[0])["tx"][0], False, sub_block[0]) decoded_coinbase = self.nodes[0].decoderawtransaction(raw_coinbase) found_ten = False for vout in decoded_coinbase["vout"]: if vout["value"] == Decimal('0.00000010') and found_ten == False: found_ten = True elif vout["value"] == 0: continue else: raise Exception("Invalid output amount in coinbase") assert (found_ten) # Block will have 0 satoshis outputs only at height 1 no_sub_block = self.nodes[1].generatetoaddress(1, addr) raw_coinbase = self.nodes[1].getrawtransaction( self.nodes[1].getblock(no_sub_block[0])["tx"][0], False, no_sub_block[0]) decoded_coinbase = self.nodes[1].decoderawtransaction(raw_coinbase) for vout in decoded_coinbase["vout"]: if vout["value"] != 0: raise Exception("Invalid output amount in coinbase") tmpl = self.nodes[0].getblocktemplate({"rules": ["segwit"]}) # Template with invalid amount(50*COIN) will be invalid in both coinbase_tx = create_coinbase(height=int(tmpl["height"])) block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) block.nTime = tmpl["curtime"] block.nBits = int(tmpl["bits"], 16) block.nNonce = 0 block.block_height = int(tmpl["height"]) block.proof = CProof(bytearray.fromhex('51')) block.vtx = [coinbase_tx] assert_template(self.nodes[0], block, "bad-cb-amount") # Set to proper value, resubmit block.vtx[0].vout[0].nValue = CTxOutValue(10) block.vtx[0].sha256 = None assert_template(self.nodes[0], block, None) # No subsidy also allowed block.vtx[0].vout[0].nValue = CTxOutValue(0) block.vtx[0].sha256 = None #assert_template(self.nodes[0], block, None) # ELEMENTS: 0-value outputs not allowed # Change previous blockhash to other nodes' genesis block and reward to 1, test again block.hashPrevBlock = int( self.nodes[1].getblockhash(self.nodes[1].getblockcount()), 16) block.vtx[0].vout[0].nValue = CTxOutValue(1) block.vtx[0].sha256 = None assert_template(self.nodes[1], block, "bad-cb-amount") block.vtx[0].vout[0].nValue = CTxOutValue(0) block.vtx[0].sha256 = None
def mine_block(self, make_transactions): # mine block in round robin sense: depending on the block number, a node # is selected to create the block, others sign it and the selected node # broadcasts it mineridx = self.nodes[0].getblockcount() % self.num_nodes # assuming in sync mineridx_next = (self.nodes[0].getblockcount() + 1) % self.num_nodes miner = self.nodes[mineridx] miner_next = self.nodes[mineridx_next] blockcount = miner.getblockcount() # If dynafed is enabled, this means signblockscript has been WSH-wrapped blockchain_info = self.nodes[0].getblockchaininfo() is_dyna = blockchain_info['softforks']['dynafed']['bip9']['status'] == "active" if is_dyna: wsh_wrap = self.nodes[0].decodescript(self.witnessScript)['segwit']['hex'] assert_equal(wsh_wrap, blockchain_info['current_signblock_hex']) assert blockchain_info['current_signblock_hex'] != blockchain_info['signblock_hex'] # Make a few transactions to make non-empty blocks for compact transmission if make_transactions: for i in range(20): miner.sendtoaddress(miner_next.getnewaddress(), int(miner.getbalance()['bitcoin']/10), "", "", True) # miner makes a block block = miner.getnewblockhex() block_struct = FromHex(CBlock(), block) # make another block with the commitment field filled out dummy_block = miner.getnewblockhex(commit_data="deadbeef") dummy_struct = FromHex(CBlock(), dummy_block) assert_equal(len(dummy_struct.vtx[0].vout), len(block_struct.vtx[0].vout) + 1) # OP_RETURN deadbeef assert_equal(CScript(dummy_struct.vtx[0].vout[0].scriptPubKey).hex(), '6a04deadbeef') # All nodes get compact blocks, first node may get complete # block in 0.5 RTT even with transactions thanks to p2p connection # with non-signing node being miner for i in range(self.num_keys): if i == mineridx: continue sketch = miner.getcompactsketch(block) compact_response = self.nodes[i].consumecompactsketch(sketch) if "block_tx_req" in compact_response: block_txn = self.nodes[i].consumegetblocktxn(block, compact_response["block_tx_req"]) final_block = self.nodes[i].finalizecompactblock(sketch, block_txn, compact_response["found_transactions"]) else: assert (mineridx == 4 and i == 0) or not make_transactions # If there's only coinbase, it should succeed immediately final_block = compact_response["blockhex"] # Block should be complete, sans signatures self.nodes[i].testproposedblock(final_block) # non-signing node can not sign assert_raises_rpc_error(-25, "Could not sign the block.", self.nodes[-1].signblock, block, self.witnessScript) # collect num_keys signatures from signers, reduce to required_signers sigs during combine sigs = [] for i in range(self.num_keys): result = miner.combineblocksigs(block, sigs, self.witnessScript) sigs = sigs + self.nodes[i].signblock(block, self.witnessScript) assert_equal(result["complete"], i >= self.required_signers) # submitting should have no effect pre-threshhold if i < self.required_signers: miner.submitblock(result["hex"]) self.check_height(blockcount) result = miner.combineblocksigs(block, sigs, self.witnessScript) assert_equal(result["complete"], True) # All signing nodes must submit... we're not connected! self.nodes[0].submitblock(result["hex"]) early_proposal = self.nodes[0].getnewblockhex() # testproposedblock should reject # Submit blocks to all other signing nodes next, as well as too-early block proposal for i in range(1, self.num_keys): assert_raises_rpc_error(-25, "proposal was not based on our best chain", self.nodes[i].testproposedblock, early_proposal) self.nodes[i].submitblock(result["hex"]) # All nodes should be synced in blocks and transactions(mempool should be empty) self.sync_all(expect_disconnected=True)
def run_test(self): self.mine_chain() node = self.nodes[0] def assert_submitblock(block, result_str_1, result_str_2=None): block.solve() result_str_2 = result_str_2 or 'duplicate-invalid' assert_equal(result_str_1, node.submitblock(hexdata=b2x(block.serialize()))) assert_equal(result_str_2, node.submitblock(hexdata=b2x(block.serialize()))) self.log.info('getmininginfo') mining_info = node.getmininginfo() assert_equal(mining_info['blocks'], 600) assert_equal(mining_info['chain'], 'regtest') assert 'currentblocktx' not in mining_info assert 'currentblockweight' not in mining_info assert_equal(mining_info['difficulty']['proof-of-work'], Decimal('4.656542373906925E-10')) assert_equal(mining_info['networkhashps'], Decimal('0.003333333333333334')) assert_equal(mining_info['pooledtx'], 0) # Mine a block to leave initial block download node.generatetoaddress(1, node.get_deterministic_priv_key().address) tmpl = node.getblocktemplate({'rules': ['segwit']}) self.log.info("getblocktemplate: Test capability advertised") assert 'proposal' in tmpl['capabilities'] assert 'coinbasetxn' not in tmpl coinbase_tx = create_coinbase(height=int(tmpl["height"])) # sequence numbers must not be max for nLockTime to have effect coinbase_tx.vin[0].nSequence = 2**32 - 2 coinbase_tx.rehash() # round-trip the encoded bip34 block height commitment assert_equal(CScriptNum.decode(coinbase_tx.vin[0].scriptSig), 602) # round-trip negative and multi-byte CScriptNums to catch python regression assert_equal(CScriptNum.decode(CScriptNum.encode(CScriptNum(1500))), 1500) assert_equal(CScriptNum.decode(CScriptNum.encode(CScriptNum(-1500))), -1500) assert_equal(CScriptNum.decode(CScriptNum.encode(CScriptNum(-1))), -1) block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) block.nTime = tmpl["curtime"] block.nBits = int(tmpl["bits"], 16) block.nNonce = 0 block.vtx = [coinbase_tx] self.log.info("getblocktemplate: segwit rule must be set") assert_raises_rpc_error( -8, "getblocktemplate must be called with the segwit rule set", node.getblocktemplate) self.log.info("getblocktemplate: Test valid block") assert_template(node, block, None) self.log.info("submitblock: Test block decode failure") assert_raises_rpc_error(-22, "Block decode failed", node.submitblock, b2x(block.serialize()[:-15])) self.log.info( "getblocktemplate: Test bad input hash for coinbase transaction") bad_block = copy.deepcopy(block) bad_block.vtx[0].vin[0].prevout.hash += 1 bad_block.vtx[0].rehash() assert_template(node, bad_block, 'bad-cb-missing') self.log.info("submitblock: Test invalid coinbase transaction") assert_raises_rpc_error(-22, "Block does not start with a coinbase", node.submitblock, b2x(bad_block.serialize())) self.log.info("getblocktemplate: Test truncated final transaction") assert_raises_rpc_error( -22, "Block decode failed", node.getblocktemplate, { 'data': b2x(block.serialize()[:-1]), 'mode': 'proposal', 'rules': ['segwit'] }) self.log.info("getblocktemplate: Test duplicate transaction") bad_block = copy.deepcopy(block) bad_block.vtx.append(bad_block.vtx[0]) assert_template(node, bad_block, 'bad-txns-duplicate') assert_submitblock(bad_block, 'bad-txns-duplicate', 'bad-txns-duplicate') self.log.info("getblocktemplate: Test invalid transaction") bad_block = copy.deepcopy(block) bad_tx = copy.deepcopy(bad_block.vtx[0]) bad_tx.vin[0].prevout.hash = 255 bad_tx.rehash() bad_block.vtx.append(bad_tx) assert_template(node, bad_block, 'bad-txns-inputs-missingorspent') assert_submitblock(bad_block, 'bad-txns-inputs-missingorspent') self.log.info("getblocktemplate: Test nonfinal transaction") bad_block = copy.deepcopy(block) bad_block.vtx[0].nLockTime = 2**32 - 1 bad_block.vtx[0].rehash() assert_template(node, bad_block, 'bad-txns-nonfinal') assert_submitblock(bad_block, 'bad-txns-nonfinal') self.log.info("getblocktemplate: Test bad tx count") # The tx count is immediately after the block header TX_COUNT_OFFSET = 181 bad_block_sn = bytearray(block.serialize()) assert_equal(bad_block_sn[TX_COUNT_OFFSET], 1) bad_block_sn[TX_COUNT_OFFSET] += 1 assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, { 'data': b2x(bad_block_sn), 'mode': 'proposal', 'rules': ['segwit'] }) self.log.info("getblocktemplate: Test bad bits") bad_block = copy.deepcopy(block) bad_block.nBits = 469762303 # impossible in the real world assert_template(node, bad_block, 'bad-diffbits') self.log.info("getblocktemplate: Test bad merkle root") bad_block = copy.deepcopy(block) bad_block.hashMerkleRoot += 1 assert_template(node, bad_block, 'bad-txnmrklroot', False) assert_submitblock(bad_block, 'bad-txnmrklroot', 'bad-txnmrklroot') #self.log.info("getblocktemplate: Test bad timestamps") #bad_block = copy.deepcopy(block) #bad_block.nTime = 2 ** 31 - 1 #assert_template(node, bad_block, 'time-too-new') #assert_submitblock(bad_block, 'time-too-new', 'time-too-new') #bad_block.nTime = 0 #assert_template(node, bad_block, 'time-too-old') #assert_submitblock(bad_block, 'time-too-old', 'time-too-old') self.log.info("getblocktemplate: Test not best block") bad_block = copy.deepcopy(block) bad_block.hashPrevBlock = 123 assert_template(node, bad_block, 'inconclusive-not-best-prevblk') assert_submitblock(bad_block, 'prev-blk-not-found', 'prev-blk-not-found') self.log.info('submitheader tests') assert_raises_rpc_error( -22, 'Block header decode failed', lambda: node.submitheader(hexdata='xx' * TX_COUNT_OFFSET)) assert_raises_rpc_error( -22, 'Block header decode failed', lambda: node.submitheader(hexdata='ff' * (TX_COUNT_OFFSET - 2))) assert_raises_rpc_error( -25, 'Must submit previous header', lambda: node.submitheader(hexdata=b2x( super(CBlock, bad_block).serialize()))) block.nTime += 1 block.solve() def chain_tip(b_hash, *, status='headers-only', branchlen=1): return { 'hash': b_hash, 'height': 602, 'branchlen': branchlen, 'status': status } assert chain_tip(block.hash) not in node.getchaintips() node.submitheader(hexdata=b2x(block.serialize())) assert chain_tip(block.hash) in node.getchaintips() node.submitheader(hexdata=b2x(CBlockHeader(block).serialize())) # Noop assert chain_tip(block.hash) in node.getchaintips() bad_block_root = copy.deepcopy(block) bad_block_root.hashMerkleRoot += 2 bad_block_root.solve() assert chain_tip(bad_block_root.hash) not in node.getchaintips() node.submitheader( hexdata=b2x(CBlockHeader(bad_block_root).serialize())) assert chain_tip(bad_block_root.hash) in node.getchaintips() # Should still reject invalid blocks, even if we have the header: assert_equal(node.submitblock(hexdata=b2x(bad_block_root.serialize())), 'bad-txnmrklroot') assert_equal(node.submitblock(hexdata=b2x(bad_block_root.serialize())), 'bad-txnmrklroot') assert chain_tip(bad_block_root.hash) in node.getchaintips() # We know the header for this invalid block, so should just return early without error: node.submitheader( hexdata=b2x(CBlockHeader(bad_block_root).serialize())) assert chain_tip(bad_block_root.hash) in node.getchaintips() bad_block_lock = copy.deepcopy(block) bad_block_lock.vtx[0].nLockTime = 2**32 - 1 bad_block_lock.vtx[0].rehash() bad_block_lock.hashMerkleRoot = bad_block_lock.calc_merkle_root() bad_block_lock.solve() assert_equal(node.submitblock(hexdata=b2x(bad_block_lock.serialize())), 'bad-txns-nonfinal') assert_equal(node.submitblock(hexdata=b2x(bad_block_lock.serialize())), 'duplicate-invalid') # Build a "good" block on top of the submitted bad block bad_block2 = copy.deepcopy(block) bad_block2.hashPrevBlock = bad_block_lock.sha256 bad_block2.solve() assert_raises_rpc_error( -25, 'bad-prevblk', lambda: node.submitheader(hexdata=b2x( CBlockHeader(bad_block2).serialize()))) # Should reject invalid header right away, only applies to PoS blocks in ncc. #bad_block_time = copy.deepcopy(block) #bad_block_time.nTime = 1 #bad_block_time.solve() #assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader(hexdata=b2x(CBlockHeader(bad_block_time).serialize()))) # Should ask for the block from a p2p node, if they announce the header as well: node.add_p2p_connection(P2PDataStore()) node.p2p.wait_for_getheaders(timeout=5) # Drop the first getheaders node.p2p.send_blocks_and_test(blocks=[block], node=node) # Must be active now: assert chain_tip(block.hash, status='active', branchlen=0) in node.getchaintips() # Building a few blocks should give the same results node.generatetoaddress(10, node.get_deterministic_priv_key().address) #assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader(hexdata=b2x(CBlockHeader(bad_block_time).serialize()))) assert_raises_rpc_error( -25, 'bad-prevblk', lambda: node.submitheader(hexdata=b2x( CBlockHeader(bad_block2).serialize()))) node.submitheader(hexdata=b2x(CBlockHeader(block).serialize())) node.submitheader( hexdata=b2x(CBlockHeader(bad_block_root).serialize())) assert_equal(node.submitblock(hexdata=b2x(block.serialize())), 'duplicate') # valid
def run_test(self): node = self.nodes[0] node.add_p2p_connection(P2PDataStore()) # OP_TRUE in P2SH address = node.decodescript('51')['p2sh'] # burn script p2sh_script = CScript([OP_HASH160, bytes(20), OP_EQUAL]) prevblockhash = node.getbestblockhash() coinbase = create_coinbase(201) coinbase.vout[1].scriptPubKey = p2sh_script coinbase.rehash() sample_block = CBlock() sample_block.vtx = [coinbase] sample_block.hashPrevBlock = int(prevblockhash, 16) sample_block.nBits = 0x207fffff sample_block.nTime = 1600000036 sample_block.nReserved = 0 sample_block.nHeaderVersion = 1 sample_block.nHeight = 201 sample_block.hashEpochBlock = 0 sample_block.hashMerkleRoot = sample_block.calc_merkle_root() sample_block.hashExtendedMetadata = hash256_int(b'\0') sample_block.update_size() # Using legacy hashing algo block = copy.deepcopy(sample_block) target = uint256_from_compact(block.nBits) block.rehash() while hash256_int( block.serialize()) > target or block.sha256 <= target: block.nNonce += 1 block.rehash() self.fail_block(block, force_send=True, reject_reason='high-hash') del block # Claimed size already excessive (before doing any other checks) block = copy.deepcopy(sample_block) block.nSize = 32_000_001 block.solve() self.fail_block(block, force_send=True, reject_reason='bad-blk-size') del block # Incorrect nBits block = copy.deepcopy(sample_block) block.nBits = 0x207ffffe block.solve() self.fail_block(block, force_send=True, reject_reason='bad-diffbits') del block # Block too old block = copy.deepcopy(sample_block) block.nTime = 1600000035 block.solve() self.fail_block(block, force_send=True, reject_reason='time-too-old') del block # nReserved must be 0 block = copy.deepcopy(sample_block) block.nReserved = 0x0100 block.solve() self.fail_block(block, force_send=True, reject_reason='bad-blk-reserved') del block # nHeaderVersion must be 1 block = copy.deepcopy(sample_block) block.nHeaderVersion = 0 block.solve() self.fail_block(block, force_send=True, reject_reason='bad-blk-version') block.nHeaderVersion = 2 block.solve() self.fail_block(block, force_send=True, reject_reason='bad-blk-version') del block # Incorrect claimed height block = copy.deepcopy(sample_block) block.nHeight = 200 block.solve() self.fail_block(block, force_send=True, reject_reason='bad-blk-height') block.nHeight = 202 block.solve() self.fail_block(block, force_send=True, reject_reason='bad-blk-height') del block # Invalid epoch block block = copy.deepcopy(sample_block) block.hashEpochBlock = 1 block.solve() self.fail_block(block, force_send=True, reject_reason='bad-blk-epoch') del block # Time too far into the future block = copy.deepcopy(sample_block) block.nTime = int(time.time()) + 2 * 60 * 60 + 1 block.solve() self.fail_block(block, force_send=True, reject_reason='time-too-new') del block # Invalid merkle root block = copy.deepcopy(sample_block) block.hashMerkleRoot = 0 block.solve() self.fail_block(block, reject_reason='bad-txnmrklroot') del block # Invalid metadata hash block = copy.deepcopy(sample_block) block.hashExtendedMetadata = 0 block.solve() self.fail_block(block, reject_reason='bad-metadata-hash') del block # Non-empty metadata block = copy.deepcopy(sample_block) block.vMetadata.append(CBlockMetadataField(0, b'')) block.rehash_extended_metadata() block.solve() self.fail_block(block, reject_reason='bad-metadata') del block # Claimed nSize doesn't match actual size block = copy.deepcopy(sample_block) block.nSize = 1 block.solve() self.fail_block(block, reject_reason='blk-size-mismatch') del block block_template = node.getblocktemplate() assert_equal(block_template.pop('capabilities'), ['proposal']) assert_equal(block_template.pop('version'), 1) assert_equal(block_template.pop('previousblockhash'), prevblockhash) assert_equal( block_template.pop('epochblockhash'), '0000000000000000000000000000000000000000000000000000000000000000') assert_equal( block_template.pop('extendedmetadatahash'), '9a538906e6466ebd2617d321f71bc94e56056ce213d366773699e28158e00614') assert_equal(block_template.pop('transactions'), []) assert_equal(block_template.pop('coinbaseaux'), {}) assert_equal(block_template.pop('coinbasevalue'), int(SUBSIDY * COIN)) assert_equal(block_template.pop('coinbasetxn'), {'minerfund': { 'outputs': [] }}) block_template.pop('longpollid') assert_equal( block_template.pop('target'), '7fffff0000000000000000000000000000000000000000000000000000000000') assert_equal(block_template.pop('mintime'), 1600000036) assert_equal(block_template.pop('mutable'), ['time', 'transactions', 'prevblock']) assert_equal(block_template.pop('noncerange'), '00000000ffffffff') assert_equal(block_template.pop('sigoplimit'), 226950) assert_equal(block_template.pop('sizelimit'), 32000000) block_template.pop('curtime') assert_equal(block_template.pop('bits'), '207fffff') assert_equal(block_template.pop('height'), 201) assert_equal(block_template, {}) # Check epoch hash is 0 for the first 20 blocks for height in range(201, 221): block_template = node.getblocktemplate() assert_equal(block_template['epochblockhash'], '00' * 32) block = self.block_from_template(block_template) block.hashEpochBlock = 0 prepare_block(block) node.p2p.send_blocks_and_test([block], node) del block # Move to end of epoch node.generatetoaddress(4819, address) assert_equal(node.getblockcount(), 5039) epochblockhash = node.getbestblockhash() epochblock = node.getblock(epochblockhash) assert_equal(epochblock['epochblockhash'], '00' * 32) # getblocktemplate gives us current tip as epoch block hash block_template = node.getblocktemplate() assert_equal(block_template['epochblockhash'], epochblockhash) assert_equal(block_template['previousblockhash'], epochblockhash) # Using 0 as epoch block hash is now invalid block = self.block_from_template(block_template) block.hashEpochBlock = 0 prepare_block(block) self.fail_block(block, force_send=True, reject_reason='bad-blk-epoch') # Setting current tip as epoch hash makes the block valid block.hashEpochBlock = int(epochblockhash, 16) prepare_block(block) node.p2p.send_blocks_and_test([block], node) del block # getblocktemplate still gives us the same epoch block hash block_template = node.getblocktemplate() assert_equal(block_template['epochblockhash'], epochblockhash) assert_equal(block_template['previousblockhash'], node.getbestblockhash()) # Block after that still requires epoch block hash block = self.block_from_template(block_template) block.hashEpochBlock = int(epochblockhash, 16) prepare_block(block) node.p2p.send_blocks_and_test([block], node) del block # Test 48-bit nTime node.setmocktime( 2**32) # smallest number that does not fit in 32-bit number block_template = node.getblocktemplate() assert_equal(block_template['curtime'], 2**32) block = self.block_from_template(block_template) block.nTime = 2**32 prepare_block(block) node.p2p.send_blocks_and_test([block], node) del block node.setmocktime(2**48 - 1) # biggest possible 48-bit number block_template = node.getblocktemplate() assert_equal(block_template['curtime'], 2**48 - 1) block = self.block_from_template(block_template) block.nTime = 2**48 - 1 prepare_block(block) node.p2p.send_blocks_and_test([block], node) del block
def test_compactblock_construction(self, test_node, use_witness_address=True): version = test_node.cmpct_version node = self.nodes[0] # Generate a bunch of transactions. node.generate(101) num_transactions = 25 address = node.getnewaddress() segwit_tx_generated = False for i in range(num_transactions): txid = node.sendtoaddress(address, 0.1) hex_tx = node.gettransaction(txid)["hex"] tx = FromHex(CTransaction(), hex_tx) if not tx.wit.is_null(): segwit_tx_generated = True if use_witness_address: assert segwit_tx_generated # check that our test is not broken # Wait until we've seen the block announcement for the resulting tip tip = int(node.getbestblockhash(), 16) test_node.wait_for_block_announcement(tip) # Make sure we will receive a fast-announce compact block self.request_cb_announcements(test_node) # Now mine a block, and look at the resulting compact block. test_node.clear_block_announcement() block_hash = int(node.generate(1)[0], 16) # Store the raw block in our internal format. block = FromHex(CBlock(), node.getblock("%064x" % block_hash, False)) for tx in block.vtx: tx.calc_sha256() block.rehash() # Wait until the block was announced (via compact blocks) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock) # Now fetch and check the compact block header_and_shortids = None with mininode_lock: assert "cmpctblock" in test_node.last_message # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs( test_node.last_message["cmpctblock"].header_and_shortids) self.check_compactblock_construction_from_block( version, header_and_shortids, block_hash, block) # Now fetch the compact block using a normal non-announce getdata with mininode_lock: test_node.clear_block_announcement() inv = CInv(4, block_hash) # 4 == "CompactBlock" test_node.send_message(msg_getdata([inv])) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock) # Now fetch and check the compact block header_and_shortids = None with mininode_lock: assert "cmpctblock" in test_node.last_message # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs( test_node.last_message["cmpctblock"].header_and_shortids) self.check_compactblock_construction_from_block( version, header_and_shortids, block_hash, block)
def test_coinbase_witness(self): block = self.nodes[0].getnewblockhex() block_struct = FromHex(CBlock(), block) # Test vanilla block round-trip self.nodes[0].testproposedblock(WitToHex(block_struct)) # Assert there's scriptWitness in the coinbase input that is the witness nonce and nothing else assert_equal(block_struct.vtx[0].wit.vtxinwit[0].scriptWitness.stack, [b'\x00' * 32]) assert_equal( block_struct.vtx[0].wit.vtxinwit[0].vchIssuanceAmountRangeproof, b'') assert_equal( block_struct.vtx[0].wit.vtxinwit[0].vchInflationKeysRangeproof, b'') assert_equal(block_struct.vtx[0].wit.vtxinwit[0].peginWitness.stack, []) # Add extra witness that isn't covered by witness merkle root, make sure blocks are still valid block_witness_stuffed = copy.deepcopy(block_struct) block_witness_stuffed.vtx[0].wit.vtxinwit[ 0].vchIssuanceAmountRangeproof = b'\x00' assert_raises_rpc_error(-25, "bad-cb-witness", self.nodes[0].testproposedblock, WitToHex(block_witness_stuffed)) block_witness_stuffed = copy.deepcopy(block_struct) block_witness_stuffed.vtx[0].wit.vtxinwit[ 0].vchInflationKeysRangeproof = b'\x00' assert_raises_rpc_error(-25, "bad-cb-witness", self.nodes[0].testproposedblock, WitToHex(block_witness_stuffed)) block_witness_stuffed = copy.deepcopy(block_struct) # Let's blow out block weight limit by adding 4MW here block_witness_stuffed.vtx[0].wit.vtxinwit[0].peginWitness.stack = [ b'\x00' * 4000000 ] assert_raises_rpc_error(-25, "bad-cb-witness", self.nodes[0].testproposedblock, WitToHex(block_witness_stuffed)) # Test that node isn't blinded to the block # Previously an over-stuffed block >4MW would have been marked permanently bad # as it already passes witness merkle and regular merkle root checks block_height = self.nodes[0].getblockcount() assert_equal( self.nodes[0].submitblock(WitToHex(block_witness_stuffed)), "bad-cb-witness") assert_equal(block_height, self.nodes[0].getblockcount()) assert_equal(self.nodes[0].submitblock(WitToHex(block_struct)), None) assert_equal(block_height + 1, self.nodes[0].getblockcount()) # New block since we used the first one block_struct = FromHex(CBlock(), self.nodes[0].getnewblockhex()) block_witness_stuffed = copy.deepcopy(block_struct) # Add extra witness data that is covered by witness merkle root, make sure invalid assert_equal( block_witness_stuffed.vtx[0].wit.vtxoutwit[0].vchSurjectionproof, b'') assert_equal( block_witness_stuffed.vtx[0].wit.vtxoutwit[0].vchRangeproof, b'') block_witness_stuffed.vtx[0].wit.vtxoutwit[ 0].vchRangeproof = b'\x00' * 100000 block_witness_stuffed.vtx[0].wit.vtxoutwit[ 0].vchSurjectionproof = b'\x00' * 100000 assert_raises_rpc_error(-25, "bad-witness-merkle-match", self.nodes[0].testproposedblock, WitToHex(block_witness_stuffed)) witness_root_hex = block_witness_stuffed.calc_witness_merkle_root() witness_root = uint256_from_str( hex_str_to_bytes(witness_root_hex)[::-1]) block_witness_stuffed.vtx[0].vout[-1] = CTxOut( 0, get_witness_script(witness_root, 0)) block_witness_stuffed.vtx[0].rehash() block_witness_stuffed.hashMerkleRoot = block_witness_stuffed.calc_merkle_root( ) block_witness_stuffed.rehash() assert_raises_rpc_error(-25, "bad-cb-amount", self.nodes[0].testproposedblock, WitToHex(block_witness_stuffed)) assert_greater_than( len(WitToHex(block_witness_stuffed)), 100000 * 4) # Make sure the witness data is actually serialized # A CTxIn that always serializes the asset issuance, even for coinbases. class AlwaysIssuanceCTxIn(CTxIn): def serialize(self): r = b'' outpoint = COutPoint() outpoint.hash = self.prevout.hash outpoint.n = self.prevout.n outpoint.n |= OUTPOINT_ISSUANCE_FLAG r += outpoint.serialize() r += ser_string(self.scriptSig) r += struct.pack("<I", self.nSequence) r += self.assetIssuance.serialize() return r # Test that issuance inputs in coinbase don't survive a serialization round-trip # (even though this can't cause issuance to occur either way due to VerifyCoinbaseAmount semantics) block_witness_stuffed = copy.deepcopy(block_struct) coinbase_orig = copy.deepcopy(block_witness_stuffed.vtx[0].vin[0]) coinbase_ser_size = len( block_witness_stuffed.vtx[0].vin[0].serialize()) block_witness_stuffed.vtx[0].vin[0] = AlwaysIssuanceCTxIn() block_witness_stuffed.vtx[0].vin[0].prevout = coinbase_orig.prevout block_witness_stuffed.vtx[0].vin[0].scriptSig = coinbase_orig.scriptSig block_witness_stuffed.vtx[0].vin[0].nSequence = coinbase_orig.nSequence block_witness_stuffed.vtx[0].vin[0].assetIssuance.nAmount.setToAmount( 1) bad_coinbase_ser_size = len( block_witness_stuffed.vtx[0].vin[0].serialize()) # 32+32+9+1 should be serialized for each assetIssuance field assert_equal(bad_coinbase_ser_size, coinbase_ser_size + 32 + 32 + 9 + 1) assert (not block_witness_stuffed.vtx[0].vin[0].assetIssuance.isNull()) assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, ToHex(block_witness_stuffed.vtx[0]))
def run_test(self): self.mine_chain() node = self.nodes[0] def assert_submitblock(block, result_str_1, result_str_2=None): block.solve() result_str_2 = result_str_2 or 'duplicate-invalid' assert_equal(result_str_1, node.submitblock(hexdata=block.serialize().hex())) assert_equal(result_str_2, node.submitblock(hexdata=block.serialize().hex())) self.log.info('getmininginfo') mining_info = node.getmininginfo() assert_equal(mining_info['blocks'], 200) assert_equal(mining_info['chain'], 'regtest') assert 'currentblocktx' not in mining_info assert 'currentblockweight' not in mining_info assert_equal(mining_info['difficulty'], Decimal('4.656542373906925E-10')) assert_equal(mining_info['networkhashps'], Decimal('0.003333333333333334')) assert_equal(mining_info['pooledtx'], 0) # Mine a block to leave initial block download node.generatetoaddress(1, node.get_deterministic_priv_key().address) tmpl = node.getblocktemplate({'rules': ['segwit']}) self.log.info("getblocktemplate: Test capability advertised") assert 'proposal' in tmpl['capabilities'] assert 'coinbasetxn' not in tmpl next_height = int(tmpl["height"]) coinbase_tx = create_coinbase(height=next_height) # sequence numbers must not be max for nLockTime to have effect coinbase_tx.vin[0].nSequence = 2 ** 32 - 2 coinbase_tx.rehash() # round-trip the encoded bip34 block height commitment assert_equal(CScriptNum.decode(coinbase_tx.vin[0].scriptSig), next_height) # round-trip negative and multi-byte CScriptNums to catch python regression assert_equal(CScriptNum.decode(CScriptNum.encode(CScriptNum(1500))), 1500) assert_equal(CScriptNum.decode(CScriptNum.encode(CScriptNum(-1500))), -1500) assert_equal(CScriptNum.decode(CScriptNum.encode(CScriptNum(-1))), -1) block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) block.nTime = tmpl["curtime"] block.nBits = int(tmpl["bits"], 16) block.nNonce = 0 block.vtx = [coinbase_tx] self.log.info("getblocktemplate: segwit rule must be set") assert_raises_rpc_error(-8, "getblocktemplate must be called with the segwit rule set", node.getblocktemplate) self.log.info("getblocktemplate: Test valid block") assert_template(node, block, None) self.log.info("submitblock: Test block decode failure") assert_raises_rpc_error(-22, "Block decode failed", node.submitblock, block.serialize()[:-15].hex()) self.log.info("getblocktemplate: Test bad input hash for coinbase transaction") bad_block = copy.deepcopy(block) bad_block.vtx[0].vin[0].prevout.hash += 1 bad_block.vtx[0].rehash() assert_template(node, bad_block, 'bad-cb-missing') self.log.info("submitblock: Test invalid coinbase transaction") assert_raises_rpc_error(-22, "Block does not start with a coinbase", node.submitblock, bad_block.serialize().hex()) self.log.info("getblocktemplate: Test truncated final transaction") assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {'data': block.serialize()[:-1].hex(), 'mode': 'proposal', 'rules': ['segwit']}) self.log.info("getblocktemplate: Test duplicate transaction") bad_block = copy.deepcopy(block) bad_block.vtx.append(bad_block.vtx[0]) assert_template(node, bad_block, 'bad-txns-duplicate') assert_submitblock(bad_block, 'bad-txns-duplicate', 'bad-txns-duplicate') self.log.info("getblocktemplate: Test invalid transaction") bad_block = copy.deepcopy(block) bad_tx = copy.deepcopy(bad_block.vtx[0]) bad_tx.vin[0].prevout.hash = 255 bad_tx.rehash() bad_block.vtx.append(bad_tx) assert_template(node, bad_block, 'bad-txns-inputs-missingorspent') assert_submitblock(bad_block, 'bad-txns-inputs-missingorspent') self.log.info("getblocktemplate: Test nonfinal transaction") bad_block = copy.deepcopy(block) bad_block.vtx[0].nLockTime = 2 ** 32 - 1 bad_block.vtx[0].rehash() assert_template(node, bad_block, 'bad-txns-nonfinal') assert_submitblock(bad_block, 'bad-txns-nonfinal') self.log.info("getblocktemplate: Test bad tx count") # The tx count is immediately after the block header bad_block_sn = bytearray(block.serialize()) assert_equal(bad_block_sn[BLOCK_HEADER_SIZE], 1) bad_block_sn[BLOCK_HEADER_SIZE] += 1 assert_raises_rpc_error(-22, "Block decode failed", node.getblocktemplate, {'data': bad_block_sn.hex(), 'mode': 'proposal', 'rules': ['segwit']}) self.log.info("getblocktemplate: Test bad bits") bad_block = copy.deepcopy(block) bad_block.nBits = 469762303 # impossible in the real world assert_template(node, bad_block, 'bad-diffbits') self.log.info("getblocktemplate: Test bad merkle root") bad_block = copy.deepcopy(block) bad_block.hashMerkleRoot += 1 assert_template(node, bad_block, 'bad-txnmrklroot', False) assert_submitblock(bad_block, 'bad-txnmrklroot', 'bad-txnmrklroot') self.log.info("getblocktemplate: Test bad timestamps") bad_block = copy.deepcopy(block) bad_block.nTime = 2 ** 31 - 1 assert_template(node, bad_block, 'time-too-new') assert_submitblock(bad_block, 'time-too-new', 'time-too-new') bad_block.nTime = 0 assert_template(node, bad_block, 'time-too-old') assert_submitblock(bad_block, 'time-too-old', 'time-too-old') self.log.info("getblocktemplate: Test not best block") bad_block = copy.deepcopy(block) bad_block.hashPrevBlock = 123 assert_template(node, bad_block, 'inconclusive-not-best-prevblk') assert_submitblock(bad_block, 'prev-blk-not-found', 'prev-blk-not-found') self.log.info('submitheader tests') assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='xx' * BLOCK_HEADER_SIZE)) assert_raises_rpc_error(-22, 'Block header decode failed', lambda: node.submitheader(hexdata='ff' * (BLOCK_HEADER_SIZE-2))) assert_raises_rpc_error(-25, 'Must submit previous header', lambda: node.submitheader(hexdata=super(CBlock, bad_block).serialize().hex())) block.nTime += 1 block.solve() def chain_tip(b_hash, *, status='headers-only', branchlen=1): return {'hash': b_hash, 'height': 202, 'branchlen': branchlen, 'status': status} assert chain_tip(block.hash) not in node.getchaintips() node.submitheader(hexdata=block.serialize().hex()) assert chain_tip(block.hash) in node.getchaintips() node.submitheader(hexdata=CBlockHeader(block).serialize().hex()) # Noop assert chain_tip(block.hash) in node.getchaintips() bad_block_root = copy.deepcopy(block) bad_block_root.hashMerkleRoot += 2 bad_block_root.solve() assert chain_tip(bad_block_root.hash) not in node.getchaintips() node.submitheader(hexdata=CBlockHeader(bad_block_root).serialize().hex()) assert chain_tip(bad_block_root.hash) in node.getchaintips() # Should still reject invalid blocks, even if we have the header: assert_equal(node.submitblock(hexdata=bad_block_root.serialize().hex()), 'bad-txnmrklroot') assert_equal(node.submitblock(hexdata=bad_block_root.serialize().hex()), 'bad-txnmrklroot') assert chain_tip(bad_block_root.hash) in node.getchaintips() # We know the header for this invalid block, so should just return early without error: node.submitheader(hexdata=CBlockHeader(bad_block_root).serialize().hex()) assert chain_tip(bad_block_root.hash) in node.getchaintips() bad_block_lock = copy.deepcopy(block) bad_block_lock.vtx[0].nLockTime = 2**32 - 1 bad_block_lock.vtx[0].rehash() bad_block_lock.hashMerkleRoot = bad_block_lock.calc_merkle_root() bad_block_lock.solve() assert_equal(node.submitblock(hexdata=bad_block_lock.serialize().hex()), 'bad-txns-nonfinal') assert_equal(node.submitblock(hexdata=bad_block_lock.serialize().hex()), 'duplicate-invalid') # Build a "good" block on top of the submitted bad block bad_block2 = copy.deepcopy(block) bad_block2.hashPrevBlock = bad_block_lock.sha256 bad_block2.solve() assert_raises_rpc_error(-25, 'bad-prevblk', lambda: node.submitheader(hexdata=CBlockHeader(bad_block2).serialize().hex())) # Should reject invalid header right away bad_block_time = copy.deepcopy(block) bad_block_time.nTime = 1 bad_block_time.solve() assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader(hexdata=CBlockHeader(bad_block_time).serialize().hex())) # Should ask for the block from a p2p node, if they announce the header as well: node.add_p2p_connection(P2PDataStore()) node.p2p.wait_for_getheaders(timeout=5) # Drop the first getheaders node.p2p.send_blocks_and_test(blocks=[block], node=node) # Must be active now: assert chain_tip(block.hash, status='active', branchlen=0) in node.getchaintips() # Building a few blocks should give the same results node.generatetoaddress(10, node.get_deterministic_priv_key().address) assert_raises_rpc_error(-25, 'time-too-old', lambda: node.submitheader(hexdata=CBlockHeader(bad_block_time).serialize().hex())) assert_raises_rpc_error(-25, 'bad-prevblk', lambda: node.submitheader(hexdata=CBlockHeader(bad_block2).serialize().hex())) node.submitheader(hexdata=CBlockHeader(block).serialize().hex()) node.submitheader(hexdata=CBlockHeader(bad_block_root).serialize().hex()) assert_equal(node.submitblock(hexdata=block.serialize().hex()), 'duplicate') # valid
def run_test(self): # Block will have 10 satoshi output, node 1 will ban addr = self.nodes[0].getnewaddress() sub_block = self.nodes[0].generatetoaddress(1, addr) raw_coinbase = self.nodes[0].getrawtransaction(self.nodes[0].getblock(sub_block[0])["tx"][0]) decoded_coinbase = self.nodes[0].decoderawtransaction(raw_coinbase) found_ten = False for vout in decoded_coinbase["vout"]: if vout["value"] == Decimal('0.00000010') and found_ten == False: found_ten = True elif vout["value"] == 0: continue else: raise Exception("Invalid output amount in coinbase") assert(found_ten) # Block will have 0 satoshis outputs only at height 1 no_sub_block = self.nodes[1].generatetoaddress(1, addr) raw_coinbase = self.nodes[1].getrawtransaction(self.nodes[1].getblock(no_sub_block[0])["tx"][0]) decoded_coinbase = self.nodes[1].decoderawtransaction(raw_coinbase) for vout in decoded_coinbase["vout"]: if vout["value"] != 0: raise Exception("Invalid output amount in coinbase") tmpl = self.nodes[0].getblocktemplate() # Template with invalid amount(50*COIN) will be invalid in both coinbase_tx = create_coinbase(height=int(tmpl["height"])) block = CBlock() block.nVersion = tmpl["version"] block.hashPrevBlock = int(tmpl["previousblockhash"], 16) block.nTime = tmpl["curtime"] block.nBits = int(tmpl["bits"], 16) block.nNonce = 0 block.block_height = int(tmpl["height"]) block.proof = CProof(bytearray.fromhex('51')) block.vtx = [coinbase_tx] assert_template(self.nodes[0], block, "bad-cb-amount") # Set to proper value, resubmit block.vtx[0].vout[0].nValue = CTxOutValue(10) block.vtx[0].sha256 = None assert_template(self.nodes[0], block, None) # No subsidy also allowed block.vtx[0].vout[0].nValue = CTxOutValue(0) block.vtx[0].sha256 = None #assert_template(self.nodes[0], block, None) # ELEMENTS: 0-value outputs not allowed # Change previous blockhash to other nodes' genesis block and reward to 1, test again block.hashPrevBlock = int(self.nodes[1].getblockhash(self.nodes[1].getblockcount()), 16) block.vtx[0].vout[0].nValue = CTxOutValue(1) block.vtx[0].sha256 = None assert_template(self.nodes[1], block, "bad-cb-amount") block.vtx[0].vout[0].nValue = CTxOutValue(0) block.vtx[0].sha256 = None