def test_getmnlistdiff_quorums(self, baseBlockHash, blockHash, baseQuorumList, expectedDeleted, expectedNew): d = self.test_getmnlistdiff_base(baseBlockHash, blockHash) assert_equal(set(d.deletedQuorums), set(expectedDeleted)) assert_equal( set([QuorumId(e.llmqType, e.quorumHash) for e in d.newQuorums]), set(expectedNew)) newQuorumList = baseQuorumList.copy() for e in d.deletedQuorums: newQuorumList.pop(e) for e in d.newQuorums: newQuorumList[QuorumId(e.llmqType, e.quorumHash)] = e hashes = [] for qc in newQuorumList.values(): hashes.append(hash256(qc.serialize())) hashes.sort() merkleRoot = CBlock.get_merkle_root(hashes) assert_equal(merkleRoot, d.merkleRootQuorums) return newQuorumList
def test_getmnlistdiff(self, baseBlockHash, blockHash, baseMNList, expectedDeleted, expectedUpdated): d = self.test_getmnlistdiff_base(baseBlockHash, blockHash) # Assert that the deletedMNs and mnList fields are what we expected assert_equal(set(d.deletedMNs), set([int(e, 16) for e in expectedDeleted])) assert_equal(set([e.proRegTxHash for e in d.mnList]), set(int(e, 16) for e in expectedUpdated)) # Build a new list based on the old list and the info from the diff newMNList = baseMNList.copy() for e in d.deletedMNs: newMNList.pop(format(e, '064x')) for e in d.mnList: newMNList[format(e.proRegTxHash, '064x')] = e # Verify that the merkle root matches what we locally calculate hashes = [] for mn in sorted(newMNList.values(), key=lambda mn: ser_uint256(mn.proRegTxHash)): hashes.append(hash256(mn.serialize())) merkleRoot = CBlock.get_merkle_root(hashes) assert_equal(merkleRoot, d.merkleRootMNList) return newMNList
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
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