Esempio n. 1
0
    def test_filter(self, filter_peer):
        # Set the bloomfilter using filterload
        filter_peer.send_and_ping(filter_peer.watch_filter_init)
        # If fRelay is not already True, sending filterload sets it to True
        assert self.nodes[0].getpeerinfo()[0]['relaytxes']

        self.log.info('Check that we receive merkleblock and tx if the filter matches a tx in a block')
        block_hash = self.generatetoscriptpubkey(filter_peer.watch_script_pubkey)
        txid = self.nodes[0].getblock(block_hash)['tx'][0]
        filter_peer.wait_for_merkleblock(block_hash)
        filter_peer.wait_for_tx(txid)

        self.log.info('Check that we only receive a merkleblock if the filter does not match a tx in a block')
        filter_peer.tx_received = False
        block_hash = self.generatetoscriptpubkey(getnewdestination()[1])
        filter_peer.wait_for_merkleblock(block_hash)
        assert not filter_peer.tx_received

        self.log.info('Check that we not receive a tx if the filter does not match a mempool tx')
        filter_peer.merkleblock_received = False
        filter_peer.tx_received = False
        self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=getnewdestination()[1], amount=7 * COIN)
        filter_peer.sync_send_with_ping()
        assert not filter_peer.merkleblock_received
        assert not filter_peer.tx_received

        self.log.info('Check that we receive a tx if the filter matches a mempool tx')
        filter_peer.merkleblock_received = False
        txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=filter_peer.watch_script_pubkey, amount=9 * COIN)
        filter_peer.wait_for_tx(txid)
        assert not filter_peer.merkleblock_received

        self.log.info('Check that after deleting filter all txs get relayed again')
        filter_peer.send_and_ping(msg_filterclear())
        for _ in range(5):
            txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=getnewdestination()[1], amount=7 * COIN)
            filter_peer.wait_for_tx(txid)

        self.log.info('Check that request for filtered blocks is ignored if no filter is set')
        filter_peer.merkleblock_received = False
        filter_peer.tx_received = False
        with self.nodes[0].assert_debug_log(expected_msgs=['received getdata']):
            block_hash = self.generatetoscriptpubkey(getnewdestination()[1])
            filter_peer.wait_for_inv([CInv(MSG_BLOCK, int(block_hash, 16))])
            filter_peer.sync_with_ping()
            assert not filter_peer.merkleblock_received
            assert not filter_peer.tx_received

        self.log.info('Check that sending "filteradd" if no filter is set is treated as misbehavior')
        with self.nodes[0].assert_debug_log(['Misbehaving']):
            filter_peer.send_and_ping(msg_filteradd(data=b'letsmisbehave'))

        self.log.info("Check that division-by-zero remote crash bug [CVE-2013-5700] is fixed")
        filter_peer.send_and_ping(msg_filterload(data=b'', nHashFuncs=1))
        filter_peer.send_and_ping(msg_filteradd(data=b'letstrytocrashthisnode'))
        self.nodes[0].disconnect_p2ps()
 def sendrawtransaction_tests(self):
     self.log.info("Test sendrawtransaction with missing input")
     inputs = [{'txid': TXID, 'vout': 1}]  # won't exist
     address = getnewdestination()[2]
     outputs = {address: 4.998}
     rawtx = self.nodes[2].createrawtransaction(inputs, outputs)
     assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent", self.nodes[2].sendrawtransaction, rawtx)
Esempio n. 3
0
 def get_keys(self):
     self.pub = []
     self.priv = []
     node0, node1, node2 = self.nodes
     for _ in range(self.nkeys):
         k = ECKey()
         k.generate()
         self.pub.append(k.get_pubkey().get_bytes().hex())
         self.priv.append(bytes_to_wif(k.get_bytes(), k.is_compressed))
     if self.is_bdb_compiled():
         self.final = node2.getnewaddress()
     else:
         self.final = getnewdestination('bech32')[2]
