def test_compactblock_construction(self, node, test_node, version, use_witness_address): # Generate a bunch of transactions. node.generate(101) num_transactions = 25 address = node.getnewaddress() if use_witness_address: # Want at least one segwit spend, so move all funds to # a witness address. address = node.addwitnessaddress(address) value_to_send = node.getbalance() node.sendtoaddress(address, satoshi_round(value_to_send - Decimal(0.1))) node.generate(1) segwit_tx_generated = False for _ in range(num_transactions): txid = node.sendtoaddress(address, 0.1) hex_tx = node.gettransaction(txid)["hex"] tx = from_hex(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, node, version) # 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 = from_hex(CBlock(), node.getblock("%02x" % block_hash, False)) for tx in block.vtx: tx.calc_x16r() block.rehash() # Wait until the block was announced (via compact blocks) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock, err_msg="test_node.received_block_announcement") # Now fetch and check the compact block 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(MsgGetdata([inv])) wait_until(test_node.received_block_announcement, timeout=30, lock=mininode_lock, err_msg="test_node.received_block_announcement") # Now fetch and check the compact block 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] 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 make_block_withP2SH_coinbase(self): tmpl = self.nodes[0].getblocktemplate() coinbase_tx = create_coinbase_P2SH( int(tmpl["height"]) + 1, example_script_hash) 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] block.hashMerkleRoot = block.calc_merkle_root() block.solve() block.rehash() return block
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): 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.159690237829E-9')) # assert_equal(mining_info['networkhashps'], Decimal('0.1841805555555555')) 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 newaddress = node.getnewaddress() pubkey = node.validateaddress(newaddress)["pubkey"] next_height = int(tmpl["height"]) coinbase_key = CECKey() coinbase_key.set_secretbytes(b"horsebattery") coinbase_pubkey = coinbase_key.get_pubkey() coinbase_tx = create_coinbase( height=next_height, pubkey=coinbase_pubkey) #bytes(pubkey,encoding='utf-8')) # 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, 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_using_template(self): tmpl = self.nodes[0].getblocktemplate() assert 'proposal' in tmpl['capabilities'] assert 'coinbasetxn' not in tmpl coinbase_tx = create_coinbase(height=int(tmpl["height"])) block = CBlock() block.nVersion = int(tmpl["version"]) block.hashPrevBlock = int(tmpl["previousblockhash"], 16) block.nTime = tmpl["curtime"] block.nBits = int(tmpl["bits"], 16) block.nNonce = 0 # extended block block.nStakeDifficulty = int(tmpl["stakedifficulty"], 16) block.nVoteBits = tmpl["votebits"] block.nTicketPoolSize = tmpl["ticketpoolsize"] block.ticketLotteryState = tmpl["ticketlotterystate"] block.nVoters = tmpl["voters"] block.nFreshStake = tmpl["freshstake"] block.nRevocations = tmpl["revocations"] block.extraData = tmpl["extradata"] block.nStakeVersion = tmpl["stakeversion"] block.vtx = [coinbase_tx] for tx in tmpl["transactions"]: ctx = CTransaction() ctx.deserialize(BytesIO(hex_str_to_bytes(tx['data']))) ctx.rehash() block.vtx.append(ctx) block.hashMerkleRoot = block.calc_merkle_root() add_witness_commitment(block) block.solve() print('solved hash', block.hash) # print("submit for height", idx) submit_result = self.nodes[0].submitblock(ToHex(block)) print(submit_result) assert(submit_result in [None,"inconclusive"])
def run_test(self): # Turn on a webhook server self.start_webhook_server() # Create a P2P connection node = self.nodes[0] peer = NodeConnCB() connection = NodeConn('127.0.0.1', p2p_port(0), node, peer) peer.add_connection(connection) NetworkThread().start() peer.wait_for_verack() # Create an initial block with a coinbase we will split into multiple utxos initialBlock, _ = make_block(connection) coinbaseTx = initialBlock.vtx[0] send_by_headers(connection, [initialBlock], do_send_blocks=True) wait_for_tip(connection, initialBlock.hash) node.generate(101) block101hex = node.getblock(node.getbestblockhash(), False) block101dict = node.getblock(node.getbestblockhash(), 2) block101 = FromHex(CBlock(), block101hex) block101.height = block101dict['height'] block101.rehash() # Create a block with a transaction spending coinbaseTx of a previous block and making multiple outputs for future transactions to spend utxoBlock, _ = make_block(connection, parent_block=block101) utxoTx = create_tx(coinbaseTx, 0, 1 * COIN) # Create additional 48 outputs (we let 1 COIN as fee) for _ in range(48): utxoTx.vout.append(CTxOut(1 * COIN, CScript([OP_TRUE]))) # Add to block utxoTx.rehash() utxoBlock.vtx.append(utxoTx) utxoBlock.hashMerkleRoot = utxoBlock.calc_merkle_root() utxoBlock.solve() send_by_headers(connection, [utxoBlock], do_send_blocks=True) wait_for_tip(connection, utxoBlock.hash) # Make sure serialization/deserialization works as expected # Create dsdetected message. The content is not important here. dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(utxoBlock), CBlockHeader(initialBlock)], DSMerkleProof(1, utxoTx, utxoBlock.hashMerkleRoot, [MerkleProofNode(utxoBlock.vtx[0].sha256)])) ]) dsdBytes = dsdMessage.serialize() dsdMessageDeserialized = msg_dsdetected() dsdMessageDeserialized.deserialize(BytesIO(dsdBytes)) assert_equal(str(dsdMessage), str(dsdMessageDeserialized)) # Send a message containing random bytes. Webhook should not receive the notification. peer.send_and_ping(fake_msg_dsdetected()) assert_equal(self.get_JSON_notification(), None) # Create two blocks with transactions spending the same utxo blockA, _ = make_block(connection, parent_block=utxoBlock) blockB, _ = make_block(connection, parent_block=utxoBlock) blockF, _ = make_block(connection, parent_block=utxoBlock) txA = create_tx(utxoBlock.vtx[1], 0, int(0.8 * COIN)) txB = create_tx(utxoBlock.vtx[1], 0, int(0.9 * COIN)) txF = create_tx(utxoBlock.vtx[1], 0, int(0.7 * COIN)) txA.rehash() txB.rehash() txF.rehash() blockA.vtx.append(txA) blockB.vtx.append(txB) blockF.vtx.append(txF) blockA.hashMerkleRoot = blockA.calc_merkle_root() blockB.hashMerkleRoot = blockB.calc_merkle_root() blockF.hashMerkleRoot = blockF.calc_merkle_root() blockA.calc_sha256() blockB.calc_sha256() blockF.calc_sha256() blockA.solve() blockB.solve() blockF.solve() start_banscore = node.getpeerinfo()[0]['banscore'] # Webhook should not receive the notification if we send dsdetected message with only one block detail. dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(blockA)], DSMerkleProof(1, txA, blockA.hashMerkleRoot, [MerkleProofNode(blockA.vtx[0].sha256)])) ]) peer.send_and_ping(dsdMessage) assert_equal(self.get_JSON_notification(), None) # Webhook should not receive the notification if we send dsdetected message with two block details and one is containing no headers. dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(blockA)], DSMerkleProof(1, txA, blockA.hashMerkleRoot, [MerkleProofNode(blockA.vtx[0].sha256)])), BlockDetails( [], DSMerkleProof(1, txB, blockB.hashMerkleRoot, [MerkleProofNode(blockB.vtx[0].sha256)])) ]) peer.send_and_ping(dsdMessage) assert_equal(self.get_JSON_notification(), None) # Webhook should not receive the notification if we send dsdetected message where last headers in block details do not have a common previous block hash. dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(blockA)], DSMerkleProof(1, txA, blockA.hashMerkleRoot, [MerkleProofNode(blockA.vtx[0].sha256)])), BlockDetails( [CBlockHeader(utxoBlock)], DSMerkleProof(1, txB, blockB.hashMerkleRoot, [MerkleProofNode(blockB.vtx[0].sha256)])) ]) peer.send_and_ping(dsdMessage) assert_equal(self.get_JSON_notification(), None) # Webhook should not receive the notification if we send dsdetected message where block details does not have headers in proper order. dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(blockA)], DSMerkleProof(1, txA, blockA.hashMerkleRoot, [MerkleProofNode(blockA.vtx[0].sha256)])), BlockDetails( [CBlockHeader(utxoBlock), CBlockHeader(blockB)], DSMerkleProof(1, txB, blockB.hashMerkleRoot, [MerkleProofNode(blockB.vtx[0].sha256)])) ]) peer.send_and_ping(dsdMessage) assert_equal(self.get_JSON_notification(), None) # Webhook should not receive the notification if we send dsdetected message with the empty merkle proof. dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(blockA)], DSMerkleProof(1, txA, blockA.hashMerkleRoot, [MerkleProofNode(blockA.vtx[0].sha256)])), BlockDetails([CBlockHeader(blockB)], DSMerkleProof()) ]) peer.send_and_ping(dsdMessage) assert_equal(self.get_JSON_notification(), None) # Webhook should not receive the notification if we send dsdetected message with the wrong index in the merkle proof (merkle root validation should fail) dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(blockA)], DSMerkleProof(1, txA, blockA.hashMerkleRoot, [MerkleProofNode(blockA.vtx[0].sha256)])), BlockDetails( [CBlockHeader(blockB)], DSMerkleProof(0, txB, blockB.hashMerkleRoot, [MerkleProofNode(blockB.vtx[0].sha256)])) ]) peer.send_and_ping(dsdMessage) assert_equal(self.get_JSON_notification(), None) # Webhook should not receive the notification if we send dsdetected message with the wrong transaction in the merkle proof (merkle root validation should fail) dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(blockA)], DSMerkleProof(1, txA, blockA.hashMerkleRoot, [MerkleProofNode(blockA.vtx[0].sha256)])), BlockDetails( [CBlockHeader(blockB)], DSMerkleProof(1, txA, blockB.hashMerkleRoot, [MerkleProofNode(blockB.vtx[0].sha256)])) ]) peer.send_and_ping(dsdMessage) assert_equal(self.get_JSON_notification(), None) # Webhook should not receive the notification if we send dsdetected message with the wrong merkle root (merkle root validation should fail) dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(blockA)], DSMerkleProof(1, txA, blockA.hashMerkleRoot, [MerkleProofNode(blockA.vtx[0].sha256)])), BlockDetails( [CBlockHeader(blockB)], DSMerkleProof(1, txB, blockA.hashMerkleRoot, [MerkleProofNode(blockB.vtx[0].sha256)])) ]) peer.send_and_ping(dsdMessage) assert_equal(self.get_JSON_notification(), None) # Webhook should not receive the notification if we send dsdetected message with the wrong merkle proof (merkle root validation should fail) dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(blockA)], DSMerkleProof(1, txA, blockA.hashMerkleRoot, [MerkleProofNode(blockA.vtx[0].sha256)])), BlockDetails( [CBlockHeader(blockB)], DSMerkleProof(1, txB, blockB.hashMerkleRoot, [MerkleProofNode(blockA.hashMerkleRoot)])) ]) peer.send_and_ping(dsdMessage) assert_equal(self.get_JSON_notification(), None) # Webhook should not receive the notification if we send dsdetected message with the merkle proof having an additional unexpected node (merkle root validation should fail) dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(blockA)], DSMerkleProof(1, txA, blockA.hashMerkleRoot, [MerkleProofNode(blockA.vtx[0].sha256)])), BlockDetails([CBlockHeader(blockB)], DSMerkleProof(1, txB, blockB.hashMerkleRoot, [ MerkleProofNode(blockB.vtx[0].sha256), MerkleProofNode(blockA.hashMerkleRoot) ])) ]) peer.send_and_ping(dsdMessage) assert_equal(self.get_JSON_notification(), None) # Webhook should not receive the notification if we send dsdetected message with the valid proof, but transaction is a coinbase transaction dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(blockA)], DSMerkleProof(1, txA, blockA.hashMerkleRoot, [MerkleProofNode(blockA.vtx[0].sha256)])), BlockDetails( [CBlockHeader(blockB)], DSMerkleProof(0, blockB.vtx[0], blockB.hashMerkleRoot, [MerkleProofNode(blockB.vtx[1].sha256)])) ]) peer.send_and_ping(dsdMessage) assert_equal(self.get_JSON_notification(), None) # Webhook should not receive the notification if we send dsdetected message with transactions that are not double spending # Create a block similar as before, but with a transaction spending a different utxo blockC, _ = make_block(connection, parent_block=utxoBlock) txC = create_tx(utxoBlock.vtx[1], 1, int(0.7 * COIN)) blockC.vtx.append(txC) blockC.hashMerkleRoot = blockC.calc_merkle_root() blockC.solve() dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(blockA)], DSMerkleProof(1, txA, blockA.hashMerkleRoot, [MerkleProofNode(blockA.vtx[0].sha256)])), BlockDetails( [CBlockHeader(blockC)], DSMerkleProof(1, txC, blockC.hashMerkleRoot, [MerkleProofNode(blockC.vtx[0].sha256)])) ]) peer.send_and_ping(dsdMessage) assert_equal(self.get_JSON_notification(), None) # Webhook should not receive the notification if the two double spending transactions are actually the same transaction (having same txid) # Create a block similar as before, but with a transaction spending a different utxo blockD, _ = make_block(connection, parent_block=utxoBlock) blockD.vtx.append(txA) blockD.hashMerkleRoot = blockD.calc_merkle_root() blockD.solve() dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(blockA)], DSMerkleProof(1, txA, blockA.hashMerkleRoot, [MerkleProofNode(blockA.vtx[0].sha256)])), BlockDetails( [CBlockHeader(blockD)], DSMerkleProof(1, txA, blockD.hashMerkleRoot, [MerkleProofNode(blockD.vtx[0].sha256)])) ]) peer.send_and_ping(dsdMessage) assert_equal(self.get_JSON_notification(), None) # Webhook should not receive the notification if header cannot pow # note hat pow is so easy in regtest that nonce can often be hence we have to select the nonce carefully blockE, _ = make_block(connection, parent_block=utxoBlock) blockE.vtx.append(txB) blockE.hashMerkleRoot = blockE.calc_merkle_root() nonce = blockE.nNonce while True: blockE.solve() if blockE.nNonce > nonce: blockE.nNonce = nonce break nonce += 1 blockE.nNonce = nonce dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(blockA)], DSMerkleProof(1, txA, blockA.hashMerkleRoot, [MerkleProofNode(blockA.vtx[0].sha256)])), BlockDetails( [CBlockHeader(blockE)], DSMerkleProof(1, txB, blockE.hashMerkleRoot, [MerkleProofNode(blockE.vtx[0].sha256)])) ]) peer.send_and_ping(dsdMessage) assert_equal(self.get_JSON_notification(), None) end_banscore = node.getpeerinfo()[0]['banscore'] assert ((end_banscore - start_banscore) / 10 == 13 ) # because we have 13 negative tests so far # Finally, webhook should receive the notification if we send a proper dsdetected message dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(blockA)], DSMerkleProof(1, txA, blockA.hashMerkleRoot, [MerkleProofNode(blockA.vtx[0].sha256)])), BlockDetails( [CBlockHeader(blockB)], DSMerkleProof(1, txB, blockB.hashMerkleRoot, [MerkleProofNode(blockB.vtx[0].sha256)])) ]) peer.send_and_ping(dsdMessage) json_notification = self.get_JSON_notification() # remove diverentBlockHash so we can compare with the ds-message assert (json_notification != None) for e in json_notification['blocks']: del e['divergentBlockHash'] assert_equal(str(dsdMessage), str(msg_dsdetected(json_notification=json_notification))) # Repeat previous test but change the order of the BlockDetails, the node should identify this as a duplicate dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(blockB)], DSMerkleProof(1, txB, blockB.hashMerkleRoot, [MerkleProofNode(blockB.vtx[0].sha256)])), BlockDetails( [CBlockHeader(blockA)], DSMerkleProof(1, txA, blockA.hashMerkleRoot, [MerkleProofNode(blockA.vtx[0].sha256)])) ]) peer.send_and_ping(dsdMessage) assert_equal(self.get_JSON_notification(), None) # repeat previous test but generate many blocks in the node to age the notificatoin message. # very old notification messages shall be ignored. We use the same thresholds as safe mode. # We will hardcode this threshold for now until branch we depend on is merged node.generate(289) dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails( [CBlockHeader(blockA)], DSMerkleProof(1, txA, blockA.hashMerkleRoot, [MerkleProofNode(blockA.vtx[0].sha256)])), BlockDetails( [CBlockHeader(blockF)], DSMerkleProof(1, txF, blockF.hashMerkleRoot, [MerkleProofNode(blockF.vtx[0].sha256)])) ]) peer.send_and_ping(dsdMessage) assert_equal(self.get_JSON_notification(), None) # Create number of random valid block trees and send dsdetected P2P message for each maxNumberOfBranches = 10 maxNumberOfBlocksPerBranch = 30 for _ in range(10): blockTree = self.createRandomBlockTree(maxNumberOfBranches, maxNumberOfBlocksPerBranch, utxoBlock, [utxoBlock.vtx[1]]) dsdMessage = self.createDsDetectedMessageFromBlockTree(blockTree) peer.send_and_ping(dsdMessage) # Notification should be received as generated dsdetected message is valid json_notification = self.get_JSON_notification() # remove diverentBlockHash so we can compare with the ds-message assert (json_notification != None) for e in json_notification['blocks']: del e['divergentBlockHash'] assert_equal( str(dsdMessage), str(msg_dsdetected(json_notification=json_notification))) self.stop_webhook_server()
def run_test(self): node = self.nodes[0] # Mine a block to leave initial block download node.generate(1) prevblk = node.getblock(node.getbestblockhash()) tmpl = node.getblocktemplate() self.log.info("getblocktemplate: Test capability advertised") assert 'transactions' in tmpl assert 'coinbaseaux' in tmpl assert 'coinbasetxn' not in tmpl assert 'mutable' in tmpl assert isinstance(tmpl['version'], int) assert isinstance(tmpl['curtime'], int) assert isinstance(tmpl['coinbasevalue'], int) assert_is_hex_string(tmpl['bits']) assert_is_hash_string(tmpl['target']) assert_is_hash_string(tmpl['previousblockhash']) assert_equal(tmpl['sizelimit'], 32000000) assert_equal(tmpl['sigoplimit'], 640000) assert_equal(tmpl['mintime'], prevblk['mediantime'] + 1) assert_equal(tmpl['height'], prevblk['height'] + 1) assert_equal(tmpl['noncerange'], "00000000ffffffff") 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] exponent = int(tmpl["bits"][:2], 16) coefficient = int(tmpl["bits"][2:], 16) target = coefficient * 256**(exponent - 3) self.log.info(f"{chr(10)*3}getblocktemplate: Test valid block") assert_template(node, block, None) self.log.info(f"{chr(10)*3}submitblock: Test block decode failure") bhashint = doPow(block, target) self.log.info(f"{'-' * 10} bhash: {bhashint:0<54x} {'-' * 10}") assert_raises_rpc_error(-22, "Block decode failed", node.submitblock, b2x(block.serialize()[:-15])) self.log.info( f"{chr(10)*3}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( f"{chr(10)*3}submitblock: Test invalid coinbase transaction") bad_block = copy.deepcopy(block) bad_block.vtx[0].vin[0].prevout.hash += 1 bad_block.vtx[0].rehash() bad_block.hashMerkleRoot = bad_block.calc_merkle_root() bhashint = doPow(bad_block, target) self.log.info(f"{'-' * 10} target: {target:0<54x} {'-' * 10}") self.log.info(f"{'-' * 10} bhash: {bhashint:0<54x} {'-' * 10}") assert_raises_rpc_error(-22, "Block does not start with a coinbase", node.submitblock, b2x(bad_block.serialize())) self.log.info( f"{chr(10)*3}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( f"{chr(10)*3}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(f"{chr(10)*3}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( f"{chr(10)*3}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(f"{chr(10)*3}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(f"{chr(10)*3}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(f"{chr(10)*3}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(f"{chr(10)*3}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(f"{chr(10)*3}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 = 0x620004 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.calc_sha256() block.solve() # Submit to the node node.submitblock(block.serialize().hex()) previousblockhash = block.sha256 height += 1 mine_large_blocks.nTime += 1
def CheckForDoubleSpends(self, nodes): spent_inputs = [] seen_transactions = [] ds_counter = 0 for node in nodes: for height in range(node.getblockcount() + 1): blockhash = node.getblockhash(height) block = node.getblock(blockhash, 2) blockHex = node.getblock(blockhash, False) for txraw in block['tx'][1:]: # exclude coinbase # skip the identical transactions in the two chains, they are no double spends if txraw['txid'] in seen_transactions: continue else: seen_transactions.append(txraw['txid']) for i in txraw['vin']: utxoA = (i['txid'], i['vout']) blockA = FromHex(CBlock(), blockHex) txA = FromHex(CTransaction(), txraw['hex']) foundB = [ j for j in spent_inputs if j['utxo'] == utxoA ] if foundB: ds_counter += 1 foundB = foundB[0] blockB = foundB['block'] txB = foundB['tx'] txA.rehash() txB.rehash() blockA.vtx[0].rehash() blockB.vtx[0].rehash() sha256_A = blockA.vtx[0].sha256 sha256_B = blockB.vtx[0].sha256 dsdMessage = msg_dsdetected(blocksDetails=[ BlockDetails([CBlockHeader(blockA)], DSMerkleProof( 1, txA, blockA.hashMerkleRoot, [MerkleProofNode(sha256_A)])), BlockDetails([CBlockHeader(blockB)], DSMerkleProof( 1, txB, blockB.hashMerkleRoot, [MerkleProofNode(sha256_B)])) ]) self.message = dsdMessage dsdBytes = dsdMessage.serialize() dsdMessageDeserialized = msg_dsdetected() dsdMessageDeserialized.deserialize( BytesIO(dsdBytes)) assert_equal(str(dsdMessage), str(dsdMessageDeserialized)) break else: spent_inputs.append({ 'txid': txraw['txid'], 'tx': txA, 'utxo': utxoA, 'block': blockA }) return ds_counter
def run_test(self): node = self.nodes[0] # Mine a block to leave initial block download node.generate(1) prevblk = node.getblock(node.getbestblockhash()) tmpl = node.getblocktemplate() self.log.info("getblocktemplate: Test capability advertised") assert 'rules' in tmpl assert 'vbavailable' in tmpl assert 'transactions' in tmpl assert 'coinbaseaux' in tmpl assert 'coinbasetxn' not in tmpl assert 'mutable' in tmpl assert isinstance(tmpl['version'], int) assert isinstance(tmpl['curtime'], int) assert isinstance(tmpl['vbrequired'], int) assert isinstance(tmpl['coinbasevalue'], int) assert_is_hex_string(tmpl['bits']) assert_is_hash_string(tmpl['target']) assert_is_hash_string(tmpl['previousblockhash']) assert_equal(tmpl['sizelimit'], 32000000) assert_equal(tmpl['sigoplimit'], 640000) assert_equal(tmpl['mintime'], prevblk['mediantime'] + 1) assert_equal(tmpl['height'], prevblk['height'] + 1) assert_equal(tmpl['noncerange'], "00000000ffffffff") 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])) block.hashMerkleRoot = block.calc_merkle_root() node.submitblock(b2x(block.serialize()[:]))