Esempio n. 4
0
    def _test_reorg_index(self):
        self.log.info("Test that index can handle reorgs")

        # Generate two block, let the index catch up, then invalidate the blocks
        index_node = self.nodes[1]
        reorg_blocks = self.generatetoaddress(index_node, 2,
                                              getnewdestination()[2])
        reorg_block = reorg_blocks[1]
        res_invalid = index_node.gettxoutsetinfo('muhash')
        index_node.invalidateblock(reorg_blocks[0])
        assert_equal(index_node.gettxoutsetinfo('muhash')['height'], 110)

        # Add two new blocks
        block = self.generate(index_node, 2, sync_fun=self.no_op)[1]
        res = index_node.gettxoutsetinfo(hash_type='muhash',
                                         hash_or_height=None,
                                         use_index=False)

        # Test that the result of the reorged block is not returned for its old block height
        res2 = index_node.gettxoutsetinfo(hash_type='muhash',
                                          hash_or_height=112)
        assert_equal(res["bestblock"], block)
        assert_equal(res["muhash"], res2["muhash"])
        assert (res["muhash"] != res_invalid["muhash"])

        # Test that requesting reorged out block by hash is still returning correct results
        res_invalid2 = index_node.gettxoutsetinfo(hash_type='muhash',
                                                  hash_or_height=reorg_block)
        assert_equal(res_invalid2["muhash"], res_invalid["muhash"])
        assert (res["muhash"] != res_invalid2["muhash"])

        # Add another block, so we don't depend on reconsiderblock remembering which
        # blocks were touched by invalidateblock
        self.generate(index_node, 1)

        # Ensure that removing and re-adding blocks yields consistent results
        block = index_node.getblockhash(99)
        index_node.invalidateblock(block)
        index_node.reconsiderblock(block)
        res3 = index_node.gettxoutsetinfo(hash_type='muhash',
                                          hash_or_height=112)
        assert_equal(res2, res3)
 def verify_txn_with_witness_script(self, tx_type):
     self.log.info(
         "Test with a {} script as the witnessScript".format(tx_type))
     eckey = ECKey()
     eckey.generate()
     embedded_privkey = bytes_to_wif(eckey.get_bytes())
     embedded_pubkey = eckey.get_pubkey().get_bytes().hex()
     witness_script = {
         'P2PKH': key_to_p2pkh_script(embedded_pubkey).hex(),
         'P2PK': key_to_p2pk_script(embedded_pubkey).hex()
     }.get(tx_type, "Invalid tx_type")
     redeem_script = script_to_p2wsh_script(witness_script).hex()
     addr = script_to_p2sh(redeem_script)
     script_pub_key = self.nodes[1].validateaddress(addr)['scriptPubKey']
     # Fund that address
     txid = self.send_to_address(addr, 10)
     vout = find_vout_for_address(self.nodes[0], txid, addr)
     self.generate(self.nodes[0], 1)
     # Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys
     spending_tx = self.nodes[0].createrawtransaction(
         [{
             'txid': txid,
             'vout': vout
         }], {getnewdestination()[2]: Decimal("9.999")})
     spending_tx_signed = self.nodes[0].signrawtransactionwithkey(
         spending_tx, [embedded_privkey], [{
             'txid': txid,
             'vout': vout,
             'scriptPubKey': script_pub_key,
             'redeemScript': redeem_script,
             'witnessScript': witness_script,
             'amount': 10
         }])
     # Check the signing completed successfully
     assert 'complete' in spending_tx_signed
     assert_equal(spending_tx_signed['complete'], True)
     self.nodes[0].sendrawtransaction(spending_tx_signed['hex'])
    def witness_script_test(self):
        self.log.info(
            "Test signing transaction to P2SH-P2WSH addresses without wallet")
        # Create a new P2SH-P2WSH 1-of-1 multisig address:
        eckey = ECKey()
        eckey.generate()
        embedded_privkey = bytes_to_wif(eckey.get_bytes())
        embedded_pubkey = eckey.get_pubkey().get_bytes().hex()
        p2sh_p2wsh_address = self.nodes[1].createmultisig(
            1, [embedded_pubkey], "p2sh-segwit")
        # send transaction to P2SH-P2WSH 1-of-1 multisig address
        self.block_hash = self.generate(self.nodes[0], COINBASE_MATURITY + 1)
        self.blk_idx = 0
        self.send_to_address(p2sh_p2wsh_address["address"], 49.999)
        self.generate(self.nodes[0], 1)
        # Get the UTXO info from scantxoutset
        unspent_output = self.nodes[1].scantxoutset(
            'start', [p2sh_p2wsh_address['descriptor']])['unspents'][0]
        spk = script_to_p2sh_p2wsh_script(
            p2sh_p2wsh_address['redeemScript']).hex()
        unspent_output['witnessScript'] = p2sh_p2wsh_address['redeemScript']
        unspent_output['redeemScript'] = script_to_p2wsh_script(
            unspent_output['witnessScript']).hex()
        assert_equal(spk, unspent_output['scriptPubKey'])
        # Now create and sign a transaction spending that output on node[0], which doesn't know the scripts or keys
        spending_tx = self.nodes[0].createrawtransaction(
            [unspent_output], {getnewdestination()[2]: Decimal("49.998")})
        spending_tx_signed = self.nodes[0].signrawtransactionwithkey(
            spending_tx, [embedded_privkey], [unspent_output])
        # Check the signing completed successfully
        assert 'complete' in spending_tx_signed
        assert_equal(spending_tx_signed['complete'], True)

        # Now test with P2PKH and P2PK scripts as the witnessScript
        for tx_type in ['P2PKH', 'P2PK']:  # these tests are order-independent
            self.verify_txn_with_witness_script(tx_type)
Esempio n. 7
0
    def run_test(self):
        self.url = urllib.parse.urlparse(self.nodes[0].url)
        self.wallet = MiniWallet(self.nodes[0])
        self.wallet.rescan_utxos()

        self.log.info("Broadcast test transaction and sync nodes")
        txid, _ = self.wallet.send_to(from_node=self.nodes[0],
                                      scriptPubKey=getnewdestination()[1],
                                      amount=int(0.1 * COIN))
        self.sync_all()

        self.log.info("Test the /tx URI")

        json_obj = self.test_rest_request(f"/tx/{txid}")
        assert_equal(json_obj['txid'], txid)

        # Check hex format response
        hex_response = self.test_rest_request(f"/tx/{txid}",
                                              req_type=ReqType.HEX,
                                              ret_type=RetType.OBJ)
        assert_greater_than_or_equal(
            int(hex_response.getheader('content-length')),
            json_obj['size'] * 2)

        spent = (
            json_obj['vin'][0]['txid'], json_obj['vin'][0]['vout']
        )  # get the vin to later check for utxo (should be spent by then)
        # get n of 0.1 outpoint
        n, = filter_output_indices_by_value(json_obj['vout'], Decimal('0.1'))
        spending = (txid, n)

        # Test /tx with an invalid and an unknown txid
        resp = self.test_rest_request(uri=f"/tx/{INVALID_PARAM}",
                                      ret_type=RetType.OBJ,
                                      status=400)
        assert_equal(resp.read().decode('utf-8').rstrip(),
                     f"Invalid hash: {INVALID_PARAM}")
        resp = self.test_rest_request(uri=f"/tx/{UNKNOWN_PARAM}",
                                      ret_type=RetType.OBJ,
                                      status=404)
        assert_equal(resp.read().decode('utf-8').rstrip(),
                     f"{UNKNOWN_PARAM} not found")

        self.log.info("Query an unspent TXO using the /getutxos URI")

        self.generate(self.wallet, 1)
        bb_hash = self.nodes[0].getbestblockhash()

        # Check chainTip response
        json_obj = self.test_rest_request(
            f"/getutxos/{spending[0]}-{spending[1]}")
        assert_equal(json_obj['chaintipHash'], bb_hash)

        # Make sure there is one utxo
        assert_equal(len(json_obj['utxos']), 1)
        assert_equal(json_obj['utxos'][0]['value'], Decimal('0.1'))

        self.log.info("Query a spent TXO using the /getutxos URI")

        json_obj = self.test_rest_request(f"/getutxos/{spent[0]}-{spent[1]}")

        # Check chainTip response
        assert_equal(json_obj['chaintipHash'], bb_hash)

        # Make sure there is no utxo in the response because this outpoint has been spent
        assert_equal(len(json_obj['utxos']), 0)

        # Check bitmap
        assert_equal(json_obj['bitmap'], "0")

        self.log.info("Query two TXOs using the /getutxos URI")

        json_obj = self.test_rest_request(
            f"/getutxos/{spending[0]}-{spending[1]}/{spent[0]}-{spent[1]}")

        assert_equal(len(json_obj['utxos']), 1)
        assert_equal(json_obj['bitmap'], "10")

        self.log.info(
            "Query the TXOs using the /getutxos URI with a binary response")

        bin_request = b'\x01\x02'
        for txid, n in [spending, spent]:
            bin_request += bytes.fromhex(txid)
            bin_request += pack("i", n)

        bin_response = self.test_rest_request("/getutxos",
                                              http_method='POST',
                                              req_type=ReqType.BIN,
                                              body=bin_request,
                                              ret_type=RetType.BYTES)
        output = BytesIO(bin_response)
        chain_height, = unpack("<i", output.read(4))
        response_hash = output.read(32)[::-1].hex()

        assert_equal(
            bb_hash, response_hash
        )  # check if getutxo's chaintip during calculation was fine
        assert_equal(
            chain_height, 201
        )  # chain height must be 201 (pre-mined chain [200] + generated block [1])

        self.log.info("Test the /getutxos URI with and without /checkmempool")
        # Create a transaction, check that it's found with /checkmempool, but
        # not found without. Then confirm the transaction and check that it's
        # found with or without /checkmempool.

        # do a tx and don't sync
        txid, _ = self.wallet.send_to(from_node=self.nodes[0],
                                      scriptPubKey=getnewdestination()[1],
                                      amount=int(0.1 * COIN))
        json_obj = self.test_rest_request(f"/tx/{txid}")
        # get the spent output to later check for utxo (should be spent by then)
        spent = (json_obj['vin'][0]['txid'], json_obj['vin'][0]['vout'])
        # get n of 0.1 outpoint
        n, = filter_output_indices_by_value(json_obj['vout'], Decimal('0.1'))
        spending = (txid, n)

        json_obj = self.test_rest_request(
            f"/getutxos/{spending[0]}-{spending[1]}")
        assert_equal(len(json_obj['utxos']), 0)

        json_obj = self.test_rest_request(
            f"/getutxos/checkmempool/{spending[0]}-{spending[1]}")
        assert_equal(len(json_obj['utxos']), 1)

        json_obj = self.test_rest_request(f"/getutxos/{spent[0]}-{spent[1]}")
        assert_equal(len(json_obj['utxos']), 1)

        json_obj = self.test_rest_request(
            f"/getutxos/checkmempool/{spent[0]}-{spent[1]}")
        assert_equal(len(json_obj['utxos']), 0)

        self.generate(self.nodes[0], 1)

        json_obj = self.test_rest_request(
            f"/getutxos/{spending[0]}-{spending[1]}")
        assert_equal(len(json_obj['utxos']), 1)

        json_obj = self.test_rest_request(
            f"/getutxos/checkmempool/{spending[0]}-{spending[1]}")
        assert_equal(len(json_obj['utxos']), 1)

        # Do some invalid requests
        self.test_rest_request("/getutxos",
                               http_method='POST',
                               req_type=ReqType.JSON,
                               body='{"checkmempool',
                               status=400,
                               ret_type=RetType.OBJ)
        self.test_rest_request("/getutxos",
                               http_method='POST',
                               req_type=ReqType.BIN,
                               body='{"checkmempool',
                               status=400,
                               ret_type=RetType.OBJ)
        self.test_rest_request("/getutxos/checkmempool",
                               http_method='POST',
                               req_type=ReqType.JSON,
                               status=400,
                               ret_type=RetType.OBJ)

        # Test limits
        long_uri = '/'.join([f"{txid}-{n_}" for n_ in range(20)])
        self.test_rest_request(f"/getutxos/checkmempool/{long_uri}",
                               http_method='POST',
                               status=400,
                               ret_type=RetType.OBJ)

        long_uri = '/'.join([f'{txid}-{n_}' for n_ in range(15)])
        self.test_rest_request(f"/getutxos/checkmempool/{long_uri}",
                               http_method='POST',
                               status=200)

        self.generate(self.nodes[0],
                      1)  # generate block to not affect upcoming tests

        self.log.info("Test the /block, /blockhashbyheight and /headers URIs")
        bb_hash = self.nodes[0].getbestblockhash()

        # Check result if block does not exists
        assert_equal(self.test_rest_request(f"/headers/1/{UNKNOWN_PARAM}"), [])
        self.test_rest_request(f"/block/{UNKNOWN_PARAM}",
                               status=404,
                               ret_type=RetType.OBJ)

        # Check result if block is not in the active chain
        self.nodes[0].invalidateblock(bb_hash)
        assert_equal(self.test_rest_request(f'/headers/1/{bb_hash}'), [])
        self.test_rest_request(f'/block/{bb_hash}')
        self.nodes[0].reconsiderblock(bb_hash)

        # Check binary format
        response = self.test_rest_request(f"/block/{bb_hash}",
                                          req_type=ReqType.BIN,
                                          ret_type=RetType.OBJ)
        assert_greater_than(int(response.getheader('content-length')),
                            BLOCK_HEADER_SIZE)
        response_bytes = response.read()

        # Compare with block header
        response_header = self.test_rest_request(f"/headers/1/{bb_hash}",
                                                 req_type=ReqType.BIN,
                                                 ret_type=RetType.OBJ)
        assert_equal(int(response_header.getheader('content-length')),
                     BLOCK_HEADER_SIZE)
        response_header_bytes = response_header.read()
        assert_equal(response_bytes[:BLOCK_HEADER_SIZE], response_header_bytes)

        # Check block hex format
        response_hex = self.test_rest_request(f"/block/{bb_hash}",
                                              req_type=ReqType.HEX,
                                              ret_type=RetType.OBJ)
        assert_greater_than(int(response_hex.getheader('content-length')),
                            BLOCK_HEADER_SIZE * 2)
        response_hex_bytes = response_hex.read().strip(b'\n')
        assert_equal(response_bytes.hex().encode(), response_hex_bytes)

        # Compare with hex block header
        response_header_hex = self.test_rest_request(f"/headers/1/{bb_hash}",
                                                     req_type=ReqType.HEX,
                                                     ret_type=RetType.OBJ)
        assert_greater_than(
            int(response_header_hex.getheader('content-length')),
            BLOCK_HEADER_SIZE * 2)
        response_header_hex_bytes = response_header_hex.read(
            BLOCK_HEADER_SIZE * 2)
        assert_equal(response_bytes[:BLOCK_HEADER_SIZE].hex().encode(),
                     response_header_hex_bytes)

        # Check json format
        block_json_obj = self.test_rest_request(f"/block/{bb_hash}")
        assert_equal(block_json_obj['hash'], bb_hash)
        assert_equal(
            self.test_rest_request(
                f"/blockhashbyheight/{block_json_obj['height']}")['blockhash'],
            bb_hash)

        # Check hex/bin format
        resp_hex = self.test_rest_request(
            f"/blockhashbyheight/{block_json_obj['height']}",
            req_type=ReqType.HEX,
            ret_type=RetType.OBJ)
        assert_equal(resp_hex.read().decode('utf-8').rstrip(), bb_hash)
        resp_bytes = self.test_rest_request(
            f"/blockhashbyheight/{block_json_obj['height']}",
            req_type=ReqType.BIN,
            ret_type=RetType.BYTES)
        blockhash = resp_bytes[::-1].hex()
        assert_equal(blockhash, bb_hash)

        # Check invalid blockhashbyheight requests
        resp = self.test_rest_request(f"/blockhashbyheight/{INVALID_PARAM}",
                                      ret_type=RetType.OBJ,
                                      status=400)
        assert_equal(resp.read().decode('utf-8').rstrip(),
                     f"Invalid height: {INVALID_PARAM}")
        resp = self.test_rest_request("/blockhashbyheight/1000000",
                                      ret_type=RetType.OBJ,
                                      status=404)
        assert_equal(resp.read().decode('utf-8').rstrip(),
                     "Block height out of range")
        resp = self.test_rest_request("/blockhashbyheight/-1",
                                      ret_type=RetType.OBJ,
                                      status=400)
        assert_equal(resp.read().decode('utf-8').rstrip(),
                     "Invalid height: -1")
        self.test_rest_request("/blockhashbyheight/",
                               ret_type=RetType.OBJ,
                               status=400)

        # Compare with json block header
        json_obj = self.test_rest_request(f"/headers/1/{bb_hash}")
        assert_equal(len(json_obj),
                     1)  # ensure that there is one header in the json response
        assert_equal(json_obj[0]['hash'],
                     bb_hash)  # request/response hash should be the same

        # Compare with normal RPC block response
        rpc_block_json = self.nodes[0].getblock(bb_hash)
        for key in [
                'hash', 'confirmations', 'height', 'version', 'merkleroot',
                'time', 'nonce', 'bits', 'difficulty', 'chainwork',
                'previousblockhash'
        ]:
            assert_equal(json_obj[0][key], rpc_block_json[key])

        # See if we can get 5 headers in one response
        self.generate(self.nodes[1], 5)
        json_obj = self.test_rest_request(f"/headers/5/{bb_hash}")
        assert_equal(len(json_obj), 5)  # now we should have 5 header objects
        json_obj = self.test_rest_request(
            f"/blockfilterheaders/basic/5/{bb_hash}")
        first_filter_header = json_obj[0]
        assert_equal(len(json_obj),
                     5)  # now we should have 5 filter header objects
        json_obj = self.test_rest_request(f"/blockfilter/basic/{bb_hash}")

        # Compare with normal RPC blockfilter response
        rpc_blockfilter = self.nodes[0].getblockfilter(bb_hash)
        assert_equal(first_filter_header, rpc_blockfilter['header'])
        assert_equal(json_obj['filter'], rpc_blockfilter['filter'])

        # Test number parsing
        for num in [
                '5a', '-5', '0', '2001', '99999999999999999999999999999999999'
        ]:
            assert_equal(
                bytes(
                    f'Header count is invalid or out of acceptable range (1-2000): {num}\r\n',
                    'ascii'),
                self.test_rest_request(f"/headers/{num}/{bb_hash}",
                                       ret_type=RetType.BYTES,
                                       status=400),
            )

        self.log.info("Test tx inclusion in the /mempool and /block URIs")

        # Make 3 chained txs and mine them on node 1
        txs = []
        input_txid = txid
        for _ in range(3):
            utxo_to_spend = self.wallet.get_utxo(txid=input_txid)
            txs.append(
                self.wallet.send_self_transfer(
                    from_node=self.nodes[0],
                    utxo_to_spend=utxo_to_spend)['txid'])
            input_txid = txs[-1]
        self.sync_all()

        # Check that there are exactly 3 transactions in the TX memory pool before generating the block
        json_obj = self.test_rest_request("/mempool/info")
        assert_equal(json_obj['size'], 3)
        # the size of the memory pool should be greater than 3x ~100 bytes
        assert_greater_than(json_obj['bytes'], 300)

        # Check that there are our submitted transactions in the TX memory pool
        json_obj = self.test_rest_request("/mempool/contents")
        for i, tx in enumerate(txs):
            assert tx in json_obj
            assert_equal(json_obj[tx]['spentby'], txs[i + 1:i + 2])
            assert_equal(json_obj[tx]['depends'], txs[i - 1:i])

        # Now mine the transactions
        newblockhash = self.generate(self.nodes[1], 1)

        # Check if the 3 tx show up in the new block
        json_obj = self.test_rest_request(f"/block/{newblockhash[0]}")
        non_coinbase_txs = {
            tx['txid']
            for tx in json_obj['tx'] if 'coinbase' not in tx['vin'][0]
        }
        assert_equal(non_coinbase_txs, set(txs))

        # Verify that the non-coinbase tx has "prevout" key set
        for tx_obj in json_obj["tx"]:
            for vin in tx_obj["vin"]:
                if "coinbase" not in vin:
                    assert "prevout" in vin
                    assert_equal(vin["prevout"]["generated"], False)
                else:
                    assert "prevout" not in vin

        # Check the same but without tx details
        json_obj = self.test_rest_request(
            f"/block/notxdetails/{newblockhash[0]}")
        for tx in txs:
            assert tx in json_obj['tx']

        self.log.info("Test the /chaininfo URI")

        bb_hash = self.nodes[0].getbestblockhash()

        json_obj = self.test_rest_request("/chaininfo")
        assert_equal(json_obj['bestblockhash'], bb_hash)
Esempio n. 8
0
    def run_test(self):
        self.wallet = MiniWallet(self.nodes[3])
        self.wallet.rescan_utxos()
        initial_height = self.nodes[3].getblockcount()
        self.generate(self.nodes[3], COINBASE_MATURITY, sync_fun=self.no_op)

        # Track test coverage statistics
        self.restart_counts = [0, 0, 0]  # Track the restarts for nodes 0-2
        self.crashed_on_restart = 0      # Track count of crashes during recovery

        # Start by creating a lot of utxos on node3
        utxo_list = self.wallet.send_self_transfer_multi(from_node=self.nodes[3], num_outputs=5000)['new_utxos']
        self.generate(self.nodes[3], 1, sync_fun=self.no_op)
        assert_equal(len(self.nodes[3].getrawmempool()), 0)
        self.log.info(f"Prepped {len(utxo_list)} utxo entries")

        # Sync these blocks with the other nodes
        block_hashes_to_sync = []
        for height in range(initial_height + 1, self.nodes[3].getblockcount() + 1):
            block_hashes_to_sync.append(self.nodes[3].getblockhash(height))

        self.log.debug(f"Syncing {len(block_hashes_to_sync)} blocks with other nodes")
        # Syncing the blocks could cause nodes to crash, so the test begins here.
        self.sync_node3blocks(block_hashes_to_sync)

        starting_tip_height = self.nodes[3].getblockcount()

        # Main test loop:
        # each time through the loop, generate a bunch of transactions,
        # and then either mine a single new block on the tip, or some-sized reorg.
        for i in range(40):
            self.log.info(f"Iteration {i}, generating 2500 transactions {self.restart_counts}")
            # Generate a bunch of small-ish transactions
            self.generate_small_transactions(self.nodes[3], 2500, utxo_list)
            # Pick a random block between current tip, and starting tip
            current_height = self.nodes[3].getblockcount()
            random_height = random.randint(starting_tip_height, current_height)
            self.log.debug(f"At height {current_height}, considering height {random_height}")
            if random_height > starting_tip_height:
                # Randomly reorg from this point with some probability (1/4 for
                # tip, 1/5 for tip-1, ...)
                if random.random() < 1.0 / (current_height + 4 - random_height):
                    self.log.debug(f"Invalidating block at height {random_height}")
                    self.nodes[3].invalidateblock(self.nodes[3].getblockhash(random_height))

            # Now generate new blocks until we pass the old tip height
            self.log.debug("Mining longer tip")
            block_hashes = []
            while current_height + 1 > self.nodes[3].getblockcount():
                block_hashes.extend(self.generatetoaddress(
                    self.nodes[3],
                    nblocks=min(10, current_height + 1 - self.nodes[3].getblockcount()),
                    # new address to avoid mining a block that has just been invalidated
                    address=getnewdestination()[2],
                    sync_fun=self.no_op,
                ))
            self.log.debug(f"Syncing {len(block_hashes)} new blocks...")
            self.sync_node3blocks(block_hashes)
            self.wallet.rescan_utxos()
            utxo_list = self.wallet.get_utxos()
            self.log.debug(f"MiniWallet utxo count: {len(utxo_list)}")

        # Check that the utxo hashes agree with node3
        # Useful side effect: each utxo cache gets flushed here, so that we
        # won't get crashes on shutdown at the end of the test.
        self.verify_utxo_hash()

        # Check the test coverage
        self.log.info(f"Restarted nodes: {self.restart_counts}; crashes on restart: {self.crashed_on_restart}")

        # If no nodes were restarted, we didn't test anything.
        assert self.restart_counts != [0, 0, 0]

        # Make sure we tested the case of crash-during-recovery.
        assert self.crashed_on_restart > 0

        # Warn if any of the nodes escaped restart.
        for i in range(3):
            if self.restart_counts[i] == 0:
                self.log.warning(f"Node {i} never crashed during utxo flush!")
    def createrawtransaction_tests(self):
        self.log.info("Test createrawtransaction")
        # Test `createrawtransaction` required parameters
        assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction)
        assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, [])

        # Test `createrawtransaction` invalid extra parameters
        assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, [], {}, 0, False, 'foo')

        # Test `createrawtransaction` invalid `inputs`
        assert_raises_rpc_error(-3, "Expected type array", self.nodes[0].createrawtransaction, 'foo', {})
        assert_raises_rpc_error(-1, "JSON value of type string is not of expected type object", self.nodes[0].createrawtransaction, ['foo'], {})
        assert_raises_rpc_error(-1, "JSON value of type null is not of expected type string", self.nodes[0].createrawtransaction, [{}], {})
        assert_raises_rpc_error(-8, "txid must be of length 64 (not 3, for 'foo')", self.nodes[0].createrawtransaction, [{'txid': 'foo'}], {})
        txid = "ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844"
        assert_raises_rpc_error(-8, f"txid must be hexadecimal string (not '{txid}')", self.nodes[0].createrawtransaction, [{'txid': txid}], {})
        assert_raises_rpc_error(-8, "Invalid parameter, missing vout key", self.nodes[0].createrawtransaction, [{'txid': TXID}], {})
        assert_raises_rpc_error(-8, "Invalid parameter, missing vout key", self.nodes[0].createrawtransaction, [{'txid': TXID, 'vout': 'foo'}], {})
        assert_raises_rpc_error(-8, "Invalid parameter, vout cannot be negative", self.nodes[0].createrawtransaction, [{'txid': TXID, 'vout': -1}], {})
        # sequence number out of range
        for invalid_seq in [-1, 4294967296]:
            inputs = [{'txid': TXID, 'vout': 1, 'sequence': invalid_seq}]
            address = getnewdestination()[2]
            outputs = {address: 1}
            assert_raises_rpc_error(-8, 'Invalid parameter, sequence number is out of range',
                                    self.nodes[0].createrawtransaction, inputs, outputs)
        # with valid sequence number
        for valid_seq in [1000, 4294967294]:
            inputs = [{'txid': TXID, 'vout': 1, 'sequence': valid_seq}]
            address = getnewdestination()[2]
            outputs = {address: 1}
            rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
            decrawtx = self.nodes[0].decoderawtransaction(rawtx)
            assert_equal(decrawtx['vin'][0]['sequence'], valid_seq)

        # Test `createrawtransaction` invalid `outputs`
        address = getnewdestination()[2]
        assert_raises_rpc_error(-1, "JSON value of type string is not of expected type array", self.nodes[0].createrawtransaction, [], 'foo')
        self.nodes[0].createrawtransaction(inputs=[], outputs={})  # Should not throw for backwards compatibility
        self.nodes[0].createrawtransaction(inputs=[], outputs=[])
        assert_raises_rpc_error(-8, "Data must be hexadecimal string", self.nodes[0].createrawtransaction, [], {'data': 'foo'})
        assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].createrawtransaction, [], {'foo': 0})
        assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].createrawtransaction, [], {address: 'foo'})
        assert_raises_rpc_error(-3, "Amount out of range", self.nodes[0].createrawtransaction, [], {address: -1})
        assert_raises_rpc_error(-8, "Invalid parameter, duplicated address: %s" % address, self.nodes[0].createrawtransaction, [], multidict([(address, 1), (address, 1)]))
        assert_raises_rpc_error(-8, "Invalid parameter, duplicated address: %s" % address, self.nodes[0].createrawtransaction, [], [{address: 1}, {address: 1}])
        assert_raises_rpc_error(-8, "Invalid parameter, duplicate key: data", self.nodes[0].createrawtransaction, [], [{"data": 'aa'}, {"data": "bb"}])
        assert_raises_rpc_error(-8, "Invalid parameter, duplicate key: data", self.nodes[0].createrawtransaction, [], multidict([("data", 'aa'), ("data", "bb")]))
        assert_raises_rpc_error(-8, "Invalid parameter, key-value pair must contain exactly one key", self.nodes[0].createrawtransaction, [], [{'a': 1, 'b': 2}])
        assert_raises_rpc_error(-8, "Invalid parameter, key-value pair not an object as expected", self.nodes[0].createrawtransaction, [], [['key-value pair1'], ['2']])

        # Test `createrawtransaction` mismatch between sequence number(s) and `replaceable` option
        assert_raises_rpc_error(-8, "Invalid parameter combination: Sequence number(s) contradict replaceable option",
                                self.nodes[0].createrawtransaction, [{'txid': TXID, 'vout': 0, 'sequence': MAX_BIP125_RBF_SEQUENCE+1}], {}, 0, True)

        # Test `createrawtransaction` invalid `locktime`
        assert_raises_rpc_error(-3, "Expected type number", self.nodes[0].createrawtransaction, [], {}, 'foo')
        assert_raises_rpc_error(-8, "Invalid parameter, locktime out of range", self.nodes[0].createrawtransaction, [], {}, -1)
        assert_raises_rpc_error(-8, "Invalid parameter, locktime out of range", self.nodes[0].createrawtransaction, [], {}, 4294967296)

        # Test `createrawtransaction` invalid `replaceable`
        assert_raises_rpc_error(-3, "Expected type bool", self.nodes[0].createrawtransaction, [], {}, 0, 'foo')

        # Test that createrawtransaction accepts an array and object as outputs
        # One output
        tx = tx_from_hex(self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs={address: 99}))
        assert_equal(len(tx.vout), 1)
        assert_equal(
            tx.serialize().hex(),
            self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs=[{address: 99}]),
        )
        # Two outputs
        address2 = getnewdestination()[2]
        tx = tx_from_hex(self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs=OrderedDict([(address, 99), (address2, 99)])))
        assert_equal(len(tx.vout), 2)
        assert_equal(
            tx.serialize().hex(),
            self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs=[{address: 99}, {address2: 99}]),
        )
        # Multiple mixed outputs
        tx = tx_from_hex(self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs=multidict([(address, 99), (address2, 99), ('data', '99')])))
        assert_equal(len(tx.vout), 3)
        assert_equal(
            tx.serialize().hex(),
            self.nodes[2].createrawtransaction(inputs=[{'txid': TXID, 'vout': 9}], outputs=[{address: 99}, {address2: 99}, {'data': '99'}]),
        )
Esempio n. 10
0
    def run_test(self):
        self.wallet = MiniWallet(self.nodes[0])
        self.wallet.rescan_utxos()

        self.log.info("Create UTXOs...")
        pubk1, spk_P2SH_SEGWIT, addr_P2SH_SEGWIT = getnewdestination(
            "p2sh-segwit")
        pubk2, spk_LEGACY, addr_LEGACY = getnewdestination("legacy")
        pubk3, spk_BECH32, addr_BECH32 = getnewdestination("bech32")
        self.sendtodestination(spk_P2SH_SEGWIT, 0.001)
        self.sendtodestination(spk_LEGACY, 0.002)
        self.sendtodestination(spk_BECH32, 0.004)

        #send to child keys of tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK
        self.sendtodestination("mkHV1C6JLheLoUSSZYk7x3FH5tnx9bu7yc",
                               0.008)  # (m/0'/0'/0')
        self.sendtodestination("mipUSRmJAj2KrjSvsPQtnP8ynUon7FhpCR",
                               0.016)  # (m/0'/0'/1')
        self.sendtodestination("n37dAGe6Mq1HGM9t4b6rFEEsDGq7Fcgfqg",
                               0.032)  # (m/0'/0'/1500')
        self.sendtodestination("mqS9Rpg8nNLAzxFExsgFLCnzHBsoQ3PRM6",
                               0.064)  # (m/0'/0'/0)
        self.sendtodestination("mnTg5gVWr3rbhHaKjJv7EEEc76ZqHgSj4S",
                               0.128)  # (m/0'/0'/1)
        self.sendtodestination("mketCd6B9U9Uee1iCsppDJJBHfvi6U6ukC",
                               0.256)  # (m/0'/0'/1500)
        self.sendtodestination("mj8zFzrbBcdaWXowCQ1oPZ4qioBVzLzAp7",
                               0.512)  # (m/1/1/0')
        self.sendtodestination("mfnKpKQEftniaoE1iXuMMePQU3PUpcNisA",
                               1.024)  # (m/1/1/1')
        self.sendtodestination("mou6cB1kaP1nNJM1sryW6YRwnd4shTbXYQ",
                               2.048)  # (m/1/1/1500')
        self.sendtodestination("mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ",
                               4.096)  # (m/1/1/0)
        self.sendtodestination("mxp7w7j8S1Aq6L8StS2PqVvtt4HGxXEvdy",
                               8.192)  # (m/1/1/1)
        self.sendtodestination("mpQ8rokAhp1TAtJQR6F6TaUmjAWkAWYYBq",
                               16.384)  # (m/1/1/1500)

        self.generate(self.nodes[0], 1)

        scan = self.nodes[0].scantxoutset("start", [])
        info = self.nodes[0].gettxoutsetinfo()
        assert_equal(scan['success'], True)
        assert_equal(scan['height'], info['height'])
        assert_equal(scan['txouts'], info['txouts'])
        assert_equal(scan['bestblock'], info['bestblock'])

        self.log.info("Test if we have found the non HD unspent outputs.")
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "pkh(" + pubk1.hex() + ")", "pkh(" + pubk2.hex() + ")",
                "pkh(" + pubk3.hex() + ")"
            ])['total_amount'], Decimal("0.002"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "wpkh(" + pubk1.hex() + ")", "wpkh(" + pubk2.hex() + ")",
                "wpkh(" + pubk3.hex() + ")"
            ])['total_amount'], Decimal("0.004"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "sh(wpkh(" + pubk1.hex() + "))", "sh(wpkh(" + pubk2.hex() +
                "))", "sh(wpkh(" + pubk3.hex() + "))"
            ])['total_amount'], Decimal("0.001"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "combo(" + pubk1.hex() + ")", "combo(" + pubk2.hex() + ")",
                "combo(" + pubk3.hex() + ")"
            ])['total_amount'], Decimal("0.007"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "addr(" + addr_P2SH_SEGWIT + ")", "addr(" + addr_LEGACY + ")",
                "addr(" + addr_BECH32 + ")"
            ])['total_amount'], Decimal("0.007"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "addr(" + addr_P2SH_SEGWIT + ")", "addr(" + addr_LEGACY + ")",
                "combo(" + pubk3.hex() + ")"
            ])['total_amount'], Decimal("0.007"))

        self.log.info("Test range validation.")
        assert_raises_rpc_error(-8, "End of range is too high",
                                self.nodes[0].scantxoutset, "start",
                                [{
                                    "desc": "desc",
                                    "range": -1
                                }])
        assert_raises_rpc_error(-8, "Range should be greater or equal than 0",
                                self.nodes[0].scantxoutset, "start",
                                [{
                                    "desc": "desc",
                                    "range": [-1, 10]
                                }])
        assert_raises_rpc_error(
            -8, "End of range is too high", self.nodes[0].scantxoutset,
            "start", [{
                "desc": "desc",
                "range": [(2 << 31 + 1) - 1000000, (2 << 31 + 1)]
            }])
        assert_raises_rpc_error(
            -8, "Range specified as [begin,end] must not have begin after end",
            self.nodes[0].scantxoutset, "start", [{
                "desc": "desc",
                "range": [2, 1]
            }])
        assert_raises_rpc_error(-8, "Range is too large",
                                self.nodes[0].scantxoutset, "start",
                                [{
                                    "desc": "desc",
                                    "range": [0, 1000001]
                                }])

        self.log.info("Test extended key derivation.")
        # Run various scans, and verify that the sum of the amounts of the matches corresponds to the expected subset.
        # Note that all amounts in the UTXO set are powers of 2 multiplied by 0.001 UMK, so each amounts uniquely identifies a subset.
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/0h)"
            ])['total_amount'], Decimal("0.008"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0'/1h)"
            ])['total_amount'], Decimal("0.016"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/1500')"
            ])['total_amount'], Decimal("0.032"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0h/0)"
            ])['total_amount'], Decimal("0.064"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/1)"
            ])['total_amount'], Decimal("0.128"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/1500)"
            ])['total_amount'], Decimal("0.256"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [{
                "desc":
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/*h)",
                "range": 1499
            }])['total_amount'], Decimal("0.024"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [{
                "desc":
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0'/*h)",
                "range": 1500
            }])['total_amount'], Decimal("0.056"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [{
                "desc":
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/*)",
                "range": 1499
            }])['total_amount'], Decimal("0.192"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [{
                "desc":
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/*)",
                "range": 1500
            }])['total_amount'], Decimal("0.448"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0')"
            ])['total_amount'], Decimal("0.512"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1')"
            ])['total_amount'], Decimal("1.024"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1500h)"
            ])['total_amount'], Decimal("2.048"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"
            ])['total_amount'], Decimal("4.096"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1)"
            ])['total_amount'], Decimal("8.192"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1500)"
            ])['total_amount'], Decimal("16.384"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0)"
            ])['total_amount'], Decimal("4.096"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "combo([abcdef88/1/2'/3/4h]tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)"
            ])['total_amount'], Decimal("8.192"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [
                "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1500)"
            ])['total_amount'], Decimal("16.384"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [{
                "desc":
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')",
                "range": 1499
            }])['total_amount'], Decimal("1.536"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [{
                "desc":
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')",
                "range": 1500
            }])['total_amount'], Decimal("3.584"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [{
                "desc":
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)",
                "range": 1499
            }])['total_amount'], Decimal("12.288"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [{
                "desc":
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)",
                "range": 1500
            }])['total_amount'], Decimal("28.672"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [{
                "desc":
                "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)",
                "range": 1499
            }])['total_amount'], Decimal("12.288"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [{
                "desc":
                "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)",
                "range": 1500
            }])['total_amount'], Decimal("28.672"))
        assert_equal(
            self.nodes[0].scantxoutset("start", [{
                "desc":
                "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)",
                "range": [1500, 1500]
            }])['total_amount'], Decimal("16.384"))

        # Test the reported descriptors for a few matches
        assert_equal(
            descriptors(self.nodes[0].scantxoutset("start", [{
                "desc":
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/*)",
                "range": 1499
            }])), [
                "pkh([0c5f9a1e/0'/0'/0]026dbd8b2315f296d36e6b6920b1579ca75569464875c7ebe869b536a7d9503c8c)#dzxw429x",
                "pkh([0c5f9a1e/0'/0'/1]033e6f25d76c00bedb3a8993c7d5739ee806397f0529b1b31dda31ef890f19a60c)#43rvceed"
            ])
        assert_equal(
            descriptors(self.nodes[0].scantxoutset("start", [
                "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"
            ])), [
                "pkh([0c5f9a1e/1/1/0]03e1c5b6e650966971d7e71ef2674f80222752740fc1dfd63bbbd220d2da9bd0fb)#cxmct4w8"
            ])
        assert_equal(
            descriptors(self.nodes[0].scantxoutset("start", [{
                "desc":
                "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)",
                "range": 1500
            }])), [
                'pkh([0c5f9a1e/1/1/0]03e1c5b6e650966971d7e71ef2674f80222752740fc1dfd63bbbd220d2da9bd0fb)#cxmct4w8',
                'pkh([0c5f9a1e/1/1/1500]03832901c250025da2aebae2bfb38d5c703a57ab66ad477f9c578bfbcd78abca6f)#vchwd07g',
                'pkh([0c5f9a1e/1/1/1]030d820fc9e8211c4169be8530efbc632775d8286167afd178caaf1089b77daba7)#z2t3ypsa'
            ])

        # Check that status and abort don't need second arg
        assert_equal(self.nodes[0].scantxoutset("status"), None)
        assert_equal(self.nodes[0].scantxoutset("abort"), False)

        # Check that second arg is needed for start
        assert_raises_rpc_error(
            -1, "scanobjects argument is required for the start action",
            self.nodes[0].scantxoutset, "start")
Esempio n. 11
0
    def run_test(self):
        node0, node1, node2 = self.nodes
        self.wallet = MiniWallet(test_node=node0)

        if self.is_bdb_compiled():
            self.check_addmultisigaddress_errors()

        self.log.info('Generating blocks ...')
        self.generate(self.wallet, 149)

        self.moved = 0
        for self.nkeys in [3, 5]:
            for self.nsigs in [2, 3]:
                for self.output_type in ["bech32", "p2sh-segwit", "legacy"]:
                    self.get_keys()
                    self.do_multisig()
        if self.is_bdb_compiled():
            self.checkbalances()

        # Test mixed compressed and uncompressed pubkeys
        self.log.info(
            'Mixed compressed and uncompressed multisigs are not allowed')
        pk0, pk1, pk2 = [
            getnewdestination('bech32')[0].hex() for _ in range(3)
        ]

        # decompress pk2
        pk_obj = ECPubKey()
        pk_obj.set(bytes.fromhex(pk2))
        pk_obj.compressed = False
        pk2 = pk_obj.get_bytes().hex()

        if self.is_bdb_compiled():
            node0.createwallet(wallet_name='wmulti0',
                               disable_private_keys=True)
            wmulti0 = node0.get_wallet_rpc('wmulti0')

        # Check all permutations of keys because order matters apparently
        for keys in itertools.permutations([pk0, pk1, pk2]):
            # Results should be the same as this legacy one
            legacy_addr = node0.createmultisig(2, keys, 'legacy')['address']

            if self.is_bdb_compiled():
                result = wmulti0.addmultisigaddress(2, keys, '', 'legacy')
                assert_equal(legacy_addr, result['address'])
                assert 'warnings' not in result

            # Generate addresses with the segwit types. These should all make legacy addresses
            err_msg = [
                "Unable to make chosen address type, please ensure no uncompressed public keys are present."
            ]

            for addr_type in ['bech32', 'p2sh-segwit']:
                result = self.nodes[0].createmultisig(nrequired=2,
                                                      keys=keys,
                                                      address_type=addr_type)
                assert_equal(legacy_addr, result['address'])
                assert_equal(result['warnings'], err_msg)

                if self.is_bdb_compiled():
                    result = wmulti0.addmultisigaddress(nrequired=2,
                                                        keys=keys,
                                                        address_type=addr_type)
                    assert_equal(legacy_addr, result['address'])
                    assert_equal(result['warnings'], err_msg)

        self.log.info(
            'Testing sortedmulti descriptors with BIP 67 test vectors')
        with open(os.path.join(os.path.dirname(os.path.realpath(__file__)),
                               'data/rpc_bip67.json'),
                  encoding='utf-8') as f:
            vectors = json.load(f)

        for t in vectors:
            key_str = ','.join(t['keys'])
            desc = descsum_create('sh(sortedmulti(2,{}))'.format(key_str))
            assert_equal(self.nodes[0].deriveaddresses(desc)[0], t['address'])
            sorted_key_str = ','.join(t['sorted_keys'])
            sorted_key_desc = descsum_create(
                'sh(multi(2,{}))'.format(sorted_key_str))
            assert_equal(self.nodes[0].deriveaddresses(sorted_key_desc)[0],
                         t['address'])

        # Check that bech32m is currently not allowed
        assert_raises_rpc_error(
            -5, "createmultisig cannot create bech32m multisig addresses",
            self.nodes[0].createmultisig, 2, self.pub, "bech32m")
Esempio n. 12
0
    def run_test(self):
        eckey = ECKey()
        eckey.generate()
        self.privkey = bytes_to_wif(eckey.get_bytes())
        self.pubkey = eckey.get_pubkey().get_bytes().hex()
        cms = self.nodes[0].createmultisig(1, [self.pubkey])
        wms = self.nodes[0].createmultisig(1, [self.pubkey], 'p2sh-segwit')
        self.ms_address = cms["address"]
        ms_unlock_details = {"scriptPubKey": self.nodes[0].validateaddress(self.ms_address)["scriptPubKey"],
                             "redeemScript": cms["redeemScript"]}
        self.wit_ms_address = wms['address']

        self.coinbase_blocks = self.generate(self.nodes[0], 2)  # block height = 2
        coinbase_txid = []
        for i in self.coinbase_blocks:
            coinbase_txid.append(self.nodes[0].getblock(i)['tx'][0])
        self.generate(self.nodes[0], COINBASE_MATURITY)  # block height = COINBASE_MATURITY + 2
        self.lastblockhash = self.nodes[0].getbestblockhash()
        self.lastblockheight = COINBASE_MATURITY + 2
        self.lastblocktime = int(time.time()) + self.lastblockheight

        self.log.info(f"Test 1: NULLDUMMY compliant base transactions should be accepted to mempool and mined before activation [{COINBASE_MATURITY + 3}]")
        test1txs = [self.create_transaction(txid=coinbase_txid[0], addr=self.ms_address, amount=49,
                                            privkey=self.nodes[0].get_deterministic_priv_key().key)]
        txid1 = self.nodes[0].sendrawtransaction(test1txs[0].serialize_with_witness().hex(), 0)
        test1txs.append(self.create_transaction(txid=txid1, input_details=ms_unlock_details,
                                                addr=self.ms_address, amount=48,
                                                privkey=self.privkey))
        txid2 = self.nodes[0].sendrawtransaction(test1txs[1].serialize_with_witness().hex(), 0)
        test1txs.append(self.create_transaction(txid=coinbase_txid[1],
                                                addr=self.wit_ms_address, amount=49,
                                                privkey=self.nodes[0].get_deterministic_priv_key().key))
        txid3 = self.nodes[0].sendrawtransaction(test1txs[2].serialize_with_witness().hex(), 0)
        self.block_submit(self.nodes[0], test1txs, accept=True)

        self.log.info("Test 2: Non-NULLDUMMY base multisig transaction should not be accepted to mempool before activation")
        test2tx = self.create_transaction(txid=txid2, input_details=ms_unlock_details,
                                          addr=self.ms_address, amount=47,
                                          privkey=self.privkey)
        invalidate_nulldummy_tx(test2tx)
        assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test2tx.serialize_with_witness().hex(), 0)

        self.log.info(f"Test 3: Non-NULLDUMMY base transactions should be accepted in a block before activation [{COINBASE_MATURITY + 4}]")
        self.block_submit(self.nodes[0], [test2tx], accept=True)

        self.log.info("Test 4: Non-NULLDUMMY base multisig transaction is invalid after activation")
        test4tx = self.create_transaction(txid=test2tx.hash, input_details=ms_unlock_details,
                                          addr=getnewdestination()[2], amount=46,
                                          privkey=self.privkey)
        test6txs = [CTransaction(test4tx)]
        invalidate_nulldummy_tx(test4tx)
        assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test4tx.serialize_with_witness().hex(), 0)
        self.block_submit(self.nodes[0], [test4tx], accept=False)

        self.log.info("Test 5: Non-NULLDUMMY P2WSH multisig transaction invalid after activation")
        test5tx = self.create_transaction(txid=txid3, input_details={"scriptPubKey": test1txs[2].vout[0].scriptPubKey.hex(),
                                          "amount": 49, "witnessScript": wms["redeemScript"]},
                                          addr=getnewdestination(address_type='p2sh-segwit')[2], amount=48,
                                          privkey=self.privkey)
        test6txs.append(CTransaction(test5tx))
        test5tx.wit.vtxinwit[0].scriptWitness.stack[0] = b'\x01'
        assert_raises_rpc_error(-26, NULLDUMMY_ERROR, self.nodes[0].sendrawtransaction, test5tx.serialize_with_witness().hex(), 0)
        self.block_submit(self.nodes[0], [test5tx], with_witness=True, accept=False)

        self.log.info(f"Test 6: NULLDUMMY compliant base/witness transactions should be accepted to mempool and in block after activation [{COINBASE_MATURITY + 5}]")
        for i in test6txs:
            self.nodes[0].sendrawtransaction(i.serialize_with_witness().hex(), 0)
        self.block_submit(self.nodes[0], test6txs, with_witness=True, accept=True)