diff --git a/test/functional/abc-magnetic-anomaly-mining.py b/test/functional/abc-magnetic-anomaly-mining.py index 0d9cd8ea8..c2581e924 100755 --- a/test/functional/abc-magnetic-anomaly-mining.py +++ b/test/functional/abc-magnetic-anomaly-mining.py @@ -1,121 +1,121 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2016 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ Test that mining RPC continues to supply correct transaction metadata after the Nov 2018 protocol upgrade which engages canonical transaction ordering """ import decimal import random import time from test_framework.test_framework import BitcoinTestFramework class CTORMiningTest(BitcoinTestFramework): def set_test_params(self): # Setup two nodes so we can getblocktemplate # it errors out if it is not connected to other nodes self.num_nodes = 2 self.setup_clean_chain = True self.block_heights = {} self.tip = None self.blocks = {} self.mocktime = int(time.time()) - 600 * 100 extra_arg = ['-spendzeroconfchange=0', '-whitelist=noban@127.0.0.1'] self.extra_args = [extra_arg, extra_arg] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): mining_node = self.nodes[0] # Helper for updating the times def update_time(): mining_node.setmocktime(self.mocktime) self.mocktime = self.mocktime + 600 mining_node.getnewaddress() # Generate some unspent utxos and also # activate magnetic anomaly for x in range(150): update_time() mining_node.generate(1) update_time() unspent = mining_node.listunspent() transactions = {} # Spend all our coinbases while len(unspent): inputs = [] # Grab a random number of inputs for _ in range(random.randrange(1, 5)): txin = unspent.pop() inputs.append({ 'txid': txin['txid'], 'vout': 0 # This is a coinbase }) if len(unspent) == 0: break outputs = {} # Calculate a unique fee for this transaction fee = decimal.Decimal(random.randint( - 1000, 2000)) / decimal.Decimal(1e8) + 1000, 2000)) / decimal.Decimal(1e2) # Spend to the same number of outputs as inputs, so we can leave # the amounts unchanged and avoid rounding errors. This also ensures # the number of sigops == number of sigchecks. # # NOTE: There will be 1 sigop per output (which equals the number # of inputs now). We need this randomization to ensure the # numbers are properly following the transactions in the block # template metadata addr = "" for _ in range(len(inputs)): addr = mining_node.getnewaddress() output = { # 50 BCH per coinbase - addr: decimal.Decimal(50) + addr: decimal.Decimal(50000000) } outputs.update(output) # Take the fee off the last output to avoid rounding errors we # need the exact fee later for assertions outputs[addr] -= fee rawtx = mining_node.createrawtransaction(inputs, outputs) signedtx = mining_node.signrawtransactionwithwallet(rawtx) txid = mining_node.sendrawtransaction(signedtx['hex']) # number of outputs is the same as the number of sigops in this # case transactions.update({txid: {'fee': fee, 'sigops': len(outputs)}}) tmpl = mining_node.getblocktemplate() assert 'proposal' in tmpl['capabilities'] # Check the template transaction metadata and ordering last_txid = 0 for txn in tmpl['transactions'][1:]: txid = txn['txid'] txnMetadata = transactions[txid] - expectedFeeSats = int(txnMetadata['fee'] * 10**8) + expectedFeeSats = int(txnMetadata['fee'] * 10**2) expectedSigOps = txnMetadata['sigops'] txid_decoded = int(txid, 16) # Assert we got the expected metadata assert expectedFeeSats == txn['fee'] assert expectedSigOps == txn['sigops'] # Assert transaction ids are in order assert last_txid == 0 or last_txid < txid_decoded last_txid = txid_decoded if __name__ == '__main__': CTORMiningTest().main() diff --git a/test/functional/abc_mining_basic.py b/test/functional/abc_mining_basic.py index ad422fb61..47e3ae524 100755 --- a/test/functional/abc_mining_basic.py +++ b/test/functional/abc_mining_basic.py @@ -1,159 +1,159 @@ #!/usr/bin/env python3 # Copyright (c) 2020 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ Tests for Bitcoin ABC mining RPCs """ from test_framework.cdefs import ( BLOCK_MAXBYTES_MAXSIGCHECKS_RATIO, DEFAULT_MAX_BLOCK_SIZE, ) from test_framework.messages import ( - COIN, + XEC, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_greater_than_or_equal, connect_nodes, ) from decimal import Decimal AXION_ACTIVATION_TIME = 2000000600 MINER_FUND_ADDR = 'ecregtest:pqnqv9lt7e5vjyp0w88zf2af0l92l8rxdgz0wv9ltl' MINER_FUND_LEGACY_ADDR = '2MviGxxFciGeWTgkUgYgjqehWt18c4ZsShd' class AbcMiningRPCTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 3 self.extra_args = [[], [ '-enableminerfund', '-axionactivationtime={}'.format(AXION_ACTIVATION_TIME), ], [ '-enableminerfund', '-usecashaddr=0', '-axionactivationtime={}'.format(AXION_ACTIVATION_TIME), ]] def setup_network(self): self.setup_nodes() # Connect node0 to all other nodes so getblocktemplate will return results # (getblocktemplate has a sanity check that ensures it's connected to a network) # Since the other nodes are mining blocks "in the future" compared to node0, # node0 will not broadcast blocks between the other nodes. for n in range(1, len(self.nodes)): connect_nodes(self.nodes[0], self.nodes[n]) def run_for_node(self, node, expectedMinerFundAddress): address = node.get_deterministic_priv_key().address # Assert the results of getblocktemplate have expected values. Keys not # in 'expected' are not checked. def assert_getblocktemplate(expected): # Always test these values in addition to those passed in expected = {**expected, **{ 'sigoplimit': DEFAULT_MAX_BLOCK_SIZE // BLOCK_MAXBYTES_MAXSIGCHECKS_RATIO, }} blockTemplate = node.getblocktemplate() for key, value in expected.items(): assert_equal(blockTemplate[key], value) # Move block time to just before axion activation node.setmocktime(AXION_ACTIVATION_TIME) node.generatetoaddress(5, address) # Before axion activation, the miner fund list is empty assert_getblocktemplate({ 'coinbasetxn': { 'minerfund': { 'addresses': [], 'minimumvalue': 0, }, }, }) # Move MTP forward to axion activation node.generatetoaddress(1, address) assert_equal( node.getblockchaininfo()['mediantime'], AXION_ACTIVATION_TIME) def get_best_coinbase(): return node.getblock(node.getbestblockhash(), 2)['tx'][0] coinbase = get_best_coinbase() assert_equal(len(coinbase['vout']), 1) block_reward = coinbase['vout'][0]['value'] # We don't need to test all fields in getblocktemplate since many of # them are covered in mining_basic.py assert_equal(node.getmempoolinfo()['size'], 0) assert_getblocktemplate({ 'coinbasetxn': { # We expect to start seeing the miner fund addresses since the # next block will start enforcing them. 'minerfund': { 'addresses': [expectedMinerFundAddress], - 'minimumvalue': block_reward * 8 // 100 * COIN, + 'minimumvalue': block_reward * 8 // 100 * XEC, }, }, # Although the coinbase value need not necessarily be the same as # the last block due to halvings and fees, we know this to be true # since we are not crossing a halving boundary and there are no # transactions in the mempool. - 'coinbasevalue': block_reward * COIN, + 'coinbasevalue': block_reward * XEC, 'mintime': AXION_ACTIVATION_TIME + 1, }) # First block with the new rules node.generatetoaddress(1, address) # We expect the coinbase to have multiple outputs now coinbase = get_best_coinbase() assert_greater_than_or_equal(len(coinbase['vout']), 2) total = Decimal() for o in coinbase['vout']: total += o['value'] assert_equal(total, block_reward) assert_getblocktemplate({ 'coinbasetxn': { 'minerfund': { 'addresses': [expectedMinerFundAddress], - 'minimumvalue': block_reward * 8 // 100 * COIN, + 'minimumvalue': block_reward * 8 // 100 * XEC, }, }, # Again, we assume the coinbase value is the same as prior blocks. - 'coinbasevalue': block_reward * COIN, + 'coinbasevalue': block_reward * XEC, 'mintime': AXION_ACTIVATION_TIME + 1, }) # Move MTP forward node.setmocktime(AXION_ACTIVATION_TIME + 1) node.generatetoaddress(6, address) assert_getblocktemplate({ 'coinbasetxn': { 'minerfund': { 'addresses': [expectedMinerFundAddress], - 'minimumvalue': block_reward * 8 // 100 * COIN, + 'minimumvalue': block_reward * 8 // 100 * XEC, }, }, - 'coinbasevalue': block_reward * COIN, + 'coinbasevalue': block_reward * XEC, 'mintime': AXION_ACTIVATION_TIME + 2, }) def run_test(self): # node0 is for connectivity only and is not mined on (see # setup_network) self.run_for_node(self.nodes[1], MINER_FUND_ADDR) self.run_for_node(self.nodes[2], MINER_FUND_LEGACY_ADDR) if __name__ == '__main__': AbcMiningRPCTest().main() diff --git a/test/functional/abc_rpc_avalancheproof.py b/test/functional/abc_rpc_avalancheproof.py index d6888a4d9..46abe7976 100644 --- a/test/functional/abc_rpc_avalancheproof.py +++ b/test/functional/abc_rpc_avalancheproof.py @@ -1,371 +1,371 @@ #!/usr/bin/env python3 # Copyright (c) 2021 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test building avalanche proofs and using them to add avalanche peers.""" import base64 from decimal import Decimal from test_framework.address import ADDRESS_BCHREG_UNSPENDABLE from test_framework.avatools import ( create_coinbase_stakes, create_stakes, get_proof_ids, ) from test_framework.key import ECKey, bytes_to_wif from test_framework.messages import ( AvalancheDelegation, AvalancheDelegationLevel, AvalancheProof, FromHex, ) from test_framework.p2p import P2PInterface, p2p_lock from test_framework.test_framework import BitcoinTestFramework from test_framework.test_node import ErrorMatch from test_framework.util import ( append_config, assert_equal, wait_until, assert_raises_rpc_error, ) AVALANCHE_MAX_PROOF_STAKES = 1000 -PROOF_DUST_THRESHOLD = 1.0 +PROOF_DUST_THRESHOLD = 1000000.0 """Minimum amount per UTXO in a proof (in coins, not in satoshis)""" def add_interface_node(test_node) -> str: """Create a mininode, connect it to test_node, return the nodeid of the mininode as registered by test_node. """ n = P2PInterface() test_node.add_p2p_connection(n) n.wait_for_verack() return test_node.getpeerinfo()[-1]['id'] class AvalancheProofTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.extra_args = [['-enableavalanche=1', '-avacooldown=0'], ] self.supports_cli = False self.rpc_timeout = 120 def run_test(self): node = self.nodes[0] addrkey0 = node.get_deterministic_priv_key() blockhashes = node.generatetoaddress(100, addrkey0.address) self.log.info( "Make build a valid proof and restart the node to use it") privkey = ECKey() privkey.set(bytes.fromhex( "12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747"), True) def get_hex_pubkey(privkey): return privkey.get_pubkey().get_bytes().hex() proof_master = get_hex_pubkey(privkey) proof_sequence = 11 proof_expiration = 12 stakes = create_coinbase_stakes(node, [blockhashes[0]], addrkey0.key) proof = node.buildavalancheproof( proof_sequence, proof_expiration, proof_master, stakes) self.log.info("Test decodeavalancheproof RPC") proofobj = FromHex(AvalancheProof(), proof) decodedproof = node.decodeavalancheproof(proof) limited_id_hex = f"{proofobj.limited_proofid:0{64}x}" assert_equal(decodedproof["sequence"], proof_sequence) assert_equal(decodedproof["expiration"], proof_expiration) assert_equal(decodedproof["master"], proof_master) assert_equal(decodedproof["proofid"], f"{proofobj.proofid:0{64}x}") assert_equal(decodedproof["limitedid"], limited_id_hex) assert_equal(decodedproof["stakes"][0]["txid"], stakes[0]["txid"]) assert_equal(decodedproof["stakes"][0]["vout"], stakes[0]["vout"]) assert_equal(decodedproof["stakes"][0]["height"], stakes[0]["height"]) assert_equal( decodedproof["stakes"][0]["iscoinbase"], stakes[0]["iscoinbase"]) assert_equal( decodedproof["stakes"][0]["signature"], base64.b64encode(proofobj.stakes[0].sig).decode("ascii")) # Invalid hex (odd number of hex digits) assert_raises_rpc_error(-22, "Proof must be an hexadecimal string", node.decodeavalancheproof, proof[:-1]) # Valid hex but invalid proof assert_raises_rpc_error(-22, "Proof has invalid format", node.decodeavalancheproof, proof[:-2]) # Restart the node, making sure it is initially in IBD mode minchainwork = int(node.getblockchaininfo()["chainwork"], 16) + 1 self.restart_node(0, self.extra_args[0] + [ "-avaproof={}".format(proof), "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", "-minimumchainwork=0x{:x}".format(minchainwork), ]) self.log.info( "The proof verification should be delayed until IBD is complete") assert node.getblockchaininfo()["initialblockdownload"] is True # Our proof cannot be verified during IBD, so we should have no peer assert not node.getavalanchepeerinfo() # Mining a few more blocks should cause us to leave IBD node.generate(2) # Our proof is now verified and our node is added as a peer assert node.getblockchaininfo()["initialblockdownload"] is False wait_until(lambda: len(node.getavalanchepeerinfo()) == 1, timeout=5) if self.is_wallet_compiled(): self.log.info( "A proof using the maximum number of stakes is accepted...") new_blocks = node.generate(AVALANCHE_MAX_PROOF_STAKES // 10 + 1) # confirm the coinbase UTXOs node.generate(101) too_many_stakes = create_stakes( node, new_blocks, AVALANCHE_MAX_PROOF_STAKES + 1) maximum_stakes = too_many_stakes[:-1] good_proof = node.buildavalancheproof( proof_sequence, proof_expiration, proof_master, maximum_stakes) peerid1 = add_interface_node(node) assert node.addavalanchenode(peerid1, proof_master, good_proof) self.log.info( "A proof using too many stakes should be rejected...") too_many_utxos = node.buildavalancheproof( proof_sequence, proof_expiration, proof_master, too_many_stakes) peerid2 = add_interface_node(node) assert not node.addavalanchenode( peerid2, proof_master, too_many_utxos) self.log.info("Generate delegations for the proof") # Stack up a few delegation levels def gen_privkey(): pk = ECKey() pk.generate() return pk delegator_privkey = privkey delegation = None for _ in range(10): delegated_privkey = gen_privkey() delegation = node.delegateavalancheproof( limited_id_hex, bytes_to_wif(delegator_privkey.get_bytes()), get_hex_pubkey(delegated_privkey), delegation, ) delegator_privkey = delegated_privkey random_privkey = gen_privkey() random_pubkey = get_hex_pubkey(random_privkey) # Invalid proof no_stake = node.buildavalancheproof(proof_sequence, proof_expiration, proof_master, []) # Invalid privkey assert_raises_rpc_error(-5, "The private key is invalid", node.delegateavalancheproof, limited_id_hex, bytes_to_wif(bytes(32)), random_pubkey, ) # Invalid delegation bad_dg = AvalancheDelegation() assert_raises_rpc_error(-8, "The supplied delegation does not match the proof", node.delegateavalancheproof, limited_id_hex, bytes_to_wif(privkey.get_bytes()), random_pubkey, bad_dg.serialize().hex(), ) # Still invalid, but with a matching proofid bad_dg.limited_proofid = proofobj.limited_proofid bad_dg.proof_master = proofobj.master bad_dg.levels = [AvalancheDelegationLevel()] assert_raises_rpc_error(-8, "The supplied delegation is not valid", node.delegateavalancheproof, limited_id_hex, bytes_to_wif(privkey.get_bytes()), random_pubkey, bad_dg.serialize().hex(), ) # Wrong privkey, match the proof but does not match the delegation assert_raises_rpc_error(-8, "The supplied private key does not match the delegation", node.delegateavalancheproof, limited_id_hex, bytes_to_wif(privkey.get_bytes()), random_pubkey, delegation, ) # Test invalid proofs dust = node.buildavalancheproof( proof_sequence, proof_expiration, proof_master, create_coinbase_stakes(node, [blockhashes[0]], addrkey0.key, amount="0")) dust_amount = Decimal(f"{PROOF_DUST_THRESHOLD * 0.9999:.4f}") dust2 = node.buildavalancheproof( proof_sequence, proof_expiration, proof_master, create_coinbase_stakes(node, [blockhashes[0]], addrkey0.key, amount=str(dust_amount))) duplicate_stake = node.buildavalancheproof( proof_sequence, proof_expiration, proof_master, create_coinbase_stakes(node, [blockhashes[0]] * 2, addrkey0.key)) missing_stake = node.buildavalancheproof( proof_sequence, proof_expiration, proof_master, [{ 'txid': '0' * 64, 'vout': 0, - 'amount': 10, + 'amount': 10000000, 'height': 42, 'iscoinbase': False, 'privatekey': addrkey0.key, }] ) bad_sig = ("0b000000000000000c0000000000000021030b4c866585dd868a9d62348" "a9cd008d6a312937048fff31670e7e920cfc7a7440105c5f72f5d6da3085" "583e75ee79340eb4eff208c89988e7ed0efb30b87298fa30000000000f20" "52a0100000003000000210227d85ba011276cf25b51df6a188b75e604b3" "8770a462b2d0e9fb2fc839ef5d3faf07f001dd38e9b4a43d07d5d449cc0" "f7d2888d96b82962b3ce516d1083c0e031773487fc3c4f2e38acd1db974" "1321b91a79b82d1c2cfd47793261e4ba003cf5") self.log.info( "Check the verifyavalancheproof and sendavalancheproof RPCs") for rpc in [node.verifyavalancheproof, node.sendavalancheproof]: assert_raises_rpc_error(-22, "Proof must be an hexadecimal string", rpc, "f00") assert_raises_rpc_error(-22, "Proof has invalid format", rpc, "f00d") def check_rpc_failure(proof, message): assert_raises_rpc_error(-8, "The proof is invalid: " + message, rpc, proof) check_rpc_failure(no_stake, "no-stake") check_rpc_failure(dust, "amount-below-dust-threshold") check_rpc_failure(duplicate_stake, "duplicated-stake") check_rpc_failure(missing_stake, "utxo-missing-or-spent") check_rpc_failure(bad_sig, "invalid-signature") if self.is_wallet_compiled(): check_rpc_failure(too_many_utxos, "too-many-utxos") conflicting_utxo = node.buildavalancheproof( proof_sequence + 1, proof_expiration, proof_master, stakes) assert_raises_rpc_error(-8, "The proof has conflicting utxo with an existing proof", node.sendavalancheproof, conflicting_utxo) # Good proof assert node.verifyavalancheproof(proof) peer = node.add_p2p_connection(P2PInterface()) proofid = FromHex(AvalancheProof(), proof).proofid node.sendavalancheproof(proof) assert proofid in get_proof_ids(node) def inv_found(): with p2p_lock: return peer.last_message.get( "inv") and peer.last_message["inv"].inv[-1].hash == proofid wait_until(inv_found) self.log.info("Check the getrawproof RPC") raw_proof = node.getrawavalancheproof("{:064x}".format(proofid)) assert_equal(raw_proof['proof'], proof) assert_equal(raw_proof['orphan'], False) assert_raises_rpc_error(-8, "Proof not found", node.getrawavalancheproof, '0' * 64) # Orphan the proof by sending the stake raw_tx = node.createrawtransaction( [{"txid": stakes[-1]["txid"], "vout": 0}], {ADDRESS_BCHREG_UNSPENDABLE: stakes[-1] - ["amount"] - Decimal('0.01')} + ["amount"] - Decimal('10000')} ) signed_tx = node.signrawtransactionwithkey(raw_tx, [addrkey0.key]) node.sendrawtransaction(signed_tx["hex"]) node.generate(1) wait_until(lambda: proofid not in get_proof_ids(node)) raw_proof = node.getrawavalancheproof("{:064x}".format(proofid)) assert_equal(raw_proof['proof'], proof) assert_equal(raw_proof['orphan'], True) self.log.info("Bad proof should be rejected at startup") self.stop_node(0) node.assert_start_raises_init_error( self.extra_args[0] + [ "-avasessionkey=0", ], expected_msg="Error: the avalanche session key is invalid", ) node.assert_start_raises_init_error( self.extra_args[0] + [ "-avaproof={}".format(proof), ], expected_msg="Error: the avalanche master key is missing for the avalanche proof", ) node.assert_start_raises_init_error( self.extra_args[0] + [ "-avaproof={}".format(proof), "-avamasterkey=0", ], expected_msg="Error: the avalanche master key is invalid", ) def check_proof_init_error(proof, message): node.assert_start_raises_init_error( self.extra_args[0] + [ "-avaproof={}".format(proof), "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", ], expected_msg="Error: " + message, ) check_proof_init_error(no_stake, "the avalanche proof has no stake") check_proof_init_error(dust, "the avalanche proof stake is too low") check_proof_init_error(dust2, "the avalanche proof stake is too low") check_proof_init_error(duplicate_stake, "the avalanche proof has duplicated stake") check_proof_init_error(bad_sig, "the avalanche proof has invalid stake signatures") if self.is_wallet_compiled(): # The too many utxos case creates a proof which is that large that it # cannot fit on the command line append_config(node.datadir, ["avaproof={}".format(too_many_utxos)]) node.assert_start_raises_init_error( self.extra_args[0] + [ "-avamasterkey=cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN", ], expected_msg="Error: the avalanche proof has too many utxos", match=ErrorMatch.PARTIAL_REGEX, ) if __name__ == '__main__': AvalancheProofTest().main() diff --git a/test/functional/abc_wallet_dumpcoins.py b/test/functional/abc_wallet_dumpcoins.py index fb71937aa..7a006d02c 100755 --- a/test/functional/abc_wallet_dumpcoins.py +++ b/test/functional/abc_wallet_dumpcoins.py @@ -1,72 +1,72 @@ #!/usr/bin/env python3 # Copyright (c) 2020 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ Test the dumpcoins RPCs call. """ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal from decimal import Decimal class DumpCoinsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): node = self.nodes[0] assert_equal(node.dumpcoins(), {}) address0 = node.getnewaddress() address1 = node.getnewaddress() coinbases = [] def generate_and_get_txid(address, expected_coins): blockhash = node.generatetoaddress(1, address)[0] assert_equal(node.dumpcoins(), expected_coins) # Get the coinbase txid coinbases.append(node.getblock(blockhash)["tx"][0]) generate_and_get_txid(address0, {}) for _ in range(99): generate_and_get_txid(address1, {}) # Coinbases reach maturity and start to show up. generate_and_get_txid(address1, { address0: [{ "txid": coinbases[0], "vout": 0, "depth": 101, - "value": Decimal('50.00000000'), + "value": Decimal('50000000.00'), }], }) # And now on address1 generate_and_get_txid(address1, { address0: [{ "txid": coinbases[0], "vout": 0, "depth": 102, - "value": Decimal('50.00000000'), + "value": Decimal('50000000.00'), }], address1: [{ "txid": coinbases[1], "vout": 0, "depth": 101, - "value": Decimal('50.00000000'), + "value": Decimal('50000000.00'), }], }) if __name__ == '__main__': DumpCoinsTest().main() diff --git a/test/functional/abc_wallet_standardness.py b/test/functional/abc_wallet_standardness.py index 48a566d8f..4919c4bbf 100755 --- a/test/functional/abc_wallet_standardness.py +++ b/test/functional/abc_wallet_standardness.py @@ -1,194 +1,194 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the response of wallet to a variety of weird / nonstandard coins that it might try to spend.""" from decimal import Decimal from test_framework.messages import ( CTransaction, CTxOut, FromHex, ToHex, ) from test_framework.script import ( CScript, OP_1, OP_5, OP_CHECKSIG, OP_CHECKMULTISIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160, OP_PUSHDATA1, hash160, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_raises_rpc_error, assert_equal, ) -SATOSHI = Decimal('0.00000001') +SATOSHI = Decimal('0.01') class WalletStandardnessTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 self.extra_args = [['-acceptnonstdtxn=0'], ['-acceptnonstdtxn=1']] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): std_node, nonstd_node = self.nodes address_nonstd = nonstd_node.getnewaddress() # make and mature some coins for the nonstandard node nonstd_node.generate(120) self.sync_blocks() def fund_and_test_wallet(scriptPubKey, is_standard, expected_in_std_wallet, amount=10000, spendfee=500, nonstd_error="scriptpubkey", sign_error=None): """ Get the nonstandard node to fund a transaction, test its standardness by trying to broadcast on the standard node, then mine it and see if it ended up in the standard node's wallet. Finally, it attempts to spend the coin. """ self.log.info("Trying script {}".format(scriptPubKey.hex(),)) # get nonstandard node to fund the script tx = CTransaction() tx.vout.append(CTxOut(max(amount, 10000), scriptPubKey)) rawtx = nonstd_node.fundrawtransaction( ToHex(tx), {'lockUnspents': True, 'changePosition': 1})['hex'] # fundrawtransaction doesn't like to fund dust outputs, so we # have to manually override the amount. FromHex(tx, rawtx) tx.vout[0].nValue = min(amount, 10000) rawtx = nonstd_node.signrawtransactionwithwallet(ToHex(tx))['hex'] # ensure signing process did not disturb scriptPubKey signedtx = FromHex(CTransaction(), rawtx) assert_equal(scriptPubKey, signedtx.vout[0].scriptPubKey) txid = signedtx.rehash() balance_initial = std_node.getbalance() # try broadcasting it on the standard node if is_standard: std_node.sendrawtransaction(rawtx) assert txid in std_node.getrawmempool() else: assert_raises_rpc_error(-26, nonstd_error, std_node.sendrawtransaction, rawtx) assert txid not in std_node.getrawmempool() # make sure it's in nonstandard node's mempool, then mine it nonstd_node.sendrawtransaction(rawtx) assert txid in nonstd_node.getrawmempool() [blockhash] = nonstd_node.generate(1) # make sure it was mined assert txid in nonstd_node.getblock(blockhash)["tx"] self.sync_blocks() wallet_outpoints = {(entry['txid'], entry['vout']) for entry in std_node.listunspent()} # calculate wallet balance change just as a double check balance_change = std_node.getbalance() - balance_initial if expected_in_std_wallet: assert (txid, 0) in wallet_outpoints assert balance_change == amount * SATOSHI else: assert (txid, 0) not in wallet_outpoints assert balance_change == 0 # try spending the funds using the wallet. outamount = (amount - spendfee) * SATOSHI if outamount < 546 * SATOSHI: # If the final amount would be too small, then just donate # to miner fees. outputs = [{"data": b"to miner, with love".hex()}] else: outputs = [{address_nonstd: outamount}] spendtx = std_node.createrawtransaction( [{'txid': txid, 'vout': 0}], outputs) signresult = std_node.signrawtransactionwithwallet(spendtx) if sign_error is None: assert_equal(signresult['complete'], True) txid = std_node.sendrawtransaction(signresult['hex']) [blockhash] = std_node.generate(1) # make sure it was mined assert txid in std_node.getblock(blockhash)["tx"] self.sync_blocks() else: assert_equal(signresult['complete'], False) assert_equal(signresult['errors'][0]['error'], sign_error) # we start with an empty wallet assert_equal(std_node.getbalance(), 0) address = std_node.getnewaddress() pubkey = bytes.fromhex(std_node.getaddressinfo(address)['pubkey']) pubkeyhash = hash160(pubkey) # P2PK fund_and_test_wallet(CScript([pubkey, OP_CHECKSIG]), True, True) fund_and_test_wallet( CScript([OP_PUSHDATA1, pubkey, OP_CHECKSIG]), False, False, sign_error='Data push larger than necessary') # P2PKH fund_and_test_wallet(CScript( [OP_DUP, OP_HASH160, pubkeyhash, OP_EQUALVERIFY, OP_CHECKSIG]), True, True) # The signing error changes here since the script check (with empty # scriptSig) hits OP_DUP before it hits the nonminimal push; in all # other cases we hit the nonminimal push first. fund_and_test_wallet(CScript( [OP_DUP, OP_HASH160, OP_PUSHDATA1, pubkeyhash, OP_EQUALVERIFY, OP_CHECKSIG]), False, False, sign_error='Unable to sign input, invalid stack size (possibly missing key)') # Bare multisig fund_and_test_wallet( CScript([OP_1, pubkey, OP_1, OP_CHECKMULTISIG]), True, False) fund_and_test_wallet( CScript([OP_1, OP_PUSHDATA1, pubkey, OP_1, OP_CHECKMULTISIG]), False, False, sign_error='Data push larger than necessary') fund_and_test_wallet( CScript([OP_1, pubkey, b'\x01', OP_CHECKMULTISIG]), False, False, sign_error='Data push larger than necessary') fund_and_test_wallet( CScript([b'\x01', pubkey, OP_1, OP_CHECKMULTISIG]), False, False, sign_error='Data push larger than necessary') # Note: 1-of-5 is nonstandard to fund yet is standard to spend. However, # trying to spend it with our wallet in particular will generate # too-dense sigchecks since our wallet currently only signs with ECDSA # (Schnorr would not have this issue). fund_and_test_wallet( CScript([OP_1, pubkey, pubkey, pubkey, pubkey, pubkey, OP_5, OP_CHECKMULTISIG]), False, False, sign_error='Input SigChecks limit exceeded') fund_and_test_wallet( CScript([OP_1, pubkey, pubkey, pubkey, OP_PUSHDATA1, pubkey, pubkey, OP_5, OP_CHECKMULTISIG]), False, False, sign_error='Data push larger than necessary') # Dust also is nonstandard to fund but standard to spend. fund_and_test_wallet( CScript([pubkey, OP_CHECKSIG]), False, True, amount=200, nonstd_error="dust") # and we end with an empty wallet assert_equal(std_node.getbalance(), 0) if __name__ == '__main__': WalletStandardnessTest().main() diff --git a/test/functional/data/rpc_getblockstats.json b/test/functional/data/rpc_getblockstats.json index c665a5e1c..c50ecd700 100644 --- a/test/functional/data/rpc_getblockstats.json +++ b/test/functional/data/rpc_getblockstats.json @@ -1,192 +1,192 @@ { "blocks": [ "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000", "0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f05c5f72f5d6da3085583e75ee79340eb4eff208c89988e7ed0efb30b87298fa3194ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f510101082f454233322e302f020000ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002059edda517fd40f325209ff637cb09e57a57a4fcf3996a91159883883173bc020290a5e3decf29ecef819d5bb6325c9a31c8c0776198e53d2ec063549e8dba7141a4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f520101082f454233322e302f020000ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020fa6031a13431d3c7e6736ff6eec6d6f788fc921460fce42ddd9784c23f59cc14bfd84ecad3dfae7049e9511fb8e4f00c2fb50ad9cbf3199dcdc846bd0239b7c21a4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f530101082f454233322e302f020000ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020b00d94fa5039c42ac0ff16746c3a7fb5a32fab104b8860cbb5ddce5db08cb72b7fc89c7179bede8976a1cffa5e5a136fc157b553ef3d8e57a21e18c7439106cb1b4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f540101082f454233322e302f020000ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000205f28d13c20222f1103c73038ddec278dfb8b74578f6772b618691edcda49eb2daf2ddb60e46d5febc513527738589aee28633e80b1c52c0ad2bade88a436d0b01b4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f550101082f454233322e302f020000ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002052942b8b2a7b0665aacffdb783b93aeb06b2459f63b2a5a436ff063088bd0c408f259cbb364cff973c79abd829237447d5896bf710888a447a1da20e41d94f821b4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f560101082f454233322e302f020000ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000202c70e730f565c59bf22b7b3953ce450eecef8787d1772552ac003501c3202736042a519ae0e663e229cac4ebc63682d79a7ccf41c2ec8ec3d501ec4acc22ad171b4ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f570101082f454233322e302f020000ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020b5bf9d76d7a679dc611246f34c986e63840d965f36d18618da2e38b50ae6904e818bc7e17a97a846ae151e75c64bc9dfcf72c50a977ef537144aa44c32799fec1c4ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f580101082f454233322e302f020000ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002087ca4d3708af0817cf527dae5006f717bf195ff3148aa071118c5c0badd1e931d9be7c7f539ec216afd83638010c0663b5106c49784651b394c3060ac5ceefb21c4ae75affff7f20020000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f590101082f454233322e302f020000ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020522e7852ceb2b4e4dd03816d9a310220fdfa56ebc020f492a600e712cbfba9117ad4ead415b2663f05872d140a64cab6c07019871e78051374cf2421a8d7246b1c4ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f5a0101082f454233322e302f020000ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020f33d24c987817611a8f69cbd2c16d9941561992f708f480af32c82031d0aa5371ec94fd47feaf46ba41dbca83b4c82bbc0f54a13a71a4d23bbc606a58919135d1c4ae75affff7f20020000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f5b0101082f454233322e302f020000ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020ef7b799800e07164fe7802f7128d58c49e1b039bb9c4cb1834bde2a97685771bb567e06bafefbbe5a31e76c8ffd83348c58c7f523fd3c154e71a6e242a4a27881c4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f5c0101082f454233322e302f020000ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002056c4a58b4e8af4d2774ab2b70d3500013fbf5673799d8a1c23ebe969e25f5f125422cf24a6d01e81080ba59001d9e26e3cf8ef8415d226bcaf7ac4367936d7411c4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f5d0101082f454233322e302f020000ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020ea3407818389d64a23b02df7a9f1dd86f26957f586e40a3f7ccbcf40995e32093021bf163fad8e64fe53c60572e92fa91388aef8e94e13eea92f732495a877df1d4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f5e0101082f454233322e302f020000ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000207b04e79b73d0ea645e3abd4ec54bed200cef19d25292dc1b354b95943acb714890ced93e8a2664eb1ad3ebd5630df026cd7acf533997e5644768304521d2c3071d4ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f5f0101082f454233322e302f020000ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000209a3da7809d36281c89b9120517f565d12d7456dc652edb4fc7da62ee1e03fa4a5418a893920efcdcae2dc82ed7956e6ca749136d43ef9eb1b485dbc626904baf1d4ae75affff7f20020000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f600101082f454233322e302f020000ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020735d257b62ecbeea12269687f5e6145f79a8f81b6059821fbd9c2f59cf726503066020e295e72e4e3cf7f3d5396c19087bc440b05f8824b093be85955fb3177b1d4ae75affff7f20030000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01110101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020a12418aa84f94d087ef8bee8cdf6722020e6c245856c1f0688d503b40ad60a223a87d9a64d2bc0a61f3cb111f8a90eb61ed9a22924273640d5b777b7b996f3ed1d4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01120101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020f1fa75d1913777d20984de4f60aa96ee22080239adb86fc178282d69f16bbc02b3b51185f4ae33ae85f760041e18b73324909cb02b809241da33329306d5e9cc1d4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01130101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020a81502121019cd65ee8dbadf7687798abcfcd04ef184dfcb3981e6a15a9828216a600907be5b07058f55b666295a33a65a41cfabcc10d0c1f4ba721063ea47231e4ae75affff7f20030000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01140101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020f8d30c529b5b978fd64f8fa2938e859fb88b5db99022b4e3dc1e4826ea214c3d6566aeda8939d15fdea3e74998cc4d35475d30ff9bb5ccc30044d4fd4aa46ad21e4ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01150101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000201a430bb8a2684e2099cd480d7ce7931c0196d6c1a9b960f511433df0a08ff359759900d2564c28b0855e4fe37093d0b2d2755854f7600b968a57502e633034221e4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01160101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000206cbb59f89b2e4a091c523e7562526b0d6c10ee44acad4a1df09800db23a76a4381cba840606219d14294251fdfe314f88da735e403242488e09494be02d00f2d1e4ae75affff7f20020000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01170101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000200e4f76b98a1070b980915b7269a947c2d3f992315dee625a5b0845be5c75bc54d5edca00269854d7803822fab4a6d0ae6c3209ac0a9fd95a78d183363e0e2a811e4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01180101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020ab07bae60c3c9ae050078df5b25a99fd4efba7a1850ef88e7d285d39a88baa6af460179022cb654958967022a62c73e969fced52cb5c7fe3bddbd763f77b3ecf1e4ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01190101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020f2e37cfeacd50894fd4a0b03ca2168239216c087face9c545287d181bf8568763bcbd9b3f6c8dc144b36610efefde0fc258284cb977e04a5bbc807595ff700371f4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f011a0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000209c45b8dc86a6c391adff4d42711395e6132a4bf69bfa31cec01b2066afad921f519a3f0f34358a00f2c976cd6ef181d4732c87deb281affa47148af6855600481f4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f011b0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002075c6b135d7a64af0cc5ad7b57c4e2ff4d9962068c75b6cbd8396c10062afa1190c005e5ce7235a85edc80dd925457a1e4430be3ee2fc441d2c5a4a91c0c0222f1f4ae75affff7f200b0000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f011c0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020475e32abe1c870d54311ce00cd5a536be9e9b478eaf3ff2fdcfe3d11414dc803f900b1d8059c29f9472e434252352aa5e7ff18c4178247157fb2e118d15c62b31f4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f011d0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020016ccae54d04937295c13c162ff42c0257d3baa7322362240183eb0dea5cda34175b1a0ebb318f059e392a738212d0f28a53e99d38a0a7e714f56fab654866dc1f4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f011e0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002041b0385c6c3eaa7db201934e1effafa4c1ba4c8f97de16a92efdb7ada050293256c14d0e403d35831bfd41b5d23da11593009cca00b37990a5821750f349cb071f4ae75affff7f20020000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f011f0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000203111530c243fe6e6750702fb7b57f6264ecbb37386db0c4bbabb3fda76edd651b342dff173fa61b4f3f24dd0f8e10bfa9a849183258c6a6f38772174261b5634204ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01200101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000201552f6879ce21ba4654e0b66e73a0fddb76e22beb42c0c608be72faa5f572026311c183b9a3c76e008f583b0895f64b27e5ab3ec1587f180347581e783107a66204ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01210101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000209b0cfeb6916a588497da7609a686b72e2e4ea12f21ae112a8906886593af105c98457a1c7ee2e3da4d58c9e8f6eec3a47d092cde747721e89fac970d95148730204ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01220101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002032213855ef899b504229add8a71930f4d53118274e721512f8c51a1a31af4c1ca5c4ff09a294302e373702e8bce86bde3336292163c5e767f09f0efba68d3e02204ae75affff7f20020000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01230101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000208cc24e11709024a436fccfb21616a64cb65b32778f82586523b6b736f8a5067710b932a617253ae376748e938ab604c81d017b30aca8b7047e0e4ad70438e7a9204ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01240101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000205372fb56e6299bc08e5b4be3eca2e1de3e543e1f7e41bac2e2d13218d3bb3e00a24f52bc1c388e8be28c7ec975ae51ffe6eba3b9d7270f6892267c26f004ee25204ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01250101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000205f935fcd7bb53564e5e82bdbb01851210e7887ad7dcf62914ec02c88e5da5010abe3c263ec01da20cdb827ab58efbc2d91bdf1b1c9eab1f13b7ef2f48d73cba0214ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01260101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020991747b03af816bcfe1d771c1e4bd41b962a4764cb70d2cfa9452f4eb125533afea040bb3aedef0184b3c3c2ece83963f6867af2da5657a2d20f771fa0c3bd44214ae75affff7f20040000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01270101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020455fa2b8d896201a065ea7f5fdbc95618eff76774c3f844e7e6fe845b7794a2d7e14c717b1d7cd13dd1c35840da2ed14277a6045b06252d5584e1ec0cd0aac15214ae75affff7f20030000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01280101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020212618756837f587af0d2ee0e6d816c2fe2c5a0b8ae16f104597323997bfd6505bff966735e2422b228844e16be40225e8074265cef6d4009c3c58d015cfbe31214ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01290101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020aaed9011205512be1abb8350abe6e4fdcb60d4a25c1383e7026042c7468a87692505ab4aefa0259d97d777c88615aecc971b79047ce2d93fc8cb8323fc530106214ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f012a0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020db1b2546a22802ceb49af9e404753fa262184eee3e8a114cb0fa14b92d61d81f8017a12ab1a96a4b5bdcb41a70a7e4ef40e1e05a3f4709de49e1eaf20a98e716214ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f012b0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002045de89366be117704fb3e5b58d7bef576abb922f4ed21cf4d1984178c80334344ed4fe13da35bb2433edfa1d1e07ee48cafdef12b6db99058e54d747a3a99e42224ae75affff7f20040000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f012c0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000206d9c18f863844f9f996ffb3322f60d479b4978e57e9e9806f9bb182e6991740a27da6307181aeda899ba154df2a304d23d9a55604e806088bbbe2f4d7f4d6324224ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f012d0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020f0c5de5e2d7bd66c28ca4b5637fbacda6de1fc0874ecab0708c80af50de5d32d016aabcc33c1f368d6befd9e7943964a62bc17c5b53697b826fd723d90c71b56224ae75affff7f20020000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f012e0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000200fb8496342f29d063d246a192ded59ef466adc6f26f66ea396f9bbbf0ff42d39d2d539072d9c6a3ffb984ca917ee0b33ffe5771b5342524435383fb4044961b3224ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f012f0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020eba9cb86a10a42b0f5f286eaa1aadbc86a4d3718541b40bf2db41b14b92c8b6c4cc12f3ec5d9b9079815f992b2e08ded31280c398b88ee290bed6992ed39957d224ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01300101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020a57a18b94c1714ea23eaef81fcfa4e4408402edceace5e116b480438ce2e6f4969206abadcab3aa431828367ca3336d9272bb8da8b80f67f719eb43535dfc3c6224ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01310101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020eef6dd6ea1d190afc766fa548d0fa7d476636683af27ce6e7a4533777774ad3981e6f90fd4af5f2a069b1c56f26fdc3ca4494978f694ecde35f9da6764bd0aaf234ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01320101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020a247081d2353d9f4e7773a52d5fa9938b19b0f90e23562b3cfda0dd8dac6555d18d645c7edae8b9416b7af709efb632ef1ae0388c9b852fd6e900b84efe957b9234ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01330101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020af342558da966f8ee26e22ca7dc1c2d9b74ac86bcaf5dcc3ce8848e2b70d486d56cb5914727471ee18bfaf43e5174bb4af879c06c3e5a4f0fa1392016fac2373234ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01340101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000205acb790c53142fe2d09c76e898d2d6f4267d506a1ff5803aed20bdce99aa901d813b77067fb4c752dbb4e8652cf1cd87431a20ca0f0c933ef064a8118a9beb0a234ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01350101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020d6be749a5ecf079cdc6cee698c8feca7d1cc252ac9a0aaff25a1bed4a32b4743fe0c5cc5c5bd9a8af2e2d5306960ef03416abe4d9b3f9458e126ce0904bbd99e234ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01360101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020086beb8b5e5cf87c2043af4d354ec1d35e5ccf56cc7b98076dddca5913eb1204969112e019c1b503c10a279f7b1118b8d8fa3f06bc3e9acf3241d3f8aad7abe5234ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01370101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020dada0bfd78900f4bd6a1af5c8113cfa4df8bf17b7a9b4cd1ad9666c0ab1a776eb8ba46a3a741145b1a167ea89d187a46469f4c9f72c5014e9c2dd5e628504720244ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01380101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002027e648724d273440ac29266167820a59540d0e46c8f945afd226885c95b3d93178c06819212994d9de4199046004cd3c918bf66c06ea698367c66bcb99ffeea8244ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01390101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000208bb802e0b250f6581b0ad779c0bfc70178a1a3382b3e8cfcd6ab4bc488edcc2f35f0f5f9f6048d0cf39d92f02c079846b7b8892ce589a51c1d5e2a8cd9e64f42244ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f013a0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020a2cf81f4f4bf14aa669517ef0faccdd42cb50eaa29da3d91f09d419feb032c2c374c3e6edda32bda2d320cd4165d0d953cec99f8eb2063d80977fb49a67f130c244ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f013b0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000205df9cc6ff50c45418fccaa77dc977dead2b82467ce3d0a4e11a258b55f60e45940fa0652178e1a6a14d1d0b0c425d16e672c4c9a9cd8bf106a27ff8c032ebf83244ae75affff7f20030000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f013c0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020e3e4b897cb10171179a3ac4f03f2962afdb3392af2984c9d5ac7112e1a1d740420d63103385cb561732bd3149228d8c50074ab1e1b719195a7bf2f2d58030853244ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f013d0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020fe83efd0801550eb20bcefeebeb60cc5a9a848ef728e104e5560a70b0ec3ca4ff9fde06edb6d559f1572dcd24a8989f36016589cee62ebaa747dd91359ec173d254ae75affff7f20020000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f013e0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020caaf425af909136270e14b6bb3939acca4ce3bdb3c6d0f5e697f2c7cd453363a79efa85492883b533962e7dfde24845888900cd359b0bdc67083f7484f4f5e92254ae75affff7f20040000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f013f0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020391c5a35679c55561d620405449ed56a28523d0de89a72690070d65c125dcb5fdc440c870669e9ae88f4ca0772ccab915bb98d03085fea3816cb03532da601f8254ae75affff7f20030000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01400101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002009a780ab1368962cc2581eea70e85001ccff594c4632b4912c209cbb0dacac0529a777b1249f08c94d232aa39d4b9fbe9ffb78a12a494b87f9205d069e607a03254ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01410101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002069cfc81ac48ab8bbd03169e10af15844ce3e546e7dfae46f581a683280d7c03975c8b863e3850f9357b32d02cc533fa3f8d512d1fd4c3d6c96b35450a97e6933254ae75affff7f20020000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01420101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020b98378035eddf8db106e65a2fe098988a04de3385347d7a16428a886f934fa61117a0992d9e4f1f7fec0180236f063fb8d2fa7b3e5af5ecbcf524bbe306f46a9254ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01430101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002085cb81e4ea539d8bdd3f8fedd8d9664f30615ea41833d621af2b130d1fce735a537b960263888ccd07985402338f9f2ca4fffdbffcc20c26fff1b1f6684fab10264ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01440101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000207c0024f5a08e97da8cfac34b75282d3af5424903b70c4012bdc6d24a5bbfd6442bc3021ebb66d045010fb79e2065a0a1afa4e982e94083d325aebbc8383a0682264ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01450101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020f2f2388fc7a228c68d6158d9c913909e57515556d84aa6d8d43f4a8f735e3d3f6115d03dca6963b2ef2b2274eada3bb37f776db275d3a0781d383e1dd4aace2f264ae75affff7f20030000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01460101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020dd7205d2817518e69e21522f846f170eecda41dbd8cd320928a793dc4312840f742585219a427da8f1100c4c1cea1d451ba8735787ebaa2c31a706bb7d15a78e264ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01470101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020e644d5e43dd26a708d8f854698c56976ac886fd1f4f2598651d7e0c5c96e0f7339c2f49528a192d8e39903c952f917589f208a800147716e053a221162d55225264ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01480101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000208902dda92498d34022c25b2fca163930d9a4690551f04ef1e5cf7841cac8e72b6cb7299ccedba0af6a55b819b686ed6d7ea64b17c5ab6ab240972afd30d714bd264ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01490101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002065ca0c01f7f1224cb92a34dfd8223acbe0527f181b70ccb41afa7514afe9f82371fbf507769b203a0dbf88fdf144f9c8d1d24aaa4112991f031e10e671476362274ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f014a0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000207a7e9a3588db2f7fa103c2b70dbba0b6a2b63fac1a9a8042e555e97e1411a14e546185c553dbe70874b8d01d777760d1a8a711843648a70cc713b094d8614c7b274ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f014b0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002076694d416421cde83d7766af651f3ba5648a0661b12fd4b2b5d4a3258661e1039d205fae743d3fdf45a6263e60f31ceae5f08d1a25e0c9f0623bba0cec88e074274ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f014c0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020a0905a6b411af2ea9663b959585d47500f181b9dacba2249aca98be4c119dd4fd5c608097db31fec92db207fe8f8a2a6c2fce52164afdae99f8dfa305ab60aae274ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f014d0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002019437bda1d49679f4f0c209efedb1c57ee0440c32ec78fe55efd110206478557008ed3339fa53a0e2c58a42fc3709bf30974f6195e1cf7a6dc5af5cd03d372d2274ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f014e0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000201ac5df6ff9f8606dcdf765ac1c8d601777f918bc688d401c9e3cefe00b84506e958dea48d6816a2c4d8d1cd5208e9e75c5a332b1623a1c59f1a49711ef3e6763274ae75affff7f20020000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f014f0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000206003d23f072d18cf43507a9a5c2203145d608c3f549d22dc1964a22956cb5928a411e8745618f521719c4ee5b3a701f16c09f79feadf02c392bef3d54507fe08284ae75affff7f20020000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01500101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020f2bc6a8251a8ffb8f8593129e2014a902f43975530d0eb25e14aff1c0ab1833dccb498798ce6cc6974dcfe0ba1f61607fcac1b252be7d2e24b11a5da7141a022284ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01510101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020b9aa528192f167604d88478aefed464d0d508ad30683a8d5c49e86bb2e28f814f6d311a7cd9818f9ee0b7566889e80e778735db7b23373ab839bc574376eadf0284ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01520101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000209ffcc98849b428c47d9b8ff7577fdaeb8281e0d04dde36d4b665071676275c17616e38f30433eeb5bc3f40d8643f0b92ab31c40c7e3f51e062731bf2cb43dff0284ae75affff7f20040000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01530101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000207660a6836474d074afa5e394e9de07ac519312a856fdca952354581dec98484d1cab2e676890ebffa46dd587f9d743db12f8ed63dc1522d65700ef143afcf486284ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01540101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000204feebca1ee4c76be636b6350644ed708b3cdb671d9cf4938f3348bfefa309a6b87474299b1fad208fb1d8fd47433d9c84671544b38ba1660bff0e691b2ddba7c284ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01550101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002073000e74e0e62c7ae1d6eb442553b6e9a28ae1773e1bf99d6debbae26bb72812e5a5598eb5d4eb93a4470c1a2ca0c00d2bbf0eddac8e5c4af8f0870447bc0c94294ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01560101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020c95e2e44260674cee71e65c0c957e9e24fe1371be28f024f582f7b094c9a6c6af4a4448150d0612d2d8ade42281765334349b450bf7d264de5a0fb8683f79ab5294ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01570101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020fda3d3a0f90125539d3dc9486f9204df9e5c6617e8a5b95376a0f86cfe529c00d29fc1df5010b8e81a9f91dd10570b4194f3624763b8c1a19beee896bc5c5bc4294ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01580101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020874c69768505ac7a68ba806b7a416f9ad738b8f9d2670ee766172726c878d7347476e8023b1c185b4b093c7c047d7a3c4cc33ac55b3cc8289e4d6aa34481a5f5294ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01590101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020f54086e370de2245c82c4cfb33fcbdfddb5ced94b57720a98024888da1a0a929f3eab66ca986518ae7a7c9f2e17bba0fd4106221cd4a21fcd7c4ca81232915a5294ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f015a0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002037a6017e2708f4bddc2ae5a7fb6356ef677da0825204e5584e41bbd4dcfe3e6d7cf3dd1fadb0a6de6a79ce4bc436eb4bfa3d084eea16c2a8755b2d7f9aae0af6294ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f015b0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000209fd12c3379c81cd804e9abf66ceaadd9093a2dab8a9576b0de8904c520c50e4cbdbdaabe755ae271f9f86a860f88f0c6c2a944f0ebdae3af375444d9ef0dcfa12a4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f015c0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020509ffd1b28ca8c5a7cc8530a115517324cc6c159b8885a4a457d37366effb749d53fd9b67cfddba5e9870de737e1e2a1b58dcfba377095ad015a0b0b883043322a4ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f015d0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000206895f0ffccc0d1bfc158ba132a23d92e4d50a2d2717fe28e4e43dcd3d53c80590472e5f9ef79a9a070cc2bbd19416299282cb50c681b3cb0dd256f6810dfd3192a4ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f015e0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020d182f4da283a2a815e60d02e6c1727b3dd0ba23c2fc849dde00567beea4a7f724bfcfc029597d943a01c25a6a06ccd4b5e1b02cbbf40172bda793063811251222a4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f015f0101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020fc9a74d8312fc37d1027c848dfd0c20cfc7cef8f79239beecbd2022f3179462f73dbb55b0fa7aaecb3a9286d5206d11bb20e0b050ecd2a5240805fc9d7a76acb2a4ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01600101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002037d0cf6304bf1b519be55cf3f7e15605a84bd9db222554f227b0d922e4129f3a85070dd8abc62650738c9f633addb1f19c1b16b073f5e8ffb44c88784fedc35b2a4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01610101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "0000002072ffe22421c091ea11313ffe40b92d32b6f987b5764625d709de8ea7d4c5bc53c8ca535211867f1e0e7e57fee5a510a7435903a86fdea5320ea93b7f95a81b792b4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01620101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "000000205bd302491bc690cea24422e800747fa3fd1b6fb137fb5018c47b11ad90c52755e2124f3b902a4f368a89d03058d0fbd69ce494ba6055bf299f5ed9d75a755fb62b4ae75affff7f20010000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01630101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020a7dcc55b04a546cddd1c764e69337ed230834ca66fd51bd23adcb673734d7c5c053a47d82618fa4df6a0db6f0d9e677c44945bba6f413fec44b29b25a770057f2b4ae75affff7f20030000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01640101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", "00000020818587bde832de22be348e70c58e5cecb25c0395bff51405a8fd3b9fad1042688109df13eb0a5c0cf872f0b9105c13345cb577fcb5c67654556ae163d51083ce2b4ae75affff7f20000000000102000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01650101082f454233322e302f0100ffffffff0100f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000", - "000000201e2815a75750af357dcb654f9f428dda7a504365c6fab2c1ef877de60ae1c241647fc8d4d1a48d1374a4bfb7ecad1493058dea42e5ee87edfc12abc50e2616a02b4ae75affff7f20020000000202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01660101082f454233322e302f0100ffffffff01e1f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000020000000105c5f72f5d6da3085583e75ee79340eb4eff208c89988e7ed0efb30b87298fa3000000006a4730440220581f48d1a5c9c4a869cec92dbe94df5c3c28a8c27d638b23e24919c4e6aee71c022024e0357d5a5976e6d201bb2f53a6251b89daeda785dfb9c6fcb610da0d1e3ed541210227d85ba011276cf25b51df6a188b75e604b38770a462b2d0e9fb2fc839ef5d3ffeffffff0200286bee000000001976a91449f2f195be42ed62b12a8f9da073abcbecb9c10288ac1fc99a3b000000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac65000000", - "00000020421d192d36ce9f0e4f2082ed947acc18b7eedc7c1f30cd79e26484ce36f3ae04fb9002c4cfb89aa6d302c2eabe7c38ccb9ac56c0c182b81357f154fde7f7655b2b4ae75affff7f20000000000402000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01670101082f454233322e302f0100ffffffff016efb062a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac000000000200000001c4d80cdcd39a6f766d637a6ac63a35255e022bda33e02d6d68202bdff1ecea6c010000006a47304402202a8e13786869874071d527f36102241df28f779a3272ea6d5f5aa89c544af47e0220262c806e828530d8a90f8677ceff72720e53e082e4228a2240dcfa6a0cc000d041210227d85ba011276cf25b51df6a188b75e604b38770a462b2d0e9fb2fc839ef5d3ffeffffff0254d9f405000000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac1fe8a435000000001976a91401df140f86cf2f6e91584e0a0d9b13579687881d88ac660000000200000001290a5e3decf29ecef819d5bb6325c9a31c8c0776198e53d2ec063549e8dba714000000006a4730440220496af6043dae18fc4b655a33bbc35680e208a37d1f6b8c2633835c1d6ff9d45602205cff70466b2d8b76f83be9a7cf711e0197cf3a60965b62525816bc67cc2f856741210227d85ba011276cf25b51df6a188b75e604b38770a462b2d0e9fb2fc839ef5d3ffeffffff021f276bee000000001976a914a138a9c7be324a2027b6c19c3ecdb7a83fccf76188ac00ca9a3b000000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac660000000200000001c4d80cdcd39a6f766d637a6ac63a35255e022bda33e02d6d68202bdff1ecea6c000000006a47304402203a8437f161265e6ea527ae7ba2c766eb4843b9d9240a1cec27f7ae98bcbb4200022037d5b5ff17c61b7218c02fba92e9cd8226f8cb0ebc24695f499339590ac49597412102c9785a4f32000e6dcde0b1d2d4e98540bece3aba965c3735fbbb0fe90fa063c3feffffff021fc99a3b000000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac005ed0b2000000001976a9146fd0eb1d6092e852b49342e13edb1a757137e38d88ac66000000" + "000000201e2815a75750af357dcb654f9f428dda7a504365c6fab2c1ef877de60ae1c2411e48c28d6c95c9fed016d75954188c8fee5a07d173964ed96f56c1aefb2b219e2b4ae75affff7f20000000000202000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01660101082f454233322e302f0100ffffffff01e1f2052a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000020000000105c5f72f5d6da3085583e75ee79340eb4eff208c89988e7ed0efb30b87298fa3000000006a47304402203f8af1c577648e5c8f26e037db1b6ebce4c9747a9a68881a2801a4083a8e743c02206f284521ecdba3d65ad24d7916777230bba81cfc3236e04bb522bcac9717f75641210227d85ba011276cf25b51df6a188b75e604b38770a462b2d0e9fb2fc839ef5d3ffeffffff021fc99a3b000000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00286bee000000001976a914f8a0fc0922ddc8bb2fd529b32a00a3711487db7c88ac65000000", + "00000020d53e17503adc625c3b7fef097cc086137cf430c615cc3075a94e4960d83df21b1d000d94e94e21c389855e185d2294f16af4f607545670d2354f85147beff0222b4ae75affff7f20000000000402000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0f01670101082f454233322e302f0100ffffffff016efb062a010000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac00000000020000000159f324cda7613a99d56240a1830a717bda217337d91b1fba1f8ce461980be007010000006a4730440220700b3707fbcb8733c3b3e2e82f071d6719638e012f5a008f79146d8df052dfe8022032de384fbca13465cf84320930aff44d00a6880eeb754a331d939704e9377db1412103cdb81a4d773249089d06bb7e7dd32e9f21fb97f878409f23b3e51b8fa2171d2bfeffffff021fc99a3b000000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac005ed0b2000000001976a9141980cde97ad6c3da1a0e7476b04c523a3f573d3f88ac66000000020000000160321f7f890ffac8ba8816865f4930db6c178f00accdebd9d7c3c9138329cdf7000000006a47304402203af1ff083070dd08a0ccf76ae208e2d9920d0a094bb812f2bb51cfff4cd506490220219680ad8cdd167d239daa016a2ed7be4af7a05b2364a10eac74d5278f3c6da8412102329a087e27fd98adee69cc8d0b7dbff77a4b88a35d18347a354834ce09b01c03feffffff0254d9f405000000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac1fb23f71000000001976a91427c43422a1f7beef12221fde2b8362861de36eb888ac3f0000000200000001fb9ac39919c7460ddfabafde0e58efd886d50183de022491fdfa91c6b46cc84c010000006a4730440220798ef637c97bdb0fc8b33d7d73b1b75a82e244a40d2bdaa01071b3748e89bc48022063c4fe316a7b16ff15d000d6ec565f68ea34e7d82ed87dfae9bb242a11ce9d94412102e7cc6f15570139869198f81ba773fbcb1373c918091f8ba25d2e3dd54db30725feffffff021f933577000000001976a9143274ce9d8bda566fb88956c1f437b1d20d47abf688ac00ca9a3b000000001976a9142b4569203694fc997e13f2c0a1383b9e16c77a0d88ac66000000" ], "mocktime": 1525107225, "stats": [ { "avgfee": 0.0, "avgfeerate": 0.0, "avgtxsize": 0, "blockhash": "41c2e10ae67d87efc1b2fac66543507ada8d429f4f65cb7d35af5057a715281e", "height": 101, "ins": 0, "maxfee": 0.0, "maxfeerate": 0.0, "maxtxsize": 0, "medianfee": 0.0, "medianfeerate": 0.0, "mediantime": 1525107242, "mediantxsize": 0, "minfee": 0.0, "minfeerate": 0.0, "mintxsize": 0, "outs": 1, - "subsidy": 50.0, + "subsidy": 50000000.0, "time": 1525107243, "total_out": 0.0, "total_size": 0, "totalfee": 0.0, "txs": 1, "utxo_increase": 1, "utxo_size_inc": 75 }, { - "avgfee": 2.25e-06, - "avgfeerate": 1e-08, + "avgfee": 2.25, + "avgfeerate": 0.01, "avgtxsize": 225, - "blockhash": "04aef336ce8464e279cd301f7cdceeb718cc7a94ed82204f0e9fce362d191d42", + "blockhash": "1bf23dd860494ea97530cc15c630f47c1386c07c09ef7f3b5c62dc3a50173ed5", "height": 102, "ins": 1, - "maxfee": 2.25e-06, - "maxfeerate": 1e-08, + "maxfee": 2.25, + "maxfeerate": 0.01, "maxtxsize": 225, - "medianfee": 2.25e-06, - "medianfeerate": 1e-08, + "medianfee": 2.25, + "medianfeerate": 0.01, "mediantime": 1525107242, "mediantxsize": 225, - "minfee": 2.25e-06, - "minfeerate": 1e-08, + "minfee": 2.25, + "minfeerate": 0.01, "mintxsize": 225, "outs": 3, - "subsidy": 50.0, + "subsidy": 50000000.0, "time": 1525107243, - "total_out": 49.99999775, + "total_out": 49999997.75, "total_size": 225, - "totalfee": 2.25e-06, + "totalfee": 2.25, "txs": 2, "utxo_increase": 2, "utxo_size_inc": 150 }, { - "avgfee": 0.0002265, - "avgfeerate": 1e-06, + "avgfee": 226.5, + "avgfeerate": 1.0, "avgtxsize": 225, - "blockhash": "69f1ebab8b9a8363efdde320066c31c261bc16b791d8777d3034dad8e5313116", + "blockhash": "568ebedbf80e79cd59deb4e6bf44deda18df2cb0bb64175a3df8ea1f5c913cd8", "height": 103, "ins": 3, - "maxfee": 0.000675, - "maxfeerate": 3e-06, + "maxfee": 675.0, + "maxfeerate": 3.0, "maxtxsize": 225, - "medianfee": 2.25e-06, - "medianfeerate": 1e-08, + "medianfee": 2.25, + "medianfeerate": 0.01, "mediantime": 1525107243, "mediantxsize": 225, - "minfee": 2.25e-06, - "minfeerate": 1e-08, + "minfee": 2.25, + "minfeerate": 0.01, "mintxsize": 225, "outs": 7, - "subsidy": 50.0, + "subsidy": 50000000.0, "time": 1525107243, - "total_out": 99.99931825, + "total_out": 89999318.25, "total_size": 675, - "totalfee": 0.0006795, + "totalfee": 679.5, "txs": 4, "utxo_increase": 4, "utxo_size_inc": 300 } ] } \ No newline at end of file diff --git a/test/functional/data/rpc_psbt.json b/test/functional/data/rpc_psbt.json index 2a90e5049..ec3162dac 100644 --- a/test/functional/data/rpc_psbt.json +++ b/test/functional/data/rpc_psbt.json @@ -1,143 +1,143 @@ { "invalid" : [ "AgAAAAEmgXE3Ht/yhek3re6ks3t4AAwFZsuzrWRkFxPKQhcb9gAAAABqRzBEAiBwsiRRI+a/R01gxbUMBD1MaRpdJDXwmjSnZiqdwlF5CgIgATKcqdrPKAvfMHQOwDkEIkIsgctFg5RXrrdvwS7dlbMBIQJlfRGNM1e44PTCzUbbezn22cONmnCry5st5dyNv+TOMf7///8C09/1BQAAAAAZdqkU0MWZA8W6woaHYOkP1SGkZlqnZSCIrADh9QUAAAAAF6kUNUXm4zuDLEcFDyTT7rk8nAOUi8eHsy4TAA==", "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQCsAQAAAAABAomjxx6rTSDgNxu7pMxpj6KVyUY6+i45f4UzzLYvlWflAQAAABcWABS+GNFSqbASA52vPafeT1M0nuy5hf////+G+KpDpx3/FEiJOlMKcjfva0YIu7LdLQFx5jrsakiQtAEAAAAXFgAU/j6e8adF6XTZAsQ1WUOryzS9U1P/////AgDC6wsAAAAAGXapFIXP8Ql/2eAIuzSvcJxiGXs4l4pIiKxy/gA=", "cHNidP8BAP0KAQIAAAACqwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QAAAAAakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpL+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAABASAA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHhwEEFgAUhdE1N/LiZUBaNNuvqePdoB+4IwgAAAA=", "cHNidP8AAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAA==", "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQDpAomjxx6rTSDgNxu7pMxpj6KVyUY6+i45f4UzzLYvlWflAQAAABcWABS+GNFSqbASA52vPafeT1M0nuy5hf////+G+KpDpx3/FEiJOlMKcjfva0YIu7LdLQFx5jrsakiQtAEAAAAXFgAU/j6e8adF6XTZAsQ1WUOryzS9U1P/////AgDC6wsAAAAAGXapFIXP8Ql/2eAIuzSvcJxiGXs4l4pIiKxy/vhOLAAAABepFDOXJboh79Yqx1OpvNBn1semo50FhwJHMEQCICcSviLgJw85T1aDEdx8qaaJcLgCX907JAIp8H+KXzoBAAwAAAAAAAAAAANqAQAAAAA=", "cHNidP8CAABVAgAAAAEnmiMjpd+1H8RfIg+liw/BPh4zQnkqhdfjbNYzO1y8OQAAAAAA/////wGgWuoLAAAAABl2qRT/6cAGEJfMO2NvLLBGD6T8Qn0rRYisAAAAAAABACCVXuoLAAAAABepFGNFIA9o0YnhrcDfHE0W6o8UwNvrhyICA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GRjBDAiAEJLWO/6qmlOFVnqXJO7/UqJBkIkBVzfBwtncUaUQtBwIfXI6w/qZRbWC4rLM61k7eYOh4W/s6qUuZvfhhUduamgEBBCIAIHcf0YrUWWZt1J89Vk49vEL0yEd042CtoWgWqO1IjVaBIgYDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYQtKa6ZwAAAIAAAACABAAAgCIGA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9ELSmumcAAACAAAAAgAUAAIABBUdSIQOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RiED3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg71SrgAA", "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAIAACCVXuoLAAAAABepFGNFIA9o0YnhrcDfHE0W6o8UwNvrhyICA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GRjBDAiAEJLWO/6qmlOFVnqXJO7/UqJBkIkBVzfBwtncUaUQtBwIfXI6w/qZRbWC4rLM61k7eYOh4W/s6qUuZvfhhUduamgEBBCIAIHcf0YrUWWZt1J89Vk49vEL0yEd042CtoWgWqO1IjVaBIgYDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYQtKa6ZwAAAIAAAACABAAAgCIGA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9ELSmumcAAACAAAAAgAUAAIABBUdSIQOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RiED3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg71SrgAA", "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEAIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIQIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYwQwIgBCS1jv+qppThVZ6lyTu/1KiQZCJAVc3wcLZ3FGlELQcCH1yOsP6mUW1guKyzOtZO3mDoeFv7OqlLmb34YVHbmpoBAQQiACB3H9GK1FlmbdSfPVZOPbxC9MhHdONgraFoFqjtSI1WgSIGA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GELSmumcAAACAAAAAgAQAAIAiBgPeVdHh2sgF4/iljB+/m5TALz26r+En/vykmV8m+CCDvRC0prpnAAAAgAAAAIAFAACAAQVHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4AAA==", "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEAIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQIEACIAIHcf0YrUWWZt1J89Vk49vEL0yEd042CtoWgWqO1IjVaBIgYDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYQtKa6ZwAAAIAAAACABAAAgCIGA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9ELSmumcAAACAAAAAgAUAAIABBUdSIQOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RiED3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg71SrgAA", "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEAIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoEhBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9ELSmumcAAACAAAAAgAQAAIAiBgPeVdHh2sgF4/iljB+/m5TALz26r+En/vykmV8m+CCDvRC0prpnAAAAgAAAAIAFAACAAQVHUiEDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUYhA95V0eHayAXj+KWMH7+blMAvPbqv4Sf+/KSZXyb4IIO9Uq4AAA==", "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHAgcA2gBHMEQCIHQBitQYAJe4czI8ABVyCzaEzIEjiRBI59vNm1WtZ5yZAiBz02m3QOPrU9zvozgjyAcFFMpVp92VRPFXwWeRMmERjAFIMEUCIQD2EDizCNwdqGWjSFJ0bwFXcpNCCMbSRFQ5PNmb3yIXdwIgBW5nWmdabQoCuFsU5eKQdNiiWptXYL6igW9mGRCgBuoBR1IhApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/IQLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU211KuAAEAIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHAgcAIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==", "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHAQfaAEcwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMAUgwRQIhAPYQOLMI3B2oZaNIUnRvAVdyk0IIxtJEVDk82ZvfIhd3AiAFbmdaZ1ptCgK4WxTl4pB02KJam1dgvqKBb2YZEKAG6gFHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4AAQAgAMLrCwAAAAAXqRS39fr0Dj1ApaRZsds1NfK3L6kh6IcBByMiACCMI1MXN0O1ld+0oHtyuo5C43l9p06H/n2ddJfjsgKJAwEI2gQARzBEAiBi63pVYQenxz9FrEq1od3fb3B1+xJ1lpp/OD7/94S8sgIgDAXbt0cNvy8IVX3TVscyXB7TCRPpls04QJRdsSIo2l8BRzBEAiBl9FulmYtZon/+GnvtAWrx8fkNVLOqj3RQql9WolEDvQIgf3JHA60e25ZoCyhLVtT/y4j3+3Weq74IqjDym4UTg9IBR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACECA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYcQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==", "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEAHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wCAwAEQQAAAAABABYAFGLp6YL/803YI5YQMWsJDNKjt0fLAAEAIgAgh2utgy8dFoAV7UEjKp6mWhgV2e8TwO+HWfZLWysnimUBASVRIQO3ziOgHFtL8ApkJTfN+rsxW2aDMoZ0eO9RMJ0r1X+Kh1GuAA==", "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAACAAAWABRi6emC//NN2COWEDFrCQzSo7dHywACAAAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A", "cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAQAWABRi6emC//NN2COWEDFrCQzSo7dHywABACIAIIdrrYMvHRaAFe1BIyqeploYFdnvE8Dvh1n2S1srJ4plIQEAJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A", "cHNidP8BAHMCAAAAAbiWoY6pOQepFsEGhUPXaulX9rvye2NH+NrdlAHg+WgpAQAAAAD/////AkBLTAAAAAAAF6kUqWwXCcLM5BN2zoNqMNT5qMlIi7+HQEtMAAAAAAAXqRSVF/in2XNxAlN1OSxkyp0z+Wtg2YcAAAAAAAEBIBNssgAAAAAAF6kUamsvautR8hRlMRY6OKNTx03DK96HAQcXFgAUo8u1LWpHprjt/uENAwBpGZD0UH0BCGsCRzBEAiAONfH3DYiw67ZbylrsxCF/XXpVwyWBRgofyRbPslzvwgIgIKCsWw5sHSIPh1icNvcVLZLHWj6NA7Dk+4Os2pOnMbQBIQPGStfYHPtyhpV7zIWtn0Q4GXv5gK1zy/tnJ+cBXu4iiwABABYAFMwmJQEz+HDpBEEabxJ5PogPsqZRAAEAFgAUyCrGc3h3FYCmiIspbv2pSTKZ5jU", "cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAQEAAQEBagA=", "cHNidP8BACoCAAAAAAFAQg8AAAAAABepFG6Rty1Vk+fUOR4v9E6R6YXDFkHwhwAAAAAAAQAAAQABagA=", "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJ//////////8AAQEJAADK/gAAAAAAAA==", "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQMErd7f7gEDBAEAAAAA", "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQQAAQQBagA=", "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJAOH1BQAAAAAAAQUAAQUBUQA=", "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQcAAQcBUQA=", "cHNidP8BADMBAAAAAREREREREREREREREREREREREfrK3hERERERERERERERfwAAAAD/////AAAAAAAAAQEJAOH1BQAAAAAAAQgBAAEIAwEBUQA=" ], "valid" : [ "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQAiAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrAAAAA==", "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQAgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA", "cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQAiAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrAEDBEEAAAAAAAA=", "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEAIgDC6wsAAAAAGXapFIXP8Ql/2eAIuzSvcJxiGXs4l4pIiKwAAQAgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIACICAurVlmh8qAYEPtw94RbN8p1eklfBls0FXPaYyNAr8k6ZELSmumcAAACAAAAAgAIAAIAAIgIDlPYr6d8ZlSxVh3aK63aYBhrSxKJciU9H2MFitNchPQUQtKa6ZwAAAIABAACAAgAAgAA=", "cHNidP8BAFUCAAAAASeaIyOl37UfxF8iD6WLD8E+HjNCeSqF1+Ns1jM7XLw5AAAAAAD/////AaBa6gsAAAAAGXapFP/pwAYQl8w7Y28ssEYPpPxCfStFiKwAAAAAAAEAIJVe6gsAAAAAF6kUY0UgD2jRieGtwN8cTRbqjxTA2+uHIgIDsTQcy6doO2r08SOM1ul+cWfVafrEfx5I1HVBhENVvUZGMEMCIAQktY7/qqaU4VWepck7v9SokGQiQFXN8HC2dxRpRC0HAh9cjrD+plFtYLisszrWTt5g6Hhb+zqpS5m9+GFR25qaAQEEIgAgdx/RitRZZm3Unz1WTj28QvTIR3TjYK2haBao7UiNVoEiBgOxNBzLp2g7avTxI4zW6X5xZ9Vp+sR/HkjUdUGEQ1W9RhC0prpnAAAAgAAAAIAEAACAIgYD3lXR4drIBeP4pYwfv5uUwC89uq/hJ/78pJlfJvggg70QtKa6ZwAAAIAAAACABQAAgAEFR1IhA7E0HMunaDtq9PEjjNbpfnFn1Wn6xH8eSNR1QYRDVb1GIQPeVdHh2sgF4/iljB+/m5TALz26r+En/vykmV8m+CCDvVKuAAA=" ], "creator" : [ { "inputs" : [ { "txid":"75ddabb27b8845f5247975c8a5ba7c6f336c4570708ebe230caf6db5217ae858", "vout":0 }, { "txid":"1dea7cd05979072a3578cab271c02244ea8a090bbb46aa680a65ecd027048d83", "vout":1 } ], "outputs" : [ { - "ecregtest:qrv9c2m36qrqkzwf3p4whq272zv3mksjf5sdqg0py5": 1.49990000 + "ecregtest:qrv9c2m36qrqkzwf3p4whq272zv3mksjf5sdqg0py5": 1499900.00 }, { - "ecregtest:qqq2a2dzuhc0sa493r0423hgwsk3mpcq3uwrz3g0nr": 1 + "ecregtest:qqq2a2dzuhc0sa493r0423hgwsk3mpcq3uwrz3g0nr": 1000000 } ], "result" : "cHNidP8BAKACAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAZdqkU2FwrcdAGCwnJiGrrgV5QmR3aEk2IrADh9QUAAAAAGXapFACuqaLl8Ph2pYjfVUbodC0dhwCPiKwAAAAAAAAAAAA=" } ], "signer" : [ { "privkeys" : [ "cP53pDbR5WtAD8dYAW9hhTjuvvTVaEiQBdrz9XPrgLBeRFiyCbQr", "cR6SXDoyfQrcp4piaiHE97Rsgta9mNhGTen9XeonVgwsh4iSgw6d" ], "psbt" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHAQMEQQAAAAEER1IhApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/IQLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU211KuIgYClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8Q2QxqTwAAAIAAAACAAAAAgCIGAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXENkMak8AAACAAAAAgAEAAIAAAQAgAMLrCwAAAAAXqRT2U5MH46SNHgE20GH10f4Z4aJAiYcBAwRBAAAAAQRHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAEFR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=", "result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHIgIClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H9HMEQCIFeeX7PDIvPZfNrFqWNn0HYRnoT1rlK5Z6WDG3bEROr1AiB2sWDcIhR/xfSYYn09htmayTznOW5Qm2bPIpe3y5lN3EEBAwRBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABACAAwusLAAAAABepFPZTkwfjpI0eATbQYfXR/hnhokCJhyICAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcRzBEAiBnrEB7WcwLuWdisBpe6dVj/d3RTo0CCmRQJjnWVNBCDQIgH3K+5DYrjna1qu3uYVvW81W5g9tGDs9qfelLV5uyeTtBAQMEQQAAAAEER1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuIgYCOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnMQ2QxqTwAAAIAAAACAAwAAgCIGAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcENkMak8AAACAAAAAgAIAAIABBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSrgAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA" }, { "privkeys" : [ "cT7J9YpCwY3AVRFSjN6ukeEeWY6mhpbJPxRaDaP5QTdygQRxP9Au", "cNBc3SWUip9PPm1GjRoLEJT6T41iNzCYtD7qro84FMnM5zEqeJsE" ], "psbt" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHAQMEQQAAAAEER1IhApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/IQLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU211KuIgYClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8Q2QxqTwAAAIAAAACAAAAAgCIGAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXENkMak8AAACAAAAAgAEAAIAAAQAgAMLrCwAAAAAXqRT2U5MH46SNHgE20GH10f4Z4aJAiYcBAwRBAAAAAQRHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAEFR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=", "result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHIgIC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdHMEQCIEc9sDvOD0k8Tt5OFB+T50LmvVA81qJJRuUoT+iwjk0TAiAUtOqy8XPsyzPaW5tbtO9ZYOx7FrGlb0xuQWMXwWvEpUEBAwRBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABACAAwusLAAAAABepFPZTkwfjpI0eATbQYfXR/hnhokCJhyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBFNHTUzHDC90JjhUb/eLIMV/TuOkV1jB6FQ5+6xXJ8RAIgdQkUT1nyjPBHJuK+Ah15FymwaCitJs94Y/+uLp5LshVBAQMEQQAAAAEER1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuIgYCOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnMQ2QxqTwAAAIAAAACAAwAAgCIGAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcENkMak8AAACAAAAAgAIAAIABBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSrgAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA" }, { "privkeys" : [ "cT7J9YpCwY3AVRFSjN6ukeEeWY6mhpbJPxRaDaP5QTdygQRxP9Au", "cNBc3SWUip9PPm1GjRoLEJT6T41iNzCYtD7qro84FMnM5zEqeJsE" ], "psbt" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHAQMEQQAAAAEER1IhApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/IQLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU211KuIgYClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8Q2QxqTwAAAIAAAACAAAAAgCIGAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXENkMak8AAACAAAAAgAEAAIAAAQAgAMLrCwAAAAAXqRT2U5MH46SNHgE20GH10f4Z4aJAiYcBAwRBAAAAAQRHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAEFR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=", "result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHIgIC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdHMEQCIEc9sDvOD0k8Tt5OFB+T50LmvVA81qJJRuUoT+iwjk0TAiAUtOqy8XPsyzPaW5tbtO9ZYOx7FrGlb0xuQWMXwWvEpUEBAwRBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABACAAwusLAAAAABepFPZTkwfjpI0eATbQYfXR/hnhokCJhyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBFNHTUzHDC90JjhUb/eLIMV/TuOkV1jB6FQ5+6xXJ8RAIgdQkUT1nyjPBHJuK+Ah15FymwaCitJs94Y/+uLp5LshVBAQMEQQAAAAEER1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuIgYCOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnMQ2QxqTwAAAIAAAACAAwAAgCIGAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcENkMak8AAACAAAAAgAIAAIABBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSrgAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA" }, { "privkeys" : [ "cNBc3SWUip9PPm1GjRoLEJT6T41iNzCYtD7qro84FMnM5zEqeJsE" ], "psbt" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHAQMEQQAAAAEER1IhApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/IQLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU211KuIgYClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8Q2QxqTwAAAIAAAACAAAAAgCIGAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXENkMak8AAACAAAAAgAEAAIAAAQAgAMLrCwAAAAAXqRT2U5MH46SNHgE20GH10f4Z4aJAiYcBAwRBAAAAAQRHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAEFR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=", "result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHAQMEQQAAAAEER1IhApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/IQLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU211KuIgYClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8Q2QxqTwAAAIAAAACAAAAAgCIGAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXENkMak8AAACAAAAAgAEAAIAAAQAgAMLrCwAAAAAXqRT2U5MH46SNHgE20GH10f4Z4aJAiYciAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgRTR01MxwwvdCY4VG/3iyDFf07jpFdYwehUOfusVyfEQCIHUJFE9Z8ozwRybivgIdeRcpsGgorSbPeGP/ri6eS7IVQQEDBEEAAAABBEdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==" }, { "privkeys" : [ "cT7J9YpCwY3AVRFSjN6ukeEeWY6mhpbJPxRaDaP5QTdygQRxP9Au", "cNBc3SWUip9PPm1GjRoLEJT6T41iNzCYtD7qro84FMnM5zEqeJsE" ], "psbt" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHIgIC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdIMEUCIQD2EDizCNwdqGWjSFJ0bwFXcpNCCMbSRFQ5PNmb3yIXdwIgBW5nWmdabQoCuFsU5eKQdNiiWptXYL6igW9mGRCgBuoBAQMEQQAAAAEER1IhApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/IQLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU211KvIgYClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8Q2QxqTwAAAIAAAACAAAAAgCIGAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXENkMak8AAACAAAAAgAEAAIAAAQAgAMLrCwAAAAAXqRT2U5MH46SNHgE20GH10f4Z4aJAiYciAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBEEAAAABBEdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==", "result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHIgIC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdIMEUCIQD2EDizCNwdqGWjSFJ0bwFXcpNCCMbSRFQ5PNmb3yIXdwIgBW5nWmdabQoCuFsU5eKQdNiiWptXYL6igW9mGRCgBuoBAQMEQQAAAAEER1IhApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/IQLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU211KvIgYClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8Q2QxqTwAAAIAAAACAAAAAgCIGAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXENkMak8AAACAAAAAgAEAAIAAAQAgAMLrCwAAAAAXqRT2U5MH46SNHgE20GH10f4Z4aJAiYciAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBEEAAAABBEdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==" }, { "privkeys" : [ "cT7J9YpCwY3AVRFSjN6ukeEeWY6mhpbJPxRaDaP5QTdygQRxP9Au", "cNBc3SWUip9PPm1GjRoLEJT6T41iNzCYtD7qro84FMnM5zEqeJsE" ], "psbt" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHIgIC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdIMEUCIQD2EDizCNwdqGWjSFJ0bwFXcpNCCMbSRFQ5PNmb3yIXdwIgBW5nWmdabQoCuFsU5eKQdNiiWptXYL6igW9mGRCgBuoBAQMEQQAAAAEER1IhApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/IQLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU211KuIgYClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8Q2QxqTwAAAIAAAACAAAAAgCIGAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXENkMak8AAACAAAAAgAEAAIAAAQAgAMLrCwAAAAAXqRT2U5MH46SNHgE20GH10f4Z4aJAiYciAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBEEAAAABBEdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==", "result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHIgIC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdIMEUCIQD2EDizCNwdqGWjSFJ0bwFXcpNCCMbSRFQ5PNmb3yIXdwIgBW5nWmdabQoCuFsU5eKQdNiiWptXYL6igW9mGRCgBuoBAQMEQQAAAAEER1IhApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/IQLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU211KuIgYClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8Q2QxqTwAAAIAAAACAAAAAgCIGAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXENkMak8AAACAAAAAgAEAAIAAAQAgAMLrCwAAAAAXqRT2U5MH46SNHgE20GH10f4Z4aJAiYciAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBEEAAAABBEdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==" }, { "privkeys" : [ "cT7J9YpCwY3AVRFSjN6ukeEeWY6mhpbJPxRaDaP5QTdygQRxP9Au", "cNBc3SWUip9PPm1GjRoLEJT6T41iNzCYtD7qro84FMnM5zEqeJsE" ], "psbt" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHIgIC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdIMEUCIQD2EDizCNwdqGWjSFJ0bwFXcpNCCMbSRFQ5PNmb3yIXdwIgBW5nWmdabQoCuFsU5eKQdNiiWptXYL6igW9mGRCgBuoBAQMEQQAAAAEER1IhApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/IQLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU211KuIgYClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8Q2QxqTwAAAIAAAACAAAAAgCIGAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXENkMak8AAACAAAAAgAEAAIAAAQAgAMLrCwAAAAAXqRQp1GpdN1TX/40vmsuPcZh12fWSZociAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBEEAAAABBEdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSrSIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq0AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==", "result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHIgIC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdIMEUCIQD2EDizCNwdqGWjSFJ0bwFXcpNCCMbSRFQ5PNmb3yIXdwIgBW5nWmdabQoCuFsU5eKQdNiiWptXYL6igW9mGRCgBuoBAQMEQQAAAAEER1IhApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/IQLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU211KuIgYClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8Q2QxqTwAAAIAAAACAAAAAgCIGAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXENkMak8AAACAAAAAgAEAAIAAAQAgAMLrCwAAAAAXqRQp1GpdN1TX/40vmsuPcZh12fWSZociAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBEEAAAABBEdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSrSIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq0AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==" } ], "combiner" : [ { "combine" : [ "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHIgIClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H9HMEQCIFeeX7PDIvPZfNrFqWNn0HYRnoT1rlK5Z6WDG3bEROr1AiB2sWDcIhR/xfSYYn09htmayTznOW5Qm2bPIpe3y5lN3EEBAwRBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABACAAwusLAAAAABepFPZTkwfjpI0eATbQYfXR/hnhokCJhyICAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcRzBEAiBnrEB7WcwLuWdisBpe6dVj/d3RTo0CCmRQJjnWVNBCDQIgH3K+5DYrjna1qu3uYVvW81W5g9tGDs9qfelLV5uyeTtBAQMEQQAAAAEER1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuIgYCOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnMQ2QxqTwAAAIAAAACAAwAAgCIGAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcENkMak8AAACAAAAAgAIAAIABBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSrgAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHIgIC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdHMEQCIEc9sDvOD0k8Tt5OFB+T50LmvVA81qJJRuUoT+iwjk0TAiAUtOqy8XPsyzPaW5tbtO9ZYOx7FrGlb0xuQWMXwWvEpUEBAwRBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABACAAwusLAAAAABepFPZTkwfjpI0eATbQYfXR/hnhokCJhyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zRzBEAiBFNHTUzHDC90JjhUb/eLIMV/TuOkV1jB6FQ5+6xXJ8RAIgdQkUT1nyjPBHJuK+Ah15FymwaCitJs94Y/+uLp5LshVBAQMEQQAAAAEER1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuIgYCOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnMQ2QxqTwAAAIAAAACAAwAAgCIGAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcENkMak8AAACAAAAAgAIAAIABBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSrgAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA" ], "result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHIgIC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdHMEQCIEc9sDvOD0k8Tt5OFB+T50LmvVA81qJJRuUoT+iwjk0TAiAUtOqy8XPsyzPaW5tbtO9ZYOx7FrGlb0xuQWMXwWvEpUEiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgV55fs8Mi89l82sWpY2fQdhGehPWuUrlnpYMbdsRE6vUCIHaxYNwiFH/F9JhifT2G2ZrJPOc5blCbZs8il7fLmU3cQQEDBEEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEAIADC6wsAAAAAF6kU9lOTB+OkjR4BNtBh9dH+GeGiQImHIgICOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNHMEQCIEU0dNTMcML3QmOFRv94sgxX9O46RXWMHoVDn7rFcnxEAiB1CRRPWfKM8Ecm4r4CHXkXKbBoKK0mz3hj/64unkuyFUEiAgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3EcwRAIgZ6xAe1nMC7lnYrAaXunVY/3d0U6NAgpkUCY51lTQQg0CIB9yvuQ2K452tart7mFb1vNVuYPbRg7Pan3pS1ebsnk7QQEDBEEAAAABBEdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSriIGAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zENkMak8AAACAAAAAgAMAAIAiBgMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3BDZDGpPAAAAgAAAAIACAACAAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIgIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1Ptnuylh3EQ2QxqTwAAAIAAAACABAAAgAAiAgJ/Y5l1fS7/VaE2rQLGhLGDi2VW5fG2s0KCqUtrUAUQlhDZDGpPAAAAgAAAAIAFAACAAA==" }, { "combine" : [ "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAKDwECAwQFBgcICQ8BAgMEBQYHCAkKCwwNDg8ACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAoPAQIDBAUGBwgJDwECAwQFBgcICQoLDA0ODwA=", "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAKDwECAwQFBgcIEA8BAgMEBQYHCAkKCwwNDg8ACg8BAgMEBQYHCBAPAQIDBAUGBwgJCgsMDQ4PAAoPAQIDBAUGBwgQDwECAwQFBgcICQoLDA0ODwA=" ], "result" : "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAKDwECAwQFBgcICQ8BAgMEBQYHCAkKCwwNDg8KDwECAwQFBgcIEA8BAgMEBQYHCAkKCwwNDg8ACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PCg8BAgMEBQYHCBAPAQIDBAUGBwgJCgsMDQ4PAAoPAQIDBAUGBwgJDwECAwQFBgcICQoLDA0ODwoPAQIDBAUGBwgQDwECAwQFBgcICQoLDA0ODwA=" } ], "finalizer" : [ { "finalize" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHIgIC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdIMEUCIQC4KZqvFEgiBXP/DSl7MJWuNK4jWoaSxkV49PQymsSmXQIgZEJJTLiXqYesRIJn+5rpHOzzEc+UvVyBhl4iRJDa/lZBIgIClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H9HMEQCIFeeX7PDIvPZfNrFqWNn0HYRnoT1rlK5Z6WDG3bEROr1AiB2sWDcIhR/xfSYYn09htmayTznOW5Qm2bPIpe3y5lN3EEBAwRBAAAAAQRHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4iBgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfxDZDGpPAAAAgAAAAIAAAACAIgYC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtcQ2QxqTwAAAIAAAACAAQAAgAABACAAwusLAAAAABepFPZTkwfjpI0eATbQYfXR/hnhokCJhyICAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zSDBFAiEA+en7HEwieXcKiw+jzz0tc53u26wCdRlBixwZ024kBmsCIFF6xUGrDJy7BNqsyTxfh+hye8PDNUWt3J3JnaZ8i6zRQSICAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcRzBEAiBnrEB7WcwLuWdisBpe6dVj/d3RTo0CCmRQJjnWVNBCDQIgH3K+5DYrjna1qu3uYVvW81W5g9tGDs9qfelLV5uyeTtBAQMEQQAAAAEER1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuIgYCOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnMQ2QxqTwAAAIAAAACAAwAAgCIGAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcENkMak8AAACAAAAAgAIAAIABBUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSrgAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA", "result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHAQfaAEcwRAIgV55fs8Mi89l82sWpY2fQdhGehPWuUrlnpYMbdsRE6vUCIHaxYNwiFH/F9JhifT2G2ZrJPOc5blCbZs8il7fLmU3cQUgwRQIhALgpmq8USCIFc/8NKXswla40riNahpLGRXj09DKaxKZdAiBkQklMuJeph6xEgmf7mukc7PMRz5S9XIGGXiJEkNr+VkFHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4AAQAgAMLrCwAAAAAXqRT2U5MH46SNHgE20GH10f4Z4aJAiYcBB9oARzBEAiBnrEB7WcwLuWdisBpe6dVj/d3RTo0CCmRQJjnWVNBCDQIgH3K+5DYrjna1qu3uYVvW81W5g9tGDs9qfelLV5uyeTtBSDBFAiEA+en7HEwieXcKiw+jzz0tc53u26wCdRlBixwZ024kBmsCIFF6xUGrDJy7BNqsyTxfh+hye8PDNUWt3J3JnaZ8i6zRQUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSrgEFR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=" } ], "extractor" : [ { "extract" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAIIDw+gIAAAAAF6kUD7lGNCFpa4LIM68kHHjBfdveSTSHAQfaAEcwRAIgV55fs8Mi89l82sWpY2fQdhGehPWuUrlnpYMbdsRE6vUCIHaxYNwiFH/F9JhifT2G2ZrJPOc5blCbZs8il7fLmU3cQUgwRQIhALgpmq8USCIFc/8NKXswla40riNahpLGRXj09DKaxKZdAiBkQklMuJeph6xEgmf7mukc7PMRz5S9XIGGXiJEkNr+VkFHUiEClYO/Oa4KYJdHrRma3dY0+mEIVZ1sXNObTCGD8auW4H8hAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXUq4AAQAgAMLrCwAAAAAXqRT2U5MH46SNHgE20GH10f4Z4aJAiYcBB9oARzBEAiBnrEB7WcwLuWdisBpe6dVj/d3RTo0CCmRQJjnWVNBCDQIgH3K+5DYrjna1qu3uYVvW81W5g9tGDs9qfelLV5uyeTtBSDBFAiEA+en7HEwieXcKiw+jzz0tc53u26wCdRlBixwZ024kBmsCIFF6xUGrDJy7BNqsyTxfh+hye8PDNUWt3J3JnaZ8i6zRQUdSIQMIncEMesbbVPkTKa9hczPbOIzq0MIx9yM3nRuZAwsC3CECOt2QTz1tz1nduQaw3uI1Kbf/ue1Q5ehhUZJoYCIfDnNSrgEFR1IhAwidwQx6xttU+RMpr2FzM9s4jOrQwjH3IzedG5kDCwLcIQI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc1KuACICA6mkw39ZltOqJdusa1cK8GUDlEkpQkYLNUdT7Z7spYdxENkMak8AAACAAAAAgAQAAIAAIgICf2OZdX0u/1WhNq0CxoSxg4tlVuXxtrNCgqlLa1AFEJYQ2QxqTwAAAIAAAACABQAAgAA=", "result" : "020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da004730440220579e5fb3c322f3d97cdac5a96367d076119e84f5ae52b967a5831b76c444eaf5022076b160dc22147fc5f498627d3d86d99ac93ce7396e509b66cf2297b7cb994ddc41483045022100b8299aaf1448220573ff0d297b3095ae34ae235a8692c64578f4f4329ac4a65d02206442494cb897a987ac448267fb9ae91cecf311cf94bd5c81865e224490dafe5641475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000da00473044022067ac407b59cc0bb96762b01a5ee9d563fdddd14e8d020a64502639d654d0420d02201f72bee4362b8e76b5aaedee615bd6f355b983db460ecf6a7de94b579bb2793b41483045022100f9e9fb1c4c2279770a8b0fa3cf3d2d739deedbac027519418b1c19d36e24066b0220517ac541ab0c9cbb04daacc93c5f87e8727bc3c33545addc9dc99da67c8bacd14147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352aeffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000" } ] } \ No newline at end of file diff --git a/test/functional/feature_bip68_sequence.py b/test/functional/feature_bip68_sequence.py index f0b15af61..be6d1b5f5 100755 --- a/test/functional/feature_bip68_sequence.py +++ b/test/functional/feature_bip68_sequence.py @@ -1,507 +1,509 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test BIP68 implementation.""" import time from test_framework.blocktools import ( create_block, create_coinbase, ) from test_framework.messages import ( - COIN, + XEC, COutPoint, CTransaction, CTxIn, CTxOut, FromHex, ToHex, ) from test_framework.script import CScript from test_framework.test_framework import BitcoinTestFramework from test_framework.txtools import pad_tx from test_framework.util import ( assert_equal, assert_greater_than, assert_raises_rpc_error, connect_nodes, disconnect_nodes, satoshi_round, ) SEQUENCE_LOCKTIME_DISABLE_FLAG = (1 << 31) # this means use time (0 means height) SEQUENCE_LOCKTIME_TYPE_FLAG = (1 << 22) # this is a bit-shift SEQUENCE_LOCKTIME_GRANULARITY = 9 SEQUENCE_LOCKTIME_MASK = 0x0000ffff # RPC error for non-BIP68 final transactions NOT_FINAL_ERROR = "non-BIP68-final" class BIP68Test(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.extra_args = [ [ "-noparkdeepreorg", "-maxreorgdepth=-1", "-acceptnonstdtxn=1", # bump because mocktime might cause a disconnect otherwise "-peertimeout=9999", ], [ "-acceptnonstdtxn=0", "-maxreorgdepth=-1" ] ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): self.relayfee = self.nodes[0].getnetworkinfo()["relayfee"] # Generate some coins self.nodes[0].generate(110) self.log.info("Running test disable flag") self.test_disable_flag() self.log.info("Running test sequence-lock-confirmed-inputs") self.test_sequence_lock_confirmed_inputs() self.log.info("Running test sequence-lock-unconfirmed-inputs") self.test_sequence_lock_unconfirmed_inputs() self.log.info( "Running test BIP68 not consensus before versionbits activation") self.test_bip68_not_consensus() self.log.info("Activating BIP68 (and 112/113)") self.activateCSV() print("Verifying nVersion=2 transactions are standard.") print("Note that with current versions of bitcoin software, nVersion=2 transactions are always standard (independent of BIP68 activation status).") self.test_version2_relay() self.log.info("Passed") # Test that BIP68 is not in effect if tx version is 1, or if # the first sequence bit is set. def test_disable_flag(self): # Create some unconfirmed inputs new_addr = self.nodes[0].getnewaddress() # send 2 BCH - self.nodes[0].sendtoaddress(new_addr, 2) + self.nodes[0].sendtoaddress(new_addr, 2000000) utxos = self.nodes[0].listunspent(0, 0) assert len(utxos) > 0 utxo = utxos[0] tx1 = CTransaction() - value = int(satoshi_round(utxo["amount"] - self.relayfee) * COIN) + value = int(satoshi_round(utxo["amount"] - self.relayfee) * XEC) # Check that the disable flag disables relative locktime. # If sequence locks were used, this would require 1 block for the # input to mature. sequence_value = SEQUENCE_LOCKTIME_DISABLE_FLAG | 1 tx1.vin = [ CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), nSequence=sequence_value)] tx1.vout = [CTxOut(value, CScript([b'a']))] pad_tx(tx1) tx1_signed = self.nodes[0].signrawtransactionwithwallet(ToHex(tx1))[ "hex"] tx1_id = self.nodes[0].sendrawtransaction(tx1_signed) tx1_id = int(tx1_id, 16) # This transaction will enable sequence-locks, so this transaction should # fail tx2 = CTransaction() tx2.nVersion = 2 sequence_value = sequence_value & 0x7fffffff tx2.vin = [CTxIn(COutPoint(tx1_id, 0), nSequence=sequence_value)] - tx2.vout = [CTxOut(int(value - self.relayfee * COIN), CScript([b'a']))] + tx2.vout = [CTxOut(int(value - self.relayfee * XEC), CScript([b'a']))] pad_tx(tx2) tx2.rehash() assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, ToHex(tx2)) # Setting the version back down to 1 should disable the sequence lock, # so this should be accepted. tx2.nVersion = 1 self.nodes[0].sendrawtransaction(ToHex(tx2)) # Calculate the median time past of a prior block ("confirmations" before # the current tip). def get_median_time_past(self, confirmations): block_hash = self.nodes[0].getblockhash( self.nodes[0].getblockcount() - confirmations) return self.nodes[0].getblockheader(block_hash)["mediantime"] # Test that sequence locks are respected for transactions spending # confirmed inputs. def test_sequence_lock_confirmed_inputs(self): # Create lots of confirmed utxos, and use them to generate lots of random # transactions. max_outputs = 50 addresses = [] while len(addresses) < max_outputs: addresses.append(self.nodes[0].getnewaddress()) while len(self.nodes[0].listunspent()) < 200: import random random.shuffle(addresses) num_outputs = random.randint(1, max_outputs) outputs = {} for i in range(num_outputs): - outputs[addresses[i]] = random.randint(1, 20) * 0.01 + outputs[addresses[i]] = random.randint(1, 20) * 10000 self.nodes[0].sendmany("", outputs) self.nodes[0].generate(1) utxos = self.nodes[0].listunspent() # Try creating a lot of random transactions. # Each time, choose a random number of inputs, and randomly set # some of those inputs to be sequence locked (and randomly choose # between height/time locking). Small random chance of making the locks # all pass. for i in range(400): # Randomly choose up to 10 inputs num_inputs = random.randint(1, 10) random.shuffle(utxos) # Track whether any sequence locks used should fail should_pass = True # Track whether this transaction was built with sequence locks using_sequence_locks = False tx = CTransaction() tx.nVersion = 2 value = 0 for j in range(num_inputs): # this disables sequence locks sequence_value = 0xfffffffe # 50% chance we enable sequence locks if random.randint(0, 1): using_sequence_locks = True # 10% of the time, make the input sequence value pass input_will_pass = (random.randint(1, 10) == 1) sequence_value = utxos[j]["confirmations"] if not input_will_pass: sequence_value += 1 should_pass = False # Figure out what the median-time-past was for the confirmed input # Note that if an input has N confirmations, we're going back N blocks # from the tip so that we're looking up MTP of the block # PRIOR to the one the input appears in, as per the BIP68 # spec. orig_time = self.get_median_time_past( utxos[j]["confirmations"]) # MTP of the tip cur_time = self.get_median_time_past(0) # can only timelock this input if it's not too old -- # otherwise use height can_time_lock = True if ((cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY) >= SEQUENCE_LOCKTIME_MASK: can_time_lock = False # if time-lockable, then 50% chance we make this a time # lock if random.randint(0, 1) and can_time_lock: # Find first time-lock value that fails, or latest one # that succeeds time_delta = sequence_value << SEQUENCE_LOCKTIME_GRANULARITY if input_will_pass and time_delta > cur_time - orig_time: sequence_value = ( (cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY) elif (not input_will_pass and time_delta <= cur_time - orig_time): sequence_value = ( (cur_time - orig_time) >> SEQUENCE_LOCKTIME_GRANULARITY) + 1 sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG tx.vin.append( CTxIn(COutPoint(int(utxos[j]["txid"], 16), utxos[j]["vout"]), nSequence=sequence_value)) - value += utxos[j]["amount"] * COIN + value += utxos[j]["amount"] * XEC # Overestimate the size of the tx - signatures should be less than # 120 bytes, and leave 50 for the output tx_size = len(ToHex(tx)) // 2 + 120 * num_inputs + 50 tx.vout.append( - CTxOut(int(value - self.relayfee * tx_size * COIN / 1000), CScript([b'a']))) + CTxOut(int(value - self.relayfee * tx_size * XEC / 1000), CScript([b'a']))) rawtx = self.nodes[0].signrawtransactionwithwallet(ToHex(tx))[ "hex"] if (using_sequence_locks and not should_pass): # This transaction should be rejected assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, rawtx) else: # This raw transaction should be accepted self.nodes[0].sendrawtransaction(rawtx) utxos = self.nodes[0].listunspent() # Test that sequence locks on unconfirmed inputs must have nSequence # height or time of 0 to be accepted. # Then test that BIP68-invalid transactions are removed from the mempool # after a reorg. def test_sequence_lock_unconfirmed_inputs(self): # Store height so we can easily reset the chain at the end of the test cur_height = self.nodes[0].getblockcount() # Create a mempool tx. - txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2) + txid = self.nodes[0].sendtoaddress( + self.nodes[0].getnewaddress(), 2000000) tx1 = FromHex(CTransaction(), self.nodes[0].getrawtransaction(txid)) tx1.rehash() # As the fees are calculated prior to the transaction being signed, # there is some uncertainty that calculate fee provides the correct # minimal fee. Since regtest coins are free, let's go ahead and # increase the fee by an order of magnitude to ensure this test # passes. fee_multiplier = 10 # Anyone-can-spend mempool tx. # Sequence lock of 0 should pass. tx2 = CTransaction() tx2.nVersion = 2 tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)] tx2.vout = [ CTxOut(int(0), CScript([b'a']))] tx2.vout[0].nValue = tx1.vout[0].nValue - \ fee_multiplier * self.nodes[0].calculate_fee(tx2) tx2_raw = self.nodes[0].signrawtransactionwithwallet(ToHex(tx2))["hex"] tx2 = FromHex(tx2, tx2_raw) tx2.rehash() self.nodes[0].sendrawtransaction(tx2_raw) # Create a spend of the 0th output of orig_tx with a sequence lock # of 1, and test what happens when submitting. # orig_tx.vout[0] must be an anyone-can-spend output def test_nonzero_locks(orig_tx, node, use_height_lock): sequence_value = 1 if not use_height_lock: sequence_value |= SEQUENCE_LOCKTIME_TYPE_FLAG tx = CTransaction() tx.nVersion = 2 tx.vin = [ CTxIn(COutPoint(orig_tx.sha256, 0), nSequence=sequence_value)] tx.vout = [ CTxOut(int(orig_tx.vout[0].nValue - fee_multiplier * node.calculate_fee(tx)), CScript([b'a']))] pad_tx(tx) tx.rehash() if (orig_tx.hash in node.getrawmempool()): # sendrawtransaction should fail if the tx is in the mempool assert_raises_rpc_error(-26, NOT_FINAL_ERROR, node.sendrawtransaction, ToHex(tx)) else: # sendrawtransaction should succeed if the tx is not in the # mempool node.sendrawtransaction(ToHex(tx)) return tx test_nonzero_locks( tx2, self.nodes[0], use_height_lock=True) test_nonzero_locks( tx2, self.nodes[0], use_height_lock=False) # Now mine some blocks, but make sure tx2 doesn't get mined. # Use prioritisetransaction to lower the effective feerate to 0 self.nodes[0].prioritisetransaction( txid=tx2.hash, fee_delta=-fee_multiplier * self.nodes[0].calculate_fee(tx2)) cur_time = int(time.time()) for i in range(10): self.nodes[0].setmocktime(cur_time + 600) self.nodes[0].generate(1) cur_time += 600 assert tx2.hash in self.nodes[0].getrawmempool() test_nonzero_locks( tx2, self.nodes[0], use_height_lock=True) test_nonzero_locks( tx2, self.nodes[0], use_height_lock=False) # Mine tx2, and then try again self.nodes[0].prioritisetransaction( txid=tx2.hash, fee_delta=fee_multiplier * self.nodes[0].calculate_fee(tx2)) # Advance the time on the node so that we can test timelocks self.nodes[0].setmocktime(cur_time + 600) self.nodes[0].generate(1) assert tx2.hash not in self.nodes[0].getrawmempool() # Now that tx2 is not in the mempool, a sequence locked spend should # succeed tx3 = test_nonzero_locks( tx2, self.nodes[0], use_height_lock=False) assert tx3.hash in self.nodes[0].getrawmempool() self.nodes[0].generate(1) assert tx3.hash not in self.nodes[0].getrawmempool() # One more test, this time using height locks tx4 = test_nonzero_locks( tx3, self.nodes[0], use_height_lock=True) assert tx4.hash in self.nodes[0].getrawmempool() # Now try combining confirmed and unconfirmed inputs tx5 = test_nonzero_locks( tx4, self.nodes[0], use_height_lock=True) assert tx5.hash not in self.nodes[0].getrawmempool() utxos = self.nodes[0].listunspent() tx5.vin.append( CTxIn(COutPoint(int(utxos[0]["txid"], 16), utxos[0]["vout"]), nSequence=1)) - tx5.vout[0].nValue += int(utxos[0]["amount"] * COIN) + tx5.vout[0].nValue += int(utxos[0]["amount"] * XEC) raw_tx5 = self.nodes[0].signrawtransactionwithwallet(ToHex(tx5))["hex"] assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, raw_tx5) # Test mempool-BIP68 consistency after reorg # # State of the transactions in the last blocks: # ... -> [ tx2 ] -> [ tx3 ] # tip-1 tip # And currently tx4 is in the mempool. # # If we invalidate the tip, tx3 should get added to the mempool, causing # tx4 to be removed (fails sequence-lock). self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) assert tx4.hash not in self.nodes[0].getrawmempool() assert tx3.hash in self.nodes[0].getrawmempool() # Now mine 2 empty blocks to reorg out the current tip (labeled tip-1 in # diagram above). # This would cause tx2 to be added back to the mempool, which in turn causes # tx3 to be removed. tip = int(self.nodes[0].getblockhash( self.nodes[0].getblockcount() - 1), 16) height = self.nodes[0].getblockcount() for i in range(2): block = create_block(tip, create_coinbase(height), cur_time) block.nVersion = 3 block.rehash() block.solve() tip = block.sha256 height += 1 assert_equal( None if i == 1 else 'inconclusive', self.nodes[0].submitblock( ToHex(block))) cur_time += 1 mempool = self.nodes[0].getrawmempool() assert tx3.hash not in mempool assert tx2.hash in mempool # Reset the chain and get rid of the mocktimed-blocks self.nodes[0].setmocktime(0) self.nodes[0].invalidateblock( self.nodes[0].getblockhash(cur_height + 1)) self.nodes[0].generate(10) def get_csv_status(self): height = self.nodes[0].getblockchaininfo()['blocks'] return height >= 576 # Make sure that BIP68 isn't being used to validate blocks, prior to # versionbits activation. If more blocks are mined prior to this test # being run, then it's possible the test has activated the soft fork, and # this test should be moved to run earlier, or deleted. def test_bip68_not_consensus(self): assert_equal(self.get_csv_status(), False) - txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 2) + txid = self.nodes[0].sendtoaddress( + self.nodes[0].getnewaddress(), 2000000) tx1 = FromHex(CTransaction(), self.nodes[0].getrawtransaction(txid)) tx1.rehash() # Make an anyone-can-spend transaction tx2 = CTransaction() tx2.nVersion = 1 tx2.vin = [CTxIn(COutPoint(tx1.sha256, 0), nSequence=0)] tx2.vout = [ - CTxOut(int(tx1.vout[0].nValue - self.relayfee * COIN), CScript([b'a']))] + CTxOut(int(tx1.vout[0].nValue - self.relayfee * XEC), CScript([b'a']))] # sign tx2 tx2_raw = self.nodes[0].signrawtransactionwithwallet(ToHex(tx2))["hex"] tx2 = FromHex(tx2, tx2_raw) pad_tx(tx2) tx2.rehash() self.nodes[0].sendrawtransaction(ToHex(tx2)) # Now make an invalid spend of tx2 according to BIP68 # 100 block relative locktime sequence_value = 100 tx3 = CTransaction() tx3.nVersion = 2 tx3.vin = [CTxIn(COutPoint(tx2.sha256, 0), nSequence=sequence_value)] tx3.vout = [ - CTxOut(int(tx2.vout[0].nValue - self.relayfee * COIN), CScript([b'a']))] + CTxOut(int(tx2.vout[0].nValue - self.relayfee * XEC), CScript([b'a']))] pad_tx(tx3) tx3.rehash() assert_raises_rpc_error(-26, NOT_FINAL_ERROR, self.nodes[0].sendrawtransaction, ToHex(tx3)) # make a block that violates bip68; ensure that the tip updates tip = int(self.nodes[0].getbestblockhash(), 16) block = create_block( tip, create_coinbase(self.nodes[0].getblockcount() + 1)) block.nVersion = 3 block.vtx.extend( sorted([tx1, tx2, tx3], key=lambda tx: tx.get_id())) block.hashMerkleRoot = block.calc_merkle_root() block.rehash() block.solve() assert_equal(None, self.nodes[0].submitblock(ToHex(block))) assert_equal(self.nodes[0].getbestblockhash(), block.hash) def activateCSV(self): # activation should happen at block height 576 csv_activation_height = 576 height = self.nodes[0].getblockcount() assert_greater_than(csv_activation_height - height, 1) self.nodes[0].generate(csv_activation_height - height - 1) assert_equal(self.get_csv_status(), False) disconnect_nodes(self.nodes[0], self.nodes[1]) self.nodes[0].generate(1) assert_equal(self.get_csv_status(), True) # We have a block that has CSV activated, but we want to be at # the activation point, so we invalidate the tip. self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) connect_nodes(self.nodes[0], self.nodes[1]) self.sync_blocks() # Use self.nodes[1] to test that version 2 transactions are standard. def test_version2_relay(self): inputs = [] - outputs = {self.nodes[1].getnewaddress(): 1.0} + outputs = {self.nodes[1].getnewaddress(): 1000000.0} rawtx = self.nodes[1].createrawtransaction(inputs, outputs) rawtxfund = self.nodes[1].fundrawtransaction(rawtx)['hex'] tx = FromHex(CTransaction(), rawtxfund) tx.nVersion = 2 tx_signed = self.nodes[1].signrawtransactionwithwallet(ToHex(tx))[ "hex"] self.nodes[1].sendrawtransaction(tx_signed) if __name__ == '__main__': BIP68Test().main() diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index c92bd662a..92f4e5128 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -1,218 +1,218 @@ #!/usr/bin/env python3 # Copyright (c) 2015-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test BIP65 (CHECKLOCKTIMEVERIFY). Test that the CHECKLOCKTIMEVERIFY soft-fork activates at (regtest) block height 1351. """ from test_framework.blocktools import create_block, create_coinbase, create_transaction, make_conform_to_ctor from test_framework.messages import ( CTransaction, FromHex, msg_block, msg_tx, ToHex, ) from test_framework.p2p import ( P2PInterface, ) from test_framework.script import ( CScript, CScriptNum, OP_1NEGATE, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_TRUE, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.txtools import pad_tx from test_framework.util import assert_equal CLTV_HEIGHT = 1351 def cltv_lock_to_height(node, tx, to_address, amount, height=-1): '''Modify the scriptPubKey to add an OP_CHECKLOCKTIMEVERIFY, and make a transaction that spends it. This transforms the output script to anyone can spend (OP_TRUE) if the lock time condition is valid. Default height is -1 which leads CLTV to fail TODO: test more ways that transactions using CLTV could be invalid (eg locktime requirements fail, sequence time requirements fail, etc). ''' height_op = OP_1NEGATE if(height > 0): tx.vin[0].nSequence = 0 tx.nLockTime = height height_op = CScriptNum(height) tx.vout[0].scriptPubKey = CScript( [height_op, OP_CHECKLOCKTIMEVERIFY, OP_DROP, OP_TRUE]) pad_tx(tx) fundtx_raw = node.signrawtransactionwithwallet(ToHex(tx))['hex'] fundtx = FromHex(CTransaction(), fundtx_raw) fundtx.rehash() # make spending tx inputs = [{ "txid": fundtx.hash, "vout": 0 }] output = {to_address: amount} spendtx_raw = node.createrawtransaction(inputs, output) spendtx = FromHex(CTransaction(), spendtx_raw) pad_tx(spendtx) return fundtx, spendtx class BIP65Test(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.extra_args = [[ '-whitelist=noban@127.0.0.1', '-par=1', # Use only one script thread to get the exact reject reason for testing '-acceptnonstdtxn=1', # cltv_invalidate is nonstandard ]] self.setup_clean_chain = True self.rpc_timeout = 120 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): self.nodes[0].add_p2p_connection(P2PInterface()) self.log.info("Mining {} blocks".format(CLTV_HEIGHT - 2)) self.coinbase_txids = [self.nodes[0].getblock( b)['tx'][0] for b in self.nodes[0].generate(CLTV_HEIGHT - 2)] self.nodeaddress = self.nodes[0].getnewaddress() self.log.info( "Test that an invalid-according-to-CLTV transaction can still appear in a block") fundtx = create_transaction(self.nodes[0], self.coinbase_txids[0], - self.nodeaddress, amount=49.99) + self.nodeaddress, amount=49990000) fundtx, spendtx = cltv_lock_to_height( - self.nodes[0], fundtx, self.nodeaddress, 49.98) + self.nodes[0], fundtx, self.nodeaddress, 49980000) tip = self.nodes[0].getbestblockhash() block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1 block = create_block(int(tip, 16), create_coinbase( CLTV_HEIGHT - 1), block_time) block.nVersion = 3 block.vtx.append(fundtx) # include the -1 CLTV in block block.vtx.append(spendtx) make_conform_to_ctor(block) block.hashMerkleRoot = block.calc_merkle_root() block.solve() self.nodes[0].p2p.send_and_ping(msg_block(block)) # This block is valid assert_equal(self.nodes[0].getbestblockhash(), block.hash) self.log.info("Test that blocks must now be at least version 4") tip = block.sha256 block_time += 1 block = create_block(tip, create_coinbase(CLTV_HEIGHT), block_time) block.nVersion = 3 block.solve() with self.nodes[0].assert_debug_log(expected_msgs=['{}, bad-version(0x00000003)'.format(block.hash)]): self.nodes[0].p2p.send_and_ping(msg_block(block)) assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip) self.nodes[0].p2p.sync_with_ping() self.log.info( "Test that invalid-according-to-cltv transactions cannot appear in a block") block.nVersion = 4 fundtx = create_transaction(self.nodes[0], self.coinbase_txids[1], - self.nodeaddress, amount=49.99) + self.nodeaddress, amount=49990000) fundtx, spendtx = cltv_lock_to_height( - self.nodes[0], fundtx, self.nodeaddress, 49.98) + self.nodes[0], fundtx, self.nodeaddress, 49980000) # The funding tx only has unexecuted bad CLTV, in scriptpubkey; this is # valid. self.nodes[0].p2p.send_and_ping(msg_tx(fundtx)) assert fundtx.hash in self.nodes[0].getrawmempool() # Mine a block containing the funding transaction block.vtx.append(fundtx) block.hashMerkleRoot = block.calc_merkle_root() block.solve() self.nodes[0].p2p.send_and_ping(msg_block(block)) # This block is valid assert_equal(self.nodes[0].getbestblockhash(), block.hash) # We show that this tx is invalid due to CLTV by getting it # rejected from the mempool for exactly that reason. assert_equal( [{'txid': spendtx.hash, 'allowed': False, 'reject-reason': 'non-mandatory-script-verify-flag (Negative locktime)'}], self.nodes[0].testmempoolaccept( rawtxs=[spendtx.serialize().hex()], maxfeerate=0) ) rejectedtx_signed = self.nodes[0].signrawtransactionwithwallet( ToHex(spendtx)) # Couldn't complete signature due to CLTV assert rejectedtx_signed['errors'][0]['error'] == 'Negative locktime' tip = block.hash block_time += 1 block = create_block( block.sha256, create_coinbase(CLTV_HEIGHT + 1), block_time) block.nVersion = 4 block.vtx.append(spendtx) block.hashMerkleRoot = block.calc_merkle_root() block.solve() with self.nodes[0].assert_debug_log(expected_msgs=['ConnectBlock {} failed, blk-bad-inputs'.format(block.hash)]): self.nodes[0].p2p.send_and_ping(msg_block(block)) assert_equal(self.nodes[0].getbestblockhash(), tip) self.nodes[0].p2p.sync_with_ping() self.log.info( "Test that a version 4 block with a valid-according-to-CLTV transaction is accepted") fundtx = create_transaction(self.nodes[0], self.coinbase_txids[2], - self.nodeaddress, amount=49.99) + self.nodeaddress, amount=49990000) fundtx, spendtx = cltv_lock_to_height( - self.nodes[0], fundtx, self.nodeaddress, 49.98, CLTV_HEIGHT) + self.nodes[0], fundtx, self.nodeaddress, 49980000, CLTV_HEIGHT) # make sure sequence is nonfinal and locktime is good spendtx.vin[0].nSequence = 0xfffffffe spendtx.nLockTime = CLTV_HEIGHT # both transactions are fully valid self.nodes[0].sendrawtransaction(ToHex(fundtx)) self.nodes[0].sendrawtransaction(ToHex(spendtx)) # Modify the transactions in the block to be valid against CLTV block.vtx.pop(1) block.vtx.append(fundtx) block.vtx.append(spendtx) make_conform_to_ctor(block) block.hashMerkleRoot = block.calc_merkle_root() block.solve() self.nodes[0].p2p.send_and_ping(msg_block(block)) # This block is now valid assert_equal(self.nodes[0].getbestblockhash(), block.hash) if __name__ == '__main__': BIP65Test().main() diff --git a/test/functional/feature_csv_activation.py b/test/functional/feature_csv_activation.py index 5775d7c65..2b6759d9e 100755 --- a/test/functional/feature_csv_activation.py +++ b/test/functional/feature_csv_activation.py @@ -1,677 +1,677 @@ #!/usr/bin/env python3 # Copyright (c) 2015-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test activation of the first version bits soft fork. This soft fork will activate the following BIPS: BIP 68 - nSequence relative lock times BIP 112 - CHECKSEQUENCEVERIFY BIP 113 - MedianTimePast semantics for nLockTime regtest lock-in with 108/144 block signalling activation after a further 144 blocks mine 82 blocks whose coinbases will be used to generate inputs for our tests mine 489 blocks and seed block chain with the 82 inputs will use for our tests at height 572 mine 3 blocks and verify still at LOCKED_IN and test that enforcement has not triggered mine 1 block and test that enforcement has triggered (which triggers ACTIVE) Test BIP 113 is enforced Mine 4 blocks so next height is 580 and test BIP 68 is enforced for time and height Mine 1 block so next height is 581 and test BIP 68 now passes time but not height Mine 1 block so next height is 582 and test BIP 68 now passes time and height Test that BIP 112 is enforced Various transactions will be used to test that the BIPs rules are not enforced before the soft fork activates And that after the soft fork activates transactions pass and fail as they should according to the rules. For each BIP, transactions of versions 1 and 2 will be tested. ---------------- BIP 113: bip113tx - modify the nLocktime variable BIP 68: bip68txs - 16 txs with nSequence relative locktime of 10 with various bits set as per the relative_locktimes below BIP 112: bip112txs_vary_nSequence - 16 txs with nSequence relative_locktimes of 10 evaluated against 10 OP_CSV OP_DROP bip112txs_vary_nSequence_9 - 16 txs with nSequence relative_locktimes of 9 evaluated against 10 OP_CSV OP_DROP bip112txs_vary_OP_CSV - 16 txs with nSequence = 10 evaluated against varying {relative_locktimes of 10} OP_CSV OP_DROP bip112txs_vary_OP_CSV_9 - 16 txs with nSequence = 9 evaluated against varying {relative_locktimes of 10} OP_CSV OP_DROP bip112tx_special - test negative argument to OP_CSV """ from decimal import Decimal from itertools import product import time from test_framework.blocktools import ( create_block, create_coinbase, make_conform_to_ctor, ) -from test_framework.messages import COIN, CTransaction, FromHex, ToHex +from test_framework.messages import XEC, CTransaction, FromHex, ToHex from test_framework.p2p import P2PDataStore from test_framework.script import ( CScript, OP_CHECKSEQUENCEVERIFY, OP_DROP, OP_TRUE, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.txtools import pad_tx from test_framework.util import assert_equal BASE_RELATIVE_LOCKTIME = 10 SEQ_DISABLE_FLAG = 1 << 31 SEQ_RANDOM_HIGH_BIT = 1 << 25 SEQ_TYPE_FLAG = 1 << 22 SEQ_RANDOM_LOW_BIT = 1 << 18 def relative_locktime(sdf, srhb, stf, srlb): """Returns a locktime with certain bits set.""" locktime = BASE_RELATIVE_LOCKTIME if sdf: locktime |= SEQ_DISABLE_FLAG if srhb: locktime |= SEQ_RANDOM_HIGH_BIT if stf: locktime |= SEQ_TYPE_FLAG if srlb: locktime |= SEQ_RANDOM_LOW_BIT return locktime def all_rlt_txs(txs): return [tx['tx'] for tx in txs] def get_csv_status(node): height = node.getblockchaininfo()['blocks'] return height >= 576 def create_transaction(node, txid, to_address, *, amount): inputs = [{"txid": txid, "vout": 0}] outputs = {to_address: amount} rawtx = node.createrawtransaction(inputs, outputs) tx = FromHex(CTransaction(), rawtx) return tx def sign_transaction(node, unsignedtx): rawtx = ToHex(unsignedtx) signresult = node.signrawtransactionwithwallet(rawtx) tx = FromHex(CTransaction(), signresult['hex']) return tx def spend_tx(node, prev_tx, address): spendtx = create_transaction( - node, prev_tx.hash, address, amount=(prev_tx.vout[0].nValue - 1000) / COIN) + node, prev_tx.hash, address, amount=(prev_tx.vout[0].nValue - 1000) / XEC) spendtx.nVersion = prev_tx.nVersion pad_tx(spendtx) spendtx.rehash() return spendtx def create_bip112special(node, input, txversion, address): tx = create_transaction( - node, input, address, amount=Decimal("49.98")) + node, input, address, amount=Decimal("49980000")) tx.nVersion = txversion tx.vout[0].scriptPubKey = CScript( [-1, OP_CHECKSEQUENCEVERIFY, OP_DROP, OP_TRUE]) tx.rehash() signtx = sign_transaction(node, tx) signtx.rehash() return signtx def send_generic_input_tx(node, coinbases, address): return node.sendrawtransaction(ToHex(sign_transaction(node, create_transaction( - node, node.getblock(coinbases.pop())['tx'][0], address, amount=Decimal("49.99"))))) + node, node.getblock(coinbases.pop())['tx'][0], address, amount=Decimal("49990000"))))) def create_bip68txs(node, bip68inputs, txversion, address, locktime_delta=0): """Returns a list of bip68 transactions with different bits set.""" txs = [] assert len(bip68inputs) >= 16 for i, (sdf, srhb, stf, srlb) in enumerate(product(*[[True, False]] * 4)): locktime = relative_locktime(sdf, srhb, stf, srlb) tx = create_transaction( - node, bip68inputs[i], address, amount=Decimal("49.98")) + node, bip68inputs[i], address, amount=Decimal("49980000")) tx.nVersion = txversion tx.vin[0].nSequence = locktime + locktime_delta tx = sign_transaction(node, tx) tx.rehash() txs.append({'tx': tx, 'sdf': sdf, 'stf': stf}) return txs def create_bip112txs(node, bip112inputs, varyOP_CSV, txversion, address, locktime_delta=0): """Returns a list of bip112 transactions with different bits set.""" txs = [] assert len(bip112inputs) >= 16 for i, (sdf, srhb, stf, srlb) in enumerate(product(*[[True, False]] * 4)): locktime = relative_locktime(sdf, srhb, stf, srlb) tx = create_transaction( - node, bip112inputs[i], address, amount=Decimal("49.98")) + node, bip112inputs[i], address, amount=Decimal("49980000")) if (varyOP_CSV): # if varying OP_CSV, nSequence is fixed tx.vin[0].nSequence = BASE_RELATIVE_LOCKTIME + locktime_delta else: # vary nSequence instead, OP_CSV is fixed tx.vin[0].nSequence = locktime + locktime_delta tx.nVersion = txversion if (varyOP_CSV): tx.vout[0].scriptPubKey = CScript( [locktime, OP_CHECKSEQUENCEVERIFY, OP_DROP, OP_TRUE]) else: tx.vout[0].scriptPubKey = CScript( [BASE_RELATIVE_LOCKTIME, OP_CHECKSEQUENCEVERIFY, OP_DROP, OP_TRUE]) tx.rehash() signtx = sign_transaction(node, tx) signtx.rehash() txs.append({'tx': signtx, 'sdf': sdf, 'stf': stf}) return txs class BIP68_112_113Test(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.extra_args = [['-whitelist=noban@127.0.0.1', '-blockversion=4']] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def generate_blocks(self, number): test_blocks = [] for i in range(number): block = self.create_test_block([]) test_blocks.append(block) self.last_block_time += 600 self.tip = block.sha256 self.tipheight += 1 return test_blocks def create_test_block(self, txs, version=536870912): block = create_block(self.tip, create_coinbase( self.tipheight + 1), self.last_block_time + 600) block.nVersion = version block.vtx.extend(txs) make_conform_to_ctor(block) block.hashMerkleRoot = block.calc_merkle_root() block.rehash() block.solve() return block # Create a block with given txs, and spend these txs in the same block. # Spending utxos in the same block is OK as long as nSequence is not enforced. # Otherwise a number of intermediate blocks should be generated, and this # method should not be used. def create_test_block_spend_utxos(self, node, txs, version=536870912): block = self.create_test_block(txs, version) block.vtx.extend([spend_tx(node, tx, self.nodeaddress) for tx in txs]) make_conform_to_ctor(block) block.hashMerkleRoot = block.calc_merkle_root() block.rehash() block.solve() return block def send_blocks(self, blocks, success=True): """Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block. Call with success = False if the tip shouldn't advance to the most recent block.""" self.nodes[0].p2p.send_blocks_and_test( blocks, self.nodes[0], success=success) def run_test(self): self.nodes[0].add_p2p_connection(P2PDataStore()) self.log.info("Generate blocks in the past for coinbase outputs.") # Enough to build up to 1000 blocks 10 minutes apart without worrying # about getting into the future long_past_time = int(time.time()) - 600 * 1000 # Enough so that the generated blocks will still all be before # long_past_time self.nodes[0].setmocktime(long_past_time - 100) # 82 blocks generated for inputs self.coinbase_blocks = self.nodes[0].generate(1 + 16 + 2 * 32 + 1) # Set time back to present so yielded blocks aren't in the future as # we advance last_block_time self.nodes[0].setmocktime(0) # height of the next block to build self.tipheight = 82 self.last_block_time = long_past_time self.tip = int(self.nodes[0].getbestblockhash(), 16) self.nodeaddress = self.nodes[0].getnewaddress() # CSV is not activated yet. assert_equal(get_csv_status(self.nodes[0]), False) # Generate 489 more version 4 blocks test_blocks = self.generate_blocks(489) # Test #1 self.send_blocks(test_blocks) # Still not activated. assert_equal(get_csv_status(self.nodes[0]), False) # Inputs at height = 572 # # Put inputs for all tests in the chain at height 572 (tip now = 571) (time increases by 600s per block) # Note we reuse inputs for v1 and v2 txs so must test these separately # 16 normal inputs bip68inputs = [] for i in range(16): bip68inputs.append(send_generic_input_tx( self.nodes[0], self.coinbase_blocks, self.nodeaddress)) # 2 sets of 16 inputs with 10 OP_CSV OP_DROP (actually will be # prepended to spending scriptSig) bip112basicinputs = [] for j in range(2): inputs = [] for i in range(16): inputs.append(send_generic_input_tx( self.nodes[0], self.coinbase_blocks, self.nodeaddress)) bip112basicinputs.append(inputs) # 2 sets of 16 varied inputs with (relative_lock_time) OP_CSV OP_DROP # (actually will be prepended to spending scriptSig) bip112diverseinputs = [] for j in range(2): inputs = [] for i in range(16): inputs.append(send_generic_input_tx( self.nodes[0], self.coinbase_blocks, self.nodeaddress)) bip112diverseinputs.append(inputs) # 1 special input with -1 OP_CSV OP_DROP (actually will be prepended to # spending scriptSig) bip112specialinput = send_generic_input_tx( self.nodes[0], self.coinbase_blocks, self.nodeaddress) # 1 normal input bip113input = send_generic_input_tx( self.nodes[0], self.coinbase_blocks, self.nodeaddress) self.nodes[0].setmocktime(self.last_block_time + 600) # 1 block generated for inputs to be in chain at height 572 inputblockhash = self.nodes[0].generate(1)[0] self.nodes[0].setmocktime(0) self.tip = int(inputblockhash, 16) self.tipheight += 1 self.last_block_time += 600 assert_equal(len(self.nodes[0].getblock( inputblockhash, True)["tx"]), 82 + 1) # 2 more version 4 blocks test_blocks = self.generate_blocks(2) # Test #2 self.send_blocks(test_blocks) self.log.info( "Not yet activated, height = 574 (will activate for block 576, not 575)") assert_equal(get_csv_status(self.nodes[0]), False) # Test both version 1 and version 2 transactions for all tests # BIP113 test transaction will be modified before each use to # put in appropriate block time bip113tx_v1 = create_transaction( - self.nodes[0], bip113input, self.nodeaddress, amount=Decimal("49.98")) + self.nodes[0], bip113input, self.nodeaddress, amount=Decimal("49980000")) bip113tx_v1.vin[0].nSequence = 0xFFFFFFFE bip113tx_v1.nVersion = 1 bip113tx_v2 = create_transaction( - self.nodes[0], bip113input, self.nodeaddress, amount=Decimal("49.98")) + self.nodes[0], bip113input, self.nodeaddress, amount=Decimal("49980000")) bip113tx_v2.vin[0].nSequence = 0xFFFFFFFE bip113tx_v2.nVersion = 2 # For BIP68 test all 16 relative sequence locktimes bip68txs_v1 = create_bip68txs( self.nodes[0], bip68inputs, 1, self.nodeaddress) bip68txs_v2 = create_bip68txs( self.nodes[0], bip68inputs, 2, self.nodeaddress) # For BIP112 test: # 16 relative sequence locktimes of 10 against 10 OP_CSV OP_DROP inputs bip112txs_vary_nSequence_v1 = create_bip112txs( self.nodes[0], bip112basicinputs[0], False, 1, self.nodeaddress) bip112txs_vary_nSequence_v2 = create_bip112txs( self.nodes[0], bip112basicinputs[0], False, 2, self.nodeaddress) # 16 relative sequence locktimes of 9 against 10 OP_CSV OP_DROP inputs bip112txs_vary_nSequence_9_v1 = create_bip112txs( self.nodes[0], bip112basicinputs[1], False, 1, self.nodeaddress, -1) bip112txs_vary_nSequence_9_v2 = create_bip112txs( self.nodes[0], bip112basicinputs[1], False, 2, self.nodeaddress, -1) # sequence lock time of 10 against 16 (relative_lock_time) OP_CSV # OP_DROP inputs bip112txs_vary_OP_CSV_v1 = create_bip112txs( self.nodes[0], bip112diverseinputs[0], True, 1, self.nodeaddress) bip112txs_vary_OP_CSV_v2 = create_bip112txs( self.nodes[0], bip112diverseinputs[0], True, 2, self.nodeaddress) # sequence lock time of 9 against 16 (relative_lock_time) OP_CSV # OP_DROP inputs bip112txs_vary_OP_CSV_9_v1 = create_bip112txs( self.nodes[0], bip112diverseinputs[1], True, 1, self.nodeaddress, -1) bip112txs_vary_OP_CSV_9_v2 = create_bip112txs( self.nodes[0], bip112diverseinputs[1], True, 2, self.nodeaddress, -1) # -1 OP_CSV OP_DROP input bip112tx_special_v1 = create_bip112special( self.nodes[0], bip112specialinput, 1, self.nodeaddress) bip112tx_special_v2 = create_bip112special( self.nodes[0], bip112specialinput, 2, self.nodeaddress) self.log.info("TESTING") self.log.info("Pre-Soft Fork Tests. All txs should pass.") self.log.info("Test version 1 txs") success_txs = [] # add BIP113 tx and -1 CSV tx # = MTP of prior block (not <) but < time put on current block bip113tx_v1.nLockTime = self.last_block_time - 600 * 5 bip113signed1 = sign_transaction(self.nodes[0], bip113tx_v1) success_txs.append(bip113signed1) success_txs.append(bip112tx_special_v1) success_txs.append( spend_tx(self.nodes[0], bip112tx_special_v1, self.nodeaddress)) # add BIP 68 txs success_txs.extend(all_rlt_txs(bip68txs_v1)) # add BIP 112 with seq=10 txs success_txs.extend(all_rlt_txs(bip112txs_vary_nSequence_v1)) success_txs.extend([spend_tx(self.nodes[0], tx, self.nodeaddress) for tx in all_rlt_txs(bip112txs_vary_nSequence_v1)]) success_txs.extend(all_rlt_txs(bip112txs_vary_OP_CSV_v1)) success_txs.extend([spend_tx(self.nodes[0], tx, self.nodeaddress) for tx in all_rlt_txs(bip112txs_vary_OP_CSV_v1)]) # try BIP 112 with seq=9 txs success_txs.extend(all_rlt_txs(bip112txs_vary_nSequence_9_v1)) success_txs.extend([spend_tx(self.nodes[0], tx, self.nodeaddress) for tx in all_rlt_txs(bip112txs_vary_nSequence_9_v1)]) success_txs.extend(all_rlt_txs(bip112txs_vary_OP_CSV_9_v1)) success_txs.extend([spend_tx(self.nodes[0], tx, self.nodeaddress) for tx in all_rlt_txs(bip112txs_vary_OP_CSV_9_v1)]) # Test #3 self.send_blocks([self.create_test_block(success_txs)]) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) self.log.info("Test version 2 txs") success_txs = [] # add BIP113 tx and -1 CSV tx # = MTP of prior block (not <) but < time put on current block bip113tx_v2.nLockTime = self.last_block_time - 600 * 5 bip113signed2 = sign_transaction(self.nodes[0], bip113tx_v2) success_txs.append(bip113signed2) success_txs.append(bip112tx_special_v2) success_txs.append( spend_tx(self.nodes[0], bip112tx_special_v2, self.nodeaddress)) # add BIP 68 txs success_txs.extend(all_rlt_txs(bip68txs_v2)) # add BIP 112 with seq=10 txs success_txs.extend(all_rlt_txs(bip112txs_vary_nSequence_v2)) success_txs.extend([spend_tx(self.nodes[0], tx, self.nodeaddress) for tx in all_rlt_txs(bip112txs_vary_nSequence_v2)]) success_txs.extend(all_rlt_txs(bip112txs_vary_OP_CSV_v2)) success_txs.extend([spend_tx(self.nodes[0], tx, self.nodeaddress) for tx in all_rlt_txs(bip112txs_vary_OP_CSV_v2)]) # try BIP 112 with seq=9 txs success_txs.extend(all_rlt_txs(bip112txs_vary_nSequence_9_v2)) success_txs.extend([spend_tx(self.nodes[0], tx, self.nodeaddress) for tx in all_rlt_txs(bip112txs_vary_nSequence_9_v2)]) success_txs.extend(all_rlt_txs(bip112txs_vary_OP_CSV_9_v2)) success_txs.extend([spend_tx(self.nodes[0], tx, self.nodeaddress) for tx in all_rlt_txs(bip112txs_vary_OP_CSV_9_v2)]) # Test #4 self.send_blocks([self.create_test_block(success_txs)]) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) # 1 more version 4 block to get us to height 575 so the fork should # now be active for the next block test_blocks = self.generate_blocks(1) # Test #5 self.send_blocks(test_blocks) assert_equal(get_csv_status(self.nodes[0]), False) self.nodes[0].generate(1) assert_equal(get_csv_status(self.nodes[0]), True) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) self.log.info("Post-Soft Fork Tests.") self.log.info("BIP 113 tests") # BIP 113 tests should now fail regardless of version number # if nLockTime isn't satisfied by new rules # = MTP of prior block (not <) but < time put on current block bip113tx_v1.nLockTime = self.last_block_time - 600 * 5 bip113signed1 = sign_transaction(self.nodes[0], bip113tx_v1) # = MTP of prior block (not <) but < time put on current block bip113tx_v2.nLockTime = self.last_block_time - 600 * 5 bip113signed2 = sign_transaction(self.nodes[0], bip113tx_v2) for bip113tx in [bip113signed1, bip113signed2]: # Test #6, Test #7 self.send_blocks( [self.create_test_block([bip113tx])], success=False) # BIP 113 tests should now pass if the locktime is < MTP # < MTP of prior block bip113tx_v1.nLockTime = self.last_block_time - 600 * 5 - 1 bip113signed1 = sign_transaction(self.nodes[0], bip113tx_v1) # < MTP of prior block bip113tx_v2.nLockTime = self.last_block_time - 600 * 5 - 1 bip113signed2 = sign_transaction(self.nodes[0], bip113tx_v2) for bip113tx in [bip113signed1, bip113signed2]: # Test #8, Test #9 self.send_blocks([self.create_test_block([bip113tx])]) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) # Next block height = 580 after 4 blocks of random version test_blocks = self.generate_blocks(4) # Test #10 self.send_blocks(test_blocks) self.log.info("BIP 68 tests") self.log.info("Test version 1 txs - all should still pass") success_txs = [] success_txs.extend(all_rlt_txs(bip68txs_v1)) # Test #11 self.send_blocks([self.create_test_block(success_txs)]) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) self.log.info("Test version 2 txs") # All txs with SEQUENCE_LOCKTIME_DISABLE_FLAG set pass bip68success_txs = [tx['tx'] for tx in bip68txs_v2 if tx['sdf']] self.send_blocks([self.create_test_block(bip68success_txs)]) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) # All txs without flag fail as we are at delta height = 8 < 10 and # delta time = 8 * 600 < 10 * 512 bip68timetxs = [tx['tx'] for tx in bip68txs_v2 if not tx['sdf'] and tx['stf']] for tx in bip68timetxs: # Test #13 - Test #16 self.send_blocks([self.create_test_block([tx])], success=False) bip68heighttxs = [tx['tx'] for tx in bip68txs_v2 if not tx['sdf'] and not tx['stf']] for tx in bip68heighttxs: # Test #17 - Test #20 self.send_blocks([self.create_test_block([tx])], success=False) # Advance one block to 581 test_blocks = self.generate_blocks(1) # Test #21 self.send_blocks(test_blocks,) # Height txs should fail and time txs should now pass 9 * 600 > 10 * # 512 bip68success_txs.extend(bip68timetxs) # Test #22 self.send_blocks([self.create_test_block(bip68success_txs)]) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) for tx in bip68heighttxs: # Test #23 - Test #26 self.send_blocks([self.create_test_block([tx])], success=False) # Advance one block to 582 test_blocks = self.generate_blocks(1) # Test #27 self.send_blocks(test_blocks) # All BIP 68 txs should pass bip68success_txs.extend(bip68heighttxs) # Test #28 self.send_blocks([self.create_test_block(bip68success_txs)]) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) self.log.info("BIP 112 tests") self.log.info("Test version 1 txs") # -1 OP_CSV tx should fail # Test #29 self.send_blocks([self.create_test_block_spend_utxos( self.nodes[0], [bip112tx_special_v1])], success=False) # If SEQUENCE_LOCKTIME_DISABLE_FLAG is set in argument to OP_CSV, # version 1 txs should still pass success_txs = [tx['tx'] for tx in bip112txs_vary_OP_CSV_v1 if tx['sdf']] success_txs += [tx['tx'] for tx in bip112txs_vary_OP_CSV_9_v1 if tx['sdf']] # Test #30 self.send_blocks( [self.create_test_block_spend_utxos(self.nodes[0], success_txs)]) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) # If SEQUENCE_LOCKTIME_DISABLE_FLAG is unset in argument to OP_CSV, # version 1 txs should now fail fail_txs = all_rlt_txs(bip112txs_vary_nSequence_v1) fail_txs += all_rlt_txs(bip112txs_vary_nSequence_9_v1) fail_txs += [tx['tx'] for tx in bip112txs_vary_OP_CSV_9_v1 if not tx['sdf']] fail_txs += [tx['tx'] for tx in bip112txs_vary_OP_CSV_9_v1 if not tx['sdf']] for tx in fail_txs: # Test #31 - Test #78 self.send_blocks([self.create_test_block_spend_utxos( self.nodes[0], [tx])], success=False) self.log.info("Test version 2 txs") # -1 OP_CSV tx should fail # Test #79 self.send_blocks([self.create_test_block_spend_utxos( self.nodes[0], [bip112tx_special_v2])], success=False) # If SEQUENCE_LOCKTIME_DISABLE_FLAG is set in argument to OP_CSV, # version 2 txs should pass success_txs = [tx['tx'] for tx in bip112txs_vary_OP_CSV_v2 if tx['sdf']] success_txs += [tx['tx'] for tx in bip112txs_vary_OP_CSV_9_v2 if tx['sdf']] # Test #80 self.send_blocks( [self.create_test_block_spend_utxos(self.nodes[0], success_txs)]) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) # SEQUENCE_LOCKTIME_DISABLE_FLAG is unset in argument to OP_CSV for all # remaining txs ## # All txs with nSequence 9 should fail either due to earlier mismatch # or failing the CSV check fail_txs = all_rlt_txs(bip112txs_vary_nSequence_9_v2) fail_txs += [tx['tx'] for tx in bip112txs_vary_OP_CSV_9_v2 if not tx['sdf']] for tx in fail_txs: # Test #81 - Test #104 self.send_blocks([self.create_test_block_spend_utxos( self.nodes[0], [tx])], success=False) # If SEQUENCE_LOCKTIME_DISABLE_FLAG is set in nSequence, tx should fail fail_txs = [tx['tx'] for tx in bip112txs_vary_nSequence_v2 if tx['sdf']] for tx in fail_txs: # Test #105 - Test #112 self.send_blocks([self.create_test_block_spend_utxos( self.nodes[0], [tx])], success=False) # If sequencelock types mismatch, tx should fail fail_txs = [tx['tx'] for tx in bip112txs_vary_nSequence_v2 if not tx['sdf'] and tx['stf']] fail_txs += [tx['tx'] for tx in bip112txs_vary_OP_CSV_v2 if not tx['sdf'] and tx['stf']] for tx in fail_txs: # Test #113 - Test #120 self.send_blocks([self.create_test_block_spend_utxos( self.nodes[0], [tx])], success=False) # Remaining txs should pass, just test masking works properly success_txs = [ tx['tx'] for tx in bip112txs_vary_nSequence_v2 if not tx['sdf'] and not tx['stf']] success_txs += [tx['tx'] for tx in bip112txs_vary_OP_CSV_v2 if not tx['sdf'] and not tx['stf']] # Test #121 self.send_blocks([self.create_test_block(success_txs)]) # Spending the previous block utxos requires a difference of 10 blocks (nSequence = 10). # Generate 9 blocks then spend in the 10th block = self.nodes[0].getbestblockhash() self.last_block_time += 600 self.tip = int("0x" + block, 0) self.tipheight += 1 # Test #122 self.send_blocks(self.generate_blocks(9)) spend_txs = [] for tx in success_txs: raw_tx = spend_tx(self.nodes[0], tx, self.nodeaddress) raw_tx.vin[0].nSequence = BASE_RELATIVE_LOCKTIME raw_tx.rehash() spend_txs.append(raw_tx) # Test #123 self.send_blocks([self.create_test_block(spend_txs)]) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) # Additional test, of checking that comparison of two time types works # properly time_txs = [] for tx in [tx['tx'] for tx in bip112txs_vary_OP_CSV_v2 if not tx['sdf'] and tx['stf']]: signtx = sign_transaction(self.nodes[0], tx) time_txs.append(signtx) # Test #124 self.send_blocks([self.create_test_block(time_txs)]) # Spending the previous block utxos requires a block time difference of # at least 10 * 512s (nSequence = 10). # Generate 8 blocks then spend in the 9th (9 * 600 > 10 * 512) block = self.nodes[0].getbestblockhash() self.last_block_time += 600 self.tip = int("0x" + block, 0) self.tipheight += 1 # Test #125 self.send_blocks(self.generate_blocks(8)) spend_txs = [] for tx in time_txs: raw_tx = spend_tx(self.nodes[0], tx, self.nodeaddress) raw_tx.vin[0].nSequence = BASE_RELATIVE_LOCKTIME | SEQ_TYPE_FLAG raw_tx.rehash() spend_txs.append(raw_tx) # Test #126 self.send_blocks([self.create_test_block(spend_txs)]) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) # TODO: Test empty stack fails if __name__ == '__main__': BIP68_112_113Test().main() diff --git a/test/functional/feature_dbcrash.py b/test/functional/feature_dbcrash.py index 8ef766c72..87f9c06cf 100755 --- a/test/functional/feature_dbcrash.py +++ b/test/functional/feature_dbcrash.py @@ -1,331 +1,331 @@ #!/usr/bin/env python3 # Copyright (c) 2017-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test recovery from a crash during chainstate writing. - 4 nodes * node0, node1, and node2 will have different dbcrash ratios, and different dbcache sizes * node3 will be a regular node, with no crashing. * The nodes will not connect to each other. - use default test framework starting chain. initialize starting_tip_height to tip height. - Main loop: * generate lots of transactions on node3, enough to fill up a block. * uniformly randomly pick a tip height from starting_tip_height to tip_height; with probability 1/(height_difference+4), invalidate this block. * mine enough blocks to overtake tip_height at start of loop. * for each node in [node0,node1,node2]: - for each mined block: * submit block to node * if node crashed on/after submitting: - restart until recovery succeeds - check that utxo matches node3 using gettxoutsetinfo""" import errno import http.client import random import time from test_framework.blocktools import create_confirmed_utxos from test_framework.cdefs import DEFAULT_MAX_BLOCK_SIZE from test_framework.messages import ( - COIN, + XEC, COutPoint, CTransaction, CTxIn, CTxOut, ToHex, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, hex_str_to_bytes, ) class ChainstateWriteCrashTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.setup_clean_chain = False self.rpc_timeout = 480 self.supports_cli = False # Set -maxmempool=0 to turn off mempool memory sharing with dbcache # Set -rpcservertimeout=900 to reduce socket disconnects in this # long-running test self.base_args = ["-limitdescendantsize=0", "-maxmempool=0", "-rpcservertimeout=900", "-dbbatchsize=200000", "-noparkdeepreorg"] # Set different crash ratios and cache sizes. Note that not all of # -dbcache goes to the in-memory coins cache. self.node0_args = ["-dbcrashratio=8", "-dbcache=4"] + self.base_args self.node1_args = ["-dbcrashratio=16", "-dbcache=8"] + self.base_args self.node2_args = ["-dbcrashratio=24", "-dbcache=16"] + self.base_args # Node3 is a normal node with default args, except will mine full blocks # and non-standard txs (e.g. txs with "dust" outputs) self.node3_args = [ "-blockmaxsize={}".format(DEFAULT_MAX_BLOCK_SIZE), "-acceptnonstdtxn"] self.extra_args = [self.node0_args, self.node1_args, self.node2_args, self.node3_args] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def setup_network(self): self.add_nodes(self.num_nodes, extra_args=self.extra_args) self.start_nodes() self.import_deterministic_coinbase_privkeys() # Leave them unconnected, we'll use submitblock directly in this test def restart_node(self, node_index, expected_tip): """Start up a given node id, wait for the tip to reach the given block hash, and calculate the utxo hash. Exceptions on startup should indicate node crash (due to -dbcrashratio), in which case we try again. Give up after 60 seconds. Returns the utxo hash of the given node.""" time_start = time.time() while time.time() - time_start < 120: try: # Any of these RPC calls could throw due to node crash self.start_node(node_index) self.nodes[node_index].waitforblock(expected_tip) utxo_hash = self.nodes[node_index].gettxoutsetinfo()[ 'hash_serialized'] return utxo_hash except Exception: # An exception here should mean the node is about to crash. # If bitcoind exits, then try again. wait_for_node_exit() # should raise an exception if bitcoind doesn't exit. self.wait_for_node_exit(node_index, timeout=15) self.crashed_on_restart += 1 time.sleep(1) # If we got here, bitcoind isn't coming back up on restart. Could be a # bug in bitcoind, or we've gotten unlucky with our dbcrash ratio -- # perhaps we generated a test case that blew up our cache? # TODO: If this happens a lot, we should try to restart without -dbcrashratio # and make sure that recovery happens. raise AssertionError( "Unable to successfully restart node {} in allotted time".format(node_index)) def submit_block_catch_error(self, node_index, block): """Try submitting a block to the given node. Catch any exceptions that indicate the node has crashed. Returns true if the block was submitted successfully; false otherwise.""" try: self.nodes[node_index].submitblock(block) return True except (http.client.CannotSendRequest, http.client.RemoteDisconnected) as e: self.log.debug( "node {} submitblock raised exception: {}".format(node_index, e)) return False except OSError as e: self.log.debug( "node {} submitblock raised OSError exception: errno={}".format(node_index, e.errno)) if e.errno in [errno.EPIPE, errno.ECONNREFUSED, errno.ECONNRESET]: # The node has likely crashed return False else: # Unexpected exception, raise raise def sync_node3blocks(self, block_hashes): """Use submitblock to sync node3's chain with the other nodes If submitblock fails, restart the node and get the new utxo hash. If any nodes crash while updating, we'll compare utxo hashes to ensure recovery was successful.""" node3_utxo_hash = self.nodes[3].gettxoutsetinfo()['hash_serialized'] # Retrieve all the blocks from node3 blocks = [] for block_hash in block_hashes: blocks.append( [block_hash, self.nodes[3].getblock(block_hash, False)]) # Deliver each block to each other node for i in range(3): nodei_utxo_hash = None self.log.debug("Syncing blocks to node {}".format(i)) for (block_hash, block) in blocks: # Get the block from node3, and submit to node_i self.log.debug("submitting block {}".format(block_hash)) if not self.submit_block_catch_error(i, block): # TODO: more carefully check that the crash is due to -dbcrashratio # (change the exit code perhaps, and check that here?) self.wait_for_node_exit(i, timeout=30) self.log.debug( "Restarting node {} after block hash {}".format(i, block_hash)) nodei_utxo_hash = self.restart_node(i, block_hash) assert nodei_utxo_hash is not None self.restart_counts[i] += 1 else: # Clear it out after successful submitblock calls -- the cached # utxo hash will no longer be correct nodei_utxo_hash = None # Check that the utxo hash matches node3's utxo set # NOTE: we only check the utxo set if we had to restart the node # after the last block submitted: # - checking the utxo hash causes a cache flush, which we don't # want to do every time; so # - we only update the utxo cache after a node restart, since flushing # the cache is a no-op at that point if nodei_utxo_hash is not None: self.log.debug( "Checking txoutsetinfo matches for node {}".format(i)) assert_equal(nodei_utxo_hash, node3_utxo_hash) def verify_utxo_hash(self): """Verify that the utxo hash of each node matches node3. Restart any nodes that crash while querying.""" node3_utxo_hash = self.nodes[3].gettxoutsetinfo()['hash_serialized'] self.log.info("Verifying utxo hash matches for all nodes") for i in range(3): try: nodei_utxo_hash = self.nodes[i].gettxoutsetinfo()[ 'hash_serialized'] except OSError: # probably a crash on db flushing nodei_utxo_hash = self.restart_node( i, self.nodes[3].getbestblockhash()) assert_equal(nodei_utxo_hash, node3_utxo_hash) def generate_small_transactions(self, node, count, utxo_list): FEE = 1000 # TODO: replace this with node relay fee based calculation num_transactions = 0 random.shuffle(utxo_list) while len(utxo_list) >= 2 and num_transactions < count: tx = CTransaction() input_amount = 0 for i in range(2): utxo = utxo_list.pop() tx.vin.append( CTxIn(COutPoint(int(utxo['txid'], 16), utxo['vout']))) - input_amount += int(utxo['amount'] * COIN) + input_amount += int(utxo['amount'] * XEC) output_amount = (input_amount - FEE) // 3 if output_amount <= 0: # Sanity check -- if we chose inputs that are too small, skip continue for i in range(3): tx.vout.append( CTxOut(output_amount, hex_str_to_bytes(utxo['scriptPubKey']))) # Sign and send the transaction to get into the mempool tx_signed_hex = node.signrawtransactionwithwallet(ToHex(tx))['hex'] node.sendrawtransaction(tx_signed_hex) num_transactions += 1 def run_test(self): # 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 initial_height = self.nodes[3].getblockcount() utxo_list = create_confirmed_utxos(self.nodes[3], 5000) self.log.info("Prepped {} utxo entries".format(len(utxo_list))) # 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("Syncing {} blocks with other nodes".format( len(block_hashes_to_sync))) # 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() # Set mock time to the last block time. This will allow us to increase # the time at each loop so the block hash will always differ for the # same block height, and avoid duplication. # Note that the current time can be behind the block time due to the # way the miner sets the block time. tip = self.nodes[3].getbestblockhash() block_time = self.nodes[3].getblockheader(tip)['time'] self.nodes[3].setmocktime(block_time) # 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): block_time += 10 self.nodes[3].setmocktime(block_time) self.log.info( "Iteration {}, generating 2500 transactions {}".format( i, 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("At height {}, considering height {}".format( current_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( "Invalidating block at height {}".format(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.nodes[3].generatetoaddress( nblocks=min(10, current_height + 1 - self.nodes[3].getblockcount()), # new address to avoid mining a block that has just been # invalidated address=self.nodes[3].getnewaddress(), )) self.log.debug("Syncing %d new blocks...", len(block_hashes)) self.sync_node3blocks(block_hashes) utxo_list = self.nodes[3].listunspent() self.log.debug("Node3 utxo count: {}".format(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("Restarted nodes: {}; crashes on restart: {}".format( self.restart_counts, 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( "Node {} never crashed during utxo flush!".format(i)) if __name__ == "__main__": ChainstateWriteCrashTest().main() diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py index b0778b76a..c98eb0095 100755 --- a/test/functional/feature_dersig.py +++ b/test/functional/feature_dersig.py @@ -1,112 +1,112 @@ #!/usr/bin/env python3 # Copyright (c) 2015-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test BIP66 (DER SIG). Test that the DERSIG soft-fork activates at (regtest) height 1251. """ from test_framework.blocktools import create_block, create_coinbase, create_transaction from test_framework.messages import msg_block from test_framework.p2p import P2PInterface from test_framework.script import CScript from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal DERSIG_HEIGHT = 1251 # A canonical signature consists of: # <30> <02> <02> def unDERify(tx): """ Make the signature in vin 0 of a tx non-DER-compliant, by adding padding after the S-value. """ scriptSig = CScript(tx.vin[0].scriptSig) newscript = [] for i in scriptSig: if (len(newscript) == 0): newscript.append(i[0:-1] + b'\0' + i[-1:]) else: newscript.append(i) tx.vin[0].scriptSig = CScript(newscript) class BIP66Test(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.extra_args = [['-whitelist=noban@127.0.0.1']] self.setup_clean_chain = True self.rpc_timeout = 120 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): self.nodes[0].add_p2p_connection(P2PInterface()) self.log.info("Mining {} blocks".format(DERSIG_HEIGHT - 1)) self.coinbase_txids = [self.nodes[0].getblock( b)['tx'][0] for b in self.nodes[0].generate(DERSIG_HEIGHT - 1)] self.nodeaddress = self.nodes[0].getnewaddress() self.log.info("Test that blocks must now be at least version 3") tip = self.nodes[0].getbestblockhash() block_time = self.nodes[0].getblockheader(tip)['mediantime'] + 1 block = create_block( int(tip, 16), create_coinbase(DERSIG_HEIGHT), block_time) block.nVersion = 2 block.rehash() block.solve() with self.nodes[0].assert_debug_log(expected_msgs=['{}, bad-version(0x00000002)'.format(block.hash)]): self.nodes[0].p2p.send_and_ping(msg_block(block)) assert_equal(self.nodes[0].getbestblockhash(), tip) self.nodes[0].p2p.sync_with_ping() self.log.info( "Test that transactions with non-DER signatures cannot appear in a block") block.nVersion = 3 spendtx = create_transaction(self.nodes[0], self.coinbase_txids[1], - self.nodeaddress, amount=1.0) + self.nodeaddress, amount=1000000) unDERify(spendtx) spendtx.rehash() # First we show that this tx is valid except for DERSIG by getting it # rejected from the mempool for exactly that reason. assert_equal( [{'txid': spendtx.hash, 'allowed': False, 'reject-reason': 'mandatory-script-verify-flag-failed (Non-canonical DER signature)'}], self.nodes[0].testmempoolaccept( rawtxs=[spendtx.serialize().hex()], maxfeerate=0) ) # Now we verify that a block with this transaction is also invalid. block.vtx.append(spendtx) block.hashMerkleRoot = block.calc_merkle_root() block.rehash() block.solve() with self.nodes[0].assert_debug_log(expected_msgs=['ConnectBlock {} failed, blk-bad-inputs'.format(block.hash)]): self.nodes[0].p2p.send_and_ping(msg_block(block)) assert_equal(self.nodes[0].getbestblockhash(), tip) self.nodes[0].p2p.sync_with_ping() self.log.info( "Test that a version 3 block with a DERSIG-compliant transaction is accepted") block.vtx[1] = create_transaction(self.nodes[0], self.coinbase_txids[1], self.nodeaddress, amount=1.0) block.hashMerkleRoot = block.calc_merkle_root() block.rehash() block.solve() self.nodes[0].p2p.send_and_ping(msg_block(block)) assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256) if __name__ == '__main__': BIP66Test().main() diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index b2bd37b87..ba26722ee 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -1,200 +1,200 @@ #!/usr/bin/env python3 # Copyright (c) 2017-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test bitcoin-cli""" from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_process_error, get_auth_cookie, ) # The block reward of coinbaseoutput.nValue (50) BTC/block matures after # COINBASE_MATURITY (100) blocks. Therefore, after mining 101 blocks we expect # node 0 to have a balance of (BLOCKS - COINBASE_MATURITY) * 50 BTC/block. BLOCKS = 101 -BALANCE = (BLOCKS - 100) * 50 +BALANCE = (BLOCKS - 100) * 50000000 class TestBitcoinCli(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_cli() def run_test(self): """Main test logic""" self.nodes[0].generate(BLOCKS) self.log.info( "Compare responses from getblockchaininfo RPC and `bitcoin-cli getblockchaininfo`") cli_response = self.nodes[0].cli.getblockchaininfo() rpc_response = self.nodes[0].getblockchaininfo() assert_equal(cli_response, rpc_response) user, password = get_auth_cookie(self.nodes[0].datadir, self.chain) self.log.info("Test -stdinrpcpass option") assert_equal(BLOCKS, self.nodes[0].cli( '-rpcuser={}'.format(user), '-stdinrpcpass', input=password).getblockcount()) assert_raises_process_error(1, "Incorrect rpcuser or rpcpassword", self.nodes[0].cli( '-rpcuser={}'.format(user), '-stdinrpcpass', input="foo").echo) self.log.info("Test -stdin and -stdinrpcpass") assert_equal(["foo", "bar"], self.nodes[0].cli('-rpcuser={}'.format(user), '-stdin', '-stdinrpcpass', input=password + "\nfoo\nbar").echo()) assert_raises_process_error(1, "Incorrect rpcuser or rpcpassword", self.nodes[0].cli( '-rpcuser={}'.format(user), '-stdin', '-stdinrpcpass', input="foo").echo) self.log.info("Test connecting to a non-existing server") assert_raises_process_error( 1, "Could not connect to the server", self.nodes[0].cli('-rpcport=1').echo) self.log.info("Test connecting with non-existing RPC cookie file") assert_raises_process_error(1, "Could not locate RPC credentials", self.nodes[0].cli( '-rpccookiefile=does-not-exist', '-rpcpassword=').echo) self.log.info("Test -getinfo with arguments fails") assert_raises_process_error( 1, "-getinfo takes no arguments", self.nodes[0].cli('-getinfo').help) self.log.info( "Test -getinfo returns expected network and blockchain info") if self.is_wallet_compiled(): self.nodes[0].encryptwallet(password) cli_get_info = self.nodes[0].cli().send_cli('-getinfo') network_info = self.nodes[0].getnetworkinfo() blockchain_info = self.nodes[0].getblockchaininfo() assert_equal(cli_get_info['version'], network_info['version']) assert_equal(cli_get_info['blocks'], blockchain_info['blocks']) assert_equal(cli_get_info['headers'], blockchain_info['headers']) assert_equal(cli_get_info['timeoffset'], network_info['timeoffset']) assert_equal(cli_get_info['connections'], network_info['connections']) assert_equal(cli_get_info['proxy'], network_info['networks'][0]['proxy']) assert_equal(cli_get_info['difficulty'], blockchain_info['difficulty']) assert_equal(cli_get_info['chain'], blockchain_info['chain']) if self.is_wallet_compiled(): self.log.info( "Test -getinfo and bitcoin-cli getwalletinfo return expected wallet info") assert_equal(cli_get_info['balance'], BALANCE) assert 'balances' not in cli_get_info.keys() wallet_info = self.nodes[0].getwalletinfo() assert_equal( cli_get_info['keypoolsize'], wallet_info['keypoolsize']) assert_equal( cli_get_info['unlocked_until'], wallet_info['unlocked_until']) assert_equal(cli_get_info['paytxfee'], wallet_info['paytxfee']) assert_equal(cli_get_info['relayfee'], network_info['relayfee']) assert_equal(self.nodes[0].cli.getwalletinfo(), wallet_info) # Setup to test -getinfo and -rpcwallet= with multiple wallets. wallets = ['', 'Encrypted', 'secret'] amounts = [ - BALANCE + Decimal('9.99999550'), - Decimal(9), - Decimal(31)] + BALANCE + Decimal('9999995.50'), + Decimal(9000000), + Decimal(31000000)] self.nodes[0].createwallet(wallet_name=wallets[1]) self.nodes[0].createwallet(wallet_name=wallets[2]) w1 = self.nodes[0].get_wallet_rpc(wallets[0]) w2 = self.nodes[0].get_wallet_rpc(wallets[1]) w3 = self.nodes[0].get_wallet_rpc(wallets[2]) w1.walletpassphrase(password, self.rpc_timeout) w2.encryptwallet(password) w1.sendtoaddress(w2.getnewaddress(), amounts[1]) w1.sendtoaddress(w3.getnewaddress(), amounts[2]) # Mine a block to confirm; adds a block reward (50 BTC) to the # default wallet. self.nodes[0].generate(1) self.log.info( "Test -getinfo with multiple wallets and -rpcwallet returns specified wallet balance") for i in range(len(wallets)): cli_get_info = self.nodes[0].cli( '-getinfo', '-rpcwallet={}'.format(wallets[i])).send_cli() assert 'balances' not in cli_get_info.keys() assert_equal(cli_get_info['balance'], amounts[i]) self.log.info( "Test -getinfo with multiple wallets and " "-rpcwallet=non-existing-wallet returns no balances") cli_get_info_keys = self.nodes[0].cli( '-getinfo', '-rpcwallet=does-not-exist').send_cli().keys() assert 'balance' not in cli_get_info_keys assert 'balances' not in cli_get_info_keys self.log.info( "Test -getinfo with multiple wallets returns all loaded " "wallet names and balances") assert_equal(set(self.nodes[0].listwallets()), set(wallets)) cli_get_info = self.nodes[0].cli('-getinfo').send_cli() assert 'balance' not in cli_get_info.keys() assert_equal(cli_get_info['balances'], {k: v for k, v in zip(wallets, amounts)}) # Unload the default wallet and re-verify. self.nodes[0].unloadwallet(wallets[0]) assert wallets[0] not in self.nodes[0].listwallets() cli_get_info = self.nodes[0].cli('-getinfo').send_cli() assert 'balance' not in cli_get_info.keys() assert_equal(cli_get_info['balances'], {k: v for k, v in zip(wallets[1:], amounts[1:])}) self.log.info( "Test -getinfo after unloading all wallets except a " "non-default one returns its balance") self.nodes[0].unloadwallet(wallets[2]) assert_equal(self.nodes[0].listwallets(), [wallets[1]]) cli_get_info = self.nodes[0].cli('-getinfo').send_cli() assert 'balances' not in cli_get_info.keys() assert_equal(cli_get_info['balance'], amounts[1]) self.log.info( "Test -getinfo with -rpcwallet=remaining-non-default-wallet" " returns only its balance") cli_get_info = self.nodes[0].cli( '-getinfo', '-rpcwallet={}'.format(wallets[1])).send_cli() assert 'balances' not in cli_get_info.keys() assert_equal(cli_get_info['balance'], amounts[1]) self.log.info( "Test -getinfo with -rpcwallet=unloaded wallet returns" " no balances") cli_get_info = self.nodes[0].cli( '-getinfo', '-rpcwallet={}'.format(wallets[2])).send_cli() assert 'balance' not in cli_get_info_keys assert 'balances' not in cli_get_info_keys else: self.log.info( "*** Wallet not compiled; cli getwalletinfo and -getinfo wallet tests skipped") # maintain block parity with the wallet_compiled conditional branch self.nodes[0].generate(1) self.log.info("Test -version with node stopped") self.stop_node(0) cli_response = self.nodes[0].cli().send_cli('-version') assert "{} RPC client version".format( self.config['environment']['PACKAGE_NAME']) in cli_response self.log.info( "Test -rpcwait option successfully waits for RPC connection") # Start node without RPC connection. self.nodes[0].start() # ensure cookie file is available to avoid race condition self.nodes[0].wait_for_cookie_credentials() blocks = self.nodes[0].cli('-rpcwait').send_cli('getblockcount') self.nodes[0].wait_for_rpc_connection() assert_equal(blocks, BLOCKS + 1) if __name__ == '__main__': TestBitcoinCli().main() diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py index 22cc86a79..81796cd0d 100755 --- a/test/functional/interface_rest.py +++ b/test/functional/interface_rest.py @@ -1,403 +1,407 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the REST API.""" import binascii from decimal import Decimal from enum import Enum import http.client from io import BytesIO import json from struct import pack, unpack import urllib.parse from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_greater_than, assert_greater_than_or_equal, hex_str_to_bytes, ) from test_framework.messages import BLOCK_HEADER_SIZE class ReqType(Enum): JSON = 1 BIN = 2 HEX = 3 class RetType(Enum): OBJ = 1 BYTES = 2 JSON = 3 def filter_output_indices_by_value(vouts, value): for vout in vouts: if vout['value'] == value: yield vout['n'] class RESTTest (BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 self.extra_args = [["-rest"], []] self.supports_cli = False def skip_test_if_missing_module(self): self.skip_if_no_wallet() def test_rest_request(self, uri, http_method='GET', req_type=ReqType.JSON, body='', status=200, ret_type=RetType.JSON): rest_uri = '/rest' + uri if req_type == ReqType.JSON: rest_uri += '.json' elif req_type == ReqType.BIN: rest_uri += '.bin' elif req_type == ReqType.HEX: rest_uri += '.hex' conn = http.client.HTTPConnection(self.url.hostname, self.url.port) self.log.debug('{} {} {}'.format(http_method, rest_uri, body)) if http_method == 'GET': conn.request('GET', rest_uri) elif http_method == 'POST': conn.request('POST', rest_uri, body) resp = conn.getresponse() assert_equal(resp.status, status) if ret_type == RetType.OBJ: return resp elif ret_type == RetType.BYTES: return resp.read() elif ret_type == RetType.JSON: return json.loads(resp.read().decode('utf-8'), parse_float=Decimal) def run_test(self): self.url = urllib.parse.urlparse(self.nodes[0].url) self.log.info("Mine blocks and send Bitcoin Cash to node 1") # Random address so node1's balance doesn't increase not_related_address = "2MxqoHEdNQTyYeX1mHcbrrpzgojbosTpCvJ" self.nodes[0].generate(1) self.sync_all() self.nodes[1].generatetoaddress(100, not_related_address) self.sync_all() - assert_equal(self.nodes[0].getbalance(), 50) + assert_equal(self.nodes[0].getbalance(), 50000000) - txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1) + txid = self.nodes[0].sendtoaddress( + self.nodes[1].getnewaddress(), 100000) self.sync_all() self.log.info("Test the /tx URI") json_obj = self.test_rest_request("/tx/{}".format(txid)) assert_equal(json_obj['txid'], txid) # Check hex format response hex_response = self.test_rest_request( "/tx/{}".format(txid), req_type=ReqType.HEX, ret_type=RetType.OBJ) assert_greater_than_or_equal(int(hex_response.getheader('content-length')), json_obj['size'] * 2) # Get the vin 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')) + n, = filter_output_indices_by_value( + json_obj['vout'], Decimal('100000')) spending = (txid, n) self.log.info("Query an unspent TXO using the /getutxos URI") self.nodes[1].generatetoaddress(1, not_related_address) self.sync_all() bb_hash = self.nodes[0].getbestblockhash() - assert_equal(self.nodes[1].getbalance(), Decimal("0.1")) + assert_equal(self.nodes[1].getbalance(), Decimal("100000")) # Check chainTip response json_obj = self.test_rest_request("/getutxos/{}-{}".format(*spending)) 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')) + assert_equal(json_obj['utxos'][0]['value'], Decimal('100000')) self.log.info("Query a spent TXO using the /getutxos URI") json_obj = self.test_rest_request("/getutxos/{}-{}".format(*spent)) # 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( "/getutxos/{}-{}/{}-{}".format(*(spending + spent))) 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 += hex_str_to_bytes(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("1650 bytes) tx.vin[0].scriptSig = CScript([b'a' * 1648]) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'scriptsig-size'}], rawtxs=[tx.serialize().hex()], ) tx = FromHex(CTransaction(), raw_tx_reference) output_p2sh_burn = CTxOut(nValue=540, scriptPubKey=CScript( [OP_HASH160, hash160(b'burn'), OP_EQUAL])) # Use enough outputs to make the tx too large for our policy num_scripts = 100000 // len(output_p2sh_burn.serialize()) tx.vout = [output_p2sh_burn] * num_scripts self.check_mempool_result( result_expected=[ {'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'tx-size'}], rawtxs=[ToHex(tx)], ) tx = FromHex(CTransaction(), raw_tx_reference) tx.vout[0] = output_p2sh_burn # Make output smaller, such that it is dust for our policy tx.vout[0].nValue -= 1 self.check_mempool_result( result_expected=[ {'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'dust'}], rawtxs=[ToHex(tx)], ) tx = FromHex(CTransaction(), raw_tx_reference) tx.vout[0].scriptPubKey = CScript([OP_RETURN, b'\xff']) tx.vout = [tx.vout[0]] * 2 self.check_mempool_result( result_expected=[ {'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'multi-op-return'}], rawtxs=[ToHex(tx)], ) self.log.info('A timelocked transaction') tx = FromHex(CTransaction(), raw_tx_reference) # Should be non-max, so locktime is not ignored tx.vin[0].nSequence -= 1 tx.nLockTime = node.getblockcount() + 1 self.check_mempool_result( result_expected=[ {'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-nonfinal'}], rawtxs=[ToHex(tx)], ) self.log.info('A transaction that is locked by BIP68 sequence logic') tx = FromHex(CTransaction(), raw_tx_reference) # We could include it in the second block mined from now, but not the # very next one tx.vin[0].nSequence = 2 # Can skip re-signing the tx because of early rejection self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'non-BIP68-final'}], rawtxs=[tx.serialize().hex()], maxfeerate=0, ) if __name__ == '__main__': MempoolAcceptanceTest().main() diff --git a/test/functional/mempool_expiry.py b/test/functional/mempool_expiry.py index 4f375de86..e90199caf 100755 --- a/test/functional/mempool_expiry.py +++ b/test/functional/mempool_expiry.py @@ -1,101 +1,101 @@ #!/usr/bin/env python3 # Copyright (c) 2020 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Tests that a mempool transaction expires after a given timeout and that its children are removed as well. Both the default expiry timeout defined by DEFAULT_MEMPOOL_EXPIRY and a user definable expiry timeout via the '-mempoolexpiry=' command line argument ( is the timeout in hours) are tested. """ from datetime import timedelta from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, find_vout_for_address, ) DEFAULT_MEMPOOL_EXPIRY = 336 # hours CUSTOM_MEMPOOL_EXPIRY = 10 # hours class MempoolExpiryTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def test_transaction_expiry(self, timeout): """Tests that a transaction expires after the expiry timeout and its children are removed as well.""" node = self.nodes[0] # Send a parent transaction that will expire. parent_address = node.getnewaddress() - parent_txid = node.sendtoaddress(parent_address, 1.0) + parent_txid = node.sendtoaddress(parent_address, 1000000) # Set the mocktime to the arrival time of the parent transaction. entry_time = node.getmempoolentry(parent_txid)['time'] node.setmocktime(entry_time) # Create child transaction spending the parent transaction vout = find_vout_for_address(node, parent_txid, parent_address) inputs = [{'txid': parent_txid, 'vout': vout}] - outputs = {node.getnewaddress(): 0.99} + outputs = {node.getnewaddress(): 990000} child_raw = node.createrawtransaction(inputs, outputs) child_signed = node.signrawtransactionwithwallet(child_raw)['hex'] # Let half of the timeout elapse and broadcast the child transaction. half_expiry_time = entry_time + int(60 * 60 * timeout / 2) node.setmocktime(half_expiry_time) child_txid = node.sendrawtransaction(child_signed) self.log.info('Broadcast child transaction after {} hours.'.format( timedelta(seconds=(half_expiry_time - entry_time)))) # Let most of the timeout elapse and check that the parent tx is still # in the mempool. nearly_expiry_time = entry_time + 60 * 60 * timeout - 5 node.setmocktime(nearly_expiry_time) # Expiry of mempool transactions is only checked when a new transaction # is added to the to the mempool. - node.sendtoaddress(node.getnewaddress(), 1.0) + node.sendtoaddress(node.getnewaddress(), 1000000) self.log.info('Test parent tx not expired after {} hours.'.format( timedelta(seconds=(nearly_expiry_time - entry_time)))) assert_equal(entry_time, node.getmempoolentry(parent_txid)['time']) # Transaction should be evicted from the mempool after the expiry time # has passed. expiry_time = entry_time + 60 * 60 * timeout + 5 node.setmocktime(expiry_time) # Expiry of mempool transactions is only checked when a new transaction # is added to the to the mempool. - node.sendtoaddress(node.getnewaddress(), 1.0) + node.sendtoaddress(node.getnewaddress(), 1000000) self.log.info('Test parent tx expiry after {} hours.'.format( timedelta(seconds=(expiry_time - entry_time)))) assert_raises_rpc_error(-5, 'Transaction not in mempool', node.getmempoolentry, parent_txid) # The child transaction should be removed from the mempool as well. self.log.info('Test child tx is evicted as well.') assert_raises_rpc_error(-5, 'Transaction not in mempool', node.getmempoolentry, child_txid) def run_test(self): self.log.info('Test default mempool expiry timeout of {} hours.'.format( DEFAULT_MEMPOOL_EXPIRY)) self.test_transaction_expiry(DEFAULT_MEMPOOL_EXPIRY) self.log.info('Test custom mempool expiry timeout of {} hours.'.format( CUSTOM_MEMPOOL_EXPIRY)) self.restart_node( 0, ['-mempoolexpiry={}'.format(CUSTOM_MEMPOOL_EXPIRY)]) self.test_transaction_expiry(CUSTOM_MEMPOOL_EXPIRY) if __name__ == '__main__': MempoolExpiryTest().main() diff --git a/test/functional/mempool_limit.py b/test/functional/mempool_limit.py index c8ac27c2e..32868129f 100755 --- a/test/functional/mempool_limit.py +++ b/test/functional/mempool_limit.py @@ -1,92 +1,92 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test mempool limiting together/eviction with the wallet.""" from decimal import Decimal from test_framework.blocktools import ( create_confirmed_utxos, send_big_transactions, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_greater_than, assert_raises_rpc_error, ) class MempoolLimitTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.extra_args = [[ "-acceptnonstdtxn=1", "-maxmempool=5", "-spendzeroconfchange=0", ]] self.supports_cli = False def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): relayfee = self.nodes[0].getnetworkinfo()['relayfee'] self.log.info('Check that mempoolminfee is minrelytxfee') assert_equal(self.nodes[0].getmempoolinfo()[ - 'minrelaytxfee'], Decimal('0.00001000')) + 'minrelaytxfee'], Decimal('10.00')) assert_equal(self.nodes[0].getmempoolinfo()[ - 'mempoolminfee'], Decimal('0.00001000')) + 'mempoolminfee'], Decimal('10.00')) txids = [] utxo_groups = 4 utxos = create_confirmed_utxos(self.nodes[0], 1 + 30 * utxo_groups) self.log.info('Create a mempool tx that will be evicted') us0 = utxos.pop() inputs = [{"txid": us0["txid"], "vout": us0["vout"]}] - outputs = {self.nodes[0].getnewaddress(): 0.0001} + outputs = {self.nodes[0].getnewaddress(): 100} tx = self.nodes[0].createrawtransaction(inputs, outputs) # specifically fund this tx with low fee self.nodes[0].settxfee(relayfee) txF = self.nodes[0].fundrawtransaction(tx) # return to automatic fee selection self.nodes[0].settxfee(0) txFS = self.nodes[0].signrawtransactionwithwallet(txF['hex']) txid = self.nodes[0].sendrawtransaction(txFS['hex']) for i in range(utxo_groups): txids.append([]) txids[i] = send_big_transactions( self.nodes[0], utxos[30 * i:30 * i + 30], 30, 10 * (i + 1)) self.log.info('The tx should be evicted by now') assert txid not in self.nodes[0].getrawmempool() txdata = self.nodes[0].gettransaction(txid) # confirmation should still be 0 assert txdata['confirmations'] == 0 self.log.info('Check that mempoolminfee is larger than minrelytxfee') assert_equal(self.nodes[0].getmempoolinfo()[ - 'minrelaytxfee'], Decimal('0.00001000')) + 'minrelaytxfee'], Decimal('10.00')) assert_greater_than(self.nodes[0].getmempoolinfo()[ - 'mempoolminfee'], Decimal('0.00001000')) + 'mempoolminfee'], Decimal('10.00')) self.log.info('Create a mempool tx that will not pass mempoolminfee') us0 = utxos.pop() inputs = [{"txid": us0["txid"], "vout": us0["vout"]}] - outputs = {self.nodes[0].getnewaddress(): 0.0001} + outputs = {self.nodes[0].getnewaddress(): 100} tx = self.nodes[0].createrawtransaction(inputs, outputs) # specifically fund this tx with a fee < mempoolminfee, >= than # minrelaytxfee txF = self.nodes[0].fundrawtransaction(tx, {'feeRate': relayfee}) txFS = self.nodes[0].signrawtransactionwithwallet(txF['hex']) assert_raises_rpc_error(-26, "mempool min fee not met", self.nodes[0].sendrawtransaction, txFS['hex']) if __name__ == '__main__': MempoolLimitTest().main() diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py index 08b7e70c1..b29b2033c 100755 --- a/test/functional/mempool_packages.py +++ b/test/functional/mempool_packages.py @@ -1,360 +1,360 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test descendant package tracking code.""" from decimal import Decimal -from test_framework.messages import COIN +from test_framework.messages import XEC from test_framework.p2p import P2PTxInvStore from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, satoshi_round, ) # default limits MAX_ANCESTORS = 50 MAX_DESCENDANTS = 50 # custom limits for node1 MAX_ANCESTORS_CUSTOM = 5 class MempoolPackagesTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 common_params = ["-maxorphantx=1000"] self.extra_args = [ common_params, common_params + ["-limitancestorcount={}".format(MAX_ANCESTORS_CUSTOM)]] def skip_test_if_missing_module(self): self.skip_if_no_wallet() # Build a transaction that spends parent_txid:vout # Return amount sent def chain_transaction(self, node, parent_txid, vout, value, fee, num_outputs): send_value = satoshi_round((value - fee) / num_outputs) inputs = [{'txid': parent_txid, 'vout': vout}] outputs = {} for i in range(num_outputs): outputs[node.getnewaddress()] = send_value rawtx = node.createrawtransaction(inputs, outputs) signedtx = node.signrawtransactionwithwallet(rawtx) txid = node.sendrawtransaction(signedtx['hex']) fulltx = node.getrawtransaction(txid, 1) # make sure we didn't generate a change output assert len(fulltx['vout']) == num_outputs return (txid, send_value) def run_test(self): # Mine some blocks and have them mature. # keep track of invs self.nodes[0].add_p2p_connection(P2PTxInvStore()) self.nodes[0].generate(101) utxo = self.nodes[0].listunspent(10) txid = utxo[0]['txid'] vout = utxo[0]['vout'] value = utxo[0]['amount'] - fee = Decimal("0.0001") + fee = Decimal("100") # MAX_ANCESTORS transactions off a confirmed tx should be fine chain = [] for i in range(MAX_ANCESTORS): (txid, sent_value) = self.chain_transaction( self.nodes[0], txid, 0, value, fee, 1) value = sent_value chain.append(txid) # Wait until mempool transactions have passed initial broadcast # (sent inv and received getdata) # Otherwise, getrawmempool may be inconsistent with getmempoolentry if # unbroadcast changes in between self.nodes[0].p2p.wait_for_broadcast(chain) # Check mempool has MAX_ANCESTORS transactions in it, and descendant and ancestor # count and fees should look correct mempool = self.nodes[0].getrawmempool(True) assert_equal(len(mempool), MAX_ANCESTORS) descendant_count = 1 descendant_fees = 0 descendant_size = 0 ancestor_size = sum([mempool[tx]['size'] for tx in mempool]) ancestor_count = MAX_ANCESTORS ancestor_fees = sum([mempool[tx]['fee'] for tx in mempool]) descendants = [] ancestors = list(chain) for x in reversed(chain): # Check that getmempoolentry is consistent with getrawmempool entry = self.nodes[0].getmempoolentry(x) assert_equal(entry, mempool[x]) # Check that the descendant calculations are correct assert_equal(mempool[x]['descendantcount'], descendant_count) descendant_fees += mempool[x]['fee'] assert_equal(mempool[x]['modifiedfee'], mempool[x]['fee']) assert_equal(mempool[x]['fees']['base'], mempool[x]['fee']) assert_equal(mempool[x]['fees']['modified'], mempool[x]['modifiedfee']) - assert_equal(mempool[x]['descendantfees'], descendant_fees * COIN) + assert_equal(mempool[x]['descendantfees'], descendant_fees * XEC) assert_equal(mempool[x]['fees']['descendant'], descendant_fees) descendant_size += mempool[x]['size'] assert_equal(mempool[x]['descendantsize'], descendant_size) descendant_count += 1 # Check that ancestor calculations are correct assert_equal(mempool[x]['ancestorcount'], ancestor_count) - assert_equal(mempool[x]['ancestorfees'], ancestor_fees * COIN) + assert_equal(mempool[x]['ancestorfees'], ancestor_fees * XEC) assert_equal(mempool[x]['ancestorsize'], ancestor_size) ancestor_size -= mempool[x]['size'] ancestor_fees -= mempool[x]['fee'] ancestor_count -= 1 # Check that parent/child list is correct assert_equal(mempool[x]['spentby'], descendants[-1:]) assert_equal(mempool[x]['depends'], ancestors[-2:-1]) # Check that getmempooldescendants is correct assert_equal(sorted(descendants), sorted( self.nodes[0].getmempooldescendants(x))) # Check getmempooldescendants verbose output is correct for descendant, dinfo in self.nodes[0].getmempooldescendants( x, True).items(): assert_equal(dinfo['depends'], [ chain[chain.index(descendant) - 1]]) if dinfo['descendantcount'] > 1: assert_equal(dinfo['spentby'], [ chain[chain.index(descendant) + 1]]) else: assert_equal(dinfo['spentby'], []) descendants.append(x) # Check that getmempoolancestors is correct ancestors.remove(x) assert_equal(sorted(ancestors), sorted( self.nodes[0].getmempoolancestors(x))) # Check that getmempoolancestors verbose output is correct for ancestor, ainfo in self.nodes[0].getmempoolancestors( x, True).items(): assert_equal(ainfo['spentby'], [ chain[chain.index(ancestor) + 1]]) if ainfo['ancestorcount'] > 1: assert_equal(ainfo['depends'], [ chain[chain.index(ancestor) - 1]]) else: assert_equal(ainfo['depends'], []) # Check that getmempoolancestors/getmempooldescendants correctly handle # verbose=true v_ancestors = self.nodes[0].getmempoolancestors(chain[-1], True) assert_equal(len(v_ancestors), len(chain) - 1) for x in v_ancestors.keys(): assert_equal(mempool[x], v_ancestors[x]) assert chain[-1] not in v_ancestors.keys() v_descendants = self.nodes[0].getmempooldescendants(chain[0], True) assert_equal(len(v_descendants), len(chain) - 1) for x in v_descendants.keys(): assert_equal(mempool[x], v_descendants[x]) assert chain[0] not in v_descendants.keys() # Check that ancestor modified fees includes fee deltas from # prioritisetransaction self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=1000) mempool = self.nodes[0].getrawmempool(True) ancestor_fees = 0 for x in chain: ancestor_fees += mempool[x]['fee'] assert_equal(mempool[x]['fees']['ancestor'], - ancestor_fees + Decimal('0.00001')) + ancestor_fees + Decimal('10.00')) assert_equal(mempool[x]['ancestorfees'], - ancestor_fees * COIN + 1000) + ancestor_fees * XEC + 1000) # Undo the prioritisetransaction for later tests self.nodes[0].prioritisetransaction(txid=chain[0], fee_delta=-1000) # Check that descendant modified fees includes fee deltas from # prioritisetransaction self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=1000) mempool = self.nodes[0].getrawmempool(True) descendant_fees = 0 for x in reversed(chain): descendant_fees += mempool[x]['fee'] assert_equal(mempool[x]['fees']['descendant'], - descendant_fees + Decimal('0.00001')) + descendant_fees + Decimal('10.00')) assert_equal(mempool[x]['descendantfees'], - descendant_fees * COIN + 1000) + descendant_fees * XEC + 1000) # Adding one more transaction on to the chain should fail. assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], txid, vout, value, fee, 1) # Check that prioritising a tx before it's added to the mempool works # First clear the mempool by mining a block. self.nodes[0].generate(1) self.sync_blocks() assert_equal(len(self.nodes[0].getrawmempool()), 0) # Prioritise a transaction that has been mined, then add it back to the # mempool by using invalidateblock. self.nodes[0].prioritisetransaction(txid=chain[-1], fee_delta=2000) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) # Keep node1's tip synced with node0 self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash()) # Now check that the transaction is in the mempool, with the right # modified fee mempool = self.nodes[0].getrawmempool(True) descendant_fees = 0 for x in reversed(chain): descendant_fees += mempool[x]['fee'] if (x == chain[-1]): assert_equal(mempool[x]['modifiedfee'], - mempool[x]['fee'] + satoshi_round(0.00002)) + mempool[x]['fee'] + satoshi_round(20.00)) assert_equal(mempool[x]['fees']['modified'], - mempool[x]['fee'] + satoshi_round(0.00002)) + mempool[x]['fee'] + satoshi_round(20.00)) assert_equal(mempool[x]['descendantfees'], - descendant_fees * COIN + 2000) + descendant_fees * XEC + 2000) assert_equal(mempool[x]['fees']['descendant'], - descendant_fees + satoshi_round(0.00002)) + descendant_fees + satoshi_round(20.00)) # Check that node1's mempool is as expected (-> custom ancestor limit) mempool0 = self.nodes[0].getrawmempool(False) mempool1 = self.nodes[1].getrawmempool(False) assert_equal(len(mempool1), MAX_ANCESTORS_CUSTOM) assert set(mempool1).issubset(set(mempool0)) for tx in chain[:MAX_ANCESTORS_CUSTOM]: assert tx in mempool1 # TODO: more detailed check of node1's mempool (fees etc.) # check transaction unbroadcast info (should be false if in both # mempools) mempool = self.nodes[0].getrawmempool(True) for tx in mempool: assert_equal(mempool[tx]['unbroadcast'], False) # TODO: test ancestor size limits # Now test descendant chain limits txid = utxo[1]['txid'] value = utxo[1]['amount'] vout = utxo[1]['vout'] transaction_package = [] tx_children = [] # First create one parent tx with 10 children (txid, sent_value) = self.chain_transaction( self.nodes[0], txid, vout, value, fee, 10) parent_transaction = txid for i in range(10): transaction_package.append( {'txid': txid, 'vout': i, 'amount': sent_value}) # Sign and send up to MAX_DESCENDANT transactions chained off the # parent tx for i in range(MAX_DESCENDANTS - 1): utxo = transaction_package.pop(0) (txid, sent_value) = self.chain_transaction( self.nodes[0], utxo['txid'], utxo['vout'], utxo['amount'], fee, 10) if utxo['txid'] is parent_transaction: tx_children.append(txid) for j in range(10): transaction_package.append( {'txid': txid, 'vout': j, 'amount': sent_value}) mempool = self.nodes[0].getrawmempool(True) assert_equal(mempool[parent_transaction] ['descendantcount'], MAX_DESCENDANTS) assert_equal(sorted(mempool[parent_transaction] ['spentby']), sorted(tx_children)) for child in tx_children: assert_equal(mempool[child]['depends'], [parent_transaction]) # Sending one more chained transaction will fail utxo = transaction_package.pop(0) assert_raises_rpc_error(-26, "too-long-mempool-chain", self.chain_transaction, self.nodes[0], utxo['txid'], utxo['vout'], utxo['amount'], fee, 10) # TODO: check that node1's mempool is as expected # TODO: test descendant size limits # Test reorg handling # First, the basics: self.nodes[0].generate(1) self.sync_blocks() self.nodes[1].invalidateblock(self.nodes[0].getbestblockhash()) self.nodes[1].reconsiderblock(self.nodes[0].getbestblockhash()) # Now test the case where node1 has a transaction T in its mempool that # depends on transactions A and B which are in a mined block, and the # block containing A and B is disconnected, AND B is not accepted back # into node1's mempool because its ancestor count is too high. # Create 8 transactions, like so: # Tx0 -> Tx1 (vout0) # \--> Tx2 (vout1) -> Tx3 -> Tx4 -> Tx5 -> Tx6 -> Tx7 # # Mine them in the next block, then generate a new tx8 that spends # Tx1 and Tx7, and add to node1's mempool, then disconnect the # last block. # Create tx0 with 2 outputs utxo = self.nodes[0].listunspent() txid = utxo[0]['txid'] value = utxo[0]['amount'] vout = utxo[0]['vout'] send_value = satoshi_round((value - fee) / 2) inputs = [{'txid': txid, 'vout': vout}] outputs = {} for i in range(2): outputs[self.nodes[0].getnewaddress()] = send_value rawtx = self.nodes[0].createrawtransaction(inputs, outputs) signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) txid = self.nodes[0].sendrawtransaction(signedtx['hex']) tx0_id = txid value = send_value # Create tx1 tx1_id, _ = self.chain_transaction( self.nodes[0], tx0_id, 0, value, fee, 1) # Create tx2-7 vout = 1 txid = tx0_id for i in range(6): (txid, sent_value) = self.chain_transaction( self.nodes[0], txid, vout, value, fee, 1) vout = 0 value = sent_value # Mine these in a block self.nodes[0].generate(1) self.sync_all() # Now generate tx8, with a big fee inputs = [{'txid': tx1_id, 'vout': 0}, {'txid': txid, 'vout': 0}] outputs = {self.nodes[0].getnewaddress(): send_value + value - 4 * fee} rawtx = self.nodes[0].createrawtransaction(inputs, outputs) signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx) txid = self.nodes[0].sendrawtransaction(signedtx['hex']) self.sync_mempools() # Now try to disconnect the tip on each node... self.nodes[1].invalidateblock(self.nodes[1].getbestblockhash()) self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) self.sync_blocks() if __name__ == '__main__': MempoolPackagesTest().main() diff --git a/test/functional/mempool_persist.py b/test/functional/mempool_persist.py index bac1ecfd7..f7cbe460a 100755 --- a/test/functional/mempool_persist.py +++ b/test/functional/mempool_persist.py @@ -1,204 +1,204 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2017 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test mempool persistence. By default, bitcoind will dump mempool on shutdown and then reload it on startup. This can be overridden with the -persistmempool=0 command line option. Test is as follows: - start node0, node1 and node2. node1 has -persistmempool=0 - create 5 transactions on node2 to its own address. Note that these are not sent to node0 or node1 addresses because we don't want them to be saved in the wallet. - check that node0 and node1 have 5 transactions in their mempools - shutdown all nodes. - startup node0. Verify that it still has 5 transactions in its mempool. Shutdown node0. This tests that by default the mempool is persistent. - startup node1. Verify that its mempool is empty. Shutdown node1. This tests that with -persistmempool=0, the mempool is not dumped to disk when the node is shut down. - Restart node0 with -persistmempool=0. Verify that its mempool is empty. Shutdown node0. This tests that with -persistmempool=0, the mempool is not loaded from disk on start up. - Restart node0 with -persistmempool. Verify that it has 5 transactions in its mempool. This tests that -persistmempool=0 does not overwrite a previously valid mempool stored on disk. - Remove node0 mempool.dat and verify savemempool RPC recreates it and verify that node1 can load it and has 5 transactions in its mempool. - Verify that savemempool throws when the RPC is called if node1 can't write to disk. """ from decimal import Decimal import os import time from test_framework.p2p import P2PTxInvStore from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_greater_than_or_equal, assert_raises_rpc_error, connect_nodes, disconnect_nodes, wait_until, ) class MempoolPersistTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 3 self.extra_args = [[], ["-persistmempool=0"], []] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): self.log.debug("Send 5 transactions from node2 (to its own address)") tx_creation_time_lower = int(time.time()) for i in range(5): last_txid = self.nodes[2].sendtoaddress( self.nodes[2].getnewaddress(), Decimal("10")) node2_balance = self.nodes[2].getbalance() self.sync_all() tx_creation_time_higher = int(time.time()) self.log.debug( "Verify that node0 and node1 have 5 transactions in their mempools") assert_equal(len(self.nodes[0].getrawmempool()), 5) assert_equal(len(self.nodes[1].getrawmempool()), 5) self.log.debug("Prioritize a transaction on node0") fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees'] assert_equal(fees['base'], fees['modified']) self.nodes[0].prioritisetransaction(txid=last_txid, fee_delta=1000) fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees'] - assert_equal(fees['base'] + Decimal('0.00001000'), fees['modified']) + assert_equal(fees['base'] + Decimal('10.0'), fees['modified']) tx_creation_time = self.nodes[0].getmempoolentry(txid=last_txid)[ 'time'] assert_greater_than_or_equal(tx_creation_time, tx_creation_time_lower) assert_greater_than_or_equal(tx_creation_time_higher, tx_creation_time) # disconnect nodes & make a txn that remains in the unbroadcast set. disconnect_nodes(self.nodes[0], self.nodes[1]) assert(len(self.nodes[0].getpeerinfo()) == 0) assert(len(self.nodes[0].p2ps) == 0) self.nodes[0].sendtoaddress( - self.nodes[2].getnewaddress(), Decimal("12")) + self.nodes[2].getnewaddress(), Decimal("12000000")) connect_nodes(self.nodes[0], self.nodes[2]) self.log.debug("Stop-start the nodes. Verify that node0 has the " "transactions in its mempool and node1 does not. " "Verify that node2 calculates its balance correctly " "after loading wallet transactions.") self.stop_nodes() # Give this one a head-start, so we can be "extra-sure" that it didn't # load anything later # Also don't store the mempool, to keep the datadir clean self.start_node(1, extra_args=["-persistmempool=0"]) self.start_node(0) self.start_node(2) # start_node is blocking on the mempool being loaded assert self.nodes[0].getmempoolinfo()["loaded"] assert self.nodes[2].getmempoolinfo()["loaded"] assert_equal(len(self.nodes[0].getrawmempool()), 6) assert_equal(len(self.nodes[2].getrawmempool()), 5) # The others have loaded their mempool. If node_1 loaded anything, we'd # probably notice by now: assert_equal(len(self.nodes[1].getrawmempool()), 0) self.log.debug('Verify prioritization is loaded correctly') fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees'] - assert_equal(fees['base'] + Decimal('0.00001000'), fees['modified']) + assert_equal(fees['base'] + Decimal('10.00'), fees['modified']) self.log.debug('Verify time is loaded correctly') assert_equal( tx_creation_time, self.nodes[0].getmempoolentry( txid=last_txid)['time']) # Verify accounting of mempool transactions after restart is correct # Flush mempool to wallet self.nodes[2].syncwithvalidationinterfacequeue() assert_equal(node2_balance, self.nodes[2].getbalance()) # start node0 with wallet disabled so wallet transactions don't get # resubmitted self.log.debug( "Stop-start node0 with -persistmempool=0. Verify that it doesn't load its mempool.dat file.") self.stop_nodes() self.start_node(0, extra_args=["-persistmempool=0", "-disablewallet"]) assert self.nodes[0].getmempoolinfo()["loaded"] assert_equal(len(self.nodes[0].getrawmempool()), 0) self.log.debug( "Stop-start node0. Verify that it has the transactions in its mempool.") self.stop_nodes() self.start_node(0) assert self.nodes[0].getmempoolinfo()["loaded"] assert_equal(len(self.nodes[0].getrawmempool()), 6) mempooldat0 = os.path.join( self.nodes[0].datadir, self.chain, 'mempool.dat') mempooldat1 = os.path.join( self.nodes[1].datadir, self.chain, 'mempool.dat') self.log.debug( "Remove the mempool.dat file. Verify that savemempool to disk via RPC re-creates it") os.remove(mempooldat0) self.nodes[0].savemempool() assert os.path.isfile(mempooldat0) self.log.debug( "Stop nodes, make node1 use mempool.dat from node0. Verify it has 6 transactions") os.rename(mempooldat0, mempooldat1) self.stop_nodes() self.start_node(1, extra_args=[]) assert self.nodes[1].getmempoolinfo()["loaded"] assert_equal(len(self.nodes[1].getrawmempool()), 6) self.log.debug( "Prevent bitcoind from writing mempool.dat to disk. Verify that `savemempool` fails") # to test the exception we are creating a tmp folder called mempool.dat.new # which is an implementation detail that could change and break this # test mempooldotnew1 = mempooldat1 + '.new' os.mkdir(mempooldotnew1) assert_raises_rpc_error(-1, "Unable to dump mempool to disk", self.nodes[1].savemempool) os.rmdir(mempooldotnew1) self.test_persist_unbroadcast() def test_persist_unbroadcast(self): node0 = self.nodes[0] self.start_node(0) # clear out mempool node0.generate(1) # ensure node0 doesn't have any connections # make a transaction that will remain in the unbroadcast set assert(len(node0.getpeerinfo()) == 0) assert(len(node0.p2ps) == 0) node0.sendtoaddress(self.nodes[1].getnewaddress(), Decimal("12")) # shutdown, then startup with wallet disabled self.stop_nodes() self.start_node(0, extra_args=["-disablewallet"]) # check that txn gets broadcast due to unbroadcast logic conn = node0.add_p2p_connection(P2PTxInvStore()) # 15 min + 1 for buffer node0.mockscheduler(16 * 60) wait_until(lambda: len(conn.get_invs()) == 1) if __name__ == '__main__': MempoolPersistTest().main() diff --git a/test/functional/mempool_reorg.py b/test/functional/mempool_reorg.py index 2af3646dd..edcf4e838 100755 --- a/test/functional/mempool_reorg.py +++ b/test/functional/mempool_reorg.py @@ -1,126 +1,126 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2016 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test mempool re-org scenarios. Test re-org scenarios with a mempool that contains transactions that spend (directly or indirectly) coinbase transactions. """ from test_framework.test_framework import BitcoinTestFramework from test_framework.blocktools import create_raw_transaction from test_framework.util import assert_equal, assert_raises_rpc_error class MempoolCoinbaseTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.extra_args = [ # immediate tx relay ['-whitelist=noban@127.0.0.1', ], [] ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() alert_filename = None # Set by setup_network def run_test(self): # Start with a 200 block chain assert_equal(self.nodes[0].getblockcount(), 200) # Mine four blocks. After this, nodes[0] blocks # 101, 102, and 103 are spend-able. new_blocks = self.nodes[1].generate(4) self.sync_all() node0_address = self.nodes[0].getnewaddress() node1_address = self.nodes[1].getnewaddress() # Three scenarios for re-orging coinbase spends in the memory pool: # 1. Direct coinbase spend : spend_101 # 2. Indirect (coinbase spend in chain, child in mempool) : spend_102 and spend_102_1 # 3. Indirect (coinbase and child both in chain) : spend_103 and spend_103_1 # Use invalidatblock to make all of the above coinbase spends invalid (immature coinbase), # and make sure the mempool code behaves correctly. b = [self.nodes[0].getblockhash(n) for n in range(101, 105)] coinbase_txids = [self.nodes[0].getblock(h)['tx'][0] for h in b] spend_101_raw = create_raw_transaction( - self.nodes[0], coinbase_txids[1], node1_address, amount=49.99) + self.nodes[0], coinbase_txids[1], node1_address, amount=49990000) spend_102_raw = create_raw_transaction( - self.nodes[0], coinbase_txids[2], node0_address, amount=49.99) + self.nodes[0], coinbase_txids[2], node0_address, amount=49990000) spend_103_raw = create_raw_transaction( - self.nodes[0], coinbase_txids[3], node0_address, amount=49.99) + self.nodes[0], coinbase_txids[3], node0_address, amount=49990000) # Create a transaction which is time-locked to two blocks in the future timelock_tx = self.nodes[0].createrawtransaction( inputs=[{ "txid": coinbase_txids[0], "vout": 0 }], - outputs={node0_address: 49.99}, + outputs={node0_address: 49990000}, locktime=self.nodes[0].getblockcount() + 2, ) timelock_tx = self.nodes[0].signrawtransactionwithwallet(timelock_tx)[ "hex"] # This will raise an exception because the timelock transaction is too # immature to spend assert_raises_rpc_error(-26, "bad-txns-nonfinal", self.nodes[0].sendrawtransaction, timelock_tx) # Broadcast and mine spend_102 and 103: spend_102_id = self.nodes[0].sendrawtransaction(spend_102_raw) spend_103_id = self.nodes[0].sendrawtransaction(spend_103_raw) self.nodes[0].generate(1) # Time-locked transaction is still too immature to spend assert_raises_rpc_error(-26, 'bad-txns-nonfinal', self.nodes[0].sendrawtransaction, timelock_tx) # Create 102_1 and 103_1: spend_102_1_raw = create_raw_transaction( - self.nodes[0], spend_102_id, node1_address, amount=49.98) + self.nodes[0], spend_102_id, node1_address, amount=49980000) spend_103_1_raw = create_raw_transaction( - self.nodes[0], spend_103_id, node1_address, amount=49.98) + self.nodes[0], spend_103_id, node1_address, amount=49980000) # Broadcast and mine 103_1: spend_103_1_id = self.nodes[0].sendrawtransaction(spend_103_1_raw) last_block = self.nodes[0].generate(1) # Sync blocks, so that peer 1 gets the block before timelock_tx # Otherwise, peer 1 would put the timelock_tx in recentRejects self.sync_all() # Time-locked transaction can now be spent timelock_tx_id = self.nodes[0].sendrawtransaction(timelock_tx) # ... now put spend_101 and spend_102_1 in memory pools: spend_101_id = self.nodes[0].sendrawtransaction(spend_101_raw) spend_102_1_id = self.nodes[0].sendrawtransaction(spend_102_1_raw) self.sync_all() assert_equal(set(self.nodes[0].getrawmempool()), { spend_101_id, spend_102_1_id, timelock_tx_id}) for node in self.nodes: node.invalidateblock(last_block[0]) # Time-locked transaction is now too immature and has been removed from the mempool # spend_103_1 has been re-orged out of the chain and is back in the # mempool assert_equal(set(self.nodes[0].getrawmempool()), { spend_101_id, spend_102_1_id, spend_103_1_id}) # Use invalidateblock to re-org back and make all those coinbase spends # immature/invalid: for node in self.nodes: node.invalidateblock(new_blocks[0]) self.sync_all() # mempool should be empty. assert_equal(set(self.nodes[0].getrawmempool()), set()) if __name__ == '__main__': MempoolCoinbaseTest().main() diff --git a/test/functional/mempool_resurrect.py b/test/functional/mempool_resurrect.py index bb8d56901..ac7625be1 100755 --- a/test/functional/mempool_resurrect.py +++ b/test/functional/mempool_resurrect.py @@ -1,75 +1,75 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test resurrection of mined transactions when the blockchain is re-organized.""" from test_framework.test_framework import BitcoinTestFramework from test_framework.blocktools import create_raw_transaction from test_framework.util import assert_equal class MempoolCoinbaseTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): node0_address = self.nodes[0].getnewaddress() # Spend block 1/2/3's coinbase transactions # Mine a block. # Create three more transactions, spending the spends # Mine another block. # ... make sure all the transactions are confirmed # Invalidate both blocks # ... make sure all the transactions are put back in the mempool # Mine a new block # ... make sure all the transactions are confirmed again. b = [self.nodes[0].getblockhash(n) for n in range(1, 4)] coinbase_txids = [self.nodes[0].getblock(h)['tx'][0] for h in b] - spends1_raw = [create_raw_transaction(self.nodes[0], txid, node0_address, amount=49.99) + spends1_raw = [create_raw_transaction(self.nodes[0], txid, node0_address, amount=49990000) for txid in coinbase_txids] spends1_id = [self.nodes[0].sendrawtransaction(tx) for tx in spends1_raw] blocks = [] blocks.extend(self.nodes[0].generate(1)) - spends2_raw = [create_raw_transaction(self.nodes[0], txid, node0_address, amount=49.98) + spends2_raw = [create_raw_transaction(self.nodes[0], txid, node0_address, amount=49980000) for txid in spends1_id] spends2_id = [self.nodes[0].sendrawtransaction(tx) for tx in spends2_raw] blocks.extend(self.nodes[0].generate(1)) # mempool should be empty, all txns confirmed assert_equal(set(self.nodes[0].getrawmempool()), set()) for txid in spends1_id + spends2_id: tx = self.nodes[0].gettransaction(txid) assert tx["confirmations"] > 0 # Use invalidateblock to re-org back for node in self.nodes: node.invalidateblock(blocks[0]) # All txns should be back in mempool with 0 confirmations assert_equal( set(self.nodes[0].getrawmempool()), set(spends1_id + spends2_id)) for txid in spends1_id + spends2_id: tx = self.nodes[0].gettransaction(txid) assert tx["confirmations"] == 0 # Generate another block, they should all get mined self.nodes[0].generate(1) # mempool should be empty, all txns confirmed assert_equal(set(self.nodes[0].getrawmempool()), set()) for txid in spends1_id + spends2_id: tx = self.nodes[0].gettransaction(txid) assert tx["confirmations"] > 0 if __name__ == '__main__': MempoolCoinbaseTest().main() diff --git a/test/functional/mempool_spend_coinbase.py b/test/functional/mempool_spend_coinbase.py index 329f28ac8..fa6a1dae2 100755 --- a/test/functional/mempool_spend_coinbase.py +++ b/test/functional/mempool_spend_coinbase.py @@ -1,59 +1,59 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2016 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test spending coinbase transactions. The coinbase transaction in block N can appear in block N+100... so is valid in the mempool when the best block height is N+99. This test makes sure coinbase spends that will be mature in the next block are accepted into the memory pool, but less mature coinbase spends are NOT. """ from test_framework.test_framework import BitcoinTestFramework from test_framework.blocktools import create_raw_transaction from test_framework.util import assert_equal, assert_raises_rpc_error class MempoolSpendCoinbaseTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): chain_height = self.nodes[0].getblockcount() assert_equal(chain_height, 200) node0_address = self.nodes[0].getnewaddress() # Coinbase at height chain_height-100+1 ok in mempool, should # get mined. Coinbase at height chain_height-100+2 is # is too immature to spend. b = [self.nodes[0].getblockhash(n) for n in range(101, 103)] coinbase_txids = [self.nodes[0].getblock(h)['tx'][0] for h in b] - spends_raw = [create_raw_transaction(self.nodes[0], txid, node0_address, amount=49.99) + spends_raw = [create_raw_transaction(self.nodes[0], txid, node0_address, amount=49990000) for txid in coinbase_txids] spend_101_id = self.nodes[0].sendrawtransaction(spends_raw[0]) # coinbase at height 102 should be too immature to spend assert_raises_rpc_error(-26, "bad-txns-premature-spend-of-coinbase", self.nodes[0].sendrawtransaction, spends_raw[1]) # mempool should have just spend_101: assert_equal(self.nodes[0].getrawmempool(), [spend_101_id]) # mine a block, spend_101 should get confirmed self.nodes[0].generate(1) assert_equal(set(self.nodes[0].getrawmempool()), set()) # ... and now height 102 can be spent: spend_102_id = self.nodes[0].sendrawtransaction(spends_raw[1]) assert_equal(self.nodes[0].getrawmempool(), [spend_102_id]) if __name__ == '__main__': MempoolSpendCoinbaseTest().main() diff --git a/test/functional/mempool_unbroadcast.py b/test/functional/mempool_unbroadcast.py index b019e88a4..711bf803f 100755 --- a/test/functional/mempool_unbroadcast.py +++ b/test/functional/mempool_unbroadcast.py @@ -1,124 +1,124 @@ #!/usr/bin/env python3 # Copyright (c) 2017-2020 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test that the mempool ensures transaction delivery by periodically sending to peers until a GETDATA is received.""" import time from test_framework.blocktools import create_confirmed_utxos from test_framework.p2p import P2PTxInvStore from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, connect_nodes, disconnect_nodes, ) # 15 minutes in seconds MAX_INITIAL_BROADCAST_DELAY = 15 * 60 class MempoolUnbroadcastTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): self.test_broadcast() self.test_txn_removal() def test_broadcast(self): self.log.info( "Test that mempool reattempts delivery of locally submitted transaction") node = self.nodes[0] min_relay_fee = node.getnetworkinfo()["relayfee"] create_confirmed_utxos(node, 10) disconnect_nodes(node, self.nodes[1]) self.log.info("Generate transactions that only node 0 knows about") # generate a wallet txn addr = node.getnewaddress() - wallet_tx_hsh = node.sendtoaddress(addr, 0.0001) + wallet_tx_hsh = node.sendtoaddress(addr, 100) utxos = node.listunspent() # generate a txn using sendrawtransaction us0 = utxos.pop() inputs = [{"txid": us0["txid"], "vout": us0["vout"]}] - outputs = {addr: 0.0001} + outputs = {addr: 100} tx = node.createrawtransaction(inputs, outputs) node.settxfee(min_relay_fee) txF = node.fundrawtransaction(tx) txFS = node.signrawtransactionwithwallet(txF["hex"]) rpc_tx_hsh = node.sendrawtransaction(txFS["hex"]) # check transactions are in unbroadcast using rpc mempoolinfo = self.nodes[0].getmempoolinfo() assert_equal(mempoolinfo['unbroadcastcount'], 2) mempool = self.nodes[0].getrawmempool(True) for tx in mempool: assert_equal(mempool[tx]['unbroadcast'], True) # check that second node doesn't have these two txns mempool = self.nodes[1].getrawmempool() assert rpc_tx_hsh not in mempool assert wallet_tx_hsh not in mempool # ensure that unbroadcast txs are persisted to mempool.dat self.restart_node(0) self.log.info("Reconnect nodes & check if they are sent to node 1") connect_nodes(node, self.nodes[1]) # fast forward into the future & ensure that the second node has the # txns node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY) self.sync_mempools(timeout=30) mempool = self.nodes[1].getrawmempool() assert rpc_tx_hsh in mempool assert wallet_tx_hsh in mempool # check that transactions are no longer in first node's unbroadcast set mempool = self.nodes[0].getrawmempool(True) for tx in mempool: assert_equal(mempool[tx]['unbroadcast'], False) self.log.info( "Add another connection & ensure transactions aren't broadcast again") conn = node.add_p2p_connection(P2PTxInvStore()) node.mockscheduler(MAX_INITIAL_BROADCAST_DELAY) # allow sufficient time for possibility of broadcast time.sleep(2) assert_equal(len(conn.get_invs()), 0) disconnect_nodes(node, self.nodes[1]) node.disconnect_p2ps() def test_txn_removal(self): self.log.info( "Test that transactions removed from mempool are removed from unbroadcast set") node = self.nodes[0] # since the node doesn't have any connections, it will not receive # any GETDATAs & thus the transaction will remain in the unbroadcast # set. addr = node.getnewaddress() - txhsh = node.sendtoaddress(addr, 0.0001) + txhsh = node.sendtoaddress(addr, 100) # check transaction was removed from unbroadcast set due to presence in # a block removal_reason = "Removed {} from set of unbroadcast txns before " \ "confirmation that txn was sent out".format(txhsh) with node.assert_debug_log([removal_reason]): node.generate(1) if __name__ == "__main__": MempoolUnbroadcastTest().main() diff --git a/test/functional/mempool_updatefromblock.py b/test/functional/mempool_updatefromblock.py index 24cca433d..5d3304b09 100755 --- a/test/functional/mempool_updatefromblock.py +++ b/test/functional/mempool_updatefromblock.py @@ -1,158 +1,158 @@ #!/usr/bin/env python3 # Copyright (c) 2020 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test mempool descendants/ancestors information update. Test mempool update of transaction descendants/ancestors information (count, size) when transactions have been re-added from a disconnected block to the mempool. """ import time from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal class MempoolUpdateFromBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.extra_args = [ ['-limitdescendantsize=5000', '-limitancestorsize=5000']] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def transaction_graph_test(self, size, n_tx_to_mine=None, start_input_txid='', end_address='', - fee=Decimal(0.00100000)): + fee=Decimal(1000)): """Create an acyclic tournament (a type of directed graph) of transactions and use it for testing. Keyword arguments: size -- the order N of the tournament which is equal to the number of the created transactions n_tx_to_mine -- the number of transaction that should be mined into a block If all of the N created transactions tx[0]..tx[N-1] reside in the mempool, the following holds: the tx[K] transaction: - has N-K descendants (including this one), and - has K+1 ancestors (including this one) More details: https://en.wikipedia.org/wiki/Tournament_(graph_theory) """ if not start_input_txid: start_input_txid = self.nodes[0].getblock( self.nodes[0].getblockhash(1))['tx'][0] if not end_address: end_address = self.nodes[0].getnewaddress() first_block_hash = '' tx_id = [] tx_size = [] self.log.info('Creating {} transactions...'.format(size)) for i in range(0, size): self.log.debug('Preparing transaction #{}...'.format(i)) # Prepare inputs. if i == 0: inputs = [{'txid': start_input_txid, 'vout': 0}] inputs_value = self.nodes[0].gettxout( start_input_txid, 0)['value'] else: inputs = [] inputs_value = 0 for j, tx in enumerate(tx_id[0:i]): # Transaction tx[K] is a child of each of previous # transactions tx[0]..tx[K-1] at their output K-1. vout = i - j - 1 inputs.append({'txid': tx_id[j], 'vout': vout}) inputs_value += self.nodes[0].gettxout(tx, vout)['value'] self.log.debug('inputs={}'.format(inputs)) self.log.debug('inputs_value={}'.format(inputs_value)) # Prepare outputs. tx_count = i + 1 if tx_count < size: # Transaction tx[K] is an ancestor of each of subsequent # transactions tx[K+1]..tx[N-1]. n_outputs = size - tx_count output_value = ( (inputs_value - fee) / Decimal(n_outputs)).quantize( - Decimal('0.00000001')) + Decimal('0.01')) outputs = {} for n in range(0, n_outputs): outputs[self.nodes[0].getnewaddress()] = output_value else: output_value = ( inputs_value - fee).quantize( - Decimal('0.00000001')) + Decimal('0.01')) outputs = {end_address: output_value} self.log.debug('output_value={}'.format(output_value)) self.log.debug('outputs={}'.format(outputs)) # Create a new transaction. unsigned_raw_tx = self.nodes[0].createrawtransaction( inputs, outputs) signed_raw_tx = self.nodes[0].signrawtransactionwithwallet( unsigned_raw_tx) tx_id.append( self.nodes[0].sendrawtransaction( signed_raw_tx['hex'])) tx_size.append(self.nodes[0].getrawmempool( True)[tx_id[-1]]['size']) if tx_count in n_tx_to_mine: # The created transactions are mined into blocks by batches. self.log.info('The batch of {} transactions has been accepted' ' into the mempool.'.format(len(self.nodes[0].getrawmempool()))) block_hash = self.nodes[0].generate(1)[0] if not first_block_hash: first_block_hash = block_hash assert_equal(len(self.nodes[0].getrawmempool()), 0) self.log.info( 'All of the transactions from the current batch have been' ' mined into a block.') elif tx_count == size: # At the end all of the mined blocks are invalidated, and all of the created # transactions should be re-added from disconnected blocks to # the mempool. self.log.info('The last batch of {} transactions has been' ' accepted into the mempool.'.format(len(self.nodes[0].getrawmempool()))) start = time.time() self.nodes[0].invalidateblock(first_block_hash) end = time.time() assert_equal(len(self.nodes[0].getrawmempool()), size) self.log.info( 'All of the recently mined transactions have been re-added' ' into the mempool in {} seconds.'.format(end - start)) self.log.info( 'Checking descendants/ancestors properties of all of the' ' in-mempool transactions...') for k, tx in enumerate(tx_id): self.log.debug('Check transaction #{}.'.format(k)) assert_equal(self.nodes[0].getrawmempool(True)[ tx]['descendantcount'], size - k) assert_equal(self.nodes[0].getrawmempool(True)[ tx]['descendantsize'], sum(tx_size[k:size])) assert_equal(self.nodes[0].getrawmempool( True)[tx]['ancestorcount'], k + 1) assert_equal(self.nodes[0].getrawmempool(True)[ tx]['ancestorsize'], sum(tx_size[0:(k + 1)])) def run_test(self): # Use batch size limited by DEFAULT_ANCESTOR_LIMIT = 50 to not fire # "too many unconfirmed parents" error. self.transaction_graph_test(size=200, n_tx_to_mine=[50, 100, 150]) if __name__ == '__main__': MempoolUpdateFromBlockTest().main() diff --git a/test/functional/mining_getblocktemplate_longpoll.py b/test/functional/mining_getblocktemplate_longpoll.py index c0cd043bd..4a9fda3f1 100755 --- a/test/functional/mining_getblocktemplate_longpoll.py +++ b/test/functional/mining_getblocktemplate_longpoll.py @@ -1,92 +1,92 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test longpolling with getblocktemplate.""" from decimal import Decimal import threading from test_framework.test_framework import BitcoinTestFramework from test_framework.util import get_rpc_proxy, random_transaction class LongpollThread(threading.Thread): def __init__(self, node): threading.Thread.__init__(self) # query current longpollid templat = node.getblocktemplate() self.longpollid = templat['longpollid'] # create a new connection to the node, we can't use the same # connection from two threads self.node = get_rpc_proxy( node.url, 1, timeout=600, coveragedir=node.coverage_dir) def run(self): self.node.getblocktemplate({'longpollid': self.longpollid}) class GetBlockTemplateLPTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.supports_cli = False def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): self.log.info( "Warning: this test will take about 70 seconds in the best case. Be patient.") self.nodes[0].generate(10) templat = self.nodes[0].getblocktemplate() longpollid = templat['longpollid'] # longpollid should not change between successive invocations if # nothing else happens templat2 = self.nodes[0].getblocktemplate() assert templat2['longpollid'] == longpollid # Test 1: test that the longpolling wait if we do nothing thr = LongpollThread(self.nodes[0]) thr.start() # check that thread still lives # wait 5 seconds or until thread exits thr.join(5) assert thr.is_alive() # Test 2: test that longpoll will terminate if another node generates a block # generate a block on another node self.nodes[1].generate(1) # check that thread will exit now that new transaction entered mempool # wait 5 seconds or until thread exits thr.join(5) assert not thr.is_alive() # Test 3: test that longpoll will terminate if we generate a block # ourselves thr = LongpollThread(self.nodes[0]) thr.start() # generate a block on another node self.nodes[0].generate(1) # wait 5 seconds or until thread exits thr.join(5) assert not thr.is_alive() # Test 4: test that introducing a new transaction into the mempool will # terminate the longpoll thr = LongpollThread(self.nodes[0]) thr.start() # generate a random transaction and submit it min_relay_fee = self.nodes[0].getnetworkinfo()["relayfee"] # min_relay_fee is fee per 1000 bytes, which should be more than # enough. (txid, txhex, fee) = random_transaction(self.nodes, - Decimal("1.1"), min_relay_fee, Decimal("0.001"), 20) + Decimal("1100000"), min_relay_fee, Decimal("1000"), 20) # after one minute, every 10 seconds the mempool is probed, so in 80 # seconds it should have returned thr.join(60 + 20) assert not thr.is_alive() if __name__ == '__main__': GetBlockTemplateLPTest().main() diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py index 0a621e11c..451e61861 100755 --- a/test/functional/p2p_blocksonly.py +++ b/test/functional/p2p_blocksonly.py @@ -1,107 +1,107 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test p2p blocksonly""" from test_framework.messages import msg_tx, CTransaction, FromHex from test_framework.p2p import P2PInterface from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal class P2PBlocksOnly(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = False self.num_nodes = 1 self.extra_args = [["-blocksonly"]] def run_test(self): self.nodes[0].add_p2p_connection(P2PInterface()) self.log.info( 'Check that txs from p2p are rejected and result in disconnect') prevtx = self.nodes[0].getblock( self.nodes[0].getblockhash(1), 2)['tx'][0] rawtx = self.nodes[0].createrawtransaction( inputs=[{ 'txid': prevtx['txid'], 'vout': 0 }], outputs=[{ - self.nodes[0].get_deterministic_priv_key().address: 50 - - 0.00125 + self.nodes[0].get_deterministic_priv_key().address: 50000000 - + 1250.00 }], ) self.log.info(prevtx) sigtx = self.nodes[0].signrawtransactionwithkey( hexstring=rawtx, privkeys=[self.nodes[0].get_deterministic_priv_key().key], prevtxs=[{ 'txid': prevtx['txid'], 'vout': 0, 'amount': prevtx['vout'][0]['value'], 'scriptPubKey': prevtx['vout'][0]['scriptPubKey']['hex'], }], )['hex'] assert_equal(self.nodes[0].getnetworkinfo()['localrelay'], False) with self.nodes[0].assert_debug_log(['transaction sent in violation of protocol peer=0']): self.nodes[0].p2p.send_message( msg_tx(FromHex(CTransaction(), sigtx))) self.nodes[0].p2p.wait_for_disconnect() assert_equal(self.nodes[0].getmempoolinfo()['size'], 0) # Remove the disconnected peer and add a new one. del self.nodes[0].p2ps[0] self.nodes[0].add_p2p_connection(P2PInterface()) self.log.info( 'Check that txs from rpc are not rejected and relayed to other peers') assert_equal(self.nodes[0].getpeerinfo()[0]['relaytxes'], True) txid = self.nodes[0].testmempoolaccept([sigtx])[0]['txid'] with self.nodes[0].assert_debug_log(['received getdata for: tx {} peer=1'.format(txid)]): self.nodes[0].sendrawtransaction(sigtx) self.nodes[0].p2p.wait_for_tx(txid) assert_equal(self.nodes[0].getmempoolinfo()['size'], 1) self.log.info( 'Check that txs from whitelisted peers are not rejected and relayed to others') self.log.info( "Restarting node 0 with whitelist permission and blocksonly") self.restart_node(0, ["-persistmempool=0", "-whitelist=127.0.0.1", "-whitelistforcerelay", "-blocksonly"]) assert_equal(self.nodes[0].getrawmempool(), []) first_peer = self.nodes[0].add_p2p_connection(P2PInterface()) second_peer = self.nodes[0].add_p2p_connection(P2PInterface()) peer_1_info = self.nodes[0].getpeerinfo()[0] assert_equal(peer_1_info['whitelisted'], True) assert_equal( peer_1_info['permissions'], [ 'noban', 'forcerelay', 'relay', 'mempool', 'download']) peer_2_info = self.nodes[0].getpeerinfo()[1] assert_equal(peer_2_info['whitelisted'], True) assert_equal( peer_2_info['permissions'], [ 'noban', 'forcerelay', 'relay', 'mempool', 'download']) assert_equal( self.nodes[0].testmempoolaccept( [sigtx])[0]['allowed'], True) txid = self.nodes[0].testmempoolaccept([sigtx])[0]['txid'] self.log.info( 'Check that the tx from whitelisted first_peer is relayed to others (ie.second_peer)') with self.nodes[0].assert_debug_log(["received getdata"]): first_peer.send_message(msg_tx(FromHex(CTransaction(), sigtx))) self.log.info( 'Check that the whitelisted peer is still connected after sending the transaction') assert_equal(first_peer.is_connected, True) second_peer.wait_for_tx(txid) assert_equal(self.nodes[0].getmempoolinfo()['size'], 1) self.log.info("Whitelisted peer's transaction is accepted and relayed") if __name__ == '__main__': P2PBlocksOnly().main() diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index 0a090f6e1..72d63f282 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -1,931 +1,931 @@ #!/usr/bin/env python3 # Copyright (c) 2016-2019 The Bitcoin Core developers # Copyright (c) 2017 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test compact blocks (BIP 152). Only testing Version 1 compact blocks (txids) """ import random from test_framework.blocktools import create_block, create_coinbase from test_framework.messages import ( BlockTransactions, BlockTransactionsRequest, calculate_shortid, CBlock, CBlockHeader, CInv, COutPoint, CTransaction, CTxIn, CTxOut, FromHex, HeaderAndShortIDs, MSG_BLOCK, MSG_CMPCT_BLOCK, msg_block, msg_blocktxn, msg_cmpctblock, msg_getblocktxn, msg_getdata, msg_getheaders, msg_headers, msg_inv, msg_sendcmpct, msg_sendheaders, msg_tx, NODE_NETWORK, P2PHeaderAndShortIDs, PrefilledTransaction, ToHex, ) from test_framework.p2p import ( p2p_lock, P2PInterface, ) from test_framework.script import CScript, OP_TRUE from test_framework.test_framework import BitcoinTestFramework from test_framework.txtools import pad_tx from test_framework.util import ( assert_equal, wait_until, ) # TestP2PConn: A peer we use to send messages to bitcoind, and store responses. class TestP2PConn(P2PInterface): def __init__(self): super().__init__() self.last_sendcmpct = [] self.block_announced = False # Store the hashes of blocks we've seen announced. # This is for synchronizing the p2p message traffic, # so we can eg wait until a particular block is announced. self.announced_blockhashes = set() def on_sendcmpct(self, message): self.last_sendcmpct.append(message) def on_cmpctblock(self, message): self.block_announced = True self.last_message["cmpctblock"].header_and_shortids.header.calc_sha256() self.announced_blockhashes.add( self.last_message["cmpctblock"].header_and_shortids.header.sha256) def on_headers(self, message): self.block_announced = True for x in self.last_message["headers"].headers: x.calc_sha256() self.announced_blockhashes.add(x.sha256) def on_inv(self, message): for x in self.last_message["inv"].inv: if x.type == MSG_BLOCK: self.block_announced = True self.announced_blockhashes.add(x.hash) # Requires caller to hold p2p_lock def received_block_announcement(self): return self.block_announced def clear_block_announcement(self): with p2p_lock: self.block_announced = False self.last_message.pop("inv", None) self.last_message.pop("headers", None) self.last_message.pop("cmpctblock", None) def get_headers(self, locator, hashstop): msg = msg_getheaders() msg.locator.vHave = locator msg.hashstop = hashstop self.send_message(msg) def send_header_for_blocks(self, new_blocks): headers_message = msg_headers() headers_message.headers = [CBlockHeader(b) for b in new_blocks] self.send_message(headers_message) def request_headers_and_sync(self, locator, hashstop=0): self.clear_block_announcement() self.get_headers(locator, hashstop) wait_until(self.received_block_announcement, timeout=30, lock=p2p_lock) self.clear_block_announcement() # Block until a block announcement for a particular block hash is # received. def wait_for_block_announcement(self, block_hash, timeout=30): def received_hash(): return (block_hash in self.announced_blockhashes) wait_until(received_hash, timeout=timeout, lock=p2p_lock) def send_await_disconnect(self, message, timeout=30): """Sends a message to the node and wait for disconnect. This is used when we want to send a message into the node that we expect will get us disconnected, eg an invalid block.""" self.send_message(message) wait_until(lambda: not self.is_connected, timeout=timeout, lock=p2p_lock) class CompactBlocksTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 self.extra_args = [["-acceptnonstdtxn=1"], ["-txindex", "-acceptnonstdtxn=1"]] self.utxos = [] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def build_block_on_tip(self, node): height = node.getblockcount() tip = node.getbestblockhash() mtp = node.getblockheader(tip)['mediantime'] block = create_block( int(tip, 16), create_coinbase(height + 1), mtp + 1) block.nVersion = 4 block.solve() return block # Create 10 more anyone-can-spend utxo's for testing. def make_utxos(self): # Doesn't matter which node we use, just use node0. block = self.build_block_on_tip(self.nodes[0]) self.test_node.send_and_ping(msg_block(block)) assert int(self.nodes[0].getbestblockhash(), 16) == block.sha256 self.nodes[0].generate(100) total_value = block.vtx[0].vout[0].nValue out_value = total_value // 10 tx = CTransaction() tx.vin.append(CTxIn(COutPoint(block.vtx[0].sha256, 0), b'')) for i in range(10): tx.vout.append(CTxOut(out_value, CScript([OP_TRUE]))) tx.rehash() block2 = self.build_block_on_tip(self.nodes[0]) block2.vtx.append(tx) block2.hashMerkleRoot = block2.calc_merkle_root() block2.solve() self.test_node.send_and_ping(msg_block(block2)) assert_equal(int(self.nodes[0].getbestblockhash(), 16), block2.sha256) self.utxos.extend([[tx.sha256, i, out_value] for i in range(10)]) return # Test "sendcmpct" (between peers preferring the same version): # - No compact block announcements unless sendcmpct is sent. # - If sendcmpct is sent with version > preferred_version, the message is ignored. # - If sendcmpct is sent with boolean 0, then block announcements are not # made with compact blocks. # - If sendcmpct is then sent with boolean 1, then new block announcements # are made with compact blocks. # If old_node is passed in, request compact blocks with version=preferred-1 # and verify that it receives block announcements via compact block. def test_sendcmpct(self, node, test_node, preferred_version, old_node=None): # Make sure we get a SENDCMPCT message from our peer def received_sendcmpct(): return (len(test_node.last_sendcmpct) > 0) wait_until(received_sendcmpct, timeout=30, lock=p2p_lock) with p2p_lock: # Check that the first version received is the preferred one assert_equal( test_node.last_sendcmpct[0].version, preferred_version) # And that we receive versions down to 1. assert_equal(test_node.last_sendcmpct[-1].version, 1) test_node.last_sendcmpct = [] tip = int(node.getbestblockhash(), 16) def check_announcement_of_new_block(node, peer, predicate): peer.clear_block_announcement() block_hash = int(node.generate(1)[0], 16) peer.wait_for_block_announcement(block_hash, timeout=30) assert peer.block_announced with p2p_lock: assert predicate(peer), ( "block_hash={!r}, cmpctblock={!r}, inv={!r}".format( block_hash, peer.last_message.get("cmpctblock", None), peer.last_message.get("inv", None))) # We shouldn't get any block announcements via cmpctblock yet. check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" not in p.last_message) # Try one more time, this time after requesting headers. test_node.request_headers_and_sync(locator=[tip]) check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" not in p.last_message and "inv" in p.last_message) # Test a few ways of using sendcmpct that should NOT # result in compact block announcements. # Before each test, sync the headers chain. test_node.request_headers_and_sync(locator=[tip]) # Now try a SENDCMPCT message with too-high version sendcmpct = msg_sendcmpct() sendcmpct.version = 999 # was: preferred_version+1 sendcmpct.announce = True test_node.send_and_ping(sendcmpct) check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" not in p.last_message) # Headers sync before next test. test_node.request_headers_and_sync(locator=[tip]) # Now try a SENDCMPCT message with valid version, but announce=False sendcmpct.version = preferred_version sendcmpct.announce = False test_node.send_and_ping(sendcmpct) check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" not in p.last_message) # Headers sync before next test. test_node.request_headers_and_sync(locator=[tip]) # Finally, try a SENDCMPCT message with announce=True sendcmpct.version = preferred_version sendcmpct.announce = True test_node.send_and_ping(sendcmpct) check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" in p.last_message) # Try one more time (no headers sync should be needed!) check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" in p.last_message) # Try one more time, after turning on sendheaders test_node.send_and_ping(msg_sendheaders()) check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" in p.last_message) # Try one more time, after sending a version-1, announce=false message. sendcmpct.version = preferred_version - 1 sendcmpct.announce = False test_node.send_and_ping(sendcmpct) check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" in p.last_message) # Now turn off announcements sendcmpct.version = preferred_version sendcmpct.announce = False test_node.send_and_ping(sendcmpct) check_announcement_of_new_block( node, test_node, lambda p: "cmpctblock" not in p.last_message and "headers" in p.last_message) if old_node is not None: # Verify that a peer using an older protocol version can receive # announcements from this node. sendcmpct.version = 1 # preferred_version-1 sendcmpct.announce = True old_node.send_and_ping(sendcmpct) # Header sync old_node.request_headers_and_sync(locator=[tip]) check_announcement_of_new_block( node, old_node, lambda p: "cmpctblock" in p.last_message) # This test actually causes bitcoind to (reasonably!) disconnect us, so do # this last. def test_invalid_cmpctblock_message(self): self.nodes[0].generate(101) block = self.build_block_on_tip(self.nodes[0]) cmpct_block = P2PHeaderAndShortIDs() cmpct_block.header = CBlockHeader(block) cmpct_block.prefilled_txn_length = 1 # This index will be too high prefilled_txn = PrefilledTransaction(1, block.vtx[0]) cmpct_block.prefilled_txn = [prefilled_txn] self.test_node.send_await_disconnect(msg_cmpctblock(cmpct_block)) assert_equal( int(self.nodes[0].getbestblockhash(), 16), block.hashPrevBlock) # Compare the generated shortids to what we expect based on BIP 152, given # bitcoind's choice of nonce. def test_compactblock_construction(self, node, test_node): # Generate a bunch of transactions. node.generate(101) num_transactions = 25 address = node.getnewaddress() for i in range(num_transactions): - txid = node.sendtoaddress(address, 0.1) + txid = node.sendtoaddress(address, 100000) hex_tx = node.gettransaction(txid)["hex"] tx = FromHex(CTransaction(), hex_tx) # Wait until we've seen the block announcement for the resulting tip tip = int(node.getbestblockhash(), 16) test_node.wait_for_block_announcement(tip) # Make sure we will receive a fast-announce compact block self.request_cb_announcements(test_node, node) # Now mine a block, and look at the resulting compact block. test_node.clear_block_announcement() block_hash = int(node.generate(1)[0], 16) # Store the raw block in our internal format. block = FromHex(CBlock(), node.getblock( "{:064x}".format(block_hash), False)) for tx in block.vtx: tx.calc_sha256() block.rehash() # Wait until the block was announced (via compact blocks) wait_until(test_node.received_block_announcement, timeout=30, lock=p2p_lock) # Now fetch and check the compact block header_and_shortids = None with p2p_lock: assert "cmpctblock" in test_node.last_message # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs( test_node.last_message["cmpctblock"].header_and_shortids) self.check_compactblock_construction_from_block( header_and_shortids, block_hash, block) # Now fetch the compact block using a normal non-announce getdata test_node.clear_block_announcement() inv = CInv(MSG_CMPCT_BLOCK, block_hash) test_node.send_message(msg_getdata([inv])) wait_until(test_node.received_block_announcement, timeout=30, lock=p2p_lock) # Now fetch and check the compact block header_and_shortids = None with p2p_lock: assert "cmpctblock" in test_node.last_message # Convert the on-the-wire representation to absolute indexes header_and_shortids = HeaderAndShortIDs( test_node.last_message["cmpctblock"].header_and_shortids) self.check_compactblock_construction_from_block( header_and_shortids, block_hash, block) def check_compactblock_construction_from_block( self, header_and_shortids, block_hash, block): # Check that we got the right block! header_and_shortids.header.calc_sha256() assert_equal(header_and_shortids.header.sha256, block_hash) # Make sure the prefilled_txn appears to have included the coinbase assert len(header_and_shortids.prefilled_txn) >= 1 assert_equal(header_and_shortids.prefilled_txn[0].index, 0) # Check that all prefilled_txn entries match what's in the block. for entry in header_and_shortids.prefilled_txn: entry.tx.calc_sha256() # This checks the tx agree assert_equal(entry.tx.sha256, block.vtx[entry.index].sha256) # Check that the cmpctblock message announced all the transactions. assert_equal(len(header_and_shortids.prefilled_txn) + len(header_and_shortids.shortids), len(block.vtx)) # And now check that all the shortids are as expected as well. # Determine the siphash keys to use. [k0, k1] = header_and_shortids.get_siphash_keys() index = 0 while index < len(block.vtx): if (len(header_and_shortids.prefilled_txn) > 0 and header_and_shortids.prefilled_txn[0].index == index): # Already checked prefilled transactions above header_and_shortids.prefilled_txn.pop(0) else: tx_hash = block.vtx[index].sha256 shortid = calculate_shortid(k0, k1, tx_hash) assert_equal(shortid, header_and_shortids.shortids[0]) header_and_shortids.shortids.pop(0) index += 1 # Test that bitcoind requests compact blocks when we announce new blocks # via header or inv, and that responding to getblocktxn causes the block # to be successfully reconstructed. def test_compactblock_requests(self, node, test_node, version): # Try announcing a block with an inv or header, expect a compactblock # request for announce in ["inv", "header"]: block = self.build_block_on_tip(node) if announce == "inv": test_node.send_message( msg_inv([CInv(MSG_BLOCK, block.sha256)])) wait_until(lambda: "getheaders" in test_node.last_message, timeout=30, lock=p2p_lock) test_node.send_header_for_blocks([block]) else: test_node.send_header_for_blocks([block]) test_node.wait_for_getdata([block.sha256], timeout=30) assert_equal(test_node.last_message["getdata"].inv[0].type, 4) # Send back a compactblock message that omits the coinbase comp_block = HeaderAndShortIDs() comp_block.header = CBlockHeader(block) comp_block.nonce = 0 [k0, k1] = comp_block.get_siphash_keys() coinbase_hash = block.vtx[0].sha256 comp_block.shortids = [ calculate_shortid(k0, k1, coinbase_hash)] test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock) # Expect a getblocktxn message. with p2p_lock: assert "getblocktxn" in test_node.last_message absolute_indexes = test_node.last_message["getblocktxn"].block_txn_request.to_absolute( ) assert_equal(absolute_indexes, [0]) # should be a coinbase request # Send the coinbase, and verify that the tip advances. msg = msg_blocktxn() msg.block_transactions.blockhash = block.sha256 msg.block_transactions.transactions = [block.vtx[0]] test_node.send_and_ping(msg) assert_equal(int(node.getbestblockhash(), 16), block.sha256) # Create a chain of transactions from given utxo, and add to a new block. # Note that num_transactions is number of transactions not including the # coinbase. def build_block_with_transactions(self, node, utxo, num_transactions): block = self.build_block_on_tip(node) for i in range(num_transactions): tx = CTransaction() tx.vin.append(CTxIn(COutPoint(utxo[0], utxo[1]), b'')) tx.vout.append(CTxOut(utxo[2] - 1000, CScript([OP_TRUE]))) pad_tx(tx) tx.rehash() utxo = [tx.sha256, 0, tx.vout[0].nValue] block.vtx.append(tx) ordered_txs = block.vtx block.vtx = [block.vtx[0]] + \ sorted(block.vtx[1:], key=lambda tx: tx.get_id()) block.hashMerkleRoot = block.calc_merkle_root() block.solve() return block, ordered_txs # Test that we only receive getblocktxn requests for transactions that the # node needs, and that responding to them causes the block to be # reconstructed. def test_getblocktxn_requests(self, node, test_node, version): def test_getblocktxn_response(compact_block, peer, expected_result): msg = msg_cmpctblock(compact_block.to_p2p()) peer.send_and_ping(msg) with p2p_lock: assert "getblocktxn" in peer.last_message absolute_indexes = peer.last_message["getblocktxn"].block_txn_request.to_absolute( ) assert_equal(absolute_indexes, expected_result) def test_tip_after_message(node, peer, msg, tip): peer.send_and_ping(msg) assert_equal(int(node.getbestblockhash(), 16), tip) # First try announcing compactblocks that won't reconstruct, and verify # that we receive getblocktxn messages back. utxo = self.utxos.pop(0) block, ordered_txs = self.build_block_with_transactions(node, utxo, 5) self.utxos.append( [ordered_txs[-1].sha256, 0, ordered_txs[-1].vout[0].nValue]) comp_block = HeaderAndShortIDs() comp_block.initialize_from_block(block) test_getblocktxn_response(comp_block, test_node, [1, 2, 3, 4, 5]) msg_bt = msg_blocktxn() msg_bt.block_transactions = BlockTransactions( block.sha256, block.vtx[1:]) test_tip_after_message(node, test_node, msg_bt, block.sha256) utxo = self.utxos.pop(0) block, ordered_txs = self.build_block_with_transactions(node, utxo, 5) self.utxos.append( [ordered_txs[-1].sha256, 0, ordered_txs[-1].vout[0].nValue]) # Now try interspersing the prefilled transactions comp_block.initialize_from_block( block, prefill_list=[0, 1, 5]) test_getblocktxn_response(comp_block, test_node, [2, 3, 4]) msg_bt.block_transactions = BlockTransactions( block.sha256, block.vtx[2:5]) test_tip_after_message(node, test_node, msg_bt, block.sha256) # Now try giving one transaction ahead of time. utxo = self.utxos.pop(0) block, ordered_txs = self.build_block_with_transactions(node, utxo, 5) self.utxos.append( [ordered_txs[-1].sha256, 0, ordered_txs[-1].vout[0].nValue]) test_node.send_and_ping(msg_tx(ordered_txs[1])) assert ordered_txs[1].hash in node.getrawmempool() test_node.send_and_ping(msg_tx(ordered_txs[1])) # Prefill 4 out of the 6 transactions, and verify that only the one # that was not in the mempool is requested. prefill_list = [0, 1, 2, 3, 4, 5] prefill_list.remove(block.vtx.index(ordered_txs[1])) expected_index = block.vtx.index(ordered_txs[-1]) prefill_list.remove(expected_index) comp_block.initialize_from_block(block, prefill_list=prefill_list) test_getblocktxn_response(comp_block, test_node, [expected_index]) msg_bt.block_transactions = BlockTransactions( block.sha256, [ordered_txs[5]]) test_tip_after_message(node, test_node, msg_bt, block.sha256) # Now provide all transactions to the node before the block is # announced and verify reconstruction happens immediately. utxo = self.utxos.pop(0) block, ordered_txs = self.build_block_with_transactions(node, utxo, 10) self.utxos.append( [ordered_txs[-1].sha256, 0, ordered_txs[-1].vout[0].nValue]) for tx in ordered_txs[1:]: test_node.send_message(msg_tx(tx)) test_node.sync_with_ping() # Make sure all transactions were accepted. mempool = node.getrawmempool() for tx in block.vtx[1:]: assert tx.hash in mempool # Clear out last request. with p2p_lock: test_node.last_message.pop("getblocktxn", None) # Send compact block comp_block.initialize_from_block(block, prefill_list=[0]) test_tip_after_message( node, test_node, msg_cmpctblock(comp_block.to_p2p()), block.sha256) with p2p_lock: # Shouldn't have gotten a request for any transaction assert "getblocktxn" not in test_node.last_message # Incorrectly responding to a getblocktxn shouldn't cause the block to be # permanently failed. def test_incorrect_blocktxn_response(self, node, test_node, version): if (len(self.utxos) == 0): self.make_utxos() utxo = self.utxos.pop(0) block, ordered_txs = self.build_block_with_transactions(node, utxo, 10) self.utxos.append( [ordered_txs[-1].sha256, 0, ordered_txs[-1].vout[0].nValue]) # Relay the first 5 transactions from the block in advance for tx in ordered_txs[1:6]: test_node.send_message(msg_tx(tx)) test_node.sync_with_ping() # Make sure all transactions were accepted. mempool = node.getrawmempool() for tx in ordered_txs[1:6]: assert tx.hash in mempool # Send compact block comp_block = HeaderAndShortIDs() comp_block.initialize_from_block(block, prefill_list=[0]) test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) absolute_indices = [] with p2p_lock: assert "getblocktxn" in test_node.last_message absolute_indices = test_node.last_message["getblocktxn"].block_txn_request.to_absolute( ) expected_indices = [] for i in [6, 7, 8, 9, 10]: expected_indices.append(block.vtx.index(ordered_txs[i])) assert_equal(absolute_indices, sorted(expected_indices)) # Now give an incorrect response. # Note that it's possible for bitcoind to be smart enough to know we're # lying, since it could check to see if the shortid matches what we're # sending, and eg disconnect us for misbehavior. If that behavior # change was made, we could just modify this test by having a # different peer provide the block further down, so that we're still # verifying that the block isn't marked bad permanently. This is good # enough for now. msg = msg_blocktxn() msg.block_transactions = BlockTransactions( block.sha256, [ordered_txs[5]] + ordered_txs[7:]) test_node.send_and_ping(msg) # Tip should not have updated assert_equal(int(node.getbestblockhash(), 16), block.hashPrevBlock) # We should receive a getdata request test_node.wait_for_getdata([block.sha256], timeout=10) assert test_node.last_message["getdata"].inv[0].type == MSG_BLOCK # Deliver the block test_node.send_and_ping(msg_block(block)) assert_equal(int(node.getbestblockhash(), 16), block.sha256) def test_getblocktxn_handler(self, node, test_node, version): # bitcoind will not send blocktxn responses for blocks whose height is # more than 10 blocks deep. MAX_GETBLOCKTXN_DEPTH = 10 chain_height = node.getblockcount() current_height = chain_height while (current_height >= chain_height - MAX_GETBLOCKTXN_DEPTH): block_hash = node.getblockhash(current_height) block = FromHex(CBlock(), node.getblock(block_hash, False)) msg = msg_getblocktxn() msg.block_txn_request = BlockTransactionsRequest( int(block_hash, 16), []) num_to_request = random.randint(1, len(block.vtx)) msg.block_txn_request.from_absolute( sorted(random.sample(range(len(block.vtx)), num_to_request))) test_node.send_message(msg) wait_until(lambda: "blocktxn" in test_node.last_message, timeout=10, lock=p2p_lock) [tx.calc_sha256() for tx in block.vtx] with p2p_lock: assert_equal(test_node.last_message["blocktxn"].block_transactions.blockhash, int( block_hash, 16)) all_indices = msg.block_txn_request.to_absolute() for index in all_indices: tx = test_node.last_message["blocktxn"].block_transactions.transactions.pop( 0) tx.calc_sha256() assert_equal(tx.sha256, block.vtx[index].sha256) test_node.last_message.pop("blocktxn", None) current_height -= 1 # Next request should send a full block response, as we're past the # allowed depth for a blocktxn response. block_hash = node.getblockhash(current_height) msg.block_txn_request = BlockTransactionsRequest( int(block_hash, 16), [0]) with p2p_lock: test_node.last_message.pop("block", None) test_node.last_message.pop("blocktxn", None) test_node.send_and_ping(msg) with p2p_lock: test_node.last_message["block"].block.calc_sha256() assert_equal( test_node.last_message["block"].block.sha256, int(block_hash, 16)) assert "blocktxn" not in test_node.last_message def test_compactblocks_not_at_tip(self, node, test_node): # Test that requesting old compactblocks doesn't work. MAX_CMPCTBLOCK_DEPTH = 5 new_blocks = [] for i in range(MAX_CMPCTBLOCK_DEPTH + 1): test_node.clear_block_announcement() new_blocks.append(node.generate(1)[0]) wait_until(test_node.received_block_announcement, timeout=30, lock=p2p_lock) test_node.clear_block_announcement() test_node.send_message(msg_getdata( [CInv(MSG_CMPCT_BLOCK, int(new_blocks[0], 16))])) wait_until(lambda: "cmpctblock" in test_node.last_message, timeout=30, lock=p2p_lock) test_node.clear_block_announcement() node.generate(1) wait_until(test_node.received_block_announcement, timeout=30, lock=p2p_lock) test_node.clear_block_announcement() with p2p_lock: test_node.last_message.pop("block", None) test_node.send_message(msg_getdata( [CInv(MSG_CMPCT_BLOCK, int(new_blocks[0], 16))])) wait_until(lambda: "block" in test_node.last_message, timeout=30, lock=p2p_lock) with p2p_lock: test_node.last_message["block"].block.calc_sha256() assert_equal( test_node.last_message["block"].block.sha256, int(new_blocks[0], 16)) # Generate an old compactblock, and verify that it's not accepted. cur_height = node.getblockcount() hashPrevBlock = int(node.getblockhash(cur_height - 5), 16) block = self.build_block_on_tip(node) block.hashPrevBlock = hashPrevBlock block.solve() comp_block = HeaderAndShortIDs() comp_block.initialize_from_block(block) test_node.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) tips = node.getchaintips() found = False for x in tips: if x["hash"] == block.hash: assert_equal(x["status"], "headers-only") found = True break assert found # Requesting this block via getblocktxn should silently fail # (to avoid fingerprinting attacks). msg = msg_getblocktxn() msg.block_txn_request = BlockTransactionsRequest(block.sha256, [0]) with p2p_lock: test_node.last_message.pop("blocktxn", None) test_node.send_and_ping(msg) with p2p_lock: assert "blocktxn" not in test_node.last_message def test_end_to_end_block_relay(self, node, listeners): utxo = self.utxos.pop(0) block, _ = self.build_block_with_transactions(node, utxo, 10) [listener.clear_block_announcement() for listener in listeners] node.submitblock(ToHex(block)) for listener in listeners: wait_until(lambda: listener.received_block_announcement(), timeout=30, lock=p2p_lock) with p2p_lock: for listener in listeners: assert "cmpctblock" in listener.last_message listener.last_message["cmpctblock"].header_and_shortids.header.calc_sha256( ) assert_equal( listener.last_message["cmpctblock"].header_and_shortids.header.sha256, block.sha256) # Test that we don't get disconnected if we relay a compact block with valid header, # but invalid transactions. def test_invalid_tx_in_compactblock(self, node, test_node): assert len(self.utxos) utxo = self.utxos[0] block, ordered_txs = self.build_block_with_transactions(node, utxo, 5) block.vtx.remove(ordered_txs[3]) block.hashMerkleRoot = block.calc_merkle_root() block.solve() # Now send the compact block with all transactions prefilled, and # verify that we don't get disconnected. comp_block = HeaderAndShortIDs() comp_block.initialize_from_block(block, prefill_list=[0, 1, 2, 3, 4]) msg = msg_cmpctblock(comp_block.to_p2p()) test_node.send_and_ping(msg) # Check that the tip didn't advance assert int(node.getbestblockhash(), 16) is not block.sha256 test_node.sync_with_ping() # Helper for enabling cb announcements # Send the sendcmpct request and sync headers def request_cb_announcements(self, peer, node, version=1): tip = node.getbestblockhash() peer.get_headers(locator=[int(tip, 16)], hashstop=0) msg = msg_sendcmpct() msg.version = version msg.announce = True peer.send_and_ping(msg) def test_compactblock_reconstruction_multiple_peers( self, node, stalling_peer, delivery_peer): assert len(self.utxos) def announce_cmpct_block(node, peer): utxo = self.utxos.pop(0) block, _ = self.build_block_with_transactions(node, utxo, 5) cmpct_block = HeaderAndShortIDs() cmpct_block.initialize_from_block(block) msg = msg_cmpctblock(cmpct_block.to_p2p()) peer.send_and_ping(msg) with p2p_lock: assert "getblocktxn" in peer.last_message return block, cmpct_block block, cmpct_block = announce_cmpct_block(node, stalling_peer) for tx in block.vtx[1:]: delivery_peer.send_message(msg_tx(tx)) delivery_peer.sync_with_ping() mempool = node.getrawmempool() for tx in block.vtx[1:]: assert tx.hash in mempool delivery_peer.send_and_ping(msg_cmpctblock(cmpct_block.to_p2p())) assert_equal(int(node.getbestblockhash(), 16), block.sha256) self.utxos.append( [block.vtx[-1].sha256, 0, block.vtx[-1].vout[0].nValue]) # Now test that delivering an invalid compact block won't break relay block, cmpct_block = announce_cmpct_block(node, stalling_peer) for tx in block.vtx[1:]: delivery_peer.send_message(msg_tx(tx)) delivery_peer.sync_with_ping() # TODO: modify txhash in a way that doesn't impact txid. delivery_peer.send_and_ping(msg_cmpctblock(cmpct_block.to_p2p())) # Because txhash isn't modified, we end up reconstructing the same block # assert int(node.getbestblockhash(), 16) != block.sha256 msg = msg_blocktxn() msg.block_transactions.blockhash = block.sha256 msg.block_transactions.transactions = block.vtx[1:] stalling_peer.send_and_ping(msg) assert_equal(int(node.getbestblockhash(), 16), block.sha256) def run_test(self): # Setup the p2p connections self.test_node = self.nodes[0].add_p2p_connection(TestP2PConn()) self.ex_softfork_node = self.nodes[1].add_p2p_connection( TestP2PConn(), services=NODE_NETWORK) self.old_node = self.nodes[1].add_p2p_connection( TestP2PConn(), services=NODE_NETWORK) # We will need UTXOs to construct transactions in later tests. self.make_utxos() self.log.info("Running tests:") self.log.info("\tTesting SENDCMPCT p2p message... ") self.test_sendcmpct(self.nodes[0], self.test_node, 1) self.sync_blocks() self.test_sendcmpct( self.nodes[1], self.ex_softfork_node, 1, old_node=self.old_node) self.sync_blocks() self.log.info("\tTesting compactblock construction...") self.test_compactblock_construction(self.nodes[0], self.test_node) self.sync_blocks() self.test_compactblock_construction( self.nodes[1], self.ex_softfork_node) self.sync_blocks() self.log.info("\tTesting compactblock requests... ") self.test_compactblock_requests(self.nodes[0], self.test_node, 1) self.sync_blocks() self.test_compactblock_requests( self.nodes[1], self.ex_softfork_node, 2) self.sync_blocks() self.log.info("\tTesting getblocktxn requests...") self.test_getblocktxn_requests(self.nodes[0], self.test_node, 1) self.sync_blocks() self.test_getblocktxn_requests(self.nodes[1], self.ex_softfork_node, 2) self.sync_blocks() self.log.info("\tTesting getblocktxn handler...") self.test_getblocktxn_handler(self.nodes[0], self.test_node, 1) self.sync_blocks() self.test_getblocktxn_handler(self.nodes[1], self.ex_softfork_node, 2) self.test_getblocktxn_handler(self.nodes[1], self.old_node, 1) self.sync_blocks() self.log.info( "\tTesting compactblock requests/announcements not at chain tip...") self.test_compactblocks_not_at_tip(self.nodes[0], self.test_node) self.sync_blocks() self.test_compactblocks_not_at_tip( self.nodes[1], self.ex_softfork_node) self.test_compactblocks_not_at_tip(self.nodes[1], self.old_node) self.sync_blocks() self.log.info("\tTesting handling of incorrect blocktxn responses...") self.test_incorrect_blocktxn_response(self.nodes[0], self.test_node, 1) self.sync_blocks() self.test_incorrect_blocktxn_response( self.nodes[1], self.ex_softfork_node, 2) self.sync_blocks() # End-to-end block relay tests self.log.info("\tTesting end-to-end block relay...") self.request_cb_announcements(self.test_node, self.nodes[0]) self.request_cb_announcements(self.old_node, self.nodes[1]) self.request_cb_announcements( self.ex_softfork_node, self.nodes[1], version=2) self.test_end_to_end_block_relay( self.nodes[0], [self.ex_softfork_node, self.test_node, self.old_node]) self.test_end_to_end_block_relay( self.nodes[1], [self.ex_softfork_node, self.test_node, self.old_node]) self.log.info("\tTesting handling of invalid compact blocks...") self.test_invalid_tx_in_compactblock(self.nodes[0], self.test_node) self.test_invalid_tx_in_compactblock( self.nodes[1], self.ex_softfork_node) self.test_invalid_tx_in_compactblock(self.nodes[1], self.old_node) self.log.info( "\tTesting reconstructing compact blocks from all peers...") self.test_compactblock_reconstruction_multiple_peers( self.nodes[1], self.ex_softfork_node, self.old_node) self.sync_blocks() self.log.info("\tTesting invalid index in cmpctblock message...") self.test_invalid_cmpctblock_message() if __name__ == '__main__': CompactBlocksTest().main() diff --git a/test/functional/p2p_eviction.py b/test/functional/p2p_eviction.py index 9e5587994..28171fa0f 100755 --- a/test/functional/p2p_eviction.py +++ b/test/functional/p2p_eviction.py @@ -1,150 +1,150 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ Test node eviction logic When the number of peers has reached the limit of maximum connections, the next connecting inbound peer will trigger the eviction mechanism. We cannot currently test the parts of the eviction logic that are based on address/netgroup since in the current framework, all peers are connecting from the same local address. See Issue #14210 for more info. Therefore, this test is limited to the remaining protection criteria. """ import time from test_framework.blocktools import create_block, create_coinbase from test_framework.messages import CTransaction, FromHex, msg_pong, msg_tx from test_framework.p2p import P2PDataStore, P2PInterface from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, wait_until class SlowP2PDataStore(P2PDataStore): def on_ping(self, message): time.sleep(0.1) self.send_message(msg_pong(message.nonce)) class SlowP2PInterface(P2PInterface): def on_ping(self, message): time.sleep(0.1) self.send_message(msg_pong(message.nonce)) class P2PEvict(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 # The choice of maxconnections=32 results in a maximum of 21 inbound connections # (32 - 10 outbound - 1 feeler). 20 inbound peers are protected from eviction: # 4 by netgroup, 4 that sent us blocks, 4 that sent us transactions and # 8 via lowest ping time self.extra_args = [['-maxconnections=32']] def run_test(self): protected_peers = set() # peers that we expect to be protected from eviction current_peer = -1 node = self.nodes[0] node.generatetoaddress(101, node.get_deterministic_priv_key().address) self.log.info( "Create 4 peers and protect them from eviction by sending us a block") for _ in range(4): block_peer = node.add_p2p_connection(SlowP2PDataStore()) current_peer += 1 block_peer.sync_with_ping() best_block = node.getbestblockhash() tip = int(best_block, 16) best_block_time = node.getblock(best_block)['time'] block = create_block( tip, create_coinbase( node.getblockcount() + 1), best_block_time + 1) block.solve() block_peer.send_blocks_and_test([block], node, success=True) protected_peers.add(current_peer) self.log.info( "Create 5 slow-pinging peers, making them eviction candidates") for _ in range(5): node.add_p2p_connection(SlowP2PInterface()) current_peer += 1 self.log.info( "Create 4 peers and protect them from eviction by sending us a tx") for i in range(4): txpeer = node.add_p2p_connection(SlowP2PInterface()) current_peer += 1 txpeer.sync_with_ping() prevtx = node.getblock(node.getblockhash(i + 1), 2)['tx'][0] rawtx = node.createrawtransaction( inputs=[{'txid': prevtx['txid'], 'vout': 0}], outputs=[ - {node.get_deterministic_priv_key().address: 50 - 0.00125}], + {node.get_deterministic_priv_key().address: 50000000 - 1250.00}], ) sigtx = node.signrawtransactionwithkey( hexstring=rawtx, privkeys=[node.get_deterministic_priv_key().key], prevtxs=[{ 'txid': prevtx['txid'], 'vout': 0, 'amount': prevtx['vout'][0]['value'], 'scriptPubKey': prevtx['vout'][0]['scriptPubKey']['hex'], }], )['hex'] txpeer.send_message(msg_tx(FromHex(CTransaction(), sigtx))) protected_peers.add(current_peer) self.log.info( "Create 8 peers and protect them from eviction by having faster pings") for _ in range(8): fastpeer = node.add_p2p_connection(P2PInterface()) current_peer += 1 wait_until(lambda: "ping" in fastpeer.last_message, timeout=10) # Make sure by asking the node what the actual min pings are peerinfo = node.getpeerinfo() pings = {} for i in range(len(peerinfo)): pings[i] = peerinfo[i]['minping'] if 'minping' in peerinfo[i] else 1000000 sorted_pings = sorted(pings.items(), key=lambda x: x[1]) # Usually the 8 fast peers are protected. In rare case of unreliable pings, # one of the slower peers might have a faster min ping though. for i in range(8): protected_peers.add(sorted_pings[i][0]) self.log.info("Create peer that triggers the eviction mechanism") node.add_p2p_connection(SlowP2PInterface()) # One of the non-protected peers must be evicted. We can't be sure which one because # 4 peers are protected via netgroup, which is identical for all peers, # and the eviction mechanism doesn't preserve the order of identical # elements. evicted_peers = [] for i in range(len(node.p2ps)): if not node.p2ps[i].is_connected: evicted_peers.append(i) self.log.info("Test that one peer was evicted") self.log.debug( "{} evicted peer: {}".format( len(evicted_peers), set(evicted_peers))) assert_equal(len(evicted_peers), 1) self.log.info("Test that no peer expected to be protected was evicted") self.log.debug( "{} protected peers: {}".format( len(protected_peers), protected_peers)) assert evicted_peers[0] not in protected_peers if __name__ == '__main__': P2PEvict().main() diff --git a/test/functional/p2p_feefilter.py b/test/functional/p2p_feefilter.py index 29e3e5306..e977fea1d 100755 --- a/test/functional/p2p_feefilter.py +++ b/test/functional/p2p_feefilter.py @@ -1,146 +1,146 @@ #!/usr/bin/env python3 # Copyright (c) 2016-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test processing of feefilter messages.""" from decimal import Decimal import time from test_framework.messages import MSG_TX, msg_feefilter from test_framework.p2p import ( P2PInterface, p2p_lock, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal def hashToHex(hash): return format(hash, '064x') # Wait up to 60 secs to see if the testnode has received all the expected invs def allInvsMatch(invsExpected, testnode): for x in range(60): with p2p_lock: if (sorted(invsExpected) == sorted(testnode.txinvs)): return True time.sleep(1) return False class FeefilterConn(P2PInterface): feefilter_received = False def on_feefilter(self, message): self.feefilter_received = True def assert_feefilter_received(self, recv: bool): with p2p_lock: assert_equal(self.feefilter_received, recv) class TestP2PConn(P2PInterface): def __init__(self): super().__init__() self.txinvs = [] def on_inv(self, message): for i in message.inv: if (i.type == MSG_TX): self.txinvs.append(hashToHex(i.hash)) def clear_invs(self): with p2p_lock: self.txinvs = [] class FeeFilterTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 # We lower the various required feerates for this test # to catch a corner-case where feefilter used to slightly undercut # mempool and wallet feerate calculation based on GetFee # rounding down 3 places, leading to stranded transactions. # See issue #16499 - self.extra_args = [["-minrelaytxfee=0.00000100", - "-mintxfee=0.00000100"]] * self.num_nodes + self.extra_args = [["-minrelaytxfee=1", + "-mintxfee=1"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): self.test_feefilter_forcerelay() self.test_feefilter() def test_feefilter_forcerelay(self): self.log.info( 'Check that peers without forcerelay permission (default) get a feefilter message') self.nodes[0].add_p2p_connection( FeefilterConn()).assert_feefilter_received(True) self.log.info( 'Check that peers with forcerelay permission do not get a feefilter message') self.restart_node(0, extra_args=['-whitelist=forcerelay@127.0.0.1']) self.nodes[0].add_p2p_connection( FeefilterConn()).assert_feefilter_received(False) # Restart to disconnect peers and load default extra_args self.restart_node(0) self.connect_nodes(1, 0) def test_feefilter(self): node1 = self.nodes[1] node0 = self.nodes[0] conn = self.nodes[0].add_p2p_connection(TestP2PConn()) # Test that invs are received by test connection for all txs at # feerate of .2 sat/byte - node1.settxfee(Decimal("0.00000200")) - txids = [node1.sendtoaddress(node1.getnewaddress(), 1) + node1.settxfee(Decimal("2")) + txids = [node1.sendtoaddress(node1.getnewaddress(), 1000000) for x in range(3)] assert allInvsMatch(txids, conn) conn.clear_invs() # Set a filter of .15 sat/byte on test connection conn.send_and_ping(msg_feefilter(150)) # Test that txs are still being received by test connection # (paying .15 sat/byte) - node1.settxfee(Decimal("0.00000150")) - txids = [node1.sendtoaddress(node1.getnewaddress(), 1) + node1.settxfee(Decimal("1.5")) + txids = [node1.sendtoaddress(node1.getnewaddress(), 1000000) for x in range(3)] assert allInvsMatch(txids, conn) conn.clear_invs() # Change tx fee rate to .1 sat/byte and test they are no longer received # by the test connection - node1.settxfee(Decimal("0.00000100")) - [node1.sendtoaddress(node1.getnewaddress(), 1) for x in range(3)] + node1.settxfee(Decimal("1")) + [node1.sendtoaddress(node1.getnewaddress(), 1000000) for x in range(3)] self.sync_mempools() # must be sure node 0 has received all txs # Send one transaction from node0 that should be received, so that we # we can sync the test on receipt (if node1's txs were relayed, they'd # be received by the time this node0 tx is received). This is # unfortunately reliant on the current relay behavior where we batch up # to 35 entries in an inv, which means that when this next transaction # is eligible for relay, the prior transactions from node1 are eligible # as well. - node0.settxfee(Decimal("0.00020000")) - txids = [node0.sendtoaddress(node0.getnewaddress(), 1)] + node0.settxfee(Decimal("200.00")) + txids = [node0.sendtoaddress(node0.getnewaddress(), 1000000)] assert allInvsMatch(txids, conn) conn.clear_invs() # Remove fee filter and check that txs are received again conn.send_and_ping(msg_feefilter(0)) - txids = [node1.sendtoaddress(node1.getnewaddress(), 1) + txids = [node1.sendtoaddress(node1.getnewaddress(), 1000000) for x in range(3)] assert allInvsMatch(txids, conn) conn.clear_invs() if __name__ == '__main__': FeeFilterTest().main() diff --git a/test/functional/p2p_inv_download.py b/test/functional/p2p_inv_download.py index a266dd0ed..a6d0bcb49 100755 --- a/test/functional/p2p_inv_download.py +++ b/test/functional/p2p_inv_download.py @@ -1,401 +1,401 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ Test inventory download behavior """ from test_framework.address import ADDRESS_BCHREG_UNSPENDABLE from test_framework.messages import ( CInv, CTransaction, FromHex, MSG_AVA_PROOF, MSG_TX, MSG_TYPE_MASK, msg_inv, msg_notfound, ) from test_framework.p2p import ( P2PInterface, p2p_lock, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, wait_until, ) import functools import time class TestP2PConn(P2PInterface): def __init__(self, inv_type): super().__init__() self.inv_type = inv_type self.getdata_count = 0 def on_getdata(self, message): for i in message.inv: if i.type & MSG_TYPE_MASK == self.inv_type: self.getdata_count += 1 class NetConstants: """Constants from net_processing""" def __init__(self, getdata_interval, inbound_peer_delay, overloaded_peer_delay, max_getdata_in_flight, max_peer_announcements, bypass_request_limits_permission_flags, ): self.getdata_interval = getdata_interval self.inbound_peer_delay = inbound_peer_delay self.overloaded_peer_delay = overloaded_peer_delay self.max_getdata_in_flight = max_getdata_in_flight self.max_peer_announcements = max_peer_announcements self.max_getdata_inbound_wait = self.getdata_interval + self.inbound_peer_delay self.bypass_request_limits_permission_flags = bypass_request_limits_permission_flags class TestContext: def __init__(self, inv_type, inv_name, constants): self.inv_type = inv_type self.inv_name = inv_name self.constants = constants def p2p_conn(self): return TestP2PConn(self.inv_type) PROOF_TEST_CONTEXT = TestContext( MSG_AVA_PROOF, "avalanche proof", NetConstants( getdata_interval=60, # seconds inbound_peer_delay=2, # seconds overloaded_peer_delay=2, # seconds max_getdata_in_flight=100, max_peer_announcements=5000, bypass_request_limits_permission_flags="bypass_proof_request_limits", ), ) TX_TEST_CONTEXT = TestContext( MSG_TX, "transaction", NetConstants( getdata_interval=60, # seconds inbound_peer_delay=2, # seconds overloaded_peer_delay=2, # seconds max_getdata_in_flight=100, max_peer_announcements=5000, bypass_request_limits_permission_flags="relay", ), ) # Python test constants NUM_INBOUND = 10 def skip(context): def decorator(test): @functools.wraps(test) def wrapper(*args, **kwargs): # Assume the signature is test(self, context) unless context is # passed by name call_context = kwargs.get("context", args[1]) if call_context == context: return lambda *args, **kwargs: None return test(*args, **kwargs) return wrapper return decorator class InventoryDownloadTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = False self.num_nodes = 2 self.extra_args = [['-enableavalanche=1', '-avacooldown=0']] * self.num_nodes def test_data_requests(self, context): self.log.info( "Test that we request data from all our peers, eventually") invid = 0xdeadbeef self.log.info("Announce the invid from each incoming peer to node 0") msg = msg_inv([CInv(t=context.inv_type, h=invid)]) for p in self.nodes[0].p2ps: p.send_and_ping(msg) outstanding_peer_index = [i for i in range(len(self.nodes[0].p2ps))] def getdata_found(peer_index): p = self.nodes[0].p2ps[peer_index] with p2p_lock: return p.last_message.get( "getdata") and p.last_message["getdata"].inv[-1].hash == invid node_0_mocktime = int(time.time()) while outstanding_peer_index: node_0_mocktime += context.constants.max_getdata_inbound_wait self.nodes[0].setmocktime(node_0_mocktime) wait_until(lambda: any(getdata_found(i) for i in outstanding_peer_index)) for i in outstanding_peer_index: if getdata_found(i): outstanding_peer_index.remove(i) self.nodes[0].setmocktime(0) self.log.info("All outstanding peers received a getdata") @skip(PROOF_TEST_CONTEXT) def test_inv_block(self, context): self.log.info("Generate a transaction on node 0") tx = self.nodes[0].createrawtransaction( inputs=[{ # coinbase "txid": self.nodes[0].getblock(self.nodes[0].getblockhash(1))['tx'][0], "vout": 0 }], - outputs={ADDRESS_BCHREG_UNSPENDABLE: 50 - 0.00025}, + outputs={ADDRESS_BCHREG_UNSPENDABLE: 50000000 - 250.00}, ) tx = self.nodes[0].signrawtransactionwithkey( hexstring=tx, privkeys=[self.nodes[0].get_deterministic_priv_key().key], )['hex'] ctx = FromHex(CTransaction(), tx) txid = int(ctx.rehash(), 16) self.log.info( "Announce the transaction to all nodes from all {} incoming peers, but never send it".format(NUM_INBOUND)) msg = msg_inv([CInv(t=context.inv_type, h=txid)]) for p in self.peers: p.send_and_ping(msg) self.log.info("Put the tx in node 0's mempool") self.nodes[0].sendrawtransaction(tx) # Since node 1 is connected outbound to an honest peer (node 0), it # should get the tx within a timeout. (Assuming that node 0 # announced the tx within the timeout) # The timeout is the sum of # * the worst case until the tx is first requested from an inbound # peer, plus # * the first time it is re-requested from the outbound peer, plus # * 2 seconds to avoid races assert self.nodes[1].getpeerinfo()[0]['inbound'] is False timeout = 2 + context.constants.inbound_peer_delay + \ context.constants.getdata_interval self.log.info( "Tx should be received at node 1 after {} seconds".format(timeout)) self.sync_mempools(timeout=timeout) def test_in_flight_max(self, context): max_getdata_in_flight = context.constants.max_getdata_in_flight max_inbound_delay = context.constants.inbound_peer_delay + \ context.constants.overloaded_peer_delay self.log.info("Test that we don't load peers with more than {} getdata requests immediately".format( max_getdata_in_flight)) invids = [i for i in range(max_getdata_in_flight + 2)] p = self.nodes[0].p2ps[0] with p2p_lock: p.getdata_count = 0 mock_time = int(time.time() + 1) self.nodes[0].setmocktime(mock_time) for i in range(max_getdata_in_flight): p.send_message(msg_inv([CInv(t=context.inv_type, h=invids[i])])) p.sync_with_ping() mock_time += context.constants.inbound_peer_delay self.nodes[0].setmocktime(mock_time) p.wait_until(lambda: p.getdata_count >= max_getdata_in_flight) for i in range(max_getdata_in_flight, len(invids)): p.send_message(msg_inv([CInv(t=context.inv_type, h=invids[i])])) p.sync_with_ping() self.log.info( "No more than {} requests should be seen within {} seconds after announcement".format( max_getdata_in_flight, max_inbound_delay - 1)) self.nodes[0].setmocktime( mock_time + max_inbound_delay - 1) p.sync_with_ping() with p2p_lock: assert_equal(p.getdata_count, max_getdata_in_flight) self.log.info( "If we wait {} seconds after announcement, we should eventually get more requests".format( max_inbound_delay)) self.nodes[0].setmocktime( mock_time + max_inbound_delay) p.wait_until(lambda: p.getdata_count == len(invids)) def test_expiry_fallback(self, context): self.log.info( 'Check that expiry will select another peer for download') peer1 = self.nodes[0].add_p2p_connection(context.p2p_conn()) peer2 = self.nodes[0].add_p2p_connection(context.p2p_conn()) for p in [peer1, peer2]: p.send_message(msg_inv([CInv(t=context.inv_type, h=0xffaa)])) # One of the peers is asked for the data peer2.wait_until( lambda: sum( p.getdata_count for p in [ peer1, peer2]) == 1) with p2p_lock: peer_expiry, peer_fallback = ( peer1, peer2) if peer1.getdata_count == 1 else ( peer2, peer1) assert_equal(peer_fallback.getdata_count, 0) # Wait for request to peer_expiry to expire self.nodes[0].setmocktime( int(time.time()) + context.constants.getdata_interval + 1) peer_fallback.wait_until( lambda: peer_fallback.getdata_count >= 1, timeout=1) with p2p_lock: assert_equal(peer_fallback.getdata_count, 1) # reset mocktime self.restart_node(0) def test_disconnect_fallback(self, context): self.log.info( 'Check that disconnect will select another peer for download') peer1 = self.nodes[0].add_p2p_connection(context.p2p_conn()) peer2 = self.nodes[0].add_p2p_connection(context.p2p_conn()) for p in [peer1, peer2]: p.send_message(msg_inv([CInv(t=context.inv_type, h=0xffbb)])) # One of the peers is asked for the data peer2.wait_until( lambda: sum( p.getdata_count for p in [ peer1, peer2]) == 1) with p2p_lock: peer_disconnect, peer_fallback = ( peer1, peer2) if peer1.getdata_count == 1 else ( peer2, peer1) assert_equal(peer_fallback.getdata_count, 0) peer_disconnect.peer_disconnect() peer_disconnect.wait_for_disconnect() peer_fallback.wait_until( lambda: peer_fallback.getdata_count >= 1, timeout=1) with p2p_lock: assert_equal(peer_fallback.getdata_count, 1) def test_notfound_fallback(self, context): self.log.info( 'Check that notfounds will select another peer for download immediately') peer1 = self.nodes[0].add_p2p_connection(context.p2p_conn()) peer2 = self.nodes[0].add_p2p_connection(context.p2p_conn()) for p in [peer1, peer2]: p.send_message(msg_inv([CInv(t=context.inv_type, h=0xffdd)])) # One of the peers is asked for the data peer2.wait_until( lambda: sum( p.getdata_count for p in [ peer1, peer2]) == 1) with p2p_lock: peer_notfound, peer_fallback = ( peer1, peer2) if peer1.getdata_count == 1 else ( peer2, peer1) assert_equal(peer_fallback.getdata_count, 0) # Send notfound, so that fallback peer is selected peer_notfound.send_and_ping(msg_notfound( vec=[CInv(context.inv_type, 0xffdd)])) peer_fallback.wait_until( lambda: peer_fallback.getdata_count >= 1, timeout=1) with p2p_lock: assert_equal(peer_fallback.getdata_count, 1) def test_preferred_inv(self, context): self.log.info( 'Check that invs from preferred peers are downloaded immediately') self.restart_node( 0, extra_args=self.extra_args[0] + ['-whitelist=noban@127.0.0.1']) peer = self.nodes[0].add_p2p_connection(context.p2p_conn()) peer.send_message(msg_inv([CInv(t=context.inv_type, h=0xff00ff00)])) peer.wait_until(lambda: peer.getdata_count >= 1, timeout=1) with p2p_lock: assert_equal(peer.getdata_count, 1) def test_large_inv_batch(self, context): max_peer_announcements = context.constants.max_peer_announcements net_permissions = context.constants.bypass_request_limits_permission_flags self.log.info( 'Test how large inv batches are handled with {} permission'.format(net_permissions)) self.restart_node( 0, extra_args=self.extra_args[0] + ['-whitelist={}@127.0.0.1'.format(net_permissions)]) peer = self.nodes[0].add_p2p_connection(context.p2p_conn()) peer.send_message(msg_inv([CInv(t=context.inv_type, h=invid) for invid in range(max_peer_announcements + 1)])) peer.wait_until(lambda: peer.getdata_count == max_peer_announcements + 1) self.log.info( 'Test how large inv batches are handled without {} permission'.format(net_permissions)) self.restart_node(0) peer = self.nodes[0].add_p2p_connection(context.p2p_conn()) peer.send_message(msg_inv([CInv(t=context.inv_type, h=invid) for invid in range(max_peer_announcements + 1)])) peer.wait_until(lambda: peer.getdata_count == max_peer_announcements) peer.sync_with_ping() with p2p_lock: assert_equal(peer.getdata_count, max_peer_announcements) def test_spurious_notfound(self, context): self.log.info('Check that spurious notfound is ignored') self.nodes[0].p2ps[0].send_message( msg_notfound(vec=[CInv(context.inv_type, 1)])) def run_test(self): for context in [TX_TEST_CONTEXT, PROOF_TEST_CONTEXT]: self.log.info( "Starting tests using " + context.inv_name + " inventory type") # Run tests without mocktime that only need one peer-connection first, # to avoid restarting the nodes self.test_expiry_fallback(context) self.test_disconnect_fallback(context) self.test_notfound_fallback(context) self.test_preferred_inv(context) self.test_large_inv_batch(context) self.test_spurious_notfound(context) # Run each test against new bitcoind instances, as setting mocktimes has long-term effects on when # the next trickle relay event happens. for test in [self.test_in_flight_max, self.test_inv_block, self.test_data_requests]: self.stop_nodes() self.start_nodes() self.connect_nodes(1, 0) # Setup the p2p connections self.peers = [] for node in self.nodes: for _ in range(NUM_INBOUND): self.peers.append( node.add_p2p_connection( context.p2p_conn())) self.log.info( "Nodes are setup with {} incoming connections each".format(NUM_INBOUND)) test(context) if __name__ == '__main__': InventoryDownloadTest().main() diff --git a/test/functional/p2p_leak_tx.py b/test/functional/p2p_leak_tx.py index b221bc0d5..344f5b772 100755 --- a/test/functional/p2p_leak_tx.py +++ b/test/functional/p2p_leak_tx.py @@ -1,63 +1,63 @@ #!/usr/bin/env python3 # Copyright (c) 2017-2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test that we don't leak txs to inbound peers that we haven't yet announced to""" from test_framework.messages import msg_getdata, CInv, MSG_TX from test_framework.p2p import P2PDataStore from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, ) class P2PNode(P2PDataStore): def on_inv(self, msg): pass class P2PLeakTxTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): # The block and tx generating node gen_node = self.nodes[0] gen_node.generate(1) # An "attacking" inbound peer inbound_peer = self.nodes[0].add_p2p_connection(P2PNode()) MAX_REPEATS = 100 self.log.info("Running test up to {} times.".format(MAX_REPEATS)) for i in range(MAX_REPEATS): self.log.info('Run repeat {}'.format(i + 1)) - txid = gen_node.sendtoaddress(gen_node.getnewaddress(), 0.01) + txid = gen_node.sendtoaddress(gen_node.getnewaddress(), 10000) want_tx = msg_getdata() want_tx.inv.append(CInv(t=MSG_TX, h=int(txid, 16))) inbound_peer.last_message.pop('notfound', None) inbound_peer.send_and_ping(want_tx) if inbound_peer.last_message.get('notfound'): self.log.debug( 'tx {} was not yet announced to us.'.format(txid)) self.log.debug( "node has responded with a notfound message. End test.") assert_equal( inbound_peer.last_message['notfound'].vec[0].hash, int(txid, 16)) inbound_peer.last_message.pop('notfound') break else: self.log.debug( 'tx {} was already announced to us. Try test again.'.format(txid)) assert int(txid, 16) in [ inv.hash for inv in inbound_peer.last_message['inv'].inv] if __name__ == '__main__': P2PLeakTxTest().main() diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 289a26bdd..f59edcba2 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -1,414 +1,414 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test RPCs related to blockchainstate. Test the following RPCs: - getblockchaininfo - gettxoutsetinfo - getdifficulty - getbestblockhash - getblockhash - getblockheader - getchaintxstats - getnetworkhashps - verifychain Tests correspond to code in rpc/blockchain.cpp. """ from decimal import Decimal import http.client import subprocess from test_framework.blocktools import ( create_block, create_coinbase, TIME_GENESIS_BLOCK, ) from test_framework.messages import ( msg_block, ) from test_framework.p2p import P2PInterface from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_greater_than, assert_greater_than_or_equal, assert_raises, assert_raises_rpc_error, assert_is_hex_string, assert_is_hash_string, ) class BlockchainTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 # TODO: remove -txindex. Currently required for getrawtransaction call. self.extra_args = [["-txindex"]] self.supports_cli = False def run_test(self): self.mine_chain() # Set extra args with pruning after rescan is complete self.restart_node(0, extra_args=['-stopatheight=207', '-prune=1']) self._test_getblockchaininfo() self._test_getchaintxstats() self._test_gettxoutsetinfo() self._test_getblockheader() self._test_getdifficulty() self._test_getnetworkhashps() self._test_stopatheight() self._test_waitforblockheight() if self.is_wallet_compiled(): self._test_getblock() assert self.nodes[0].verifychain(4, 0) def mine_chain(self): self.log.info('Create some old blocks') address = self.nodes[0].get_deterministic_priv_key().address for t in range(TIME_GENESIS_BLOCK, TIME_GENESIS_BLOCK + 200 * 600, 600): # ten-minute steps from genesis block time self.nodes[0].setmocktime(t) self.nodes[0].generatetoaddress(1, address) assert_equal(self.nodes[0].getblockchaininfo()['blocks'], 200) def _test_getblockchaininfo(self): self.log.info("Test getblockchaininfo") keys = [ 'bestblockhash', 'blocks', 'chain', 'chainwork', 'difficulty', 'headers', 'initialblockdownload', 'mediantime', 'pruned', 'size_on_disk', 'softforks', 'verificationprogress', 'warnings', ] res = self.nodes[0].getblockchaininfo() # result should have these additional pruning keys if manual pruning is # enabled assert_equal(sorted(res.keys()), sorted( ['pruneheight', 'automatic_pruning'] + keys)) # size_on_disk should be > 0 assert_greater_than(res['size_on_disk'], 0) assert_equal(res['softforks'], { 'testdummy': { 'type': 'bip9', 'bip9': { 'status': 'started', 'bit': 28, 'start_time': 0, # testdummy does not have a timeout so is set to the max # int64 value 'timeout': 0x7fffffffffffffff, 'since': 144, 'statistics': { 'period': 144, 'threshold': 108, 'elapsed': 57, 'count': 57, 'possible': True, }, }, 'active': False, }, }) # pruneheight should be greater or equal to 0 assert_greater_than_or_equal(res['pruneheight'], 0) # check other pruning fields given that prune=1 assert res['pruned'] assert not res['automatic_pruning'] self.restart_node(0, ['-stopatheight=207']) res = self.nodes[0].getblockchaininfo() # should have exact keys assert_equal(sorted(res.keys()), keys) self.restart_node(0, ['-stopatheight=207', '-prune=550']) res = self.nodes[0].getblockchaininfo() # result should have these additional pruning keys if prune=550 assert_equal(sorted(res.keys()), sorted( ['pruneheight', 'automatic_pruning', 'prune_target_size'] + keys)) # check related fields assert res['pruned'] assert_equal(res['pruneheight'], 0) assert res['automatic_pruning'] assert_equal(res['prune_target_size'], 576716800) assert_greater_than(res['size_on_disk'], 0) def _test_getchaintxstats(self): self.log.info("Test getchaintxstats") # Test `getchaintxstats` invalid extra parameters assert_raises_rpc_error( -1, 'getchaintxstats', self.nodes[0].getchaintxstats, 0, '', 0) # Test `getchaintxstats` invalid `nblocks` assert_raises_rpc_error( -1, "JSON value is not an integer as expected", self.nodes[0].getchaintxstats, '') assert_raises_rpc_error( -8, "Invalid block count: should be between 0 and the block's height - 1", self.nodes[0].getchaintxstats, -1) assert_raises_rpc_error(-8, "Invalid block count: should be between 0 and the block's height - 1", self.nodes[ 0].getchaintxstats, self.nodes[0].getblockcount()) # Test `getchaintxstats` invalid `blockhash` assert_raises_rpc_error( -1, "JSON value is not a string as expected", self.nodes[0].getchaintxstats, blockhash=0) assert_raises_rpc_error(-8, "blockhash must be of length 64 (not 1, for '0')", self.nodes[0].getchaintxstats, blockhash='0') assert_raises_rpc_error( -8, "blockhash must be hexadecimal string (not 'ZZZ0000000000000000000000000000000000000000000000000000000000000')", self.nodes[0].getchaintxstats, blockhash='ZZZ0000000000000000000000000000000000000000000000000000000000000') assert_raises_rpc_error( -5, "Block not found", self.nodes[0].getchaintxstats, blockhash='0000000000000000000000000000000000000000000000000000000000000000') blockhash = self.nodes[0].getblockhash(200) self.nodes[0].invalidateblock(blockhash) assert_raises_rpc_error( -8, "Block is not in main chain", self.nodes[0].getchaintxstats, blockhash=blockhash) self.nodes[0].reconsiderblock(blockhash) chaintxstats = self.nodes[0].getchaintxstats(nblocks=1) # 200 txs plus genesis tx assert_equal(chaintxstats['txcount'], 201) # tx rate should be 1 per 10 minutes, or 1/600 # we have to round because of binary math assert_equal(round(chaintxstats['txrate'] * 600, 10), Decimal(1)) b1_hash = self.nodes[0].getblockhash(1) b1 = self.nodes[0].getblock(b1_hash) b200_hash = self.nodes[0].getblockhash(200) b200 = self.nodes[0].getblock(b200_hash) time_diff = b200['mediantime'] - b1['mediantime'] chaintxstats = self.nodes[0].getchaintxstats() assert_equal(chaintxstats['time'], b200['time']) assert_equal(chaintxstats['txcount'], 201) assert_equal(chaintxstats['window_final_block_hash'], b200_hash) assert_equal(chaintxstats['window_final_block_height'], 200) assert_equal(chaintxstats['window_block_count'], 199) assert_equal(chaintxstats['window_tx_count'], 199) assert_equal(chaintxstats['window_interval'], time_diff) assert_equal( round(chaintxstats['txrate'] * time_diff, 10), Decimal(199)) chaintxstats = self.nodes[0].getchaintxstats(blockhash=b1_hash) assert_equal(chaintxstats['time'], b1['time']) assert_equal(chaintxstats['txcount'], 2) assert_equal(chaintxstats['window_final_block_hash'], b1_hash) assert_equal(chaintxstats['window_final_block_height'], 1) assert_equal(chaintxstats['window_block_count'], 0) assert 'window_tx_count' not in chaintxstats assert 'window_interval' not in chaintxstats assert 'txrate' not in chaintxstats def _test_gettxoutsetinfo(self): node = self.nodes[0] res = node.gettxoutsetinfo() - assert_equal(res['total_amount'], Decimal('8725.00000000')) + assert_equal(res['total_amount'], Decimal('8725000000.00')) assert_equal(res['transactions'], 200) assert_equal(res['height'], 200) assert_equal(res['txouts'], 200) assert_equal(res['bogosize'], 15000), assert_equal(res['bestblock'], node.getblockhash(200)) size = res['disk_size'] assert size > 6400 assert size < 64000 assert_equal(len(res['bestblock']), 64) assert_equal(len(res['hash_serialized']), 64) self.log.info( "Test that gettxoutsetinfo() works for blockchain with just the genesis block") b1hash = node.getblockhash(1) node.invalidateblock(b1hash) res2 = node.gettxoutsetinfo() assert_equal(res2['transactions'], 0) assert_equal(res2['total_amount'], Decimal('0')) assert_equal(res2['height'], 0) assert_equal(res2['txouts'], 0) assert_equal(res2['bogosize'], 0), assert_equal(res2['bestblock'], node.getblockhash(0)) assert_equal(len(res2['hash_serialized']), 64) self.log.info( "Test that gettxoutsetinfo() returns the same result after invalidate/reconsider block") node.reconsiderblock(b1hash) res3 = node.gettxoutsetinfo() # The field 'disk_size' is non-deterministic and can thus not be # compared between res and res3. Everything else should be the same. del res['disk_size'], res3['disk_size'] assert_equal(res, res3) def _test_getblockheader(self): node = self.nodes[0] assert_raises_rpc_error(-8, "hash must be of length 64 (not 8, for 'nonsense')", node.getblockheader, "nonsense") assert_raises_rpc_error( -8, "hash must be hexadecimal string (not 'ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844')", node.getblockheader, "ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844") assert_raises_rpc_error(-5, "Block not found", node.getblockheader, "0cf7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844") besthash = node.getbestblockhash() secondbesthash = node.getblockhash(199) header = node.getblockheader(blockhash=besthash) assert_equal(header['hash'], besthash) assert_equal(header['height'], 200) assert_equal(header['confirmations'], 1) assert_equal(header['previousblockhash'], secondbesthash) assert_is_hex_string(header['chainwork']) assert_equal(header['nTx'], 1) assert_is_hash_string(header['hash']) assert_is_hash_string(header['previousblockhash']) assert_is_hash_string(header['merkleroot']) assert_is_hash_string(header['bits'], length=None) assert isinstance(header['time'], int) assert isinstance(header['mediantime'], int) assert isinstance(header['nonce'], int) assert isinstance(header['version'], int) assert isinstance(int(header['versionHex'], 16), int) assert isinstance(header['difficulty'], Decimal) def _test_getdifficulty(self): difficulty = self.nodes[0].getdifficulty() # 1 hash in 2 should be valid, so difficulty should be 1/2**31 # binary => decimal => binary math is why we do this check assert abs(difficulty * 2**31 - 1) < 0.0001 def _test_getnetworkhashps(self): hashes_per_second = self.nodes[0].getnetworkhashps() # This should be 2 hashes every 10 minutes or 1/300 assert abs(hashes_per_second * 300 - 1) < 0.0001 def _test_stopatheight(self): assert_equal(self.nodes[0].getblockcount(), 200) self.nodes[0].generatetoaddress( 6, self.nodes[0].get_deterministic_priv_key().address) assert_equal(self.nodes[0].getblockcount(), 206) self.log.debug('Node should not stop at this height') assert_raises(subprocess.TimeoutExpired, lambda: self.nodes[0].process.wait(timeout=3)) try: self.nodes[0].generatetoaddress( 1, self.nodes[0].get_deterministic_priv_key().address) except (ConnectionError, http.client.BadStatusLine): pass # The node already shut down before response self.log.debug('Node should stop at this height...') self.nodes[0].wait_until_stopped() self.start_node(0) assert_equal(self.nodes[0].getblockcount(), 207) def _test_waitforblockheight(self): self.log.info("Test waitforblockheight") node = self.nodes[0] node.add_p2p_connection(P2PInterface()) current_height = node.getblock(node.getbestblockhash())['height'] # Create a fork somewhere below our current height, invalidate the tip # of that fork, and then ensure that waitforblockheight still # works as expected. # # (Previously this was broken based on setting # `rpc/blockchain.cpp:latestblock` incorrectly.) # b20hash = node.getblockhash(20) b20 = node.getblock(b20hash) def solve_and_send_block(prevhash, height, time): b = create_block(prevhash, create_coinbase(height), time) b.solve() node.p2p.send_and_ping(msg_block(b)) return b b21f = solve_and_send_block(int(b20hash, 16), 21, b20['time'] + 1) b22f = solve_and_send_block(b21f.sha256, 22, b21f.nTime + 1) node.invalidateblock(b22f.hash) def assert_waitforheight(height, timeout=2): assert_equal( node.waitforblockheight( height=height, timeout=timeout)['height'], current_height) assert_waitforheight(0) assert_waitforheight(current_height - 1) assert_waitforheight(current_height) assert_waitforheight(current_height + 1) def _test_getblock(self): # Checks for getblock verbose outputs node = self.nodes[0] (blockhash, nextblockhash) = node.generate(2) blockinfo = node.getblock(blockhash, 2) transactioninfo = node.gettransaction(blockinfo['tx'][0]['txid']) blockheaderinfo = node.getblockheader(blockhash, True) assert_equal(blockinfo['hash'], transactioninfo['blockhash']) assert_equal( blockinfo['confirmations'], transactioninfo['confirmations']) assert_equal(blockinfo['height'], blockheaderinfo['height']) assert_equal(blockinfo['versionHex'], blockheaderinfo['versionHex']) assert_equal(blockinfo['version'], blockheaderinfo['version']) assert_equal(blockinfo['size'], 181) assert_equal(blockinfo['merkleroot'], blockheaderinfo['merkleroot']) # Verify transaction data by check the hex values for tx in blockinfo['tx']: rawtransaction = node.getrawtransaction(tx['txid'], True) assert_equal(tx['hex'], rawtransaction['hex']) assert_equal(blockinfo['time'], blockheaderinfo['time']) assert_equal(blockinfo['mediantime'], blockheaderinfo['mediantime']) assert_equal(blockinfo['nonce'], blockheaderinfo['nonce']) assert_equal(blockinfo['bits'], blockheaderinfo['bits']) assert_equal(blockinfo['difficulty'], blockheaderinfo['difficulty']) assert_equal(blockinfo['chainwork'], blockheaderinfo['chainwork']) assert_equal( blockinfo['previousblockhash'], blockheaderinfo['previousblockhash']) assert_equal(blockinfo['nextblockhash'], nextblockhash) assert_equal( blockinfo['nextblockhash'], blockheaderinfo['nextblockhash']) if __name__ == '__main__': BlockchainTest().main() diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py index c4d212713..a423af982 100755 --- a/test/functional/rpc_createmultisig.py +++ b/test/functional/rpc_createmultisig.py @@ -1,184 +1,184 @@ #!/usr/bin/env python3 # Copyright (c) 2015-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test multisig RPCs""" from test_framework.descriptors import descsum_create, drop_origins from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_raises_rpc_error, assert_equal, ) from test_framework.key import ECPubKey import binascii import decimal import itertools import json import os class RpcCreateMultiSigTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 3 self.supports_cli = False def skip_test_if_missing_module(self): self.skip_if_no_wallet() def get_keys(self): node0, node1, node2 = self.nodes add = [node1.getnewaddress() for _ in range(self.nkeys)] self.pub = [node1.getaddressinfo(a)["pubkey"] for a in add] self.priv = [node1.dumpprivkey(a) for a in add] self.final = node2.getnewaddress() def run_test(self): node0, node1, node2 = self.nodes self.check_addmultisigaddress_errors() self.log.info('Generating blocks ...') node0.generate(149) self.sync_all() self.moved = 0 for self.nkeys in [3, 5]: for self.nsigs in [2, 3]: self.get_keys() self.do_multisig() self.checkbalances() # Test mixed compressed and uncompressed pubkeys self.log.info( 'Mixed compressed and uncompressed multisigs are not allowed') pk0 = node0.getaddressinfo(node0.getnewaddress())['pubkey'] pk1 = node1.getaddressinfo(node1.getnewaddress())['pubkey'] pk2 = node2.getaddressinfo(node2.getnewaddress())['pubkey'] # decompress pk2 pk_obj = ECPubKey() pk_obj.set(binascii.unhexlify(pk2)) pk_obj.compressed = False pk2 = binascii.hexlify(pk_obj.get_bytes()).decode() # 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)['address'] assert_equal( legacy_addr, node0.addmultisigaddress( 2, keys, '')['address']) # Generate addresses with the segwit types. These should all make # legacy addresses assert_equal(legacy_addr, node0.createmultisig(2, keys)['address']) 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']) def check_addmultisigaddress_errors(self): self.log.info( 'Check that addmultisigaddress fails when the private keys are missing') addresses = [self.nodes[1].getnewaddress( address_type='legacy') for _ in range(2)] assert_raises_rpc_error(-5, 'no full public key for address', lambda: self.nodes[0].addmultisigaddress(nrequired=1, keys=addresses)) for a in addresses: # Importing all addresses should not change the result self.nodes[0].importaddress(a) assert_raises_rpc_error(-5, 'no full public key for address', lambda: self.nodes[0].addmultisigaddress(nrequired=1, keys=addresses)) def checkbalances(self): node0, node1, node2 = self.nodes node0.generate(100) self.sync_all() bal0 = node0.getbalance() bal1 = node1.getbalance() bal2 = node2.getbalance() height = node0.getblockchaininfo()["blocks"] assert 150 < height < 350 - total = 149 * 50 + (height - 149 - 100) * 25 + total = 149 * 50000000 + (height - 149 - 100) * 25000000 assert bal1 == 0 assert bal2 == self.moved assert bal0 + bal1 + bal2 == total def do_multisig(self): node0, node1, node2 = self.nodes # Construct the expected descriptor desc = 'multi({},{})'.format(self.nsigs, ','.join(self.pub)) desc = 'sh({})'.format(desc) desc = descsum_create(desc) msig = node2.createmultisig(self.nsigs, self.pub) madd = msig["address"] mredeem = msig["redeemScript"] assert_equal(desc, msig['descriptor']) # compare against addmultisigaddress msigw = node1.addmultisigaddress(self.nsigs, self.pub, None) maddw = msigw["address"] mredeemw = msigw["redeemScript"] assert_equal(desc, drop_origins(msigw['descriptor'])) # addmultisigiaddress and createmultisig work the same assert maddw == madd assert mredeemw == mredeem - txid = node0.sendtoaddress(madd, 40) + txid = node0.sendtoaddress(madd, 40000000) tx = node0.getrawtransaction(txid, True) vout = [v["n"] for v in tx["vout"] if madd in v["scriptPubKey"].get("addresses", [])] assert len(vout) == 1 vout = vout[0] scriptPubKey = tx["vout"][vout]["scriptPubKey"]["hex"] value = tx["vout"][vout]["value"] prevtxs = [{"txid": txid, "vout": vout, "scriptPubKey": scriptPubKey, "redeemScript": mredeem, "amount": value}] node0.generate(1) - outval = value - decimal.Decimal("0.00001000") + outval = value - decimal.Decimal("10.00") rawtx = node2.createrawtransaction( [{"txid": txid, "vout": vout}], [{self.final: outval}]) rawtx2 = node2.signrawtransactionwithkey( rawtx, self.priv[0:self.nsigs - 1], prevtxs) rawtx3 = node2.signrawtransactionwithkey( rawtx2["hex"], [self.priv[-1]], prevtxs) self.moved += outval tx = node0.sendrawtransaction(rawtx3["hex"], 0) blk = node0.generate(1)[0] assert tx in node0.getblock(blk)["tx"] txinfo = node0.getrawtransaction(tx, True, blk) self.log.info("n/m={}/{} size={}".format(self.nsigs, self.nkeys, txinfo["size"])) if __name__ == '__main__': RpcCreateMultiSigTest().main() diff --git a/test/functional/rpc_estimatefee.py b/test/functional/rpc_estimatefee.py index 010c92a12..f43b193f5 100755 --- a/test/functional/rpc_estimatefee.py +++ b/test/functional/rpc_estimatefee.py @@ -1,49 +1,49 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error class EstimateFeeTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 3 self.extra_args = [ [], - ["-minrelaytxfee=0.001"], - ["-mintxfee=0.00002", "-maxtxfee=0.000025"], + ["-minrelaytxfee=1000"], + ["-mintxfee=20", "-maxtxfee=25"], ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): default_node = self.nodes[0] diff_relay_fee_node = self.nodes[1] diff_tx_fee_node = self.nodes[2] for i in range(5): self.nodes[0].generate(1) # estimatefee is 0.00001 by default, regardless of block contents - assert_equal(default_node.estimatefee(), Decimal('0.00001')) + assert_equal(default_node.estimatefee(), Decimal('10.00')) # estimatefee may be different for nodes that set it in their # config - assert_equal(diff_relay_fee_node.estimatefee(), Decimal('0.001')) + assert_equal(diff_relay_fee_node.estimatefee(), Decimal('1000.00')) # Check the reasonableness of settxfee assert_raises_rpc_error(-8, "txfee cannot be less than min relay tx fee", - diff_tx_fee_node.settxfee, Decimal('0.000005')) + diff_tx_fee_node.settxfee, Decimal('5.00')) assert_raises_rpc_error(-8, "txfee cannot be less than wallet min fee", - diff_tx_fee_node.settxfee, Decimal('0.000015')) + diff_tx_fee_node.settxfee, Decimal('15.00')) assert_raises_rpc_error(-8, "txfee cannot be more than wallet max tx fee", - diff_tx_fee_node.settxfee, Decimal('0.00003')) + diff_tx_fee_node.settxfee, Decimal('30.00')) if __name__ == '__main__': EstimateFeeTest().main() diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index cbfeeb8c0..064407756 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -1,863 +1,864 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. from decimal import Decimal from test_framework.messages import CTransaction, FromHex from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_fee_amount, assert_greater_than, assert_greater_than_or_equal, assert_raises_rpc_error, connect_nodes, find_vout_for_address, ) def get_unspent(listunspent, amount): for utx in listunspent: if utx['amount'] == amount: return utx raise AssertionError( 'Could not find unspent with amount={}'.format(amount)) class RawTransactionsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.setup_clean_chain = True # This test isn't testing tx relay. Set whitelist on the peers for # instant tx relay. self.extra_args = [['-whitelist=noban@127.0.0.1']] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() def setup_network(self): self.setup_nodes() connect_nodes(self.nodes[0], self.nodes[1]) connect_nodes(self.nodes[1], self.nodes[2]) connect_nodes(self.nodes[0], self.nodes[2]) connect_nodes(self.nodes[0], self.nodes[3]) def run_test(self): self.log.info("Connect nodes, set fees, generate blocks, and sync") self.min_relay_tx_fee = self.nodes[0].getnetworkinfo()['relayfee'] # This test is not meant to test fee estimation and we'd like # to be sure all txs are sent at a consistent desired feerate for node in self.nodes: node.settxfee(self.min_relay_tx_fee) # if the fee's positive delta is higher than this value tests will fail, # neg. delta always fail the tests. # The size of the signature of every input may be at most 2 bytes larger # than a minimum sized signature. # = 2 bytes * minRelayTxFeePerByte self.fee_tolerance = 2 * self.min_relay_tx_fee / 1000 self.nodes[2].generate(1) self.sync_all() self.nodes[0].generate(121) self.sync_all() self.test_change_position() self.test_simple() self.test_simple_two_coins() self.test_simple_two_outputs() self.test_change() self.test_no_change() self.test_invalid_option() self.test_invalid_change_address() self.test_valid_change_address() self.test_coin_selection() self.test_two_vin() self.test_two_vin_two_vout() self.test_invalid_input() self.test_fee_p2pkh() self.test_fee_p2pkh_multi_out() self.test_fee_p2sh() self.test_fee_4of5() self.test_spend_2of2() self.test_locked_wallet() self.test_many_inputs_fee() self.test_many_inputs_send() self.test_op_return() self.test_watchonly() self.test_all_watched_funds() self.test_option_feerate() self.test_address_reuse() self.test_option_subtract_fee_from_outputs() self.test_subtract_fee_with_presets() def test_change_position(self): """Ensure setting changePosition in fundraw with an exact match is handled properly.""" self.log.info("Test fundrawtxn changePosition option") rawmatch = self.nodes[2].createrawtransaction( - [], {self.nodes[2].getnewaddress(): 50}) + [], {self.nodes[2].getnewaddress(): 50000000}) rawmatch = self.nodes[2].fundrawtransaction( rawmatch, {"changePosition": 1, "subtractFeeFromOutputs": [0]}) assert_equal(rawmatch["changepos"], -1) watchonly_address = self.nodes[0].getnewaddress() watchonly_pubkey = self.nodes[0].getaddressinfo(watchonly_address)[ "pubkey"] - self.watchonly_amount = Decimal(200) + self.watchonly_amount = Decimal(200000000) self.nodes[3].importpubkey(watchonly_pubkey, "", True) self.watchonly_txid = self.nodes[0].sendtoaddress( watchonly_address, self.watchonly_amount) # Lock UTXO so nodes[0] doesn't accidentally spend it self.watchonly_vout = find_vout_for_address( self.nodes[0], self.watchonly_txid, watchonly_address) self.nodes[0].lockunspent( False, [{"txid": self.watchonly_txid, "vout": self.watchonly_vout}]) self.nodes[0].sendtoaddress( self.nodes[3].getnewaddress(), self.watchonly_amount / 10) - self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.5) - self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0) - self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 5.0) + self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1500000) + self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1000000) + self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 5000000) self.nodes[0].generate(1) self.sync_all() def test_simple(self): self.log.info("Test fundrawtxn") inputs = [] - outputs = {self.nodes[0].getnewaddress(): 1.0} + outputs = {self.nodes[0].getnewaddress(): 1000000} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawTx) rawtxfund = self.nodes[2].fundrawtransaction(rawTx) dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) # test that we have enough inputs assert len(dec_tx['vin']) > 0 def test_simple_two_coins(self): self.log.info("Test fundrawtxn with 2 coins") inputs = [] - outputs = {self.nodes[0].getnewaddress(): 2.2} + outputs = {self.nodes[0].getnewaddress(): 2200000} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawTx) rawtxfund = self.nodes[2].fundrawtransaction(rawTx) dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) # test if we have enough inputs assert len(dec_tx['vin']) > 0 assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') def test_simple_two_outputs(self): self.log.info("Test fundrawtxn with 2 outputs") inputs = [] outputs = { - self.nodes[0].getnewaddress(): 2.6, self.nodes[1].getnewaddress(): 2.5} + self.nodes[0].getnewaddress(): 2600000, self.nodes[1].getnewaddress(): 2500000} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawTx) rawtxfund = self.nodes[2].fundrawtransaction(rawTx) dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) totalOut = 0 for out in dec_tx['vout']: totalOut += out['value'] assert len(dec_tx['vin']) > 0 assert_equal(dec_tx['vin'][0]['scriptSig']['hex'], '') def test_change(self): self.log.info("Test fundrawtxn with a vin > required amount") - utx = get_unspent(self.nodes[2].listunspent(), 5) + utx = get_unspent(self.nodes[2].listunspent(), 5000000) inputs = [{'txid': utx['txid'], 'vout': utx['vout']}] - outputs = {self.nodes[0].getnewaddress(): 1.0} + outputs = {self.nodes[0].getnewaddress(): 1000000} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawTx) assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) rawtxfund = self.nodes[2].fundrawtransaction(rawTx) fee = rawtxfund['fee'] # Use the same fee for the next tx self.test_no_change_fee = fee dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) totalOut = 0 for out in dec_tx['vout']: totalOut += out['value'] # compare vin total and totalout+fee assert_equal(fee + totalOut, utx['amount']) def test_no_change(self): self.log.info("Test fundrawtxn not having a change output") - utx = get_unspent(self.nodes[2].listunspent(), 5) + utx = get_unspent(self.nodes[2].listunspent(), 5000000) inputs = [{'txid': utx['txid'], 'vout': utx['vout']}] outputs = { - self.nodes[0].getnewaddress(): Decimal(5.0) - + self.nodes[0].getnewaddress(): Decimal(5000000) - self.test_no_change_fee - self.fee_tolerance} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawTx) assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) rawtxfund = self.nodes[2].fundrawtransaction(rawTx) fee = rawtxfund['fee'] dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) totalOut = 0 for out in dec_tx['vout']: totalOut += out['value'] assert_equal(rawtxfund['changepos'], -1) # compare vin total and totalout+fee assert_equal(fee + totalOut, utx['amount']) def test_invalid_option(self): self.log.info("Test fundrawtxn with an invalid option") - utx = get_unspent(self.nodes[2].listunspent(), 5) + utx = get_unspent(self.nodes[2].listunspent(), 5000000) inputs = [{'txid': utx['txid'], 'vout': utx['vout']}] - outputs = {self.nodes[0].getnewaddress(): Decimal(4.0)} + outputs = {self.nodes[0].getnewaddress(): Decimal(4000000)} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawTx) assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) assert_raises_rpc_error(-3, "Unexpected key foo", self.nodes[ 2].fundrawtransaction, rawTx, {'foo': 'bar'}) # reserveChangeKey was deprecated and is now removed assert_raises_rpc_error(-3, "Unexpected key reserveChangeKey", lambda: self.nodes[2].fundrawtransaction(hexstring=rawTx, options={'reserveChangeKey': True})) def test_invalid_change_address(self): self.log.info("Test fundrawtxn with an invalid change address") - utx = get_unspent(self.nodes[2].listunspent(), 5) + utx = get_unspent(self.nodes[2].listunspent(), 5000000) inputs = [{'txid': utx['txid'], 'vout': utx['vout']}] - outputs = {self.nodes[0].getnewaddress(): Decimal(4.0)} + outputs = {self.nodes[0].getnewaddress(): Decimal(4000000)} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawTx) assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) assert_raises_rpc_error( -5, "changeAddress must be a valid bitcoin address", self.nodes[2].fundrawtransaction, rawTx, {'changeAddress': 'foobar'}) def test_valid_change_address(self): self.log.info("Test fundrawtxn with a provided change address") - utx = get_unspent(self.nodes[2].listunspent(), 5) + utx = get_unspent(self.nodes[2].listunspent(), 5000000) inputs = [{'txid': utx['txid'], 'vout': utx['vout']}] - outputs = {self.nodes[0].getnewaddress(): Decimal(4.0)} + outputs = {self.nodes[0].getnewaddress(): Decimal(4000000)} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawTx) assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) change = self.nodes[2].getnewaddress() assert_raises_rpc_error(-8, "changePosition out of bounds", self.nodes[ 2].fundrawtransaction, rawTx, {'changeAddress': change, 'changePosition': 2}) rawtxfund = self.nodes[2].fundrawtransaction( rawTx, {'changeAddress': change, 'changePosition': 0}) dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) out = dec_tx['vout'][0] assert_equal(change, out['scriptPubKey']['addresses'][0]) def test_coin_selection(self): self.log.info("Test fundrawtxn with a vin < required amount") - utx = get_unspent(self.nodes[2].listunspent(), 1) + utx = get_unspent(self.nodes[2].listunspent(), 1000000) inputs = [{'txid': utx['txid'], 'vout': utx['vout']}] - outputs = {self.nodes[0].getnewaddress(): 1.0} + outputs = {self.nodes[0].getnewaddress(): 1000000} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) # 4-byte version + 1-byte vin count + 36-byte prevout then script_len rawTx = rawTx[:82] + "0100" + rawTx[84:] dec_tx = self.nodes[2].decoderawtransaction(rawTx) assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) assert_equal("00", dec_tx['vin'][0]['scriptSig']['hex']) # Should fail without add_inputs: assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawTx, {"add_inputs": False}) # add_inputs is enabled by default rawtxfund = self.nodes[2].fundrawtransaction(rawTx) dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) totalOut = 0 matchingOuts = 0 for i, out in enumerate(dec_tx['vout']): totalOut += out['value'] if out['scriptPubKey']['addresses'][0] in outputs: matchingOuts += 1 else: assert_equal(i, rawtxfund['changepos']) assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) assert_equal("00", dec_tx['vin'][0]['scriptSig']['hex']) assert_equal(matchingOuts, 1) assert_equal(len(dec_tx['vout']), 2) def test_two_vin(self): self.log.info("Test fundrawtxn with 2 vins") - utx = get_unspent(self.nodes[2].listunspent(), 1) - utx2 = get_unspent(self.nodes[2].listunspent(), 5) + utx = get_unspent(self.nodes[2].listunspent(), 1000000) + utx2 = get_unspent(self.nodes[2].listunspent(), 5000000) inputs = [{'txid': utx['txid'], 'vout': utx['vout']}, {'txid': utx2['txid'], 'vout': utx2['vout']}] - outputs = {self.nodes[0].getnewaddress(): 6.0} + outputs = {self.nodes[0].getnewaddress(): 6000000} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawTx) assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) # Should fail without add_inputs: assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawTx, {"add_inputs": False}) rawtxfund = self.nodes[2].fundrawtransaction( rawTx, {"add_inputs": True}) dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) totalOut = 0 matchingOuts = 0 for out in dec_tx['vout']: totalOut += out['value'] if out['scriptPubKey']['addresses'][0] in outputs: matchingOuts += 1 assert_equal(matchingOuts, 1) assert_equal(len(dec_tx['vout']), 2) matchingIns = 0 for vinOut in dec_tx['vin']: for vinIn in inputs: if vinIn['txid'] == vinOut['txid']: matchingIns += 1 # we now must see two vins identical to vins given as params assert_equal(matchingIns, 2) def test_two_vin_two_vout(self): self.log.info("Test fundrawtxn with 2 vins and 2 vouts") - utx = get_unspent(self.nodes[2].listunspent(), 1) - utx2 = get_unspent(self.nodes[2].listunspent(), 5) + utx = get_unspent(self.nodes[2].listunspent(), 1000000) + utx2 = get_unspent(self.nodes[2].listunspent(), 5000000) inputs = [{'txid': utx['txid'], 'vout': utx['vout']}, {'txid': utx2['txid'], 'vout': utx2['vout']}] outputs = { - self.nodes[0].getnewaddress(): 6.0, self.nodes[0].getnewaddress(): 1.0} + self.nodes[0].getnewaddress(): 6000000, self.nodes[0].getnewaddress(): 1000000} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) dec_tx = self.nodes[2].decoderawtransaction(rawTx) assert_equal(utx['txid'], dec_tx['vin'][0]['txid']) # Should fail without add_inputs: assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawTx, {"add_inputs": False}) rawtxfund = self.nodes[2].fundrawtransaction( rawTx, {"add_inputs": True}) dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) totalOut = 0 matchingOuts = 0 for out in dec_tx['vout']: totalOut += out['value'] if out['scriptPubKey']['addresses'][0] in outputs: matchingOuts += 1 assert_equal(matchingOuts, 2) assert_equal(len(dec_tx['vout']), 3) def test_invalid_input(self): self.log.info("Test fundrawtxn with an invalid vin") inputs = [ {'txid': "1c7f966dab21119bac53213a2bc7532bff1fa844c124fd750a7d0b1332440bd1", 'vout': 0}] - outputs = {self.nodes[0].getnewaddress(): 1.0} + outputs = {self.nodes[0].getnewaddress(): 1000000} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) assert_raises_rpc_error( -4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawTx) def test_fee_p2pkh(self): """Compare fee of a standard pubkeyhash transaction.""" self.log.info("Test fundrawtxn p2pkh fee") inputs = [] - outputs = {self.nodes[1].getnewaddress(): 1.1} + outputs = {self.nodes[1].getnewaddress(): 1100000} rawTx = self.nodes[0].createrawtransaction(inputs, outputs) fundedTx = self.nodes[0].fundrawtransaction(rawTx) # Create same transaction over sendtoaddress. - txId = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1.1) + txId = self.nodes[0].sendtoaddress( + self.nodes[1].getnewaddress(), 1100000) signedFee = self.nodes[0].getrawmempool(True)[txId]['fee'] # Compare fee. feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) assert feeDelta >= 0 and feeDelta <= self.fee_tolerance def test_fee_p2pkh_multi_out(self): """Compare fee of a standard pubkeyhash transaction with multiple outputs.""" self.log.info("Test fundrawtxn p2pkh fee with multiple outputs") inputs = [] outputs = { - self.nodes[1].getnewaddress(): 1.1, - self.nodes[1].getnewaddress(): 1.2, - self.nodes[1].getnewaddress(): 0.1, - self.nodes[1].getnewaddress(): 1.3, - self.nodes[1].getnewaddress(): 0.2, - self.nodes[1].getnewaddress(): 0.3, + self.nodes[1].getnewaddress(): 1100000, + self.nodes[1].getnewaddress(): 1200000, + self.nodes[1].getnewaddress(): 100000, + self.nodes[1].getnewaddress(): 1300000, + self.nodes[1].getnewaddress(): 200000, + self.nodes[1].getnewaddress(): 300000, } rawtx = self.nodes[0].createrawtransaction(inputs, outputs) fundedTx = self.nodes[0].fundrawtransaction(rawtx) # Create same transaction over sendtoaddress. txId = self.nodes[0].sendmany("", outputs) signedFee = self.nodes[0].getrawmempool(True)[txId]['fee'] # Compare fee. feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) assert feeDelta >= 0 and feeDelta <= self.fee_tolerance def test_fee_p2sh(self): """Compare fee of a 2-of-2 multisig p2sh transaction.""" # Create 2-of-2 addr. addr1 = self.nodes[1].getnewaddress() addr2 = self.nodes[1].getnewaddress() addr1Obj = self.nodes[1].getaddressinfo(addr1) addr2Obj = self.nodes[1].getaddressinfo(addr2) mSigObj = self.nodes[1].addmultisigaddress( 2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address'] inputs = [] - outputs = {mSigObj: 1.1} + outputs = {mSigObj: 1100000} rawTx = self.nodes[0].createrawtransaction(inputs, outputs) fundedTx = self.nodes[0].fundrawtransaction(rawTx) # Create same transaction over sendtoaddress. - txId = self.nodes[0].sendtoaddress(mSigObj, 1.1) + txId = self.nodes[0].sendtoaddress(mSigObj, 1100000) signedFee = self.nodes[0].getrawmempool(True)[txId]['fee'] # Compare fee. feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) assert feeDelta >= 0 and feeDelta <= self.fee_tolerance def test_fee_4of5(self): """Compare fee of a standard pubkeyhash transaction.""" self.log.info("Test fundrawtxn fee with 4-of-5 addresses") # Create 4-of-5 addr. addr1 = self.nodes[1].getnewaddress() addr2 = self.nodes[1].getnewaddress() addr3 = self.nodes[1].getnewaddress() addr4 = self.nodes[1].getnewaddress() addr5 = self.nodes[1].getnewaddress() addr1Obj = self.nodes[1].getaddressinfo(addr1) addr2Obj = self.nodes[1].getaddressinfo(addr2) addr3Obj = self.nodes[1].getaddressinfo(addr3) addr4Obj = self.nodes[1].getaddressinfo(addr4) addr5Obj = self.nodes[1].getaddressinfo(addr5) mSigObj = self.nodes[1].addmultisigaddress( 4, [ addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey'], addr4Obj['pubkey'], addr5Obj['pubkey'], ] )['address'] inputs = [] - outputs = {mSigObj: 1.1} + outputs = {mSigObj: 1100000} rawTx = self.nodes[0].createrawtransaction(inputs, outputs) fundedTx = self.nodes[0].fundrawtransaction(rawTx) # Create same transaction over sendtoaddress. - txId = self.nodes[0].sendtoaddress(mSigObj, 1.1) + txId = self.nodes[0].sendtoaddress(mSigObj, 1100000) signedFee = self.nodes[0].getrawmempool(True)[txId]['fee'] # Compare fee. feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) assert feeDelta >= 0 and feeDelta <= self.fee_tolerance def test_spend_2of2(self): """Spend a 2-of-2 multisig transaction over fundraw.""" self.log.info("Test fundrawtxn spending 2-of-2 multisig") # Create 2-of-2 addr. addr1 = self.nodes[2].getnewaddress() addr2 = self.nodes[2].getnewaddress() addr1Obj = self.nodes[2].getaddressinfo(addr1) addr2Obj = self.nodes[2].getaddressinfo(addr2) mSigObj = self.nodes[2].addmultisigaddress( 2, [ addr1Obj['pubkey'], addr2Obj['pubkey'], ] )['address'] # Send 1.2 BCH to msig addr. - self.nodes[0].sendtoaddress(mSigObj, 1.2) + self.nodes[0].sendtoaddress(mSigObj, 1200000) self.nodes[0].generate(1) self.sync_all() oldBalance = self.nodes[1].getbalance() inputs = [] - outputs = {self.nodes[1].getnewaddress(): 1.1} + outputs = {self.nodes[1].getnewaddress(): 1100000} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) fundedTx = self.nodes[2].fundrawtransaction(rawTx) signedTx = self.nodes[2].signrawtransactionwithwallet(fundedTx['hex']) self.nodes[2].sendrawtransaction(signedTx['hex']) self.nodes[2].generate(1) self.sync_all() # Make sure funds are received at node1. assert_equal( - oldBalance + Decimal('1.10000000'), self.nodes[1].getbalance()) + oldBalance + Decimal('1100000.00'), self.nodes[1].getbalance()) def test_locked_wallet(self): self.log.info("Test fundrawtxn with locked wallet") self.nodes[1].encryptwallet("test") # Drain the keypool. self.nodes[1].getnewaddress() self.nodes[1].getrawchangeaddress() inputs = [] - outputs = {self.nodes[0].getnewaddress(): 1.09999700} + outputs = {self.nodes[0].getnewaddress(): 1099997.00} rawtx = self.nodes[1].createrawtransaction(inputs, outputs) # fund a transaction that does not require a new key for the change # output self.nodes[1].fundrawtransaction(rawtx) # fund a transaction that requires a new key for the change output # creating the key must be impossible because the wallet is locked - outputs = {self.nodes[0].getnewaddress(): 1.1} + outputs = {self.nodes[0].getnewaddress(): 1100000} rawtx = self.nodes[1].createrawtransaction(inputs, outputs) assert_raises_rpc_error( -4, "Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.", self.nodes[1].fundrawtransaction, rawtx) # Refill the keypool. self.nodes[1].walletpassphrase("test", 100) # need to refill the keypool to get an internal change address self.nodes[1].keypoolrefill(8) self.nodes[1].walletlock() assert_raises_rpc_error(-13, "walletpassphrase", self.nodes[ - 1].sendtoaddress, self.nodes[0].getnewaddress(), 1.2) + 1].sendtoaddress, self.nodes[0].getnewaddress(), 1200000) oldBalance = self.nodes[0].getbalance() inputs = [] - outputs = {self.nodes[0].getnewaddress(): 1.1} + outputs = {self.nodes[0].getnewaddress(): 1100000} rawTx = self.nodes[1].createrawtransaction(inputs, outputs) fundedTx = self.nodes[1].fundrawtransaction(rawTx) # Now we need to unlock. self.nodes[1].walletpassphrase("test", 600) signedTx = self.nodes[1].signrawtransactionwithwallet(fundedTx['hex']) self.nodes[1].sendrawtransaction(signedTx['hex']) self.nodes[1].generate(1) self.sync_all() # Make sure funds are received at node1. assert_equal( - oldBalance + Decimal('51.10000000'), self.nodes[0].getbalance()) + oldBalance + Decimal('51100000.00'), self.nodes[0].getbalance()) def test_many_inputs_fee(self): """Multiple (~19) inputs tx test | Compare fee.""" self.log.info("Test fundrawtxn fee with many inputs") # Empty node1, send some small coins from node0 to node1. self.nodes[1].sendtoaddress( self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True) self.nodes[1].generate(1) self.sync_all() for _ in range(0, 20): - self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.01) + self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 10000) self.nodes[0].generate(1) self.sync_all() # Fund a tx with ~20 small inputs. inputs = [] outputs = { - self.nodes[0].getnewaddress(): 0.15, self.nodes[0].getnewaddress(): 0.04} + self.nodes[0].getnewaddress(): 150000, self.nodes[0].getnewaddress(): 40000} rawTx = self.nodes[1].createrawtransaction(inputs, outputs) fundedTx = self.nodes[1].fundrawtransaction(rawTx) # Create same transaction over sendtoaddress. txId = self.nodes[1].sendmany("", outputs) signedFee = self.nodes[1].getrawmempool(True)[txId]['fee'] # Compare fee. feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee) # ~19 inputs assert feeDelta >= 0 and feeDelta <= self.fee_tolerance * 19 def test_many_inputs_send(self): """Multiple (~19) inputs tx test | sign/send.""" self.log.info("Test fundrawtxn sign+send with many inputs") # Again, empty node1, send some small coins from node0 to node1. self.nodes[1].sendtoaddress( self.nodes[0].getnewaddress(), self.nodes[1].getbalance(), "", "", True) self.nodes[1].generate(1) self.sync_all() for _ in range(0, 20): - self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.01) + self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 10000) self.nodes[0].generate(1) self.sync_all() # Fund a tx with ~20 small inputs. oldBalance = self.nodes[0].getbalance() inputs = [] outputs = { - self.nodes[0].getnewaddress(): 0.15, self.nodes[0].getnewaddress(): 0.04} + self.nodes[0].getnewaddress(): 150000, self.nodes[0].getnewaddress(): 40000} rawTx = self.nodes[1].createrawtransaction(inputs, outputs) fundedTx = self.nodes[1].fundrawtransaction(rawTx) fundedAndSignedTx = self.nodes[1].signrawtransactionwithwallet( fundedTx['hex']) self.nodes[1].sendrawtransaction(fundedAndSignedTx['hex']) self.nodes[1].generate(1) self.sync_all() - assert_equal(oldBalance + Decimal('50.19000000'), + assert_equal(oldBalance + Decimal('50190000.00'), self.nodes[0].getbalance()) # 0.19+block reward def test_op_return(self): self.log.info("Test fundrawtxn with OP_RETURN and no vin") rawTx = "0100000000010000000000000000066a047465737400000000" dec_tx = self.nodes[2].decoderawtransaction(rawTx) assert_equal(len(dec_tx['vin']), 0) assert_equal(len(dec_tx['vout']), 1) rawtxfund = self.nodes[2].fundrawtransaction(rawTx) dec_tx = self.nodes[2].decoderawtransaction(rawtxfund['hex']) # at least one vin assert_greater_than(len(dec_tx['vin']), 0) # one change output added assert_equal(len(dec_tx['vout']), 2) def test_watchonly(self): self.log.info("Test fundrawtxn using only watchonly") inputs = [] outputs = {self.nodes[2].getnewaddress(): self.watchonly_amount / 2} rawTx = self.nodes[3].createrawtransaction(inputs, outputs) result = self.nodes[3].fundrawtransaction( rawTx, {'includeWatching': True}) res_dec = self.nodes[0].decoderawtransaction(result["hex"]) assert_equal(len(res_dec["vin"]), 1) assert_equal(res_dec["vin"][0]["txid"], self.watchonly_txid) assert "fee" in result.keys() assert_greater_than(result["changepos"], -1) def test_all_watched_funds(self): self.log.info("Test fundrawtxn using entirety of watched funds") inputs = [] outputs = {self.nodes[2].getnewaddress(): self.watchonly_amount} rawTx = self.nodes[3].createrawtransaction(inputs, outputs) # Backward compatibility test (2nd param is includeWatching). result = self.nodes[3].fundrawtransaction(rawTx, True) res_dec = self.nodes[0].decoderawtransaction(result["hex"]) assert_equal(len(res_dec["vin"]), 2) assert res_dec["vin"][0]["txid"] == self.watchonly_txid or res_dec[ "vin"][1]["txid"] == self.watchonly_txid assert_greater_than(result["fee"], 0) assert_greater_than(result["changepos"], -1) assert_equal(result["fee"] + res_dec["vout"][ result["changepos"]]["value"], self.watchonly_amount / 10) signedtx = self.nodes[3].signrawtransactionwithwallet(result["hex"]) assert not signedtx["complete"] signedtx = self.nodes[0].signrawtransactionwithwallet(signedtx["hex"]) assert signedtx["complete"] self.nodes[0].sendrawtransaction(signedtx["hex"]) self.nodes[0].generate(1) self.sync_all() def test_option_feerate(self): self.log.info("Test fundrawtxn feeRate option") # Make sure there is exactly one input so coin selection can't skew the # result. assert_equal(len(self.nodes[3].listunspent(1)), 1) inputs = [] - outputs = {self.nodes[3].getnewaddress(): 1} + outputs = {self.nodes[3].getnewaddress(): 1000000} rawTx = self.nodes[3].createrawtransaction(inputs, outputs) # uses self.min_relay_tx_fee (set by settxfee) result = self.nodes[3].fundrawtransaction(rawTx) result2 = self.nodes[3].fundrawtransaction( rawTx, {"feeRate": 2 * self.min_relay_tx_fee}) result_fee_rate = result['fee'] * 1000 / \ FromHex(CTransaction(), result['hex']).billable_size() assert_fee_amount( result2['fee'], FromHex(CTransaction(), result2['hex']).billable_size(), 2 * result_fee_rate) result3 = self.nodes[3].fundrawtransaction( rawTx, {"feeRate": 10 * self.min_relay_tx_fee}) assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[3].fundrawtransaction, rawTx, - {"feeRate": 1}) + {"feeRate": 1000000}) # allow this transaction to be underfunded by 10 bytes. This is due # to the first transaction possibly being overfunded by up to .9 # satoshi due to fee ceilings being used. assert_fee_amount( result3['fee'], FromHex(CTransaction(), result3['hex']).billable_size(), 10 * result_fee_rate, 10) def test_address_reuse(self): """Test no address reuse occurs.""" self.log.info("Test fundrawtxn does not reuse addresses") rawTx = self.nodes[3].createrawtransaction( - inputs=[], outputs={self.nodes[3].getnewaddress(): 1}) + inputs=[], outputs={self.nodes[3].getnewaddress(): 1000000}) result3 = self.nodes[3].fundrawtransaction(rawTx) res_dec = self.nodes[0].decoderawtransaction(result3["hex"]) changeaddress = "" for out in res_dec['vout']: - if out['value'] > 1.0: + if out['value'] > 1000000.0: changeaddress += out['scriptPubKey']['addresses'][0] assert changeaddress != "" nextaddr = self.nodes[3].getnewaddress() # Now the change address key should be removed from the keypool. assert changeaddress != nextaddr def test_option_subtract_fee_from_outputs(self): self.log.info("Test fundrawtxn subtractFeeFromOutputs option") # Make sure there is exactly one input so coin selection can't skew the # result. assert_equal(len(self.nodes[3].listunspent(1)), 1) inputs = [] - outputs = {self.nodes[2].getnewaddress(): 1} + outputs = {self.nodes[2].getnewaddress(): 1000000} rawTx = self.nodes[3].createrawtransaction(inputs, outputs) # uses self.min_relay_tx_fee (set by settxfee) result = [self.nodes[3].fundrawtransaction(rawTx), # empty subtraction list self.nodes[3].fundrawtransaction( rawTx, {"subtractFeeFromOutputs": []}), # uses self.min_relay_tx_fee (set by settxfee) self.nodes[3].fundrawtransaction( rawTx, {"subtractFeeFromOutputs": [0]}), self.nodes[3].fundrawtransaction( rawTx, {"feeRate": 2 * self.min_relay_tx_fee}), self.nodes[3].fundrawtransaction( rawTx, {"feeRate": 2 * self.min_relay_tx_fee, "subtractFeeFromOutputs": [0]}), ] dec_tx = [self.nodes[3].decoderawtransaction(tx_['hex']) for tx_ in result] output = [d['vout'][1 - r['changepos']]['value'] for d, r in zip(dec_tx, result)] change = [d['vout'][r['changepos']]['value'] for d, r in zip(dec_tx, result)] assert_equal(result[0]['fee'], result[1]['fee'], result[2]['fee']) assert_equal(result[3]['fee'], result[4]['fee']) assert_equal(change[0], change[1]) assert_equal(output[0], output[1]) assert_equal(output[0], output[2] + result[2]['fee']) assert_equal(change[0] + result[0]['fee'], change[2]) assert_equal(output[3], output[4] + result[4]['fee']) assert_equal(change[3] + result[3]['fee'], change[4]) inputs = [] outputs = { - self.nodes[2].getnewaddress(): value for value in (1.0, 1.1, 1.2, 1.3)} + self.nodes[2].getnewaddress(): value for value in (1000000.0, 1100000.0, 1200000.0, 1300000.0)} rawTx = self.nodes[3].createrawtransaction(inputs, outputs) # Split the fee between outputs 0, 2, and 3, but not output 1 result = [self.nodes[3].fundrawtransaction(rawTx), self.nodes[3].fundrawtransaction(rawTx, {"subtractFeeFromOutputs": [0, 2, 3]})] dec_tx = [self.nodes[3].decoderawtransaction(result[0]['hex']), self.nodes[3].decoderawtransaction(result[1]['hex'])] # Nested list of non-change output amounts for each transaction. output = [[out['value'] for i, out in enumerate(d['vout']) if i != r['changepos']] for d, r in zip(dec_tx, result)] # List of differences in output amounts between normal and subtractFee # transactions. share = [o0 - o1 for o0, o1 in zip(output[0], output[1])] # Output 1 is the same in both transactions. assert_equal(share[1], 0) # The other 3 outputs are smaller as a result of # subtractFeeFromOutputs. assert_greater_than(share[0], 0) assert_greater_than(share[2], 0) assert_greater_than(share[3], 0) # Outputs 2 and 3 take the same share of the fee. assert_equal(share[2], share[3]) # Output 0 takes at least as much share of the fee, and no more than 2 # satoshis more, than outputs 2 and 3. assert_greater_than_or_equal(share[0], share[2]) assert_greater_than_or_equal(share[2] + Decimal(2e-8), share[0]) # The fee is the same in both transactions. assert_equal(result[0]['fee'], result[1]['fee']) # The total subtracted from the outputs is equal to the fee. assert_equal(share[0] + share[2] + share[3], result[0]['fee']) def test_subtract_fee_with_presets(self): self.log.info( "Test fundrawtxn subtract fee from outputs with preset inputs that are sufficient") addr = self.nodes[0].getnewaddress() - txid = self.nodes[0].sendtoaddress(addr, 10) + txid = self.nodes[0].sendtoaddress(addr, 10000000) vout = find_vout_for_address(self.nodes[0], txid, addr) rawtx = self.nodes[0].createrawtransaction([{'txid': txid, 'vout': vout}], [ - {self.nodes[0].getnewaddress(): 5}]) + {self.nodes[0].getnewaddress(): 5000000}]) fundedtx = self.nodes[0].fundrawtransaction( rawtx, {'subtractFeeFromOutputs': [0]}) signedtx = self.nodes[0].signrawtransactionwithwallet(fundedtx['hex']) self.nodes[0].sendrawtransaction(signedtx['hex']) if __name__ == '__main__': RawTransactionsTest().main() diff --git a/test/functional/rpc_generateblock.py b/test/functional/rpc_generateblock.py index 5208220a4..21f576775 100755 --- a/test/functional/rpc_generateblock.py +++ b/test/functional/rpc_generateblock.py @@ -1,146 +1,146 @@ #!/usr/bin/env python3 # Copyright (c) 2020 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. '''Test generateblock rpc. ''' from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, ) class GenerateBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): node = self.nodes[0] self.log.info('Generate an empty block to address') address = node.getnewaddress() hash = node.generateblock(output=address, transactions=[])['hash'] block = node.getblock(blockhash=hash, verbose=2) assert_equal(len(block['tx']), 1) assert_equal(block['tx'][0]['vout'][0] ['scriptPubKey']['addresses'][0], address) self.log.info('Generate an empty block to a descriptor') hash = node.generateblock('addr(' + address + ')', [])['hash'] block = node.getblock(blockhash=hash, verbosity=2) assert_equal(len(block['tx']), 1) assert_equal(block['tx'][0]['vout'][0] ['scriptPubKey']['addresses'][0], address) self.log.info( 'Generate an empty block to a combo descriptor with compressed pubkey') combo_key = '0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798' combo_address = 'ecregtest:qp63uahgrxged4z5jswyt5dn5v3lzsem6c49crxznd' hash = node.generateblock('combo(' + combo_key + ')', [])['hash'] block = node.getblock(hash, 2) assert_equal(len(block['tx']), 1) assert_equal(block['tx'][0]['vout'][0]['scriptPubKey'] ['addresses'][0], combo_address) self.log.info( 'Generate an empty block to a combo descriptor with uncompressed pubkey') combo_key = '0408ef68c46d20596cc3f6ddf7c8794f71913add807f1dc55949fa805d764d191c0b7ce6894c126fce0babc6663042f3dde9b0cf76467ea315514e5a6731149c67' combo_address = 'ecregtest:qqmagqc48ln8p7zk6ez2h64amcamr86qwqku5075py' hash = node.generateblock('combo(' + combo_key + ')', [])['hash'] block = node.getblock(hash, 2) assert_equal(len(block['tx']), 1) assert_equal(block['tx'][0]['vout'][0]['scriptPubKey'] ['addresses'][0], combo_address) # Generate 110 blocks to spend node.generatetoaddress(110, address) # Generate some extra mempool transactions to verify they don't get # mined for i in range(10): - node.sendtoaddress(address, 0.001) + node.sendtoaddress(address, 1000) self.log.info('Generate block with txid') - txid = node.sendtoaddress(address, 1) + txid = node.sendtoaddress(address, 1000000) hash = node.generateblock(address, [txid])['hash'] block = node.getblock(hash, 1) assert_equal(len(block['tx']), 2) assert_equal(block['tx'][1], txid) self.log.info('Generate block with raw tx') utxos = node.listunspent(addresses=[address]) raw = node.createrawtransaction( - [{'txid': utxos[0]['txid'], 'vout':utxos[0]['vout']}], [{address: 1}]) + [{'txid': utxos[0]['txid'], 'vout':utxos[0]['vout']}], [{address: 1000000}]) signed_raw = node.signrawtransactionwithwallet(raw)['hex'] hash = node.generateblock(address, [signed_raw])['hash'] block = node.getblock(hash, 1) assert_equal(len(block['tx']), 2) txid = block['tx'][1] assert_equal(node.gettransaction(txid)['hex'], signed_raw) self.log.info('Fail to generate block with out of order txs') raw1 = node.createrawtransaction( - [{'txid': txid, 'vout': 0}], [{address: 0.9999}]) + [{'txid': txid, 'vout': 0}], [{address: 999900}]) signed_raw1 = node.signrawtransactionwithwallet(raw1)['hex'] txid1 = node.sendrawtransaction(signed_raw1) raw2 = node.createrawtransaction( - [{'txid': txid1, 'vout': 0}], [{address: 0.999}]) + [{'txid': txid1, 'vout': 0}], [{address: 999000}]) signed_raw2 = node.signrawtransactionwithwallet(raw2)['hex'] txid2 = node.sendrawtransaction(signed_raw2) # Reversed CTOR txids = sorted([txid1, txid2], reverse=True) assert_raises_rpc_error(-25, 'TestBlockValidity failed: tx-ordering', node.generateblock, address, txids) self.log.info('Fail to generate block with txid not in mempool') missing_txid = '0000000000000000000000000000000000000000000000000000000000000000' assert_raises_rpc_error(-5, 'Transaction ' + missing_txid + ' not in mempool.', node.generateblock, address, [missing_txid]) self.log.info('Fail to generate block with invalid raw tx') invalid_raw_tx = '0000' assert_raises_rpc_error(-22, 'Transaction decode failed for ' + invalid_raw_tx, node.generateblock, address, [invalid_raw_tx]) self.log.info('Fail to generate block with invalid address/descriptor') assert_raises_rpc_error(-5, 'Invalid address or descriptor', node.generateblock, '1234', []) self.log.info('Fail to generate block with a ranged descriptor') ranged_descriptor = 'pkh(tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp/0/*)' assert_raises_rpc_error( -8, 'Ranged descriptor not accepted. Maybe pass through deriveaddresses first?', node.generateblock, ranged_descriptor, []) self.log.info( 'Fail to generate block with a descriptor missing a private key') child_descriptor = 'pkh(tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp/0\'/0)' assert_raises_rpc_error(-5, 'Cannot derive script without private keys', node.generateblock, child_descriptor, []) if __name__ == '__main__': GenerateBlockTest().main() diff --git a/test/functional/rpc_getblockstats.py b/test/functional/rpc_getblockstats.py index d47ab4100..a8c3b4fb7 100755 --- a/test/functional/rpc_getblockstats.py +++ b/test/functional/rpc_getblockstats.py @@ -1,200 +1,200 @@ #!/usr/bin/env python3 # Copyright (c) 2017-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. # # Test getblockstats rpc call # import decimal import json import os from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, ) TESTSDIR = os.path.dirname(os.path.realpath(__file__)) def EncodeDecimal(o): if isinstance(o, decimal.Decimal): # json.load will read a quoted float as a string and not convert it back # to decimal, so store the value as unquoted float instead. return float(o) raise TypeError(repr(o) + " is not JSON serializable") class GetblockstatsTest(BitcoinTestFramework): start_height = 101 max_stat_pos = 2 def add_options(self, parser): parser.add_argument('--gen-test-data', dest='gen_test_data', default=False, action='store_true', help='Generate test data') parser.add_argument('--test-data', dest='test_data', default='data/rpc_getblockstats.json', action='store', metavar='FILE', help='Test data file') def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True self.supports_cli = False def get_stats(self): return [self.nodes[0].getblockstats( hash_or_height=self.start_height + i) for i in range(self.max_stat_pos + 1)] def generate_test_data(self, filename): mocktime = 1525107225 self.nodes[0].setmocktime(mocktime) self.nodes[0].generate(101) address = self.nodes[0].get_deterministic_priv_key().address self.nodes[0].sendtoaddress( address=address, - amount=10, + amount=10000000, subtractfeefromamount=True) self.nodes[0].generate(1) self.sync_all() self.nodes[0].sendtoaddress( address=address, - amount=10, + amount=10000000, subtractfeefromamount=True) self.nodes[0].sendtoaddress( address=address, - amount=10, + amount=10000000, subtractfeefromamount=False) - self.nodes[0].settxfee(amount=0.003) + self.nodes[0].settxfee(amount=3000) self.nodes[0].sendtoaddress( address=address, - amount=1, + amount=1000000, subtractfeefromamount=True) self.sync_all() self.nodes[0].generate(1) self.expected_stats = self.get_stats() blocks = [] tip = self.nodes[0].getbestblockhash() blockhash = None height = 0 while tip != blockhash: blockhash = self.nodes[0].getblockhash(height) blocks.append(self.nodes[0].getblock(blockhash, 0)) height += 1 to_dump = { 'blocks': blocks, 'mocktime': int(mocktime), 'stats': self.expected_stats, } with open(filename, 'w', encoding="utf8") as f: json.dump(to_dump, f, sort_keys=True, indent=2, default=EncodeDecimal) def load_test_data(self, filename): with open(filename, 'r', encoding="utf8") as f: d = json.load(f, parse_float=decimal.Decimal) blocks = d['blocks'] mocktime = d['mocktime'] self.expected_stats = d['stats'] self.log.info(self.expected_stats) # Set the timestamps from the file so that the nodes can get out of # Initial Block Download self.nodes[0].setmocktime(mocktime) self.sync_all() for i, b in enumerate(blocks): self.nodes[0].submitblock(b) def run_test(self): test_data = os.path.join(TESTSDIR, self.options.test_data) if self.options.gen_test_data: self.generate_test_data(test_data) else: self.load_test_data(test_data) self.sync_all() stats = self.get_stats() # Make sure all valid statistics are included but nothing else is expected_keys = self.expected_stats[0].keys() assert_equal(set(stats[0].keys()), set(expected_keys)) assert_equal(stats[0]['height'], self.start_height) assert_equal(stats[self.max_stat_pos]['height'], self.start_height + self.max_stat_pos) for i in range(self.max_stat_pos + 1): self.log.info('Checking block {}\n'.format(i)) assert_equal(stats[i], self.expected_stats[i]) # Check selecting block by hash too blockhash = self.expected_stats[i]['blockhash'] stats_by_hash = self.nodes[0].getblockstats( hash_or_height=blockhash) assert_equal(stats_by_hash, self.expected_stats[i]) # Make sure each stat can be queried on its own for stat in expected_keys: for i in range(self.max_stat_pos + 1): result = self.nodes[0].getblockstats( hash_or_height=self.start_height + i, stats=[stat]) assert_equal(list(result.keys()), [stat]) if result[stat] != self.expected_stats[i][stat]: self.log.info('result[{}] ({}) failed, {!r} != {!r}'.format( stat, i, result[stat], self.expected_stats[i][stat])) assert_equal(result[stat], self.expected_stats[i][stat]) # Make sure only the selected statistics are included (more than one) some_stats = {'minfee', 'maxfee'} stats = self.nodes[0].getblockstats( hash_or_height=1, stats=list(some_stats)) assert_equal(set(stats.keys()), some_stats) # Test invalid parameters raise the proper json exceptions tip = self.start_height + self.max_stat_pos assert_raises_rpc_error(-8, 'Target block height {} after current tip {}'.format( tip + 1, tip), self.nodes[0].getblockstats, hash_or_height=tip + 1) assert_raises_rpc_error(-8, 'Target block height {} is negative'.format(-1), self.nodes[0].getblockstats, hash_or_height=-1) # Make sure not valid stats aren't allowed inv_sel_stat = 'asdfghjkl' inv_stats = [ [inv_sel_stat], ['minfee', inv_sel_stat], [inv_sel_stat, 'minfee'], ['minfee', inv_sel_stat, 'maxfee'], ] for inv_stat in inv_stats: assert_raises_rpc_error(-8, 'Invalid selected statistic {}'.format( inv_sel_stat), self.nodes[0].getblockstats, hash_or_height=1, stats=inv_stat) # Make sure we aren't always returning inv_sel_stat as the culprit stat # Mainchain's genesis block shouldn't be found on regtest assert_raises_rpc_error(-5, 'Block not found', self.nodes[0].getblockstats, hash_or_height='000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f') # Invalid number of args assert_raises_rpc_error(-1, 'getblockstats hash_or_height ( stats )', self.nodes[0].getblockstats, '00', 1, 2) assert_raises_rpc_error(-1, 'getblockstats hash_or_height ( stats )', self.nodes[0].getblockstats) if __name__ == '__main__': GetblockstatsTest().main() diff --git a/test/functional/rpc_net.py b/test/functional/rpc_net.py index a72f76a92..8fbdc0e31 100755 --- a/test/functional/rpc_net.py +++ b/test/functional/rpc_net.py @@ -1,235 +1,235 @@ #!/usr/bin/env python3 # Copyright (c) 2017 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test RPC calls related to net. Tests correspond to code in rpc/net.cpp. """ from decimal import Decimal from itertools import product import time from test_framework.p2p import P2PInterface import test_framework.messages from test_framework.messages import ( CAddress, msg_addr, NODE_NETWORK, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_approx, assert_equal, assert_greater_than_or_equal, assert_greater_than, assert_raises_rpc_error, connect_nodes, p2p_port, wait_until, ) def assert_net_servicesnames(servicesflag, servicenames): """Utility that checks if all flags are correctly decoded in `getpeerinfo` and `getnetworkinfo`. :param servicesflag: The services as an integer. :param servicenames: The list of decoded services names, as strings. """ servicesflag_generated = 0 for servicename in servicenames: servicesflag_generated |= getattr( test_framework.messages, 'NODE_' + servicename) assert servicesflag_generated == servicesflag class NetTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 - self.extra_args = [["-minrelaytxfee=0.00001000"], - ["-minrelaytxfee=0.00000500"]] + self.extra_args = [["-minrelaytxfee=10"], + ["-minrelaytxfee=5"]] self.supports_cli = False def run_test(self): # Get out of IBD for the minfeefilter and getpeerinfo tests. self.nodes[0].generate(101) # Connect nodes both ways. connect_nodes(self.nodes[0], self.nodes[1]) connect_nodes(self.nodes[1], self.nodes[0]) self.test_connection_count() self.test_getpeerinfo() self.test_getnettotals() self.test_getnetworkinfo() self.test_getaddednodeinfo() self.test_service_flags() self.test_getnodeaddresses() def test_connection_count(self): self.log.info("Test getconnectioncount") # After using `connect_nodes` to connect nodes 0 and 1 to each other. assert_equal(self.nodes[0].getconnectioncount(), 2) def test_getnettotals(self): self.log.info("Test getnettotals") # getnettotals totalbytesrecv and totalbytessent should be # consistent with getpeerinfo. Since the RPC calls are not atomic, # and messages might have been recvd or sent between RPC calls, call # getnettotals before and after and verify that the returned values # from getpeerinfo are bounded by those values. net_totals_before = self.nodes[0].getnettotals() peer_info = self.nodes[0].getpeerinfo() net_totals_after = self.nodes[0].getnettotals() assert_equal(len(peer_info), 2) peers_recv = sum([peer['bytesrecv'] for peer in peer_info]) peers_sent = sum([peer['bytessent'] for peer in peer_info]) assert_greater_than_or_equal( peers_recv, net_totals_before['totalbytesrecv']) assert_greater_than_or_equal( net_totals_after['totalbytesrecv'], peers_recv) assert_greater_than_or_equal( peers_sent, net_totals_before['totalbytessent']) assert_greater_than_or_equal( net_totals_after['totalbytessent'], peers_sent) # test getnettotals and getpeerinfo by doing a ping # the bytes sent/received should change # note ping and pong are 32 bytes each self.nodes[0].ping() wait_until(lambda: (self.nodes[0].getnettotals()[ 'totalbytessent'] >= net_totals_after['totalbytessent'] + 32 * 2), timeout=1) wait_until(lambda: (self.nodes[0].getnettotals()[ 'totalbytesrecv'] >= net_totals_after['totalbytesrecv'] + 32 * 2), timeout=1) peer_info_after_ping = self.nodes[0].getpeerinfo() for before, after in zip(peer_info, peer_info_after_ping): assert_greater_than_or_equal( after['bytesrecv_per_msg'].get( 'pong', 0), before['bytesrecv_per_msg'].get( 'pong', 0) + 32) assert_greater_than_or_equal( after['bytessent_per_msg'].get( 'ping', 0), before['bytessent_per_msg'].get( 'ping', 0) + 32) def test_getnetworkinfo(self): self.log.info("Test getnetworkinfo") assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True) assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2) self.nodes[0].setnetworkactive(state=False) assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], False) # Wait a bit for all sockets to close wait_until(lambda: self.nodes[0].getnetworkinfo()[ 'connections'] == 0, timeout=3) self.nodes[0].setnetworkactive(state=True) # Connect nodes both ways. connect_nodes(self.nodes[0], self.nodes[1]) connect_nodes(self.nodes[1], self.nodes[0]) assert_equal(self.nodes[0].getnetworkinfo()['networkactive'], True) assert_equal(self.nodes[0].getnetworkinfo()['connections'], 2) # check the `servicesnames` field network_info = [node.getnetworkinfo() for node in self.nodes] for info in network_info: assert_net_servicesnames(int(info["localservices"], 0x10), info["localservicesnames"]) def test_getaddednodeinfo(self): self.log.info("Test getaddednodeinfo") assert_equal(self.nodes[0].getaddednodeinfo(), []) # add a node (node2) to node0 ip_port = "127.0.0.1:{}".format(p2p_port(2)) self.nodes[0].addnode(node=ip_port, command='add') # check that the node has indeed been added added_nodes = self.nodes[0].getaddednodeinfo(ip_port) assert_equal(len(added_nodes), 1) assert_equal(added_nodes[0]['addednode'], ip_port) # check that a non-existent node returns an error assert_raises_rpc_error(-24, "Node has not been added", self.nodes[0].getaddednodeinfo, '1.1.1.1') def test_getpeerinfo(self): self.log.info("Test getpeerinfo") # Create a few getpeerinfo last_block/last_transaction values. if self.is_wallet_compiled(): - self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1) + self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 1000000) self.nodes[1].generate(1) self.sync_all() time_now = int(time.time()) peer_info = [x.getpeerinfo() for x in self.nodes] # Verify last_block and last_transaction keys/values. for node, peer, field in product(range(self.num_nodes), range(2), [ 'last_block', 'last_transaction']): assert field in peer_info[node][peer].keys() if peer_info[node][peer][field] != 0: assert_approx(peer_info[node][peer][field], time_now, vspan=60) # check both sides of bidirectional connection between nodes # the address bound to on one side will be the source address for the # other node assert_equal(peer_info[0][0]['addrbind'], peer_info[1][0]['addr']) assert_equal(peer_info[1][0]['addrbind'], peer_info[0][0]['addr']) - assert_equal(peer_info[0][0]['minfeefilter'], Decimal("0.00000500")) - assert_equal(peer_info[1][0]['minfeefilter'], Decimal("0.00001000")) + assert_equal(peer_info[0][0]['minfeefilter'], Decimal("5.00")) + assert_equal(peer_info[1][0]['minfeefilter'], Decimal("10.00")) # check the `servicesnames` field for info in peer_info: assert_net_servicesnames(int(info[0]["services"], 0x10), info[0]["servicesnames"]) def test_service_flags(self): self.log.info("Test service flags") self.nodes[0].add_p2p_connection( P2PInterface(), services=( 1 << 5) | ( 1 << 63)) assert_equal(['UNKNOWN[2^5]', 'UNKNOWN[2^63]'], self.nodes[0].getpeerinfo()[-1]['servicesnames']) self.nodes[0].disconnect_p2ps() def test_getnodeaddresses(self): self.log.info("Test getnodeaddresses") self.nodes[0].add_p2p_connection(P2PInterface()) # send some addresses to the node via the p2p message addr msg = msg_addr() imported_addrs = [] for i in range(256): a = "123.123.123.{}".format(i) imported_addrs.append(a) addr = CAddress() addr.time = 100000000 addr.nServices = NODE_NETWORK addr.ip = a addr.port = 8333 msg.addrs.append(addr) self.nodes[0].p2p.send_and_ping(msg) # obtain addresses via rpc call and check they were ones sent in before REQUEST_COUNT = 10 node_addresses = self.nodes[0].getnodeaddresses(REQUEST_COUNT) assert_equal(len(node_addresses), REQUEST_COUNT) for a in node_addresses: assert_greater_than(a["time"], 1527811200) # 1st June 2018 assert_equal(a["services"], NODE_NETWORK) assert a["address"] in imported_addrs assert_equal(a["port"], 8333) assert_raises_rpc_error(-8, "Address count out of range", self.nodes[0].getnodeaddresses, -1) # addrman's size cannot be known reliably after insertion, as hash collisions may occur # so only test that requesting a large number of addresses returns less # than that LARGE_REQUEST_COUNT = 10000 node_addresses = self.nodes[0].getnodeaddresses(LARGE_REQUEST_COUNT) assert_greater_than(LARGE_REQUEST_COUNT, len(node_addresses)) if __name__ == '__main__': NetTest().main() diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index f09d460e9..bf3c26f90 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -1,451 +1,460 @@ #!/usr/bin/env python3 # Copyright (c) 2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the Partially Signed Transaction RPCs. """ import json import os from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_approx, assert_equal, assert_raises_rpc_error, find_output, ) # Create one-input, one-output, no-fee transaction: class PSBTTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = False self.num_nodes = 3 self.supports_cli = False def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): # Create and fund a raw tx for sending 10 BTC psbtx1 = self.nodes[0].walletcreatefundedpsbt( - [], {self.nodes[2].getnewaddress(): 10})['psbt'] + [], {self.nodes[2].getnewaddress(): 10000000})['psbt'] # If inputs are specified, do not automatically add more: utxo1 = self.nodes[0].listunspent()[0] assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[0].walletcreatefundedpsbt, [{"txid": utxo1['txid'], "vout": utxo1['vout']}], - {self.nodes[2].getnewaddress(): 90}) + {self.nodes[2].getnewaddress(): 90000000}) psbtx1 = self.nodes[0].walletcreatefundedpsbt([{"txid": utxo1['txid'], "vout": utxo1['vout']}], { - self.nodes[2].getnewaddress(): 90}, 0, {"add_inputs": True})['psbt'] + self.nodes[2].getnewaddress(): 90000000}, 0, {"add_inputs": True})['psbt'] assert_equal(len(self.nodes[0].decodepsbt(psbtx1)['tx']['vin']), 2) # Node 1 should not be able to add anything to it but still return the # psbtx same as before psbtx = self.nodes[1].walletprocesspsbt(psbtx1)['psbt'] assert_equal(psbtx1, psbtx) # Sign the transaction and send signed_tx = self.nodes[0].walletprocesspsbt(psbtx)['psbt'] final_tx = self.nodes[0].finalizepsbt(signed_tx)['hex'] self.nodes[0].sendrawtransaction(final_tx) # Create p2sh, p2pkh addresses pubkey0 = self.nodes[0].getaddressinfo( self.nodes[0].getnewaddress())['pubkey'] pubkey1 = self.nodes[1].getaddressinfo( self.nodes[1].getnewaddress())['pubkey'] pubkey2 = self.nodes[2].getaddressinfo( self.nodes[2].getnewaddress())['pubkey'] p2sh = self.nodes[1].addmultisigaddress( 2, [pubkey0, pubkey1, pubkey2], "")['address'] p2pkh = self.nodes[1].getnewaddress("") # fund those addresses - rawtx = self.nodes[0].createrawtransaction([], {p2sh: 10, p2pkh: 10}) + rawtx = self.nodes[0].createrawtransaction( + [], {p2sh: 10000000, p2pkh: 10000000}) rawtx = self.nodes[0].fundrawtransaction(rawtx, {"changePosition": 0}) signed_tx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex'])[ 'hex'] txid = self.nodes[0].sendrawtransaction(signed_tx) self.nodes[0].generate(6) self.sync_all() # Find the output pos p2sh_pos = -1 p2pkh_pos = -1 decoded = self.nodes[0].decoderawtransaction(signed_tx) for out in decoded['vout']: if out['scriptPubKey']['addresses'][0] == p2sh: p2sh_pos = out['n'] elif out['scriptPubKey']['addresses'][0] == p2pkh: p2pkh_pos = out['n'] # spend single key from node 1 rawtx = self.nodes[1].walletcreatefundedpsbt([{"txid": txid, "vout": p2pkh_pos}], { - self.nodes[1].getnewaddress(): 9.99})['psbt'] + self.nodes[1].getnewaddress(): 9990000})['psbt'] walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(rawtx) # Make sure it has UTXOs decoded = self.nodes[1].decodepsbt(walletprocesspsbt_out['psbt']) assert 'utxo' in decoded['inputs'][0] assert_equal(walletprocesspsbt_out['complete'], True) self.nodes[1].sendrawtransaction( self.nodes[1].finalizepsbt(walletprocesspsbt_out['psbt'])['hex']) # feeRate of 0.1 BCH / KB produces a total fee slightly below -maxtxfee res = self.nodes[1].walletcreatefundedpsbt( [ { "txid": txid, "vout": p2sh_pos}, { "txid": txid, "vout": p2pkh_pos}], { - self.nodes[1].getnewaddress(): 29.99}, 0, { - "feeRate": 0.1, "add_inputs": True}) - assert_approx(res["fee"], 0.065, 0.005) + self.nodes[1].getnewaddress(): 29990000}, 0, { + "feeRate": 100000, "add_inputs": True}) + assert_approx(res["fee"], 65000, 5000) # feeRate of 10 BCH / KB produces a total fee well above -maxtxfee # previously this was silently capped at -maxtxfee assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[1].walletcreatefundedpsbt, [{"txid": txid, "vout": p2sh_pos}, {"txid": txid, "vout": p2pkh_pos}], - {self.nodes[1].getnewaddress(): 29.99}, + {self.nodes[1].getnewaddress(): 29990000}, 0, - {"feeRate": 10, + {"feeRate": 10000000, "add_inputs": True}) assert_raises_rpc_error(-4, "Fee exceeds maximum configured by -maxtxfee", self.nodes[1].walletcreatefundedpsbt, [{"txid": txid, "vout": p2sh_pos}, {"txid": txid, "vout": p2pkh_pos}], - {self.nodes[1].getnewaddress(): 1}, + {self.nodes[1].getnewaddress(): 1000000}, 0, - {"feeRate": 10, + {"feeRate": 10000000, "add_inputs": False}) # partially sign multisig things with node 1 psbtx = self.nodes[1].walletcreatefundedpsbt([{"txid": txid, "vout": p2sh_pos}], { - self.nodes[1].getnewaddress(): 9.99})['psbt'] + self.nodes[1].getnewaddress(): 9990000})['psbt'] walletprocesspsbt_out = self.nodes[1].walletprocesspsbt(psbtx) psbtx = walletprocesspsbt_out['psbt'] assert_equal(walletprocesspsbt_out['complete'], False) # partially sign with node 2. This should be complete and sendable walletprocesspsbt_out = self.nodes[2].walletprocesspsbt(psbtx) assert_equal(walletprocesspsbt_out['complete'], True) self.nodes[2].sendrawtransaction( self.nodes[2].finalizepsbt(walletprocesspsbt_out['psbt'])['hex']) # check that walletprocesspsbt fails to decode a non-psbt rawtx = self.nodes[1].createrawtransaction([{"txid": txid, "vout": p2pkh_pos}], { - self.nodes[1].getnewaddress(): 9.99}) + self.nodes[1].getnewaddress(): 9990000}) assert_raises_rpc_error(-22, "TX decode failed", self.nodes[1].walletprocesspsbt, rawtx) # Convert a non-psbt to psbt and make sure we can decode it rawtx = self.nodes[0].createrawtransaction( - [], {self.nodes[1].getnewaddress(): 10}) + [], {self.nodes[1].getnewaddress(): 10000000}) rawtx = self.nodes[0].fundrawtransaction(rawtx) new_psbt = self.nodes[0].converttopsbt(rawtx['hex']) self.nodes[0].decodepsbt(new_psbt) # Make sure that a non-psbt with signatures cannot be converted # Error is "Inputs must not have scriptSigs" signedtx = self.nodes[0].signrawtransactionwithwallet(rawtx['hex']) assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, signedtx['hex']) assert_raises_rpc_error(-22, "", self.nodes[0].converttopsbt, signedtx['hex'], False) # Unless we allow it to convert and strip signatures self.nodes[0].converttopsbt(signedtx['hex'], True) # Explicilty allow converting non-empty txs new_psbt = self.nodes[0].converttopsbt(rawtx['hex']) self.nodes[0].decodepsbt(new_psbt) # Create outputs to nodes 1 and 2 node1_addr = self.nodes[1].getnewaddress() node2_addr = self.nodes[2].getnewaddress() - txid1 = self.nodes[0].sendtoaddress(node1_addr, 13) - txid2 = self.nodes[0].sendtoaddress(node2_addr, 13) + txid1 = self.nodes[0].sendtoaddress(node1_addr, 13000000) + txid2 = self.nodes[0].sendtoaddress(node2_addr, 13000000) blockhash = self.nodes[0].generate(6)[0] self.sync_all() - vout1 = find_output(self.nodes[1], txid1, 13, blockhash=blockhash) - vout2 = find_output(self.nodes[2], txid2, 13, blockhash=blockhash) + vout1 = find_output( + self.nodes[1], + txid1, + 13000000, + blockhash=blockhash) + vout2 = find_output( + self.nodes[2], + txid2, + 13000000, + blockhash=blockhash) # Create a psbt spending outputs from nodes 1 and 2 psbt_orig = self.nodes[0].createpsbt([{"txid": txid1, "vout": vout1}, { - "txid": txid2, "vout": vout2}], {self.nodes[0].getnewaddress(): 25.999}) + "txid": txid2, "vout": vout2}], {self.nodes[0].getnewaddress(): 25999000}) # Update psbts, should only have data for one input and not the other psbt1 = self.nodes[1].walletprocesspsbt( psbt_orig, False, "ALL|FORKID")['psbt'] psbt1_decoded = self.nodes[0].decodepsbt(psbt1) assert psbt1_decoded['inputs'][0] and not psbt1_decoded['inputs'][1] # Check that BIP32 path was added assert "bip32_derivs" in psbt1_decoded['inputs'][0] psbt2 = self.nodes[2].walletprocesspsbt( psbt_orig, False, "ALL|FORKID", False)['psbt'] psbt2_decoded = self.nodes[0].decodepsbt(psbt2) assert not psbt2_decoded['inputs'][0] and psbt2_decoded['inputs'][1] # Check that BIP32 paths were not added assert "bip32_derivs" not in psbt2_decoded['inputs'][1] # Sign PSBTs (workaround issue #18039) psbt1 = self.nodes[1].walletprocesspsbt(psbt_orig)['psbt'] psbt2 = self.nodes[2].walletprocesspsbt(psbt_orig)['psbt'] # Combine, finalize, and send the psbts combined = self.nodes[0].combinepsbt([psbt1, psbt2]) finalized = self.nodes[0].finalizepsbt(combined)['hex'] self.nodes[0].sendrawtransaction(finalized) self.nodes[0].generate(6) self.sync_all() block_height = self.nodes[0].getblockcount() unspent = self.nodes[0].listunspent()[0] # Make sure change address wallet does not have P2SH innerscript access to results in success # when attempting BnB coin selection self.nodes[0].walletcreatefundedpsbt( [], - [{self.nodes[2].getnewaddress():unspent["amount"] + 1}], + [{self.nodes[2].getnewaddress():unspent["amount"] + 1000000}], block_height + 2, {"changeAddress": self.nodes[1].getnewaddress()}, False) # Regression test for 14473 (mishandling of already-signed # transaction): psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid": unspent["txid"], "vout":unspent["vout"]}], [ - {self.nodes[2].getnewaddress():unspent["amount"] + 1}], 0, {"add_inputs": True}) + {self.nodes[2].getnewaddress():unspent["amount"] + 1000000}], 0, {"add_inputs": True}) complete_psbt = self.nodes[0].walletprocesspsbt(psbtx_info["psbt"]) double_processed_psbt = self.nodes[0].walletprocesspsbt( complete_psbt["psbt"]) assert_equal(complete_psbt, double_processed_psbt) # We don't care about the decode result, but decoding must succeed. self.nodes[0].decodepsbt(double_processed_psbt["psbt"]) # BIP 174 Test Vectors # Check that unknown values are just passed through unknown_psbt = "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA=" unknown_out = self.nodes[0].walletprocesspsbt(unknown_psbt)['psbt'] assert_equal(unknown_psbt, unknown_out) # Open the data file with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data/rpc_psbt.json'), encoding='utf-8') as f: d = json.load(f) invalids = d['invalid'] valids = d['valid'] creators = d['creator'] signers = d['signer'] combiners = d['combiner'] finalizers = d['finalizer'] extractors = d['extractor'] # Invalid PSBTs for invalid in invalids: assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decodepsbt, invalid) # Valid PSBTs for valid in valids: self.nodes[0].decodepsbt(valid) # Creator Tests for creator in creators: created_tx = self.nodes[0].createpsbt( creator['inputs'], creator['outputs']) assert_equal(created_tx, creator['result']) # Signer tests for i, signer in enumerate(signers): self.nodes[2].createwallet("wallet{}".format(i)) wrpc = self.nodes[2].get_wallet_rpc("wallet{}".format(i)) for key in signer['privkeys']: wrpc.importprivkey(key) signed_tx = wrpc.walletprocesspsbt(signer['psbt'])['psbt'] assert_equal(signed_tx, signer['result']) # Combiner test for combiner in combiners: combined = self.nodes[2].combinepsbt(combiner['combine']) assert_equal(combined, combiner['result']) # Empty combiner test assert_raises_rpc_error(-8, "Parameter 'txs' cannot be empty", self.nodes[0].combinepsbt, []) # Finalizer test for finalizer in finalizers: finalized = self.nodes[2].finalizepsbt( finalizer['finalize'], False)['psbt'] assert_equal(finalized, finalizer['result']) # Extractor test for extractor in extractors: extracted = self.nodes[2].finalizepsbt( extractor['extract'], True)['hex'] assert_equal(extracted, extractor['result']) # Test decoding error: invalid base64 assert_raises_rpc_error(-22, "TX decode failed invalid base64", self.nodes[0].decodepsbt, ";definitely not base64;") # Test that psbts with p2pkh outputs are created properly p2pkh = self.nodes[0].getnewaddress() psbt = self.nodes[1].walletcreatefundedpsbt( - [], [{p2pkh: 1}], 0, {"includeWatching": True}, True) + [], [{p2pkh: 1000000}], 0, {"includeWatching": True}, True) self.nodes[0].decodepsbt(psbt['psbt']) # Send to all types of addresses addr1 = self.nodes[1].getnewaddress("") # originally bech32 - txid1 = self.nodes[0].sendtoaddress(addr1, 11) - vout1 = find_output(self.nodes[0], txid1, 11) + txid1 = self.nodes[0].sendtoaddress(addr1, 11000000) + vout1 = find_output(self.nodes[0], txid1, 11000000) addr2 = self.nodes[1].getnewaddress("") # originally legacy - txid2 = self.nodes[0].sendtoaddress(addr2, 11) - vout2 = find_output(self.nodes[0], txid2, 11) + txid2 = self.nodes[0].sendtoaddress(addr2, 11000000) + vout2 = find_output(self.nodes[0], txid2, 11000000) addr3 = self.nodes[1].getnewaddress("") # originally p2sh-segwit - txid3 = self.nodes[0].sendtoaddress(addr3, 11) - vout3 = find_output(self.nodes[0], txid3, 11) + txid3 = self.nodes[0].sendtoaddress(addr3, 11000000) + vout3 = find_output(self.nodes[0], txid3, 11000000) self.sync_all() def test_psbt_input_keys(psbt_input, keys): """Check that the psbt input has only the expected keys.""" assert_equal(set(keys), set(psbt_input.keys())) # Create a PSBT. None of the inputs are filled initially psbt = self.nodes[1].createpsbt([{"txid": txid1, "vout": vout1}, {"txid": txid2, "vout": vout2}, { - "txid": txid3, "vout": vout3}], {self.nodes[0].getnewaddress(): 32.999}) + "txid": txid3, "vout": vout3}], {self.nodes[0].getnewaddress(): 32999000}) decoded = self.nodes[1].decodepsbt(psbt) test_psbt_input_keys(decoded['inputs'][0], []) test_psbt_input_keys(decoded['inputs'][1], []) test_psbt_input_keys(decoded['inputs'][2], []) # Update a PSBT with UTXOs from the node updated = self.nodes[1].utxoupdatepsbt(psbt) decoded = self.nodes[1].decodepsbt(updated) test_psbt_input_keys(decoded['inputs'][1], []) test_psbt_input_keys(decoded['inputs'][2], []) # Try again, now while providing descriptors descs = [self.nodes[1].getaddressinfo(addr)['desc'] for addr in [ addr1, addr2, addr3]] updated = self.nodes[1].utxoupdatepsbt(psbt=psbt, descriptors=descs) decoded = self.nodes[1].decodepsbt(updated) test_psbt_input_keys(decoded['inputs'][1], []) # Two PSBTs with a common input should not be joinable psbt1 = self.nodes[1].createpsbt([{"txid": txid1, "vout": vout1}], { - self.nodes[0].getnewaddress(): Decimal('10.999')}) + self.nodes[0].getnewaddress(): Decimal('10999000')}) assert_raises_rpc_error(-8, "exists in multiple PSBTs", self.nodes[1].joinpsbts, [psbt1, updated]) # Join two distinct PSBTs addr4 = self.nodes[1].getnewaddress("") - txid4 = self.nodes[0].sendtoaddress(addr4, 5) - vout4 = find_output(self.nodes[0], txid4, 5) + txid4 = self.nodes[0].sendtoaddress(addr4, 5000000) + vout4 = find_output(self.nodes[0], txid4, 5000000) self.nodes[0].generate(6) self.sync_all() psbt2 = self.nodes[1].createpsbt([{"txid": txid4, "vout": vout4}], { - self.nodes[0].getnewaddress(): Decimal('4.999')}) + self.nodes[0].getnewaddress(): Decimal('4999000')}) psbt2 = self.nodes[1].walletprocesspsbt(psbt2)['psbt'] psbt2_decoded = self.nodes[0].decodepsbt(psbt2) assert "final_scriptSig" in psbt2_decoded['inputs'][0] joined = self.nodes[0].joinpsbts([psbt, psbt2]) joined_decoded = self.nodes[0].decodepsbt(joined) assert len(joined_decoded['inputs']) == 4 and len( joined_decoded['outputs']) == 2 and "final_scriptSig" not in joined_decoded['inputs'][3] # Fail when trying to join less than two PSBTs assert_raises_rpc_error(-8, "At least two PSBTs are required to join PSBTs.", self.nodes[1].joinpsbts, []) assert_raises_rpc_error(-8, "At least two PSBTs are required to join PSBTs.", self.nodes[1].joinpsbts, [psbt2]) # Check that joining shuffles the inputs and outputs # 10 attempts should be enough to get a shuffled join shuffled = False for i in range(0, 10): shuffled_joined = self.nodes[0].joinpsbts([psbt, psbt2]) shuffled |= joined != shuffled_joined if shuffled: break assert shuffled # Newly created PSBT needs UTXOs and updating addr = self.nodes[1].getnewaddress("") - txid = self.nodes[0].sendtoaddress(addr, 7) + txid = self.nodes[0].sendtoaddress(addr, 7000000) blockhash = self.nodes[0].generate(6)[0] self.sync_all() - vout = find_output(self.nodes[0], txid, 7, blockhash=blockhash) + vout = find_output(self.nodes[0], txid, 7000000, blockhash=blockhash) psbt = self.nodes[1].createpsbt([{"txid": txid, "vout": vout}], { - self.nodes[0].getnewaddress(""): Decimal('6.999')}) + self.nodes[0].getnewaddress(""): Decimal('6999000')}) analyzed = self.nodes[0].analyzepsbt(psbt) assert not analyzed['inputs'][0]['has_utxo'] and not analyzed['inputs'][0][ 'is_final'] and analyzed['inputs'][0]['next'] == 'updater' and analyzed['next'] == 'updater' # After update with wallet, only needs signing updated = self.nodes[1].walletprocesspsbt( psbt, False, 'ALL|FORKID', True)['psbt'] analyzed = self.nodes[0].analyzepsbt(updated) assert analyzed['inputs'][0]['has_utxo'] and not analyzed['inputs'][0][ 'is_final'] and analyzed['inputs'][0]['next'] == 'signer' and analyzed['next'] == 'signer' # Check fee and size things assert analyzed['fee'] == Decimal( - '0.001') and analyzed['estimated_vsize'] == 191 and analyzed['estimated_feerate'] == Decimal('0.00523560') + '1000') and analyzed['estimated_vsize'] == 191 and analyzed['estimated_feerate'] == Decimal('5235.60') # After signing and finalizing, needs extracting signed = self.nodes[1].walletprocesspsbt(updated)['psbt'] analyzed = self.nodes[0].analyzepsbt(signed) assert analyzed['inputs'][0]['has_utxo'] and analyzed['inputs'][0]['is_final'] and analyzed['next'] == 'extractor' self.log.info( "PSBT spending unspendable outputs should have error message and Creator as next") analysis = self.nodes[0].analyzepsbt( 'cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWAEHYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFv8/wADXYP/7//////8JxOh0LR2HAI8AAAAAAAEAIADC6wsAAAAAF2oUt/X69ELjeX2nTof+fZ10l+OyAokDAQcJAwEHEAABAACAAAEAIADC6wsAAAAAF2oUt/X69ELjeX2nTof+fZ10l+OyAokDAQcJAwEHENkMak8AAAAA') assert_equal(analysis['next'], 'creator') assert_equal( analysis['error'], 'PSBT is not valid. Input 0 spends unspendable output') self.log.info( "PSBT with invalid values should have error message and Creator as next") analysis = self.nodes[0].analyzepsbt( 'cHNidP8BAHECAAAAAfA00BFgAm6tp86RowwH6BMImQNL5zXUcTT97XoLGz0BAAAAAAD/////AgD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XL87QKVAAAAABYAFPck4gF7iL4NL4wtfRAKgQbghiTUAAAAAAABAB8AgIFq49AHABYAFJUDtxf2PHo641HEOBOAIvFMNTr2AAAA') assert_equal(analysis['next'], 'creator') assert_equal( analysis['error'], 'PSBT is not valid. Input 0 has invalid value') analysis = self.nodes[0].analyzepsbt( 'cHNidP8BAHECAAAAAfA00BFgAm6tp86RowwH6BMImQNL5zXUcTT97XoLGz0BAAAAAAD/////AgCAgWrj0AcAFgAUKNw0x8HRctAgmvoevm4u1SbN7XL87QKVAAAAABYAFPck4gF7iL4NL4wtfRAKgQbghiTUAAAAAAABAB8A8gUqAQAAABYAFJUDtxf2PHo641HEOBOAIvFMNTr2AAAA') assert_equal(analysis['next'], 'creator') assert_equal( analysis['error'], 'PSBT is not valid. Output amount invalid') if __name__ == '__main__': PSBTTest().main() diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 4445c3524..329d443b0 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -1,685 +1,688 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the rawtranscation RPCs. Test the following RPCs: - createrawtransaction - signrawtransactionwithwallet - sendrawtransaction - decoderawtransaction - getrawtransaction """ from decimal import Decimal from collections import OrderedDict from io import BytesIO from test_framework.messages import ( COutPoint, CTransaction, CTxIn, CTxOut, ToHex, ) from test_framework.script import CScript from test_framework.test_framework import BitcoinTestFramework from test_framework.txtools import pad_raw_tx from test_framework.util import ( assert_equal, assert_greater_than, assert_raises_rpc_error, connect_nodes, hex_str_to_bytes, ) class multidict(dict): """Dictionary that allows duplicate keys. Constructed with a list of (key, value) tuples. When dumped by the json module, will output invalid json with repeated keys, eg: >>> json.dumps(multidict([(1,2),(1,2)]) '{"1": 2, "1": 2}' Used to test calls to rpc methods with repeated keys in the json object.""" def __init__(self, x): dict.__init__(self, x) self.x = x def items(self): return self.x # Create one-input, one-output, no-fee transaction: class RawTransactionsTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 3 self.extra_args = [["-txindex"], ["-txindex"], ["-txindex"]] self.supports_cli = False def skip_test_if_missing_module(self): self.skip_if_no_wallet() def setup_network(self): super().setup_network() connect_nodes(self.nodes[0], self.nodes[2]) def run_test(self): self.log.info( 'prepare some coins for multiple *rawtransaction commands') self.nodes[2].generate(1) self.sync_all() self.nodes[0].generate(101) self.sync_all() - self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.5) - self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0) - self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 5.0) + self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1500000) + self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1000000) + self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 5000000) self.sync_all() self.nodes[0].generate(5) self.sync_all() self.log.info( 'Test getrawtransaction on genesis block coinbase returns an error') block = self.nodes[0].getblock(self.nodes[0].getblockhash(0)) assert_raises_rpc_error(-5, "The genesis block coinbase is not considered an ordinary transaction", self.nodes[0].getrawtransaction, block['merkleroot']) self.log.info( 'Check parameter types and required parameters of 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, 'foo') # Test `createrawtransaction` invalid `inputs` txid = '1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000' assert_raises_rpc_error(-3, "Expected type array", self.nodes[0].createrawtransaction, 'foo', {}) assert_raises_rpc_error(-1, "JSON value is not an object as expected", self.nodes[0].createrawtransaction, ['foo'], {}) assert_raises_rpc_error(-1, "JSON value is not a string as expected", 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'}], {}) assert_raises_rpc_error(-8, "txid must be hexadecimal string (not 'ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844')", self.nodes[0].createrawtransaction, [{'txid': 'ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844'}], {}) assert_raises_rpc_error(-8, "Invalid parameter, missing vout key", self.nodes[0].createrawtransaction, [{'txid': txid}], {}) assert_raises_rpc_error(-8, "Invalid parameter, vout must be a number", self.nodes[0].createrawtransaction, [{'txid': txid, 'vout': 'foo'}], {}) assert_raises_rpc_error(-8, "Invalid parameter, vout must be positive", self.nodes[0].createrawtransaction, [{'txid': txid, 'vout': -1}], {}) assert_raises_rpc_error(-8, "Invalid parameter, sequence number is out of range", self.nodes[0].createrawtransaction, [{'txid': txid, 'vout': 0, 'sequence': -1}], {}) # Test `createrawtransaction` invalid `outputs` address = self.nodes[0].getnewaddress() address2 = self.nodes[0].getnewaddress() assert_raises_rpc_error(-1, "JSON value is not an array as expected", self.nodes[0].createrawtransaction, [], 'foo') # Should not throw for backwards compatibility self.nodes[0].createrawtransaction(inputs=[], outputs={}) 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: {}".format( address), self.nodes[0].createrawtransaction, [], multidict([(address, 1), (address, 1)])) assert_raises_rpc_error(-8, "Invalid parameter, duplicated address: {}".format( 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` 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) self.log.info( 'Check that createrawtransaction accepts an array and object as outputs') tx = CTransaction() # One output tx.deserialize(BytesIO(hex_str_to_bytes(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 tx.deserialize(BytesIO(hex_str_to_bytes(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.deserialize(BytesIO(hex_str_to_bytes(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'}]), ) for type in ["legacy"]: addr = self.nodes[0].getnewaddress("", type) addrinfo = self.nodes[0].getaddressinfo(addr) pubkey = addrinfo["scriptPubKey"] self.log.info( 'sendrawtransaction with missing prevtx info ({})'.format(type)) # Test `signrawtransactionwithwallet` invalid `prevtxs` inputs = [{'txid': txid, 'vout': 3, 'sequence': 1000}] outputs = {self.nodes[0].getnewaddress(): 1} rawtx = self.nodes[0].createrawtransaction(inputs, outputs) prevtx = dict(txid=txid, scriptPubKey=pubkey, vout=3, amount=1) succ = self.nodes[0].signrawtransactionwithwallet(rawtx, [prevtx]) assert succ["complete"] assert_raises_rpc_error(-8, "Missing amount", self.nodes[0].signrawtransactionwithwallet, rawtx, [ { "txid": txid, "scriptPubKey": pubkey, "vout": 3, } ]) assert_raises_rpc_error(-3, "Missing vout", self.nodes[0].signrawtransactionwithwallet, rawtx, [ { "txid": txid, "scriptPubKey": pubkey, "amount": 1, } ]) assert_raises_rpc_error(-3, "Missing txid", self.nodes[0].signrawtransactionwithwallet, rawtx, [ { "scriptPubKey": pubkey, "vout": 3, "amount": 1, } ]) assert_raises_rpc_error(-3, "Missing scriptPubKey", self.nodes[0].signrawtransactionwithwallet, rawtx, [ { "txid": txid, "vout": 3, "amount": 1 } ]) ######################################### # sendrawtransaction with missing input # ######################################### self.log.info('sendrawtransaction with missing input') # won't exists inputs = [ {'txid': "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout': 1}] - outputs = {self.nodes[0].getnewaddress(): 4.998} + outputs = {self.nodes[0].getnewaddress(): 4998000} rawtx = self.nodes[2].createrawtransaction(inputs, outputs) rawtx = pad_raw_tx(rawtx) rawtx = self.nodes[2].signrawtransactionwithwallet(rawtx) # This will raise an exception since there are missing inputs assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent", self.nodes[2].sendrawtransaction, rawtx['hex']) ##################################### # getrawtransaction with block hash # ##################################### # make a tx by sending then generate 2 blocks; block1 has the tx in it - tx = self.nodes[2].sendtoaddress(self.nodes[1].getnewaddress(), 1) + tx = self.nodes[2].sendtoaddress( + self.nodes[1].getnewaddress(), 1000000) block1, block2 = self.nodes[2].generate(2) self.sync_all() # We should be able to get the raw transaction by providing the correct # block gottx = self.nodes[0].getrawtransaction(tx, True, block1) assert_equal(gottx['txid'], tx) assert_equal(gottx['in_active_chain'], True) # We should not have the 'in_active_chain' flag when we don't provide a # block gottx = self.nodes[0].getrawtransaction(tx, True) assert_equal(gottx['txid'], tx) assert 'in_active_chain' not in gottx # We should not get the tx if we provide an unrelated block assert_raises_rpc_error(-5, "No such transaction found", self.nodes[0].getrawtransaction, tx, True, block2) # An invalid block hash should raise the correct errors assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[0].getrawtransaction, tx, True, True) assert_raises_rpc_error(-8, "parameter 3 must be of length 64 (not 6, for 'foobar')", self.nodes[0].getrawtransaction, tx, True, "foobar") assert_raises_rpc_error(-8, "parameter 3 must be of length 64 (not 8, for 'abcd1234')", self.nodes[0].getrawtransaction, tx, True, "abcd1234") assert_raises_rpc_error( -8, "parameter 3 must be hexadecimal string (not 'ZZZ0000000000000000000000000000000000000000000000000000000000000')", self.nodes[0].getrawtransaction, tx, True, "ZZZ0000000000000000000000000000000000000000000000000000000000000") assert_raises_rpc_error(-5, "Block hash not found", self.nodes[0].getrawtransaction, tx, True, "0000000000000000000000000000000000000000000000000000000000000000") # Undo the blocks and check in_active_chain self.nodes[0].invalidateblock(block1) gottx = self.nodes[0].getrawtransaction( txid=tx, verbose=True, blockhash=block1) assert_equal(gottx['in_active_chain'], False) self.nodes[0].reconsiderblock(block1) assert_equal(self.nodes[0].getbestblockhash(), block2) # # RAW TX MULTISIG TESTS # # # 2of2 test addr1 = self.nodes[2].getnewaddress() addr2 = self.nodes[2].getnewaddress() addr1Obj = self.nodes[2].getaddressinfo(addr1) addr2Obj = self.nodes[2].getaddressinfo(addr2) # Tests for createmultisig and addmultisigaddress assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 1, ["01020304"]) # createmultisig can only take public keys self.nodes[0].createmultisig( 2, [addr1Obj['pubkey'], addr2Obj['pubkey']]) # addmultisigaddress can take both pubkeys and addresses so long as # they are in the wallet, which is tested here. assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 2, [addr1Obj['pubkey'], addr1]) mSigObj = self.nodes[2].addmultisigaddress( 2, [addr1Obj['pubkey'], addr1])['address'] # use balance deltas instead of absolute values bal = self.nodes[2].getbalance() # send 1.2 BCH to msig adr - txId = self.nodes[0].sendtoaddress(mSigObj, 1.2) + txId = self.nodes[0].sendtoaddress(mSigObj, 1200000) self.sync_all() self.nodes[0].generate(1) self.sync_all() # node2 has both keys of the 2of2 ms addr., tx should affect the # balance - assert_equal(self.nodes[2].getbalance(), bal + Decimal('1.20000000')) + assert_equal(self.nodes[2].getbalance(), bal + Decimal('1200000.00')) # 2of3 test from different nodes bal = self.nodes[2].getbalance() addr1 = self.nodes[1].getnewaddress() addr2 = self.nodes[2].getnewaddress() addr3 = self.nodes[2].getnewaddress() addr1Obj = self.nodes[1].getaddressinfo(addr1) addr2Obj = self.nodes[2].getaddressinfo(addr2) addr3Obj = self.nodes[2].getaddressinfo(addr3) mSigObj = self.nodes[2].addmultisigaddress( 2, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey']])['address'] - txId = self.nodes[0].sendtoaddress(mSigObj, 2.2) + txId = self.nodes[0].sendtoaddress(mSigObj, 2200000) decTx = self.nodes[0].gettransaction(txId) rawTx = self.nodes[0].decoderawtransaction(decTx['hex']) self.sync_all() self.nodes[0].generate(1) self.sync_all() # THIS IS AN INCOMPLETE FEATURE # NODE2 HAS TWO OF THREE KEY AND THE FUNDS SHOULD BE SPENDABLE AND # COUNT AT BALANCE CALCULATION # for now, assume the funds of a 2of3 multisig tx are not marked as # spendable assert_equal(self.nodes[2].getbalance(), bal) txDetails = self.nodes[0].gettransaction(txId, True) rawTx = self.nodes[0].decoderawtransaction(txDetails['hex']) vout = next(o for o in rawTx['vout'] - if o['value'] == Decimal('2.20000000')) + if o['value'] == Decimal('2200000.00')) bal = self.nodes[0].getbalance() inputs = [{ "txid": txId, "vout": vout['n'], "scriptPubKey": vout['scriptPubKey']['hex'], "amount": vout['value'], }] - outputs = {self.nodes[0].getnewaddress(): 2.19} + outputs = {self.nodes[0].getnewaddress(): 2190000} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) rawTxPartialSigned = self.nodes[1].signrawtransactionwithwallet( rawTx, inputs) # node1 only has one key, can't comp. sign the tx assert_equal(rawTxPartialSigned['complete'], False) rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx, inputs) # node2 can sign the tx compl., own two of three keys assert_equal(rawTxSigned['complete'], True) self.nodes[2].sendrawtransaction(rawTxSigned['hex']) rawTx = self.nodes[0].decoderawtransaction(rawTxSigned['hex']) self.sync_all() self.nodes[0].generate(1) self.sync_all() assert_equal(self.nodes[0].getbalance(), bal + Decimal( - '50.00000000') + Decimal('2.19000000')) # block reward + tx + '50000000.00') + Decimal('2190000.00')) # block reward + tx rawTxBlock = self.nodes[0].getblock(self.nodes[0].getbestblockhash()) # 2of2 test for combining transactions bal = self.nodes[2].getbalance() addr1 = self.nodes[1].getnewaddress() addr2 = self.nodes[2].getnewaddress() addr1Obj = self.nodes[1].getaddressinfo(addr1) addr2Obj = self.nodes[2].getaddressinfo(addr2) self.nodes[1].addmultisigaddress( 2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address'] mSigObj = self.nodes[2].addmultisigaddress( 2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address'] mSigObjValid = self.nodes[2].getaddressinfo(mSigObj) - txId = self.nodes[0].sendtoaddress(mSigObj, 2.2) + txId = self.nodes[0].sendtoaddress(mSigObj, 2200000) decTx = self.nodes[0].gettransaction(txId) rawTx2 = self.nodes[0].decoderawtransaction(decTx['hex']) self.sync_all() self.nodes[0].generate(1) self.sync_all() # the funds of a 2of2 multisig tx should not be marked as spendable assert_equal(self.nodes[2].getbalance(), bal) txDetails = self.nodes[0].gettransaction(txId, True) rawTx2 = self.nodes[0].decoderawtransaction(txDetails['hex']) vout = next(o for o in rawTx2['vout'] - if o['value'] == Decimal('2.20000000')) + if o['value'] == Decimal('2200000.00')) bal = self.nodes[0].getbalance() inputs = [{"txid": txId, "vout": vout['n'], "scriptPubKey": vout['scriptPubKey'] ['hex'], "redeemScript": mSigObjValid['hex'], "amount": vout['value']}] - outputs = {self.nodes[0].getnewaddress(): 2.19} + outputs = {self.nodes[0].getnewaddress(): 2190000} rawTx2 = self.nodes[2].createrawtransaction(inputs, outputs) rawTxPartialSigned1 = self.nodes[1].signrawtransactionwithwallet( rawTx2, inputs) self.log.debug(rawTxPartialSigned1) # node1 only has one key, can't comp. sign the tx assert_equal(rawTxPartialSigned1['complete'], False) rawTxPartialSigned2 = self.nodes[2].signrawtransactionwithwallet( rawTx2, inputs) self.log.debug(rawTxPartialSigned2) # node2 only has one key, can't comp. sign the tx assert_equal(rawTxPartialSigned2['complete'], False) rawTxComb = self.nodes[2].combinerawtransaction( [rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex']]) self.log.debug(rawTxComb) self.nodes[2].sendrawtransaction(rawTxComb) rawTx2 = self.nodes[0].decoderawtransaction(rawTxComb) self.sync_all() self.nodes[0].generate(1) self.sync_all() assert_equal(self.nodes[0].getbalance( - ), bal + Decimal('50.00000000') + Decimal('2.19000000')) # block reward + tx + ), bal + Decimal('50000000.00') + Decimal('2190000.00')) # block reward + tx # getrawtransaction tests # 1. valid parameters - only supply txid txId = rawTx["txid"] assert_equal( self.nodes[0].getrawtransaction(txId), rawTxSigned['hex']) # 2. valid parameters - supply txid and 0 for non-verbose assert_equal( self.nodes[0].getrawtransaction(txId, 0), rawTxSigned['hex']) # 3. valid parameters - supply txid and False for non-verbose assert_equal(self.nodes[0].getrawtransaction(txId, False), rawTxSigned['hex']) # 4. valid parameters - supply txid and 1 for verbose. # We only check the "hex" field of the output so we don't need to # update this test every time the output format changes. assert_equal(self.nodes[0].getrawtransaction(txId, 1)["hex"], rawTxSigned['hex']) # 5. valid parameters - supply txid and True for non-verbose assert_equal(self.nodes[0].getrawtransaction(txId, True)["hex"], rawTxSigned['hex']) # 6. invalid parameters - supply txid and string "Flase" assert_raises_rpc_error(-1, "not a boolean", self.nodes[0].getrawtransaction, txId, "Flase") # 7. invalid parameters - supply txid and empty array assert_raises_rpc_error(-1, "not a boolean", self.nodes[0].getrawtransaction, txId, []) # 8. invalid parameters - supply txid and empty dict assert_raises_rpc_error( -1, "not a boolean", self.nodes[0].getrawtransaction, txId, {}) # Sanity checks on verbose getrawtransaction output rawTxOutput = self.nodes[0].getrawtransaction(txId, True) assert_equal(rawTxOutput["hex"], rawTxSigned["hex"]) assert_equal(rawTxOutput["txid"], txId) assert_equal(rawTxOutput["hash"], txId) assert_greater_than(rawTxOutput["size"], 300) assert_equal(rawTxOutput["version"], 0x02) assert_equal(rawTxOutput["locktime"], 0) assert_equal(len(rawTxOutput["vin"]), 1) assert_equal(len(rawTxOutput["vout"]), 1) assert_equal(rawTxOutput["blockhash"], rawTxBlock["hash"]) assert_equal(rawTxOutput["confirmations"], 3) assert_equal(rawTxOutput["time"], rawTxBlock["time"]) assert_equal(rawTxOutput["blocktime"], rawTxBlock["time"]) inputs = [ {'txid': "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'sequence': 1000}] outputs = {self.nodes[0].getnewaddress(): 1} assert_raises_rpc_error( -8, 'Invalid parameter, missing vout key', self.nodes[0].createrawtransaction, inputs, outputs) inputs[0]['vout'] = "1" assert_raises_rpc_error( -8, 'Invalid parameter, vout must be a number', self.nodes[0].createrawtransaction, inputs, outputs) inputs[0]['vout'] = -1 assert_raises_rpc_error( -8, 'Invalid parameter, vout must be positive', self.nodes[0].createrawtransaction, inputs, outputs) inputs[0]['vout'] = 1 rawtx = self.nodes[0].createrawtransaction(inputs, outputs) decrawtx = self.nodes[0].decoderawtransaction(rawtx) assert_equal(decrawtx['vin'][0]['sequence'], 1000) # 9. invalid parameters - sequence number out of range inputs[0]['sequence'] = -1 assert_raises_rpc_error( -8, 'Invalid parameter, sequence number is out of range', self.nodes[0].createrawtransaction, inputs, outputs) # 10. invalid parameters - sequence number out of range inputs[0]['sequence'] = 4294967296 assert_raises_rpc_error( -8, 'Invalid parameter, sequence number is out of range', self.nodes[0].createrawtransaction, inputs, outputs) inputs[0]['sequence'] = 4294967294 rawtx = self.nodes[0].createrawtransaction(inputs, outputs) decrawtx = self.nodes[0].decoderawtransaction(rawtx) assert_equal(decrawtx['vin'][0]['sequence'], 4294967294) #################################### # TRANSACTION VERSION NUMBER TESTS # #################################### # Test the minimum transaction version number that fits in a signed # 32-bit integer. tx = CTransaction() tx.nVersion = -0x80000000 rawtx = ToHex(tx) decrawtx = self.nodes[0].decoderawtransaction(rawtx) assert_equal(decrawtx['version'], -0x80000000) # Test the maximum transaction version number that fits in a signed # 32-bit integer. tx = CTransaction() tx.nVersion = 0x7fffffff rawtx = ToHex(tx) decrawtx = self.nodes[0].decoderawtransaction(rawtx) assert_equal(decrawtx['version'], 0x7fffffff) self.log.info('sendrawtransaction/testmempoolaccept with maxfeerate') # Test a transaction with a small fee. - txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0) + txId = self.nodes[0].sendtoaddress( + self.nodes[2].getnewaddress(), 1000000) rawTx = self.nodes[0].getrawtransaction(txId, True) vout = next(o for o in rawTx['vout'] - if o['value'] == Decimal('1.00000000')) + if o['value'] == Decimal('1000000.00')) self.sync_all() inputs = [{"txid": txId, "vout": vout['n']}] # Fee 10,000 satoshis, (1 - (10000 sat * 0.00000001 BCH/sat)) = 0.9999 - outputs = {self.nodes[0].getnewaddress(): Decimal("0.99990000")} + outputs = {self.nodes[0].getnewaddress(): Decimal("999900.00")} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx) assert_equal(rawTxSigned['complete'], True) # Fee 10,000 satoshis, ~200 b transaction, fee rate should land around 50 sat/byte = 0.00050000 BCH/kB # Thus, testmempoolaccept should reject testres = self.nodes[2].testmempoolaccept( - [rawTxSigned['hex']], 0.00050000)[0] + [rawTxSigned['hex']], 500.00)[0] assert_equal(testres['allowed'], False) assert_equal(testres['reject-reason'], 'absurdly-high-fee') # and sendrawtransaction should throw assert_raises_rpc_error(-26, "absurdly-high-fee", self.nodes[2].sendrawtransaction, rawTxSigned['hex'], - 0.00001000) + 10.00) # and the following calls should both succeed testres = self.nodes[2].testmempoolaccept( rawtxs=[rawTxSigned['hex']])[0] assert_equal(testres['allowed'], True) self.nodes[2].sendrawtransaction(hexstring=rawTxSigned['hex']) # Test a transaction with a large fee. - txId = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.0) + txId = self.nodes[0].sendtoaddress( + self.nodes[2].getnewaddress(), 1000000) rawTx = self.nodes[0].getrawtransaction(txId, True) vout = next(o for o in rawTx['vout'] - if o['value'] == Decimal('1.00000000')) + if o['value'] == Decimal('1000000.00')) self.sync_all() inputs = [{"txid": txId, "vout": vout['n']}] # Fee 2,000,000 satoshis, (1 - (2000000 sat * 0.00000001 BCH/sat)) = # 0.98 - outputs = {self.nodes[0].getnewaddress(): Decimal("0.98000000")} + outputs = {self.nodes[0].getnewaddress(): Decimal("980000.00")} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx) assert_equal(rawTxSigned['complete'], True) # Fee 2,000,000 satoshis, ~100 b transaction, fee rate should land around 20,000 sat/byte = 0.20000000 BCH/kB # Thus, testmempoolaccept should reject testres = self.nodes[2].testmempoolaccept([rawTxSigned['hex']])[0] assert_equal(testres['allowed'], False) assert_equal(testres['reject-reason'], 'absurdly-high-fee') # and sendrawtransaction should throw assert_raises_rpc_error(-26, "absurdly-high-fee", self.nodes[2].sendrawtransaction, rawTxSigned['hex']) # and the following calls should both succeed testres = self.nodes[2].testmempoolaccept( - rawtxs=[rawTxSigned['hex']], maxfeerate='0.20000000')[0] + rawtxs=[rawTxSigned['hex']], maxfeerate='200000.00')[0] assert_equal(testres['allowed'], True) self.nodes[2].sendrawtransaction( hexstring=rawTxSigned['hex'], - maxfeerate='0.20000000') + maxfeerate='200000.00') ########################################## # Decoding weird scripts in transactions # ########################################## self.log.info('Decode correctly-formatted but weird transactions') tx = CTransaction() # empty self.nodes[0].decoderawtransaction(ToHex(tx)) # truncated push tx.vin.append(CTxIn(COutPoint(42, 0), b'\x4e\x00\x00')) tx.vin.append(CTxIn(COutPoint(42, 0), b'\x4c\x10TRUNC')) tx.vout.append(CTxOut(0, b'\x4e\x00\x00')) tx.vout.append(CTxOut(0, b'\x4c\x10TRUNC')) self.nodes[0].decoderawtransaction(ToHex(tx)) # giant pushes and long scripts tx.vin.append( CTxIn(COutPoint(42, 0), CScript([b'giant push' * 10000]))) tx.vout.append(CTxOut(0, CScript([b'giant push' * 10000]))) self.nodes[0].decoderawtransaction(ToHex(tx)) self.log.info('Refuse garbage after transaction') assert_raises_rpc_error(-22, 'TX decode failed', self.nodes[0].decoderawtransaction, ToHex(tx) + '00') if __name__ == '__main__': RawTransactionsTest().main() diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py index a0b14fe17..991a83479 100755 --- a/test/functional/rpc_scantxoutset.py +++ b/test/functional/rpc_scantxoutset.py @@ -1,231 +1,231 @@ #!/usr/bin/env python3 # Copyright (c) 2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the scantxoutset rpc call.""" import os import shutil from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error def descriptors(out): return sorted(u['desc'] for u in out['unspents']) class ScantxoutsetTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): self.log.info("Mining blocks...") self.nodes[0].generate(110) addr = self.nodes[0].getnewaddress("") pubkey = self.nodes[0].getaddressinfo(addr)['pubkey'] - self.nodes[0].sendtoaddress(addr, 0.002) + self.nodes[0].sendtoaddress(addr, 2000) # send to child keys of tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK # (m/0'/0'/0') self.nodes[0].sendtoaddress( - "mkHV1C6JLheLoUSSZYk7x3FH5tnx9bu7yc", 0.008) + "mkHV1C6JLheLoUSSZYk7x3FH5tnx9bu7yc", 8000) # (m/0'/0'/1') self.nodes[0].sendtoaddress( - "mipUSRmJAj2KrjSvsPQtnP8ynUon7FhpCR", 0.016) + "mipUSRmJAj2KrjSvsPQtnP8ynUon7FhpCR", 16000) # (m/0'/0'/1500') self.nodes[0].sendtoaddress( - "n37dAGe6Mq1HGM9t4b6rFEEsDGq7Fcgfqg", 0.032) + "n37dAGe6Mq1HGM9t4b6rFEEsDGq7Fcgfqg", 32000) # (m/0'/0'/0) self.nodes[0].sendtoaddress( - "mqS9Rpg8nNLAzxFExsgFLCnzHBsoQ3PRM6", 0.064) + "mqS9Rpg8nNLAzxFExsgFLCnzHBsoQ3PRM6", 64000) # (m/0'/0'/1) self.nodes[0].sendtoaddress( - "mnTg5gVWr3rbhHaKjJv7EEEc76ZqHgSj4S", 0.128) + "mnTg5gVWr3rbhHaKjJv7EEEc76ZqHgSj4S", 128000) # (m/0'/0'/1500) self.nodes[0].sendtoaddress( - "mketCd6B9U9Uee1iCsppDJJBHfvi6U6ukC", 0.256) + "mketCd6B9U9Uee1iCsppDJJBHfvi6U6ukC", 256000) # (m/1/1/0') self.nodes[0].sendtoaddress( - "mj8zFzrbBcdaWXowCQ1oPZ4qioBVzLzAp7", 0.512) + "mj8zFzrbBcdaWXowCQ1oPZ4qioBVzLzAp7", 512000) # (m/1/1/1') self.nodes[0].sendtoaddress( - "mfnKpKQEftniaoE1iXuMMePQU3PUpcNisA", 1.024) + "mfnKpKQEftniaoE1iXuMMePQU3PUpcNisA", 1024000) # (m/1/1/1500') self.nodes[0].sendtoaddress( - "mou6cB1kaP1nNJM1sryW6YRwnd4shTbXYQ", 2.048) + "mou6cB1kaP1nNJM1sryW6YRwnd4shTbXYQ", 2048000) # (m/1/1/0) self.nodes[0].sendtoaddress( - "mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", 4.096) + "mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", 4096000) # (m/1/1/1) self.nodes[0].sendtoaddress( - "mxp7w7j8S1Aq6L8StS2PqVvtt4HGxXEvdy", 8.192) + "mxp7w7j8S1Aq6L8StS2PqVvtt4HGxXEvdy", 8192000) # (m/1/1/1500) self.nodes[0].sendtoaddress( - "mpQ8rokAhp1TAtJQR6F6TaUmjAWkAWYYBq", 16.384) + "mpQ8rokAhp1TAtJQR6F6TaUmjAWkAWYYBq", 16384000) self.nodes[0].generate(1) self.log.info("Stop node, remove wallet, mine again some blocks...") self.stop_node(0) shutil.rmtree(os.path.join( self.nodes[0].datadir, self.chain, 'wallets')) self.start_node(0) self.nodes[0].generate(110) 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.restart_node(0, ['-nowallet']) self.log.info("Test if we have found the non HD unspent outputs.") assert_equal(self.nodes[0].scantxoutset( - "start", ["pkh(" + pubkey + ")"])['total_amount'], Decimal("0.002")) + "start", ["pkh(" + pubkey + ")"])['total_amount'], Decimal("2000")) assert_equal(self.nodes[0].scantxoutset( - "start", ["combo(" + pubkey + ")"])['total_amount'], Decimal("0.002")) + "start", ["combo(" + pubkey + ")"])['total_amount'], Decimal("2000")) assert_equal(self.nodes[0].scantxoutset( - "start", ["addr(" + addr + ")"])['total_amount'], Decimal("0.002")) + "start", ["addr(" + addr + ")"])['total_amount'], Decimal("2000")) assert_equal(self.nodes[0].scantxoutset( - "start", ["addr(" + addr + ")"])['total_amount'], Decimal("0.002")) + "start", ["addr(" + addr + ")"])['total_amount'], Decimal("2000")) 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 BTC, so each amounts uniquely identifies a subset. assert_equal(self.nodes[0].scantxoutset("start", [ - "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/0h)"])['total_amount'], Decimal("0.008")) + "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/0h)"])['total_amount'], Decimal("8000")) assert_equal(self.nodes[0].scantxoutset("start", [ - "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0'/1h)"])['total_amount'], Decimal("0.016")) + "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0'/1h)"])['total_amount'], Decimal("16000")) assert_equal(self.nodes[0].scantxoutset("start", [ - "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/1500')"])['total_amount'], Decimal("0.032")) + "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/1500')"])['total_amount'], Decimal("32000")) assert_equal(self.nodes[0].scantxoutset("start", [ - "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0h/0)"])['total_amount'], Decimal("0.064")) + "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0h/0)"])['total_amount'], Decimal("64000")) assert_equal(self.nodes[0].scantxoutset("start", [ - "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/1)"])['total_amount'], Decimal("0.128")) + "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/1)"])['total_amount'], Decimal("128000")) assert_equal(self.nodes[0].scantxoutset("start", [ - "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/1500)"])['total_amount'], Decimal("0.256")) + "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/1500)"])['total_amount'], Decimal("256000")) assert_equal(self.nodes[0].scantxoutset("start", [ - {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/*h)", "range": 1499}])['total_amount'], Decimal("0.024")) + {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/*h)", "range": 1499}])['total_amount'], Decimal("24000")) assert_equal(self.nodes[0].scantxoutset("start", [ - {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0'/*h)", "range": 1500}])['total_amount'], Decimal("0.056")) + {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0'/*h)", "range": 1500}])['total_amount'], Decimal("56000")) assert_equal(self.nodes[0].scantxoutset("start", [ - {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/*)", "range": 1499}])['total_amount'], Decimal("0.192")) + {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/*)", "range": 1499}])['total_amount'], Decimal("192000")) assert_equal(self.nodes[0].scantxoutset("start", [ - {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/*)", "range": 1500}])['total_amount'], Decimal("0.448")) + {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/*)", "range": 1500}])['total_amount'], Decimal("448000")) assert_equal(self.nodes[0].scantxoutset("start", [ - "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0')"])['total_amount'], Decimal("0.512")) + "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0')"])['total_amount'], Decimal("0512000")) assert_equal(self.nodes[0].scantxoutset("start", [ - "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1')"])['total_amount'], Decimal("1.024")) + "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1')"])['total_amount'], Decimal("1024000")) assert_equal(self.nodes[0].scantxoutset("start", [ - "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1500h)"])['total_amount'], Decimal("2.048")) + "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1500h)"])['total_amount'], Decimal("2048000")) assert_equal(self.nodes[0].scantxoutset("start", [ - "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"])['total_amount'], Decimal("4.096")) + "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"])['total_amount'], Decimal("4096000")) assert_equal(self.nodes[0].scantxoutset("start", [ - "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1)"])['total_amount'], Decimal("8.192")) + "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1)"])['total_amount'], Decimal("8192000")) assert_equal(self.nodes[0].scantxoutset("start", [ - "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1500)"])['total_amount'], Decimal("16.384")) + "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1500)"])['total_amount'], Decimal("16384000")) assert_equal(self.nodes[0].scantxoutset("start", [ - "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0)"])['total_amount'], Decimal("4.096")) + "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0)"])['total_amount'], Decimal("4096000")) assert_equal(self.nodes[0].scantxoutset("start", [ - "combo([abcdef88/1/2'/3/4h]tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)"])['total_amount'], Decimal("8.192")) + "combo([abcdef88/1/2'/3/4h]tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)"])['total_amount'], Decimal("8192000")) assert_equal(self.nodes[0].scantxoutset("start", [ - "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1500)"])['total_amount'], Decimal("16.384")) + "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1500)"])['total_amount'], Decimal("16384000")) assert_equal(self.nodes[0].scantxoutset("start", [ - {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')", "range": 1499}])['total_amount'], Decimal("1.536")) + {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')", "range": 1499}])['total_amount'], Decimal("1536000")) assert_equal(self.nodes[0].scantxoutset("start", [ - {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')", "range": 1500}])['total_amount'], Decimal("3.584")) + {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')", "range": 1500}])['total_amount'], Decimal("3584000")) assert_equal(self.nodes[0].scantxoutset("start", [ - {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", "range": 1499}])['total_amount'], Decimal("12.288")) + {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", "range": 1499}])['total_amount'], Decimal("12288000")) assert_equal(self.nodes[0].scantxoutset("start", [ - {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672")) + {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", "range": 1500}])['total_amount'], Decimal("28672000")) assert_equal(self.nodes[0].scantxoutset("start", [ - {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1499}])['total_amount'], Decimal("12.288")) + {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1499}])['total_amount'], Decimal("12288000")) assert_equal(self.nodes[0].scantxoutset("start", [ - {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672")) + {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])['total_amount'], Decimal("28672000")) assert_equal( self.nodes[0].scantxoutset( "start", [ { "desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": [ 1500, 1500]}])['total_amount'], - Decimal("16.384")) + Decimal("16384000")) # 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") if __name__ == '__main__': ScantxoutsetTest().main() diff --git a/test/functional/rpc_signrawtransaction.py b/test/functional/rpc_signrawtransaction.py index da0a72cdf..7783b79c9 100755 --- a/test/functional/rpc_signrawtransaction.py +++ b/test/functional/rpc_signrawtransaction.py @@ -1,262 +1,262 @@ #!/usr/bin/env python3 # Copyright (c) 2015-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test transaction signing using the signrawtransaction* RPCs.""" from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error RPC_WALLET_NOT_SPECIFIED = "Wallet file not specified (must request wallet " + \ "RPC through /wallet/ uri-path)." class SignRawTransactionsTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def successful_signing_test(self): """Creates and signs a valid raw transaction with one input. Expected results: 1) The transaction has a complete set of signatures 2) No script verification error occurred""" self.log.info("Test valid raw transaction with one input") privKeys = ['cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N', 'cVKpPfVKSJxKqVpE9awvXNWuLHCa5j5tiE7K6zbUSptFpTEtiFrA'] inputs = [ # Valid pay-to-pubkey scripts {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', - 'vout': 0, 'amount': 3.14159, + 'vout': 0, 'amount': 3141590, 'scriptPubKey': '76a91460baa0f494b38ce3c940dea67f3804dc52d1fb9488ac'}, {'txid': '83a4f6a6b73660e13ee6cb3c6063fa3759c50c9b7521d0536022961898f4fb02', - 'vout': 0, 'amount': '123.456', + 'vout': 0, 'amount': '123456000', 'scriptPubKey': '76a914669b857c03a5ed269d5d85a1ffac9ed5d663072788ac'}, ] - outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1} + outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 100000} rawTx = self.nodes[0].createrawtransaction(inputs, outputs) rawTxSigned = self.nodes[0].signrawtransactionwithkey( rawTx, privKeys, inputs) # 1) The transaction has a complete set of signatures assert rawTxSigned['complete'] # 2) No script verification error occurred assert 'errors' not in rawTxSigned def test_with_lock_outputs(self): self.log.info( "Test correct error reporting when trying to sign a locked output") self.nodes[0].encryptwallet("password") rawTx = '020000000156b958f78e3f24e0b2f4e4db1255426b0902027cb37e3ddadb52e37c3557dddb0000000000ffffffff01c0a6b929010000001600149a2ee8c77140a053f36018ac8124a6ececc1668a00000000' assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].signrawtransactionwithwallet, rawTx) def script_verification_error_test(self): """Creates and signs a raw transaction with valid (vin 0), invalid (vin 1) and one missing (vin 2) input script. Expected results: 3) The transaction has no complete set of signatures 4) Two script verification errors occurred 5) Script verification errors have certain properties ("txid", "vout", "scriptSig", "sequence", "error") 6) The verification errors refer to the invalid (vin 1) and missing input (vin 2)""" self.log.info("Test script verification errors") privKeys = ['cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N'] inputs = [ # Valid pay-to-pubkey script {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 0, 'amount': 0}, # Invalid script {'txid': '5b8673686910442c644b1f4993d8f7753c7c8fcb5c87ee40d56eaeef25204547', 'vout': 7, 'amount': '1.1'}, # Missing scriptPubKey {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 1, 'amount': 2.0}, ] scripts = [ # Valid pay-to-pubkey script {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', 'vout': 0, 'amount': 0, 'scriptPubKey': '76a91460baa0f494b38ce3c940dea67f3804dc52d1fb9488ac'}, # Invalid script {'txid': '5b8673686910442c644b1f4993d8f7753c7c8fcb5c87ee40d56eaeef25204547', 'vout': 7, 'amount': '1.1', 'scriptPubKey': 'badbadbadbad'} ] outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1} rawTx = self.nodes[0].createrawtransaction(inputs, outputs) # Make sure decoderawtransaction is at least marginally sane decodedRawTx = self.nodes[0].decoderawtransaction(rawTx) for i, inp in enumerate(inputs): assert_equal(decodedRawTx["vin"][i]["txid"], inp["txid"]) assert_equal(decodedRawTx["vin"][i]["vout"], inp["vout"]) # Make sure decoderawtransaction throws if there is extra data assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, rawTx + "00") rawTxSigned = self.nodes[0].signrawtransactionwithkey( rawTx, privKeys, scripts) # 3) The transaction has no complete set of signatures assert not rawTxSigned['complete'] # 4) Two script verification errors occurred assert 'errors' in rawTxSigned assert_equal(len(rawTxSigned['errors']), 2) # 5) Script verification errors have certain properties assert 'txid' in rawTxSigned['errors'][0] assert 'vout' in rawTxSigned['errors'][0] assert 'scriptSig' in rawTxSigned['errors'][0] assert 'sequence' in rawTxSigned['errors'][0] assert 'error' in rawTxSigned['errors'][0] # 6) The verification errors refer to the invalid (vin 1) and missing # input (vin 2) assert_equal(rawTxSigned['errors'][0]['txid'], inputs[1]['txid']) assert_equal(rawTxSigned['errors'][0]['vout'], inputs[1]['vout']) assert_equal(rawTxSigned['errors'][1]['txid'], inputs[2]['txid']) assert_equal(rawTxSigned['errors'][1]['vout'], inputs[2]['vout']) def test_sighashes(self): """Creates and signs a raw transaction with various sighashes. Expected result: 1) The transaction is complete if the sighash is valid and has FORKID. 2) The RPC throws an error if the sighash does not contain FORKID. 3) The RPC throws an error if the sighash is invalid.""" privKeys = ['cUeKHd5orzT3mz8P9pxyREHfsWtVfgsfDjiZZBcjUBAaGk1BTj7N'] inputs = [ # Valid pay-to-pubkey script {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', - 'vout': 0, 'amount': 3.14159, + 'vout': 0, 'amount': 3141590, 'scriptPubKey': '76a91460baa0f494b38ce3c940dea67f3804dc52d1fb9488ac'} ] - outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1} + outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 100000} rawTx = self.nodes[0].createrawtransaction(inputs, outputs) valid_sighashes = [ "ALL|FORKID", "NONE|FORKID", "SINGLE|FORKID", "ALL|FORKID|ANYONECANPAY", "NONE|FORKID|ANYONECANPAY", "SINGLE|FORKID|ANYONECANPAY", ] no_forkid_sighashes = [ "ALL", "NONE", "SINGLE", "ALL|ANYONECANPAY", "NONE|ANYONECANPAY", "SINGLE|ANYONECANPAY", ] invalid_sighashes = [ "", "ALL|SINGLE|FORKID", str(0), str(0x20), ] # 1) If the sighash is valid with FORKID, the signature is complete for sighash in valid_sighashes: rawTxSigned = self.nodes[0].signrawtransactionwithkey( rawTx, privKeys, inputs, sighash) assert 'complete' in rawTxSigned assert_equal(rawTxSigned['complete'], True) assert 'errors' not in rawTxSigned # 2) If FORKID is missing in the sighash, the RPC throws an error for sighash in no_forkid_sighashes: assert_raises_rpc_error(-8, "Signature must use SIGHASH_FORKID", self.nodes[0].signrawtransactionwithkey, rawTx, privKeys, inputs, sighash) # 3) If the sighash is invalid the RPC throws an error for sighash in invalid_sighashes: assert_raises_rpc_error(-1, sighash + " is not a valid sighash parameter.", self.nodes[0].signrawtransactionwithkey, rawTx, privKeys, inputs, sighash) def multiwallet_signing_test(self): """Creates and signs a raw transaction with a multiwallet node. Expected results: 1) The transaction is not signed if no wallet is specified 2) The transaction is signed if the correct wallet URI is given""" inputs = [ # Valid pay-to-pubkey scripts {'txid': '9b907ef1e3c26fc71fe4a4b3580bc75264112f95050014157059c736f0202e71', - 'vout': 0, 'amount': 3.14159, + 'vout': 0, 'amount': 3141590, 'scriptPubKey': '76a91460baa0f494b38ce3c940dea67f3804dc52d1fb9488ac'}, ] - outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1} + outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 100000} multiwallet_node = self.nodes[0] rawTx = multiwallet_node.createrawtransaction(inputs, outputs) # The multiwallet node cannot sign the transaction if no wallet is # specified assert_raises_rpc_error(-19, RPC_WALLET_NOT_SPECIFIED, multiwallet_node.signrawtransactionwithwallet, rawTx) # The multiwallet node can sign the transaction using w1 w1 = multiwallet_node.get_wallet_rpc('w1') multiwallet_node.generatetoaddress( nblocks=101, address=w1.getnewaddress(label='coinbase')) utxo = w1.listunspent()[0] inputs = [{ 'txid': utxo['txid'], 'vout': utxo['vout'], }] rawTx_w1 = w1.createrawtransaction(inputs, outputs) rawTxSigned_w1 = w1.signrawtransactionwithwallet(rawTx_w1) assert rawTxSigned_w1['complete'] assert 'errors' not in rawTxSigned_w1 def run_test(self): self.successful_signing_test() self.script_verification_error_test() self.test_sighashes() self.test_with_lock_outputs() # The multiwalet require the node to use different flags, so we run it # last. self.restart_node(0, ["-wallet=w1", "-wallet=w2"]) self.multiwallet_signing_test() if __name__ == '__main__': SignRawTransactionsTest().main() diff --git a/test/functional/rpc_txoutproof.py b/test/functional/rpc_txoutproof.py index 8a13b4270..b68843951 100755 --- a/test/functional/rpc_txoutproof.py +++ b/test/functional/rpc_txoutproof.py @@ -1,160 +1,160 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2016 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test gettxoutproof and verifytxoutproof RPCs.""" from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, connect_nodes, ) from test_framework.messages import CMerkleBlock, FromHex, ToHex class MerkleBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.setup_clean_chain = True # Nodes 0/1 are "wallet" nodes, Nodes 2/3 are used for testing self.extra_args = [[], [], [], ["-txindex"]] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def setup_network(self): self.setup_nodes() connect_nodes(self.nodes[0], self.nodes[1]) connect_nodes(self.nodes[0], self.nodes[2]) connect_nodes(self.nodes[0], self.nodes[3]) self.sync_all() def run_test(self): self.log.info("Mining blocks...") self.nodes[0].generate(105) self.sync_all() chain_height = self.nodes[1].getblockcount() assert_equal(chain_height, 105) assert_equal(self.nodes[1].getbalance(), 0) assert_equal(self.nodes[2].getbalance(), 0) node0utxos = self.nodes[0].listunspent(1) tx1 = self.nodes[0].createrawtransaction( - [node0utxos.pop()], {self.nodes[1].getnewaddress(): 49.99}) + [node0utxos.pop()], {self.nodes[1].getnewaddress(): 49990000}) txid1 = self.nodes[0].sendrawtransaction( self.nodes[0].signrawtransactionwithwallet(tx1)["hex"]) tx2 = self.nodes[0].createrawtransaction( - [node0utxos.pop()], {self.nodes[1].getnewaddress(): 49.99}) + [node0utxos.pop()], {self.nodes[1].getnewaddress(): 49990000}) txid2 = self.nodes[0].sendrawtransaction( self.nodes[0].signrawtransactionwithwallet(tx2)["hex"]) # This will raise an exception because the transaction is not yet in a # block assert_raises_rpc_error(-5, "Transaction not yet in block", self.nodes[0].gettxoutproof, [txid1]) self.nodes[0].generate(1) blockhash = self.nodes[0].getblockhash(chain_height + 1) self.sync_all() txlist = [] blocktxn = self.nodes[0].getblock(blockhash, True)["tx"] txlist.append(blocktxn[1]) txlist.append(blocktxn[2]) assert_equal(self.nodes[2].verifytxoutproof( self.nodes[2].gettxoutproof([txid1])), [txid1]) assert_equal(self.nodes[2].verifytxoutproof( self.nodes[2].gettxoutproof([txid1, txid2])), txlist) assert_equal(self.nodes[2].verifytxoutproof( self.nodes[2].gettxoutproof([txid1, txid2], blockhash)), txlist) txin_spent = self.nodes[1].listunspent(1).pop() tx3 = self.nodes[1].createrawtransaction( - [txin_spent], {self.nodes[0].getnewaddress(): 49.98}) + [txin_spent], {self.nodes[0].getnewaddress(): 49980000}) txid3 = self.nodes[0].sendrawtransaction( self.nodes[1].signrawtransactionwithwallet(tx3)["hex"]) self.nodes[0].generate(1) self.sync_all() txid_spent = txin_spent["txid"] txid_unspent = txid1 if txin_spent["txid"] != txid1 else txid2 # Invalid txids assert_raises_rpc_error( -8, "txid must be of length 64 (not 32, for '00000000000000000000000000000000')", self.nodes[2].gettxoutproof, ["00000000000000000000000000000000"], blockhash) assert_raises_rpc_error( -8, "txid must be hexadecimal string (not 'ZZZ0000000000000000000000000000000000000000000000000000000000000')", self.nodes[2].gettxoutproof, ["ZZZ0000000000000000000000000000000000000000000000000000000000000"], blockhash) # Invalid blockhashes assert_raises_rpc_error( -8, "blockhash must be of length 64 (not 32, for '00000000000000000000000000000000')", self.nodes[2].gettxoutproof, [txid_spent], "00000000000000000000000000000000") assert_raises_rpc_error( -8, "blockhash must be hexadecimal string (not 'ZZZ0000000000000000000000000000000000000000000000000000000000000')", self.nodes[2].gettxoutproof, [txid_spent], "ZZZ0000000000000000000000000000000000000000000000000000000000000") # We can't find the block from a fully-spent tx assert_raises_rpc_error(-5, "Transaction not yet in block", self.nodes[2].gettxoutproof, [txid_spent]) # We can get the proof if we specify the block assert_equal(self.nodes[2].verifytxoutproof( self.nodes[2].gettxoutproof([txid_spent], blockhash)), [txid_spent]) # We can't get the proof if we specify a non-existent block assert_raises_rpc_error(-5, "Block not found", self.nodes[2].gettxoutproof, [ txid_spent], "0000000000000000000000000000000000000000000000000000000000000000") # We can get the proof if the transaction is unspent assert_equal(self.nodes[2].verifytxoutproof( self.nodes[2].gettxoutproof([txid_unspent])), [txid_unspent]) # We can get the proof if we provide a list of transactions and one of # them is unspent. The ordering of the list should not matter. assert_equal(sorted(self.nodes[2].verifytxoutproof( self.nodes[2].gettxoutproof([txid1, txid2]))), sorted(txlist)) assert_equal(sorted(self.nodes[2].verifytxoutproof( self.nodes[2].gettxoutproof([txid2, txid1]))), sorted(txlist)) # We can always get a proof if we have a -txindex assert_equal(self.nodes[2].verifytxoutproof( self.nodes[3].gettxoutproof([txid_spent])), [txid_spent]) # We can't get a proof if we specify transactions from different blocks assert_raises_rpc_error(-5, "Not all transactions found in specified or retrieved block", self.nodes[2].gettxoutproof, [txid1, txid3]) # Now we'll try tweaking a proof. proof = self.nodes[3].gettxoutproof([txid1, txid2]) assert txid1 in self.nodes[0].verifytxoutproof(proof) assert txid2 in self.nodes[1].verifytxoutproof(proof) tweaked_proof = FromHex(CMerkleBlock(), proof) # Make sure that our serialization/deserialization is working assert txid1 in self.nodes[2].verifytxoutproof(ToHex(tweaked_proof)) # Check to see if we can go up the merkle tree and pass this off as a # single-transaction block tweaked_proof.txn.nTransactions = 1 tweaked_proof.txn.vHash = [tweaked_proof.header.hashMerkleRoot] tweaked_proof.txn.vBits = [True] + [False] * 7 for n in self.nodes: assert not n.verifytxoutproof(ToHex(tweaked_proof)) # TODO: try more variants, eg transactions at different depths, and # verify that the proofs are invalid if __name__ == '__main__': MerkleBlockTest().main() diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index 0c24add75..47dc3ff10 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -1,237 +1,238 @@ #!/usr/bin/env python3 # Copyright (c) 2015-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Utilities for manipulating blocks and transactions.""" import unittest from .script import ( CScript, CScriptNum, CScriptOp, OP_1, OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160, OP_RETURN, OP_TRUE, ) from .messages import ( CBlock, COIN, + XEC, COutPoint, CTransaction, CTxIn, CTxOut, FromHex, ToHex, ) from .txtools import pad_tx from .util import assert_equal, satoshi_round # Genesis block time (regtest) TIME_GENESIS_BLOCK = 1296688602 def create_block(hashprev, coinbase, ntime=None, *, version=1): """Create a block (with regtest difficulty).""" block = CBlock() block.nVersion = version if ntime is None: import time block.nTime = int(time.time() + 600) else: block.nTime = ntime block.hashPrevBlock = hashprev # difficulty retargeting is disabled in REGTEST chainparams block.nBits = 0x207fffff block.vtx.append(coinbase) block.hashMerkleRoot = block.calc_merkle_root() block.calc_sha256() return block def make_conform_to_ctor(block): for tx in block.vtx: tx.rehash() block.vtx = [block.vtx[0]] + \ sorted(block.vtx[1:], key=lambda tx: tx.get_id()) def script_BIP34_coinbase_height(height): if height <= 16: res = CScriptOp.encode_op_n(height) # Append dummy to increase scriptSig size above 2 # (see bad-cb-length consensus rule) return CScript([res, OP_1]) return CScript([CScriptNum(height)]) def create_coinbase(height, pubkey=None): """Create a coinbase transaction, assuming no miner fees. If pubkey is passed in, the coinbase output will be a P2PK output; otherwise an anyone-can-spend output.""" coinbase = CTransaction() coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), 0xffffffff)) coinbaseoutput = CTxOut() coinbaseoutput.nValue = 50 * COIN halvings = int(height / 150) # regtest coinbaseoutput.nValue >>= halvings if (pubkey is not None): coinbaseoutput.scriptPubKey = CScript([pubkey, OP_CHECKSIG]) else: coinbaseoutput.scriptPubKey = CScript([OP_TRUE]) coinbase.vout = [coinbaseoutput] # Make sure the coinbase is at least 100 bytes pad_tx(coinbase) coinbase.calc_sha256() return coinbase def create_tx_with_script(prevtx, n, script_sig=b"", *, amount, script_pub_key=CScript()): """Return one-input, one-output transaction object spending the prevtx's n-th output with the given amount. Can optionally pass scriptPubKey and scriptSig, default is anyone-can-spend output. """ tx = CTransaction() assert(n < len(prevtx.vout)) tx.vin.append(CTxIn(COutPoint(prevtx.sha256, n), script_sig, 0xffffffff)) tx.vout.append(CTxOut(amount, script_pub_key)) pad_tx(tx) tx.calc_sha256() return tx def create_transaction(node, txid, to_address, *, amount): """ Return signed transaction spending the first output of the input txid. Note that the node must be able to sign for the output that is being spent, and the node must not be running multiple wallets. """ raw_tx = create_raw_transaction(node, txid, to_address, amount=amount) tx = FromHex(CTransaction(), raw_tx) return tx def create_raw_transaction(node, txid, to_address, *, amount): """ Return raw signed transaction spending the first output of the input txid. Note that the node must be able to sign for the output that is being spent, and the node must not be running multiple wallets. """ rawtx = node.createrawtransaction( inputs=[{"txid": txid, "vout": 0}], outputs={to_address: amount}) signresult = node.signrawtransactionwithwallet(rawtx) assert_equal(signresult["complete"], True) return signresult['hex'] def get_legacy_sigopcount_block(block, accurate=True): count = 0 for tx in block.vtx: count += get_legacy_sigopcount_tx(tx, accurate) return count def get_legacy_sigopcount_tx(tx, accurate=True): count = 0 for i in tx.vout: count += i.scriptPubKey.GetSigOpCount(accurate) for j in tx.vin: # scriptSig might be of type bytes, so convert to CScript for the # moment count += CScript(j.scriptSig).GetSigOpCount(accurate) return count def create_confirmed_utxos(node, count, age=101): """ Helper to create at least "count" utxos """ to_generate = int(0.5 * count) + age while to_generate > 0: node.generate(min(25, to_generate)) to_generate -= 25 utxos = node.listunspent() iterations = count - len(utxos) addr1 = node.getnewaddress() addr2 = node.getnewaddress() if iterations <= 0: return utxos for i in range(iterations): t = utxos.pop() inputs = [] inputs.append({"txid": t["txid"], "vout": t["vout"]}) outputs = {} outputs[addr1] = satoshi_round(t['amount'] / 2) outputs[addr2] = satoshi_round(t['amount'] / 2) raw_tx = node.createrawtransaction(inputs, outputs) ctx = FromHex(CTransaction(), raw_tx) fee = node.calculate_fee(ctx) // 2 ctx.vout[0].nValue -= fee # Due to possible truncation, we go ahead and take another satoshi in # fees to ensure the transaction gets through ctx.vout[1].nValue -= fee + 1 signed_tx = node.signrawtransactionwithwallet(ToHex(ctx))["hex"] node.sendrawtransaction(signed_tx) while (node.getmempoolinfo()['size'] > 0): node.generate(1) utxos = node.listunspent() assert len(utxos) >= count return utxos def mine_big_block(node, utxos=None): # generate a 66k transaction, # and 14 of them is close to the 1MB block limit num = 14 utxos = utxos if utxos is not None else [] if len(utxos) < num: utxos.clear() utxos.extend(node.listunspent()) send_big_transactions(node, utxos, num, 100) node.generate(1) def send_big_transactions(node, utxos, num, fee_multiplier): from .cashaddr import decode txids = [] padding = "1" * 512 addrHash = decode(node.getnewaddress())[2] for _ in range(num): ctx = CTransaction() utxo = utxos.pop() txid = int(utxo['txid'], 16) ctx.vin.append(CTxIn(COutPoint(txid, int(utxo["vout"])), b"")) ctx.vout.append( - CTxOut(int(satoshi_round(utxo['amount'] * COIN)), + CTxOut(int(satoshi_round(utxo['amount'] * XEC)), CScript([OP_DUP, OP_HASH160, addrHash, OP_EQUALVERIFY, OP_CHECKSIG]))) for i in range(0, 127): ctx.vout.append(CTxOut(0, CScript( [OP_RETURN, bytes(padding, 'utf-8')]))) # Create a proper fee for the transaction to be mined ctx.vout[0].nValue -= int(fee_multiplier * node.calculate_fee(ctx)) signresult = node.signrawtransactionwithwallet( ToHex(ctx), None, "NONE|FORKID") txid = node.sendrawtransaction(signresult["hex"], 0) txids.append(txid) return txids class TestFrameworkBlockTools(unittest.TestCase): def test_create_coinbase(self): height = 20 coinbase_tx = create_coinbase(height=height) assert_equal(CScriptNum.decode(coinbase_tx.vin[0].scriptSig), height) diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 4b9501973..a7fc51f41 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -1,2087 +1,2089 @@ #!/usr/bin/env python3 # Copyright (c) 2010 ArtForz -- public domain half-a-node # Copyright (c) 2012 Jeff Garzik # Copyright (c) 2010-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Bitcoin test framework primitive and message structures CBlock, CTransaction, CBlockHeader, CTxIn, CTxOut, etc....: data structures that should map to corresponding structures in bitcoin/primitives msg_block, msg_tx, msg_headers, etc.: data structures that represent network messages ser_*, deser_*: functions that handle serialization/deserialization. Classes use __slots__ to ensure extraneous attributes aren't accidentally added by tests, compromising their intended effect. """ from codecs import encode import copy import hashlib from io import BytesIO import random import socket import struct import time import unittest from typing import List from test_framework.siphash import siphash256 from test_framework.util import hex_str_to_bytes, assert_equal MIN_VERSION_SUPPORTED = 60001 # past bip-31 for ping/pong MY_VERSION = 70014 MY_SUBVERSION = b"/python-p2p-tester:0.0.3/" # from version 70001 onwards, fRelay should be appended to version # messages (BIP37) MY_RELAY = 1 MAX_LOCATOR_SZ = 101 MAX_BLOCK_BASE_SIZE = 1000000 MAX_BLOOM_FILTER_SIZE = 36000 MAX_BLOOM_HASH_FUNCS = 50 # 1 BCH in satoshis COIN = 100000000 +# 1 XEC in satoshis +XEC = 100 MAX_MONEY = 21000000 * COIN # Maximum length of incoming protocol messages MAX_PROTOCOL_MESSAGE_LENGTH = 2 * 1024 * 1024 MAX_HEADERS_RESULTS = 2000 # Number of headers sent in one getheaders result MAX_INV_SIZE = 50000 # Maximum number of entries in an 'inv' protocol message NODE_NETWORK = (1 << 0) NODE_GETUTXO = (1 << 1) NODE_BLOOM = (1 << 2) # NODE_WITNESS = (1 << 3) # NODE_XTHIN = (1 << 4) # removed in v0.22.12 NODE_COMPACT_FILTERS = (1 << 6) NODE_NETWORK_LIMITED = (1 << 10) NODE_AVALANCHE = (1 << 24) MSG_TX = 1 MSG_BLOCK = 2 MSG_FILTERED_BLOCK = 3 MSG_CMPCT_BLOCK = 4 MSG_AVA_PROOF = 0x1f000001 MSG_TYPE_MASK = 0xffffffff >> 2 FILTER_TYPE_BASIC = 0 # Serialization/deserialization tools def sha256(s): return hashlib.new('sha256', s).digest() def hash256(s): return sha256(sha256(s)) def ser_compact_size(size): r = b"" if size < 253: r = struct.pack("B", size) elif size < 0x10000: r = struct.pack(">= 32 return rs def uint256_from_str(s): r = 0 t = struct.unpack("> 24) & 0xFF v = (c & 0xFFFFFF) << (8 * (nbytes - 3)) return v # deser_function_name: Allow for an alternate deserialization function on the # entries in the vector. def deser_vector(f, c, deser_function_name=None): nit = deser_compact_size(f) r = [] for i in range(nit): t = c() if deser_function_name: getattr(t, deser_function_name)(f) else: t.deserialize(f) r.append(t) return r # ser_function_name: Allow for an alternate serialization function on the # entries in the vector. def ser_vector(v, ser_function_name=None): r = ser_compact_size(len(v)) for i in v: if ser_function_name: r += getattr(i, ser_function_name)() else: r += i.serialize() return r def deser_uint256_vector(f): nit = deser_compact_size(f) r = [] for i in range(nit): t = deser_uint256(f) r.append(t) return r def ser_uint256_vector(v): r = ser_compact_size(len(v)) for i in v: r += ser_uint256(i) return r def deser_string_vector(f): nit = deser_compact_size(f) r = [] for i in range(nit): t = deser_string(f) r.append(t) return r def ser_string_vector(v): r = ser_compact_size(len(v)) for sv in v: r += ser_string(sv) return r def FromHex(obj, hex_string): """Deserialize from a hex string representation (eg from RPC)""" obj.deserialize(BytesIO(hex_str_to_bytes(hex_string))) return obj def ToHex(obj): """Convert a binary-serializable object to hex (eg for submission via RPC)""" return obj.serialize().hex() # Objects that map to bitcoind objects, which can be serialized/deserialized class CAddress: __slots__ = ("net", "ip", "nServices", "port", "time") # see https://github.com/bitcoin/bips/blob/master/bip-0155.mediawiki NET_IPV4 = 1 ADDRV2_NET_NAME = { NET_IPV4: "IPv4" } ADDRV2_ADDRESS_LENGTH = { NET_IPV4: 4 } def __init__(self): self.time = 0 self.nServices = 1 self.net = self.NET_IPV4 self.ip = "0.0.0.0" self.port = 0 def deserialize(self, f, *, with_time=True): """Deserialize from addrv1 format (pre-BIP155)""" if with_time: # VERSION messages serialize CAddress objects without time self.time = struct.unpack("H", f.read(2))[0] def serialize(self, *, with_time=True): """Serialize in addrv1 format (pre-BIP155)""" assert self.net == self.NET_IPV4 r = b"" if with_time: # VERSION messages serialize CAddress objects without time r += struct.pack("H", self.port) return r def deserialize_v2(self, f): """Deserialize from addrv2 format (BIP155)""" self.time = struct.unpack("H", f.read(2))[0] def serialize_v2(self): """Serialize in addrv2 format (BIP155)""" assert self.net == self.NET_IPV4 r = b"" r += struct.pack("H", self.port) return r def __repr__(self): return ("CAddress(nServices=%i net=%s addr=%s port=%i)" % (self.nServices, self.ADDRV2_NET_NAME[self.net], self.ip, self.port)) class CInv: __slots__ = ("hash", "type") typemap = { 0: "Error", MSG_TX: "TX", MSG_BLOCK: "Block", MSG_FILTERED_BLOCK: "filtered Block", MSG_CMPCT_BLOCK: "CompactBlock", MSG_AVA_PROOF: "avalanche proof", } def __init__(self, t=0, h=0): self.type = t self.hash = h def deserialize(self, f): self.type = struct.unpack(" 21000000 * COIN: return False return True def __repr__(self): return "CTransaction(nVersion={} vin={} vout={} nLockTime={})".format( self.nVersion, repr(self.vin), repr(self.vout), self.nLockTime) class CBlockHeader: __slots__ = ("hash", "hashMerkleRoot", "hashPrevBlock", "nBits", "nNonce", "nTime", "nVersion", "sha256") def __init__(self, header=None): if header is None: self.set_null() else: self.nVersion = header.nVersion self.hashPrevBlock = header.hashPrevBlock self.hashMerkleRoot = header.hashMerkleRoot self.nTime = header.nTime self.nBits = header.nBits self.nNonce = header.nNonce self.sha256 = header.sha256 self.hash = header.hash self.calc_sha256() def set_null(self): self.nVersion = 1 self.hashPrevBlock = 0 self.hashMerkleRoot = 0 self.nTime = 0 self.nBits = 0 self.nNonce = 0 self.sha256 = None self.hash = None def deserialize(self, f): self.nVersion = struct.unpack(" 1: newhashes = [] for i in range(0, len(hashes), 2): i2 = min(i + 1, len(hashes) - 1) newhashes.append(hash256(hashes[i] + hashes[i2])) hashes = newhashes return uint256_from_str(hashes[0]) def calc_merkle_root(self): hashes = [] for tx in self.vtx: tx.calc_sha256() hashes.append(ser_uint256(tx.sha256)) return self.get_merkle_root(hashes) def is_valid(self): self.calc_sha256() target = uint256_from_compact(self.nBits) if self.sha256 > target: return False for tx in self.vtx: if not tx.is_valid(): return False if self.calc_merkle_root() != self.hashMerkleRoot: return False return True def solve(self): self.rehash() target = uint256_from_compact(self.nBits) while self.sha256 > target: self.nNonce += 1 self.rehash() def __repr__(self): return "CBlock(nVersion={} hashPrevBlock={:064x} hashMerkleRoot={:064x} nTime={} nBits={:08x} nNonce={:08x} vtx={})".format( self.nVersion, self.hashPrevBlock, self.hashMerkleRoot, self.nTime, self.nBits, self.nNonce, repr(self.vtx)) class PrefilledTransaction: __slots__ = ("index", "tx") def __init__(self, index=0, tx=None): self.index = index self.tx = tx def deserialize(self, f): self.index = deser_compact_size(f) self.tx = CTransaction() self.tx.deserialize(f) def serialize(self): r = b"" r += ser_compact_size(self.index) r += self.tx.serialize() return r def __repr__(self): return "PrefilledTransaction(index={}, tx={})".format( self.index, repr(self.tx)) # This is what we send on the wire, in a cmpctblock message. class P2PHeaderAndShortIDs: __slots__ = ("header", "nonce", "prefilled_txn", "prefilled_txn_length", "shortids", "shortids_length") def __init__(self): self.header = CBlockHeader() self.nonce = 0 self.shortids_length = 0 self.shortids = [] self.prefilled_txn_length = 0 self.prefilled_txn = [] def deserialize(self, f): self.header.deserialize(f) self.nonce = struct.unpack("> 1 self.pubkey = deser_string(f) def serialize(self) -> bytes: r = self.utxo.serialize() height_ser = self.height << 1 | int(self.is_coinbase) r += struct.pack(' bytes: """Return the bitcoin hash of the concatenation of proofid and the serialized stake.""" return hash256(proofid + self.serialize()) def __repr__(self): return f"AvalancheStake(utxo={self.utxo}, amount={self.amount}," \ f" height={self.height}, " \ f"pubkey={self.pubkey.hex()})" class AvalancheSignedStake: def __init__(self, stake=None, sig=b""): self.stake: AvalancheStake = stake or AvalancheStake() self.sig: bytes = sig """Signature for this stake, bytes of length 64""" def deserialize(self, f): self.stake = AvalancheStake() self.stake.deserialize(f) self.sig = f.read(64) def serialize(self) -> bytes: return self.stake.serialize() + self.sig class AvalancheProof: __slots__ = ( "sequence", "expiration", "master", "stakes", "limited_proofid", "proofid") def __init__(self, sequence=0, expiration=0, master=b"", signed_stakes=None): self.sequence: int = sequence self.expiration: int = expiration self.master: bytes = master self.stakes: List[AvalancheSignedStake] = signed_stakes or [ AvalancheSignedStake()] self.limited_proofid: int = None self.proofid: int = None self.compute_proof_id() def compute_proof_id(self): """Compute Bitcoin's 256-bit hash (double SHA-256) of the serialized proof data. """ ss = struct.pack(" int: return uint256_from_str(hash256( ser_uint256(self.limited_proofid) + ser_string(self.proof_master))) def deserialize(self, f): self.limited_proofid = deser_uint256(f) self.proof_master = deser_string(f) self.levels = deser_vector(f, AvalancheDelegationLevel) self.proofid = self.compute_proofid() def serialize(self): r = b"" r += ser_uint256(self.limited_proofid) r += ser_string(self.proof_master) r += ser_vector(self.levels) return r def __repr__(self): return f"AvalancheDelegation(limitedProofId={self.limited_proofid:064x}, " \ f"proofMaster={self.proof_master.hex()}, proofid={self.proofid:064x}, " \ f"levels={self.levels})" def getid(self): h = ser_uint256(self.proofid) for level in self.levels: h = hash256(h + ser_string(level.pubkey)) return h class AvalancheHello(): __slots__ = ("delegation", "sig") def __init__(self, delegation=AvalancheDelegation(), sig=b"\0" * 64): self.delegation = delegation self.sig = sig def deserialize(self, f): self.delegation.deserialize(f) self.sig = f.read(64) def serialize(self): r = b"" r += self.delegation.serialize() r += self.sig return r def __repr__(self): return "AvalancheHello(delegation={}, sig={})".format( repr(self.delegation), self.sig) def get_sighash(self, node): b = self.delegation.getid() b += struct.pack(" class msg_headers: __slots__ = ("headers",) msgtype = b"headers" def __init__(self, headers=None): self.headers = headers if headers is not None else [] def deserialize(self, f): # comment in bitcoind indicates these should be deserialized as blocks blocks = deser_vector(f, CBlock) for x in blocks: self.headers.append(CBlockHeader(x)) def serialize(self): blocks = [CBlock(x) for x in self.headers] return ser_vector(blocks) def __repr__(self): return "msg_headers(headers={})".format(repr(self.headers)) class msg_merkleblock: __slots__ = ("merkleblock",) msgtype = b"merkleblock" def __init__(self, merkleblock=None): if merkleblock is None: self.merkleblock = CMerkleBlock() else: self.merkleblock = merkleblock def deserialize(self, f): self.merkleblock.deserialize(f) def serialize(self): return self.merkleblock.serialize() def __repr__(self): return "msg_merkleblock(merkleblock={})".format(repr(self.merkleblock)) class msg_filterload: __slots__ = ("data", "nHashFuncs", "nTweak", "nFlags") msgtype = b"filterload" def __init__(self, data=b'00', nHashFuncs=0, nTweak=0, nFlags=0): self.data = data self.nHashFuncs = nHashFuncs self.nTweak = nTweak self.nFlags = nFlags def deserialize(self, f): self.data = deser_string(f) self.nHashFuncs = struct.unpack(" {}".format(self.binary, sys.argv[0])) self.coverage_dir = coverage_dir self.cwd = cwd if extra_conf is not None: append_config(datadir, extra_conf) # Most callers will just need to add extra args to the default list # below. # For those callers that need more flexibility, they can access the # default args using the provided facilities. # Note that common args are set in the config file (see # initialize_datadir) self.extra_args = extra_args # Configuration for logging is set as command-line args rather than in the bitcoin.conf file. # This means that starting a bitcoind using the temp dir to debug a failed test won't # spam debug.log. self.default_args = [ "-datadir=" + self.datadir, "-logtimemicros", "-logthreadnames", "-debug", "-debugexclude=libevent", "-debugexclude=leveldb", "-uacomment=" + self.name, "-noprinttoconsole", - "-ecash" + "-ecash", + "-xec" ] if use_valgrind: default_suppressions_file = os.path.join( os.path.dirname(os.path.realpath(__file__)), "..", "..", "..", "contrib", "valgrind.supp") suppressions_file = os.getenv("VALGRIND_SUPPRESSIONS_FILE", default_suppressions_file) self.binary = "valgrind" self.bitcoind_args = [bitcoind] + self.default_args self.default_args = ["--suppressions={}".format(suppressions_file), "--gen-suppressions=all", "--exit-on-first-error=yes", "--error-exitcode=1", "--quiet"] + self.bitcoind_args if emulator is not None: if not os.path.isfile(emulator): raise FileNotFoundError( "Emulator '{}' could not be found.".format(emulator)) self.emulator = emulator if use_cli and not os.path.isfile(bitcoin_cli): raise FileNotFoundError( "Binary '{}' could not be found.\nTry setting it manually:\n\tBITCOINCLI= {}".format(bitcoin_cli, sys.argv[0])) self.cli = TestNodeCLI(bitcoin_cli, self.datadir, self.emulator) self.use_cli = use_cli self.start_perf = start_perf self.running = False self.process = None self.rpc_connected = False self.rpc = None self.url = None self.relay_fee_cache = None self.log = logging.getLogger('TestFramework.node{}'.format(i)) # Whether to kill the node when this object goes away self.cleanup_on_exit = True # Cache perf subprocesses here by their data output filename. self.perf_subprocesses = {} self.p2ps = [] self.timeout_factor = timeout_factor AddressKeyPair = collections.namedtuple( 'AddressKeyPair', ['address', 'key']) PRIV_KEYS = [ # address , privkey AddressKeyPair( 'mjTkW3DjgyZck4KbiRusZsqTgaYTxdSz6z', 'cVpF924EspNh8KjYsfhgY96mmxvT6DgdWiTYMtMjuM74hJaU5psW'), AddressKeyPair( 'msX6jQXvxiNhx3Q62PKeLPrhrqZQdSimTg', 'cUxsWyKyZ9MAQTaAhUQWJmBbSvHMwSmuv59KgxQV7oZQU3PXN3KE'), AddressKeyPair( 'mnonCMyH9TmAsSj3M59DsbH8H63U3RKoFP', 'cTrh7dkEAeJd6b3MRX9bZK8eRmNqVCMH3LSUkE3dSFDyzjU38QxK'), AddressKeyPair( 'mqJupas8Dt2uestQDvV2NH3RU8uZh2dqQR', 'cVuKKa7gbehEQvVq717hYcbE9Dqmq7KEBKqWgWrYBa2CKKrhtRim'), AddressKeyPair( 'msYac7Rvd5ywm6pEmkjyxhbCDKqWsVeYws', 'cQDCBuKcjanpXDpCqacNSjYfxeQj8G6CAtH1Dsk3cXyqLNC4RPuh'), AddressKeyPair( 'n2rnuUnwLgXqf9kk2kjvVm8R5BZK1yxQBi', 'cQakmfPSLSqKHyMFGwAqKHgWUiofJCagVGhiB4KCainaeCSxeyYq'), AddressKeyPair( 'myzuPxRwsf3vvGzEuzPfK9Nf2RfwauwYe6', 'cQMpDLJwA8DBe9NcQbdoSb1BhmFxVjWD5gRyrLZCtpuF9Zi3a9RK'), AddressKeyPair( 'mumwTaMtbxEPUswmLBBN3vM9oGRtGBrys8', 'cSXmRKXVcoouhNNVpcNKFfxsTsToY5pvB9DVsFksF1ENunTzRKsy'), AddressKeyPair( 'mpV7aGShMkJCZgbW7F6iZgrvuPHjZjH9qg', 'cSoXt6tm3pqy43UMabY6eUTmR3eSUYFtB2iNQDGgb3VUnRsQys2k'), AddressKeyPair( 'mq4fBNdckGtvY2mijd9am7DRsbRB4KjUkf', 'cN55daf1HotwBAgAKWVgDcoppmUNDtQSfb7XLutTLeAgVc3u8hik'), AddressKeyPair( 'mpFAHDjX7KregM3rVotdXzQmkbwtbQEnZ6', 'cT7qK7g1wkYEMvKowd2ZrX1E5f6JQ7TM246UfqbCiyF7kZhorpX3'), AddressKeyPair( 'mzRe8QZMfGi58KyWCse2exxEFry2sfF2Y7', 'cPiRWE8KMjTRxH1MWkPerhfoHFn5iHPWVK5aPqjW8NxmdwenFinJ'), ] def get_deterministic_priv_key(self): """Return a deterministic priv key in base58, that only depends on the node's index""" assert len(self.PRIV_KEYS) == MAX_NODES return self.PRIV_KEYS[self.index] def _node_msg(self, msg: str) -> str: """Return a modified msg that identifies this node by its index as a debugging aid.""" return "[node {}] {}".format(self.index, msg) def _raise_assertion_error(self, msg: str): """Raise an AssertionError with msg modified to identify this node.""" raise AssertionError(self._node_msg(msg)) def __del__(self): # Ensure that we don't leave any bitcoind processes lying around after # the test ends if self.process and self.cleanup_on_exit: # Should only happen on test failure # Avoid using logger, as that may have already been shutdown when # this destructor is called. print(self._node_msg("Cleaning up leftover process")) self.process.kill() def __getattr__(self, name): """Dispatches any unrecognised messages to the RPC connection or a CLI instance.""" if self.use_cli: return getattr(RPCOverloadWrapper(self.cli, True), name) else: assert self.rpc is not None, self._node_msg( "Error: RPC not initialized") assert self.rpc_connected, self._node_msg( "Error: No RPC connection") return getattr(RPCOverloadWrapper(self.rpc), name) def clear_default_args(self): self.default_args.clear() def extend_default_args(self, args): self.default_args.extend(args) def remove_default_args(self, args): for rm_arg in args: # Remove all occurrences of rm_arg in self.default_args: # - if the arg is a flag (-flag), then the names must match # - if the arg is a value (-key=value) then the name must starts # with "-key=" (the '"' char is to avoid removing "-key_suffix" # arg is "-key" is the argument to remove). self.default_args = [def_arg for def_arg in self.default_args if rm_arg != def_arg and not def_arg.startswith(rm_arg + '=')] def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, **kwargs): """Start the node.""" if extra_args is None: extra_args = self.extra_args # Add a new stdout and stderr file each time bitcoind is started if stderr is None: stderr = tempfile.NamedTemporaryFile( dir=self.stderr_dir, delete=False) if stdout is None: stdout = tempfile.NamedTemporaryFile( dir=self.stdout_dir, delete=False) self.stderr = stderr self.stdout = stdout if cwd is None: cwd = self.cwd # Delete any existing cookie file -- if such a file exists (eg due to # unclean shutdown), it will get overwritten anyway by bitcoind, and # potentially interfere with our attempt to authenticate delete_cookie_file(self.datadir, self.chain) # add environment variable LIBC_FATAL_STDERR_=1 so that libc errors are # written to stderr and not the terminal subp_env = dict(os.environ, LIBC_FATAL_STDERR_="1") p_args = [self.binary] + self.default_args + extra_args if self.emulator is not None: p_args = [self.emulator] + p_args self.process = subprocess.Popen( p_args, env=subp_env, stdout=stdout, stderr=stderr, cwd=cwd, **kwargs) self.running = True self.log.debug("bitcoind started, waiting for RPC to come up") if self.start_perf: self._start_perf() def wait_for_rpc_connection(self): """Sets up an RPC connection to the bitcoind process. Returns False if unable to connect.""" # Poll at a rate of four times per second poll_per_s = 4 for _ in range(poll_per_s * self.rpc_timeout): if self.process.poll() is not None: raise FailedToStartError(self._node_msg( 'bitcoind exited with status {} during initialization'.format(self.process.returncode))) try: rpc = get_rpc_proxy( rpc_url( self.datadir, self.chain, self.host, self.rpc_port), self.index, # Shorter timeout to allow for one retry in case of # ETIMEDOUT timeout=self.rpc_timeout // 2, coveragedir=self.coverage_dir ) rpc.getblockcount() # If the call to getblockcount() succeeds then the RPC # connection is up wait_until(lambda: rpc.getmempoolinfo()['loaded']) # Wait for the node to finish reindex, block import, and # loading the mempool. Usually importing happens fast or # even "immediate" when the node is started. However, there # is no guarantee and sometimes ThreadImport might finish # later. This is going to cause intermittent test failures, # because generally the tests assume the node is fully # ready after being started. # # For example, the node will reject block messages from p2p # when it is still importing with the error "Unexpected # block message received" # # The wait is done here to make tests as robust as possible # and prevent racy tests and intermittent failures as much # as possible. Some tests might not need this, but the # overhead is trivial, and the added guarantees are worth # the minimal performance cost. self.log.debug("RPC successfully started") if self.use_cli: return self.rpc = rpc self.rpc_connected = True self.url = self.rpc.url return except JSONRPCException as e: # Initialization phase # -28 RPC in warmup # -342 Service unavailable, RPC server started but is shutting down due to error if e.error['code'] != -28 and e.error['code'] != -342: raise # unknown JSON RPC exception except ConnectionResetError: # This might happen when the RPC server is in warmup, but shut down before the call to getblockcount # succeeds. Try again to properly raise the FailedToStartError pass except OSError as e: if e.errno == errno.ETIMEDOUT: # Treat identical to ConnectionResetError pass elif e.errno == errno.ECONNREFUSED: # Port not yet open? pass else: # unknown OS error raise except ValueError as e: # cookie file not found and no rpcuser or rpcpassword; # bitcoind is still starting if "No RPC credentials" not in str(e): raise time.sleep(1.0 / poll_per_s) self._raise_assertion_error("Unable to connect to bitcoind") def wait_for_cookie_credentials(self): """Ensures auth cookie credentials can be read, e.g. for testing CLI with -rpcwait before RPC connection is up.""" self.log.debug("Waiting for cookie credentials") # Poll at a rate of four times per second. poll_per_s = 4 for _ in range(poll_per_s * self.rpc_timeout): try: get_auth_cookie(self.datadir, self.chain) self.log.debug("Cookie credentials successfully retrieved") return except ValueError: # cookie file not found and no rpcuser or rpcpassword; # bitcoind is still starting so we continue polling until # RPC credentials are retrieved pass time.sleep(1.0 / poll_per_s) self._raise_assertion_error( "Unable to retrieve cookie credentials after {}s".format( self.rpc_timeout)) def generate(self, nblocks, maxtries=1000000): self.log.debug( "TestNode.generate() dispatches `generate` call to `generatetoaddress`") return self.generatetoaddress( nblocks=nblocks, address=self.get_deterministic_priv_key().address, maxtries=maxtries) def get_wallet_rpc(self, wallet_name): if self.use_cli: return RPCOverloadWrapper( self.cli("-rpcwallet={}".format(wallet_name)), True) else: assert self.rpc is not None, self._node_msg( "Error: RPC not initialized") assert self.rpc_connected, self._node_msg( "Error: RPC not connected") wallet_path = "wallet/{}".format(urllib.parse.quote(wallet_name)) return RPCOverloadWrapper(self.rpc / wallet_path) def stop_node(self, expected_stderr='', *, wait=0, wait_until_stopped=True): """Stop the node.""" if not self.running: return self.log.debug("Stopping node") try: self.stop(wait=wait) except http.client.CannotSendRequest: self.log.exception("Unable to stop node.") # If there are any running perf processes, stop them. for profile_name in tuple(self.perf_subprocesses.keys()): self._stop_perf(profile_name) # Check that stderr is as expected self.stderr.seek(0) stderr = self.stderr.read().decode('utf-8').strip() if stderr != expected_stderr: raise AssertionError( "Unexpected stderr {} != {}".format(stderr, expected_stderr)) self.stdout.close() self.stderr.close() del self.p2ps[:] if wait_until_stopped: self.wait_until_stopped() def is_node_stopped(self): """Checks whether the node has stopped. Returns True if the node has stopped. False otherwise. This method is responsible for freeing resources (self.process).""" if not self.running: return True return_code = self.process.poll() if return_code is None: return False # process has stopped. Assert that it didn't return an error code. assert return_code == 0, self._node_msg( "Node returned non-zero exit code ({}) when stopping".format(return_code)) self.running = False self.process = None self.rpc_connected = False self.rpc = None self.log.debug("Node stopped") return True def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT): wait_until( self.is_node_stopped, timeout=timeout, timeout_factor=self.timeout_factor) @contextlib.contextmanager def assert_debug_log(self, expected_msgs, unexpected_msgs=None, timeout=2): """Assert that some debug messages are present within some timeout. Unexpected debug messages may be optionally provided to fail a test if they appear before expected messages. Note: expected_msgs must always be non-empty even if the goal is to check for unexpected_msgs. This provides a bounded scenario such that "we expect to reach some target resulting in expected_msgs without seeing unexpected_msgs. Otherwise, we are testing that something never happens, which is fundamentally not robust test logic. """ if not expected_msgs: raise AssertionError("Expected debug messages is empty") if unexpected_msgs is None: unexpected_msgs = [] time_end = time.time() + timeout * self.timeout_factor debug_log = os.path.join(self.datadir, self.chain, 'debug.log') with open(debug_log, encoding='utf-8') as dl: dl.seek(0, 2) prev_size = dl.tell() yield while True: found = True with open(debug_log, encoding='utf-8') as dl: dl.seek(prev_size) log = dl.read() print_log = " - " + "\n - ".join(log.splitlines()) for unexpected_msg in unexpected_msgs: if re.search(re.escape(unexpected_msg), log, flags=re.MULTILINE): self._raise_assertion_error( 'Unexpected message "{}" partially matches log:\n\n{}\n\n'.format( unexpected_msg, print_log)) for expected_msg in expected_msgs: if re.search(re.escape(expected_msg), log, flags=re.MULTILINE) is None: found = False if found: return if time.time() >= time_end: break time.sleep(0.05) self._raise_assertion_error( 'Expected messages "{}" does not partially match log:\n\n{}\n\n'.format( str(expected_msgs), print_log)) @contextlib.contextmanager def profile_with_perf(self, profile_name): """ Context manager that allows easy profiling of node activity using `perf`. See `test/functional/README.md` for details on perf usage. Args: profile_name (str): This string will be appended to the profile data filename generated by perf. """ subp = self._start_perf(profile_name) yield if subp: self._stop_perf(profile_name) def _start_perf(self, profile_name=None): """Start a perf process to profile this node. Returns the subprocess running perf.""" subp = None def test_success(cmd): return subprocess.call( # shell=True required for pipe use below cmd, shell=True, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) == 0 if not sys.platform.startswith('linux'): self.log.warning( "Can't profile with perf; only availabe on Linux platforms") return None if not test_success('which perf'): self.log.warning( "Can't profile with perf; must install perf-tools") return None if not test_success( 'readelf -S {} | grep .debug_str'.format(shlex.quote(self.binary))): self.log.warning( "perf output won't be very useful without debug symbols compiled into bitcoind") output_path = tempfile.NamedTemporaryFile( dir=self.datadir, prefix="{}.perf.data.".format(profile_name or 'test'), delete=False, ).name cmd = [ 'perf', 'record', '-g', # Record the callgraph. # Compatibility for gcc's --fomit-frame-pointer. '--call-graph', 'dwarf', '-F', '101', # Sampling frequency in Hz. '-p', str(self.process.pid), '-o', output_path, ] subp = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.perf_subprocesses[profile_name] = subp return subp def _stop_perf(self, profile_name): """Stop (and pop) a perf subprocess.""" subp = self.perf_subprocesses.pop(profile_name) output_path = subp.args[subp.args.index('-o') + 1] subp.terminate() subp.wait(timeout=10) stderr = subp.stderr.read().decode() if 'Consider tweaking /proc/sys/kernel/perf_event_paranoid' in stderr: self.log.warning( "perf couldn't collect data! Try " "'sudo sysctl -w kernel.perf_event_paranoid=-1'") else: report_cmd = "perf report -i {}".format(output_path) self.log.info("See perf output by running '{}'".format(report_cmd)) def assert_start_raises_init_error( self, extra_args=None, expected_msg=None, match=ErrorMatch.FULL_TEXT, *args, **kwargs): """Attempt to start the node and expect it to raise an error. extra_args: extra arguments to pass through to bitcoind expected_msg: regex that stderr should match when bitcoind fails Will throw if bitcoind starts without an error. Will throw if an expected_msg is provided and it does not match bitcoind's stdout.""" with tempfile.NamedTemporaryFile(dir=self.stderr_dir, delete=False) as log_stderr, \ tempfile.NamedTemporaryFile(dir=self.stdout_dir, delete=False) as log_stdout: try: self.start(extra_args, stdout=log_stdout, stderr=log_stderr, *args, **kwargs) self.wait_for_rpc_connection() self.stop_node() self.wait_until_stopped() except FailedToStartError as e: self.log.debug('bitcoind failed to start: {}'.format(e)) self.running = False self.process = None # Check stderr for expected message if expected_msg is not None: log_stderr.seek(0) stderr = log_stderr.read().decode('utf-8').strip() if match == ErrorMatch.PARTIAL_REGEX: if re.search(expected_msg, stderr, flags=re.MULTILINE) is None: self._raise_assertion_error( 'Expected message "{}" does not partially match stderr:\n"{}"'.format(expected_msg, stderr)) elif match == ErrorMatch.FULL_REGEX: if re.fullmatch(expected_msg, stderr) is None: self._raise_assertion_error( 'Expected message "{}" does not fully match stderr:\n"{}"'.format(expected_msg, stderr)) elif match == ErrorMatch.FULL_TEXT: if expected_msg != stderr: self._raise_assertion_error( 'Expected message "{}" does not fully match stderr:\n"{}"'.format(expected_msg, stderr)) else: if expected_msg is None: assert_msg = "bitcoind should have exited with an error" else: assert_msg = "bitcoind should have exited with expected error " + expected_msg self._raise_assertion_error(assert_msg) def relay_fee(self, cached=True): if not self.relay_fee_cache or not cached: self.relay_fee_cache = self.getnetworkinfo()["relayfee"] return self.relay_fee_cache def calculate_fee(self, tx): """ Estimate the necessary fees (in sats) for an unsigned CTransaction assuming: - the current relayfee on node - all inputs are compressed-key p2pkh, and will be signed ecdsa or schnorr - all inputs currently unsigned (empty scriptSig) """ billable_size_estimate = tx.billable_size() # Add some padding for signatures / public keys # 107 = length of PUSH(longest_sig = 72 bytes), PUSH(pubkey = 33 bytes) billable_size_estimate += len(tx.vin) * 107 # relay_fee gives a value in BCH per kB. - return int(self.relay_fee() / 1000 * billable_size_estimate * COIN) + return int(self.relay_fee() / 1000 * billable_size_estimate * XEC) def calculate_fee_from_txid(self, txid): ctx = FromHex(CTransaction(), self.getrawtransaction(txid)) return self.calculate_fee(ctx) def add_p2p_connection(self, p2p_conn, *, wait_for_verack=True, **kwargs): """Add a p2p connection to the node. This method adds the p2p connection to the self.p2ps list and also returns the connection to the caller.""" if 'dstport' not in kwargs: kwargs['dstport'] = p2p_port(self.index) if 'dstaddr' not in kwargs: kwargs['dstaddr'] = '127.0.0.1' p2p_conn.peer_connect( **kwargs, net=self.chain, timeout_factor=self.timeout_factor)() self.p2ps.append(p2p_conn) if wait_for_verack: # Wait for the node to send us the version and verack p2p_conn.wait_for_verack() # At this point we have sent our version message and received the version and verack, however the full node # has not yet received the verack from us (in reply to their version). So, the connection is not yet fully # established (fSuccessfullyConnected). # # This shouldn't lead to any issues when sending messages, since the verack will be in-flight before the # message we send. However, it might lead to races where we are expecting to receive a message. E.g. a # transaction that will be added to the mempool as soon as we return here. # # So syncing here is redundant when we only want to send a message, but the cost is low (a few milliseconds) # in comparison to the upside of making tests less fragile and # unexpected intermittent errors less likely. p2p_conn.sync_with_ping() return p2p_conn @property def p2p(self): """Return the first p2p connection Convenience property - most tests only use a single p2p connection to each node, so this saves having to write node.p2ps[0] many times.""" assert self.p2ps, self._node_msg("No p2p connection") return self.p2ps[0] def num_test_p2p_connections(self): """Return number of test framework p2p connections to the node.""" return len([peer for peer in self.getpeerinfo() if peer['subver'] == MY_SUBVERSION]) def disconnect_p2ps(self): """Close all p2p connections to the node.""" for p in self.p2ps: p.peer_disconnect() del self.p2ps[:] wait_until(lambda: self.num_test_p2p_connections() == 0) class TestNodeCLIAttr: def __init__(self, cli, command): self.cli = cli self.command = command def __call__(self, *args, **kwargs): return self.cli.send_cli(self.command, *args, **kwargs) def get_request(self, *args, **kwargs): return lambda: self(*args, **kwargs) def arg_to_cli(arg): if isinstance(arg, bool): return str(arg).lower() elif isinstance(arg, dict) or isinstance(arg, list): return json.dumps(arg, default=EncodeDecimal) else: return str(arg) class TestNodeCLI(): """Interface to bitcoin-cli for an individual node""" def __init__(self, binary, datadir, emulator=None): self.options = [] self.binary = binary self.datadir = datadir self.input = None self.log = logging.getLogger('TestFramework.bitcoincli') self.emulator = emulator def __call__(self, *options, input=None): # TestNodeCLI is callable with bitcoin-cli command-line options cli = TestNodeCLI(self.binary, self.datadir, self.emulator) cli.options = [str(o) for o in options] cli.input = input return cli def __getattr__(self, command): return TestNodeCLIAttr(self, command) def batch(self, requests): results = [] for request in requests: try: results.append(dict(result=request())) except JSONRPCException as e: results.append(dict(error=e)) return results def send_cli(self, command=None, *args, **kwargs): """Run bitcoin-cli command. Deserializes returned string as python object.""" pos_args = [arg_to_cli(arg) for arg in args] named_args = [str(key) + "=" + arg_to_cli(value) for (key, value) in kwargs.items()] assert not ( pos_args and named_args), "Cannot use positional arguments and named arguments in the same bitcoin-cli call" p_args = [self.binary, "-datadir=" + self.datadir] + self.options if named_args: p_args += ["-named"] if command is not None: p_args += [command] p_args += pos_args + named_args self.log.debug("Running bitcoin-cli {}".format(p_args[2:])) if self.emulator is not None: p_args = [self.emulator] + p_args process = subprocess.Popen(p_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) cli_stdout, cli_stderr = process.communicate(input=self.input) returncode = process.poll() if returncode: match = re.match( r'error code: ([-0-9]+)\nerror message:\n(.*)', cli_stderr) if match: code, message = match.groups() raise JSONRPCException(dict(code=int(code), message=message)) # Ignore cli_stdout, raise with cli_stderr raise subprocess.CalledProcessError( returncode, self.binary, output=cli_stderr) try: return json.loads(cli_stdout, parse_float=decimal.Decimal) except (json.JSONDecodeError, decimal.InvalidOperation): return cli_stdout.rstrip("\n") class RPCOverloadWrapper(): def __init__(self, rpc, cli=False): self.rpc = rpc self.is_cli = cli def __getattr__(self, name): return getattr(self.rpc, name) def importprivkey(self, privkey, label=None, rescan=None): wallet_info = self.getwalletinfo() if self.is_cli: if label is None: label = 'null' if rescan is None: rescan = 'null' if 'descriptors' not in wallet_info or ( 'descriptors' in wallet_info and not wallet_info['descriptors']): return self.__getattr__('importprivkey')(privkey, label, rescan) desc = descsum_create('combo(' + privkey + ')') req = [{ 'desc': desc, 'timestamp': 0 if rescan else 'now', 'label': label if label else '' }] import_res = self.importdescriptors(req) if not import_res[0]['success']: raise JSONRPCException(import_res[0]['error']) def addmultisigaddress(self, nrequired, keys, label=None): wallet_info = self.getwalletinfo() if self.is_cli: if label is None: label = 'null' if 'descriptors' not in wallet_info or ( 'descriptors' in wallet_info and not wallet_info['descriptors']): return self.__getattr__('addmultisigaddress')( nrequired, keys, label) cms = self.createmultisig(nrequired, keys) req = [{ 'desc': cms['descriptor'], 'timestamp': 0, 'label': label if label else '' }] import_res = self.importdescriptors(req) if not import_res[0]['success']: raise JSONRPCException(import_res[0]['error']) return cms def importpubkey(self, pubkey, label=None, rescan=None): wallet_info = self.getwalletinfo() if self.is_cli: if label is None: label = 'null' if rescan is None: rescan = 'null' if 'descriptors' not in wallet_info or ( 'descriptors' in wallet_info and not wallet_info['descriptors']): return self.__getattr__('importpubkey')(pubkey, label, rescan) desc = descsum_create('combo(' + pubkey + ')') req = [{ 'desc': desc, 'timestamp': 0 if rescan else 'now', 'label': label if label else '' }] import_res = self.importdescriptors(req) if not import_res[0]['success']: raise JSONRPCException(import_res[0]['error']) def importaddress(self, address, label=None, rescan=None, p2sh=None): wallet_info = self.getwalletinfo() if self.is_cli: if label is None: label = 'null' if rescan is None: rescan = 'null' if p2sh is None: p2sh = 'null' if 'descriptors' not in wallet_info or ( 'descriptors' in wallet_info and not wallet_info['descriptors']): return self.__getattr__('importaddress')( address, label, rescan, p2sh) is_hex = False try: int(address, 16) is_hex = True desc = descsum_create('raw(' + address + ')') except BaseException: desc = descsum_create('addr(' + address + ')') reqs = [{ 'desc': desc, 'timestamp': 0 if rescan else 'now', 'label': label if label else '' }] if is_hex and p2sh: reqs.append({ 'desc': descsum_create('p2sh(raw(' + address + '))'), 'timestamp': 0 if rescan else 'now', 'label': label if label else '' }) import_res = self.importdescriptors(reqs) for res in import_res: if not res['success']: raise JSONRPCException(res['error']) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index 1258fbb8d..513338ff9 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -1,604 +1,604 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Helpful routines for regression testing.""" from base64 import b64encode from binascii import unhexlify from decimal import Decimal, ROUND_DOWN from io import BytesIO from subprocess import CalledProcessError import inspect import json import logging import os import random import re import time from . import coverage from .authproxy import AuthServiceProxy, JSONRPCException logger = logging.getLogger("TestFramework.utils") # Assert functions ################## -def assert_approx(v, vexp, vspan=0.00001): +def assert_approx(v, vexp, vspan=10): """Assert that `v` is within `vspan` of `vexp`""" if v < vexp - vspan: raise AssertionError("{} < [{}..{}]".format( str(v), str(vexp - vspan), str(vexp + vspan))) if v > vexp + vspan: raise AssertionError("{} > [{}..{}]".format( str(v), str(vexp - vspan), str(vexp + vspan))) def assert_fee_amount(fee, tx_size, fee_per_kB, wiggleroom=2): """ Assert the fee was in range wiggleroom defines an amount that the test expects the wallet to be off by when estimating fees. This can be due to the dummy signature that is added during fee calculation, or due to the wallet funding transactions using the ceiling of the calculated fee. """ target_fee = round(tx_size * fee_per_kB / 1000, 8) if fee < (tx_size - wiggleroom) * fee_per_kB / 1000: raise AssertionError( "Fee of {} BCH too low! (Should be {} BCH)".format(str(fee), str(target_fee))) if fee > (tx_size + wiggleroom) * fee_per_kB / 1000: raise AssertionError( "Fee of {} BCH too high! (Should be {} BCH)".format(str(fee), str(target_fee))) def assert_equal(thing1, thing2, *args): if thing1 != thing2 or any(thing1 != arg for arg in args): raise AssertionError("not({})".format(" == ".join(str(arg) for arg in (thing1, thing2) + args))) def assert_greater_than(thing1, thing2): if thing1 <= thing2: raise AssertionError("{} <= {}".format(str(thing1), str(thing2))) def assert_greater_than_or_equal(thing1, thing2): if thing1 < thing2: raise AssertionError("{} < {}".format(str(thing1), str(thing2))) def assert_raises(exc, fun, *args, **kwds): assert_raises_message(exc, None, fun, *args, **kwds) def assert_raises_message(exc, message, fun, *args, **kwds): try: fun(*args, **kwds) except JSONRPCException: raise AssertionError( "Use assert_raises_rpc_error() to test RPC failures") except exc as e: if message is not None and message not in e.error['message']: raise AssertionError( "Expected substring not found in error message:\nsubstring: '{}'\nerror message: '{}'.".format( message, e.error['message'])) except Exception as e: raise AssertionError( "Unexpected exception raised: " + type(e).__name__) else: raise AssertionError("No exception raised") def assert_raises_process_error(returncode, output, fun, *args, **kwds): """Execute a process and asserts the process return code and output. Calls function `fun` with arguments `args` and `kwds`. Catches a CalledProcessError and verifies that the return code and output are as expected. Throws AssertionError if no CalledProcessError was raised or if the return code and output are not as expected. Args: returncode (int): the process return code. output (string): [a substring of] the process output. fun (function): the function to call. This should execute a process. args*: positional arguments for the function. kwds**: named arguments for the function. """ try: fun(*args, **kwds) except CalledProcessError as e: if returncode != e.returncode: raise AssertionError( "Unexpected returncode {}".format(e.returncode)) if output not in e.output: raise AssertionError("Expected substring not found:" + e.output) else: raise AssertionError("No exception raised") def assert_raises_rpc_error(code, message, fun, *args, **kwds): """Run an RPC and verify that a specific JSONRPC exception code and message is raised. Calls function `fun` with arguments `args` and `kwds`. Catches a JSONRPCException and verifies that the error code and message are as expected. Throws AssertionError if no JSONRPCException was raised or if the error code/message are not as expected. Args: code (int), optional: the error code returned by the RPC call (defined in src/rpc/protocol.h). Set to None if checking the error code is not required. message (string), optional: [a substring of] the error string returned by the RPC call. Set to None if checking the error string is not required. fun (function): the function to call. This should be the name of an RPC. args*: positional arguments for the function. kwds**: named arguments for the function. """ assert try_rpc(code, message, fun, *args, **kwds), "No exception raised" def try_rpc(code, message, fun, *args, **kwds): """Tries to run an rpc command. Test against error code and message if the rpc fails. Returns whether a JSONRPCException was raised.""" try: fun(*args, **kwds) except JSONRPCException as e: # JSONRPCException was thrown as expected. Check the code and message # values are correct. if (code is not None) and (code != e.error["code"]): raise AssertionError( "Unexpected JSONRPC error code {}".format(e.error["code"])) if (message is not None) and (message not in e.error['message']): raise AssertionError( "Expected substring not found in error message:\nsubstring: '{}'\nerror message: '{}'.".format( message, e.error['message'])) return True except Exception as e: raise AssertionError( "Unexpected exception raised: " + type(e).__name__) else: return False def assert_is_hex_string(string): try: int(string, 16) except Exception as e: raise AssertionError( "Couldn't interpret {!r} as hexadecimal; raised: {}".format(string, e)) def assert_is_hash_string(string, length=64): if not isinstance(string, str): raise AssertionError( "Expected a string, got type {!r}".format(type(string))) elif length and len(string) != length: raise AssertionError( "String of length {} expected; got {}".format(length, len(string))) elif not re.match('[abcdef0-9]+$', string): raise AssertionError( "String {!r} contains invalid characters for a hash.".format(string)) def assert_array_result(object_array, to_match, expected, should_not_find=False): """ Pass in array of JSON objects, a dictionary with key/value pairs to match against, and another dictionary with expected key/value pairs. If the should_not_find flag is true, to_match should not be found in object_array """ if should_not_find: assert_equal(expected, {}) num_matched = 0 for item in object_array: all_match = True for key, value in to_match.items(): if item[key] != value: all_match = False if not all_match: continue elif should_not_find: num_matched = num_matched + 1 for key, value in expected.items(): if item[key] != value: raise AssertionError("{} : expected {}={}".format( str(item), str(key), str(value))) num_matched = num_matched + 1 if num_matched == 0 and not should_not_find: raise AssertionError("No objects matched {}".format(str(to_match))) if num_matched > 0 and should_not_find: raise AssertionError("Objects were found {}".format(str(to_match))) # Utility functions ################### def check_json_precision(): """Make sure json library being used does not lose precision converting BCH values""" n = Decimal("20000000.00000003") satoshis = int(json.loads(json.dumps(float(n))) * 1.0e8) if satoshis != 2000000000000003: raise RuntimeError("JSON encode/decode loses precision") def EncodeDecimal(o): if isinstance(o, Decimal): return str(o) raise TypeError(repr(o) + " is not JSON serializable") def count_bytes(hex_string): return len(bytearray.fromhex(hex_string)) def hex_str_to_bytes(hex_str): return unhexlify(hex_str.encode('ascii')) def str_to_b64str(string): return b64encode(string.encode('utf-8')).decode('ascii') def satoshi_round(amount): - return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN) + return Decimal(amount).quantize(Decimal('0.01'), rounding=ROUND_DOWN) def wait_until(predicate, *, attempts=float('inf'), timeout=float('inf'), lock=None, timeout_factor=1.0): if attempts == float('inf') and timeout == float('inf'): timeout = 60 timeout = timeout * timeout_factor attempt = 0 time_end = time.time() + timeout while attempt < attempts and time.time() < time_end: if lock: with lock: if predicate(): return else: if predicate(): return attempt += 1 time.sleep(0.05) # Print the cause of the timeout predicate_source = "''''\n" + inspect.getsource(predicate) + "'''" logger.error("wait_until() failed. Predicate: {}".format(predicate_source)) if attempt >= attempts: raise AssertionError("Predicate {} not true after {} attempts".format( predicate_source, attempts)) elif time.time() >= time_end: raise AssertionError( "Predicate {} not true after {} seconds".format(predicate_source, timeout)) raise RuntimeError('Unreachable') # RPC/P2P connection constants and functions ############################################ # The maximum number of nodes a single test can spawn MAX_NODES = 12 # Don't assign rpc or p2p ports lower than this (for example: 18333 is the # default testnet port) PORT_MIN = int(os.getenv('TEST_RUNNER_PORT_MIN', default=20000)) # The number of ports to "reserve" for p2p and rpc, each PORT_RANGE = 5000 class PortSeed: # Must be initialized with a unique integer for each process n = None def get_rpc_proxy(url, node_number, *, timeout=None, coveragedir=None): """ Args: url (str): URL of the RPC server to call node_number (int): the node number (or id) that this calls to Kwargs: timeout (int): HTTP timeout in seconds coveragedir (str): Directory Returns: AuthServiceProxy. convenience object for making RPC calls. """ proxy_kwargs = {} if timeout is not None: proxy_kwargs['timeout'] = int(timeout) proxy = AuthServiceProxy(url, **proxy_kwargs) proxy.url = url # store URL on proxy for info coverage_logfile = coverage.get_filename( coveragedir, node_number) if coveragedir else None return coverage.AuthServiceProxyWrapper(proxy, coverage_logfile) def p2p_port(n): assert n <= MAX_NODES return PORT_MIN + n + \ (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES) def rpc_port(n): return PORT_MIN + PORT_RANGE + n + \ (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES) def rpc_url(datadir, chain, host, port): rpc_u, rpc_p = get_auth_cookie(datadir, chain) if host is None: host = '127.0.0.1' return "http://{}:{}@{}:{}".format(rpc_u, rpc_p, host, int(port)) # Node functions ################ def initialize_datadir(dirname, n, chain): datadir = get_datadir_path(dirname, n) if not os.path.isdir(datadir): os.makedirs(datadir) # Translate chain name to config name if chain == 'testnet3': chain_name_conf_arg = 'testnet' chain_name_conf_section = 'test' else: chain_name_conf_arg = chain chain_name_conf_section = chain with open(os.path.join(datadir, "bitcoin.conf"), 'w', encoding='utf8') as f: f.write("{}=1\n".format(chain_name_conf_arg)) f.write("[{}]\n".format(chain_name_conf_section)) f.write("port=" + str(p2p_port(n)) + "\n") f.write("rpcport=" + str(rpc_port(n)) + "\n") - f.write("fallbackfee=0.0002\n") + f.write("fallbackfee=200\n") f.write("server=1\n") f.write("keypool=1\n") f.write("discover=0\n") f.write("dnsseed=0\n") f.write("listenonion=0\n") f.write("usecashaddr=1\n") f.write("shrinkdebugfile=0\n") os.makedirs(os.path.join(datadir, 'stderr'), exist_ok=True) os.makedirs(os.path.join(datadir, 'stdout'), exist_ok=True) return datadir def get_datadir_path(dirname, n): return os.path.join(dirname, "node" + str(n)) def append_config(datadir, options): with open(os.path.join(datadir, "bitcoin.conf"), 'a', encoding='utf8') as f: for option in options: f.write(option + "\n") def get_auth_cookie(datadir, chain): user = None password = None if os.path.isfile(os.path.join(datadir, "bitcoin.conf")): with open(os.path.join(datadir, "bitcoin.conf"), 'r', encoding='utf8') as f: for line in f: if line.startswith("rpcuser="): assert user is None # Ensure that there is only one rpcuser line user = line.split("=")[1].strip("\n") if line.startswith("rpcpassword="): assert password is None # Ensure that there is only one rpcpassword line password = line.split("=")[1].strip("\n") try: with open(os.path.join(datadir, chain, ".cookie"), 'r', encoding="ascii") as f: userpass = f.read() split_userpass = userpass.split(':') user = split_userpass[0] password = split_userpass[1] except OSError: pass if user is None or password is None: raise ValueError("No RPC credentials") return user, password # If a cookie file exists in the given datadir, delete it. def delete_cookie_file(datadir, chain): if os.path.isfile(os.path.join(datadir, chain, ".cookie")): logger.debug("Deleting leftover cookie file") os.remove(os.path.join(datadir, chain, ".cookie")) def set_node_times(nodes, t): for node in nodes: node.setmocktime(t) def disconnect_nodes(from_node, to_node): def get_peer_ids(): result = [] for peer in from_node.getpeerinfo(): if to_node.name in peer['subver']: result.append(peer['id']) return result peer_ids = get_peer_ids() if not peer_ids: logger.warning( f"disconnect_nodes: {from_node.index} and {to_node.index} were not connected") return for peer_id in peer_ids: try: from_node.disconnectnode(nodeid=peer_id) except JSONRPCException as e: # If this node is disconnected between calculating the peer id # and issuing the disconnect, don't worry about it. # This avoids a race condition if we're mass-disconnecting peers. if e.error['code'] != -29: # RPC_CLIENT_NODE_NOT_CONNECTED raise # wait to disconnect wait_until(lambda: not get_peer_ids(), timeout=5) def connect_nodes(from_node, to_node): host = to_node.host if host is None: host = '127.0.0.1' ip_port = host + ':' + str(to_node.p2p_port) from_node.addnode(ip_port, "onetry") # poll until version handshake complete to avoid race conditions # with transaction relaying # See comments in net_processing: # * Must have a version message before anything else # * Must have a verack message before anything else wait_until( lambda: all( peer['version'] != 0 for peer in from_node.getpeerinfo())) wait_until( lambda: all( peer['bytesrecv_per_msg'].pop( 'verack', 0) == 24 for peer in from_node.getpeerinfo())) # Transaction/Block functions ############################# def find_output(node, txid, amount, *, blockhash=None): """ Return index to output of txid with value amount Raises exception if there is none. """ txdata = node.getrawtransaction(txid, 1, blockhash) for i in range(len(txdata["vout"])): if txdata["vout"][i]["value"] == amount: return i raise RuntimeError("find_output txid {} : {} not found".format( txid, str(amount))) def gather_inputs(from_node, amount_needed, confirmations_required=1): """ Return a random set of unspent txouts that are enough to pay amount_needed """ assert confirmations_required >= 0 utxo = from_node.listunspent(confirmations_required) random.shuffle(utxo) inputs = [] total_in = Decimal("0.00000000") while total_in < amount_needed and len(utxo) > 0: t = utxo.pop() total_in += t["amount"] inputs.append( {"txid": t["txid"], "vout": t["vout"], "address": t["address"]}) if total_in < amount_needed: raise RuntimeError("Insufficient funds: need {}, have {}".format( amount_needed, total_in)) return (total_in, inputs) def make_change(from_node, amount_in, amount_out, fee): """ Create change output(s), return them """ outputs = {} amount = amount_out + fee change = amount_in - amount if change > amount * 2: # Create an extra change output to break up big inputs change_address = from_node.getnewaddress() # Split change in two, being careful of rounding: outputs[change_address] = Decimal( change / 2).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN) change = amount_in - amount - outputs[change_address] if change > 0: outputs[from_node.getnewaddress()] = change return outputs def random_transaction(nodes, amount, min_fee, fee_increment, fee_variants): """ Create a random transaction. Returns (txid, hex-encoded-transaction-data, fee) """ from_node = random.choice(nodes) to_node = random.choice(nodes) fee = min_fee + fee_increment * random.randint(0, fee_variants) (total_in, inputs) = gather_inputs(from_node, amount + fee) outputs = make_change(from_node, total_in, amount, fee) outputs[to_node.getnewaddress()] = float(amount) rawtx = from_node.createrawtransaction(inputs, outputs) signresult = from_node.signrawtransactionwithwallet(rawtx) txid = from_node.sendrawtransaction(signresult["hex"], 0) return (txid, signresult["hex"], fee) # Create large OP_RETURN txouts that can be appended to a transaction # to make it large (helper for constructing large transactions). def gen_return_txouts(): # Some pre-processing to create a bunch of OP_RETURN txouts to insert into transactions we create # So we have big transactions (and therefore can't fit very many into each block) # create one script_pubkey script_pubkey = "6a4d0200" # OP_RETURN OP_PUSH2 512 bytes for i in range(512): script_pubkey = script_pubkey + "01" # concatenate 128 txouts of above script_pubkey which we'll insert before # the txout for change txouts = [] from .messages import CTxOut txout = CTxOut() txout.nValue = 0 txout.scriptPubKey = hex_str_to_bytes(script_pubkey) for k in range(128): txouts.append(txout) return txouts # Create a spend of each passed-in utxo, splicing in "txouts" to each raw # transaction to make it large. See gen_return_txouts() above. def create_lots_of_big_transactions(node, txouts, utxos, num, fee): addr = node.getnewaddress() txids = [] from .messages import CTransaction for _ in range(num): t = utxos.pop() inputs = [{"txid": t["txid"], "vout": t["vout"]}] outputs = {} change = t['amount'] - fee outputs[addr] = satoshi_round(change) rawtx = node.createrawtransaction(inputs, outputs) tx = CTransaction() tx.deserialize(BytesIO(hex_str_to_bytes(rawtx))) for txout in txouts: tx.vout.append(txout) newtx = tx.serialize().hex() signresult = node.signrawtransactionwithwallet( newtx, None, "NONE|FORKID") txid = node.sendrawtransaction(signresult["hex"], 0) txids.append(txid) return txids def find_vout_for_address(node, txid, addr): """ Locate the vout index of the given transaction sending to the given address. Raises runtime error exception if not found. """ tx = node.getrawtransaction(txid, True) for i in range(len(tx["vout"])): if any([addr == a for a in tx["vout"][i]["scriptPubKey"]["addresses"]]): return i raise RuntimeError( "Vout not found for address: txid={}, addr={}".format(txid, addr)) diff --git a/test/functional/wallet_abandonconflict.py b/test/functional/wallet_abandonconflict.py index f1805691a..af9717cf5 100755 --- a/test/functional/wallet_abandonconflict.py +++ b/test/functional/wallet_abandonconflict.py @@ -1,225 +1,233 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the abandontransaction RPC. The abandontransaction RPC marks a transaction and all its in-wallet descendants as abandoned which allows their inputs to be respent. It can be used to replace "stuck" or evicted transactions. It only works on transactions which are not included in a block and are not currently in the mempool. It has no effect on transactions which are already abandoned. """ from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, connect_nodes, disconnect_nodes, satoshi_round, ) class AbandonConflictTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 - self.extra_args = [["-minrelaytxfee=0.00001"], []] + self.extra_args = [["-minrelaytxfee=10"], []] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): def total_fees(*txids): total = 0 for txid in txids: # '-=' is because gettransaction(txid)['fee'] returns a negative total -= self.nodes[0].gettransaction(txid)['fee'] return satoshi_round(total) self.nodes[1].generate(100) self.sync_blocks() balance = self.nodes[0].getbalance() txA = self.nodes[0].sendtoaddress( - self.nodes[0].getnewaddress(), Decimal("10")) + self.nodes[0].getnewaddress(), Decimal("10000000")) txB = self.nodes[0].sendtoaddress( - self.nodes[0].getnewaddress(), Decimal("10")) + self.nodes[0].getnewaddress(), Decimal("10000000")) txC = self.nodes[0].sendtoaddress( - self.nodes[0].getnewaddress(), Decimal("10")) + self.nodes[0].getnewaddress(), Decimal("10000000")) self.sync_mempools() self.nodes[1].generate(1) # Can not abandon non-wallet transaction assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', lambda: self.nodes[0].abandontransaction(txid='ff' * 32)) # Can not abandon confirmed transaction assert_raises_rpc_error(-5, 'Transaction not eligible for abandonment', lambda: self.nodes[0].abandontransaction(txid=txA)) self.sync_blocks() newbalance = self.nodes[0].getbalance() # no more than fees lost assert balance - newbalance <= total_fees(txA, txB, txC) balance = newbalance # Disconnect nodes so node0's transactions don't get into node1's # mempool disconnect_nodes(self.nodes[0], self.nodes[1]) # Identify the 10btc outputs nA = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction( - txA)["details"] if tx_out["amount"] == Decimal("10")) + txA)["details"] if tx_out["amount"] == Decimal("10000000")) nB = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction( - txB)["details"] if tx_out["amount"] == Decimal("10")) + txB)["details"] if tx_out["amount"] == Decimal("10000000")) nC = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction( - txC)["details"] if tx_out["amount"] == Decimal("10")) + txC)["details"] if tx_out["amount"] == Decimal("10000000")) inputs = [] # spend 10btc outputs from txA and txB inputs.append({"txid": txA, "vout": nA}) inputs.append({"txid": txB, "vout": nB}) outputs = {} - outputs[self.nodes[0].getnewaddress()] = Decimal("14.99998") - outputs[self.nodes[1].getnewaddress()] = Decimal("5") + outputs[self.nodes[0].getnewaddress()] = Decimal("14999980") + outputs[self.nodes[1].getnewaddress()] = Decimal("5000000") signed = self.nodes[0].signrawtransactionwithwallet( self.nodes[0].createrawtransaction(inputs, outputs)) txAB1 = self.nodes[0].sendrawtransaction(signed["hex"]) # Identify the 14.99998btc output nAB = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction( - txAB1)["details"] if tx_out["amount"] == Decimal("14.99998")) + txAB1)["details"] if tx_out["amount"] == Decimal("14999980")) # Create a child tx spending AB1 and C inputs = [] # Amount 14.99998 BCH inputs.append({"txid": txAB1, "vout": nAB}) # Amount 10 BCH inputs.append({"txid": txC, "vout": nC}) outputs = {} - outputs[self.nodes[0].getnewaddress()] = Decimal("24.9996") + outputs[self.nodes[0].getnewaddress()] = Decimal("24999600") signed2 = self.nodes[0].signrawtransactionwithwallet( self.nodes[0].createrawtransaction(inputs, outputs)) txABC2 = self.nodes[0].sendrawtransaction(signed2["hex"]) # Create a child tx spending ABC2 - signed3_change = Decimal("24.999") + signed3_change = Decimal("24999000") inputs = [{"txid": txABC2, "vout": 0}] outputs = {self.nodes[0].getnewaddress(): signed3_change} signed3 = self.nodes[0].signrawtransactionwithwallet( self.nodes[0].createrawtransaction(inputs, outputs)) # note tx is never directly referenced, only abandoned as a child of # the above self.nodes[0].sendrawtransaction(signed3["hex"]) # In mempool txs from self should increase balance from change newbalance = self.nodes[0].getbalance() - assert_equal(newbalance, balance - Decimal("30") + signed3_change) + assert_equal( + newbalance, + balance - + Decimal("30000000") + + signed3_change) balance = newbalance # Restart the node with a higher min relay fee so the parent tx is no longer in mempool # TODO: redo with eviction - self.restart_node(0, extra_args=["-minrelaytxfee=0.0001"]) + self.restart_node(0, extra_args=["-minrelaytxfee=100"]) assert self.nodes[0].getmempoolinfo()['loaded'] # Verify txs no longer in either node's mempool assert_equal(len(self.nodes[0].getrawmempool()), 0) assert_equal(len(self.nodes[1].getrawmempool()), 0) # Transactions which are not in the mempool should only reduce wallet balance. # Transaction inputs should still be spent, but the change not yet # received. newbalance = self.nodes[0].getbalance() assert_equal(newbalance, balance - signed3_change) # Unconfirmed received funds that are not in mempool also shouldn't show # up in unconfirmed balance. Note that the transactions stored in the wallet # are not necessarily in the node's mempool. balances = self.nodes[0].getbalances()['mine'] assert_equal( balances['untrusted_pending'] + balances['trusted'], newbalance) # Unconfirmed transactions which are not in the mempool should also # not be in listunspent assert txABC2 not in [utxo["txid"] for utxo in self.nodes[0].listunspent(0)] balance = newbalance # Abandon original transaction and verify inputs are available again # including that the child tx was also abandoned self.nodes[0].abandontransaction(txAB1) newbalance = self.nodes[0].getbalance() - assert_equal(newbalance, balance + Decimal("30")) + assert_equal(newbalance, balance + Decimal("30000000")) balance = newbalance # Verify that even with a low min relay fee, the tx is not re-accepted # from wallet on startup once abandoned. - self.restart_node(0, extra_args=["-minrelaytxfee=0.00001"]) + self.restart_node(0, extra_args=["-minrelaytxfee=10"]) assert self.nodes[0].getmempoolinfo()['loaded'] assert_equal(len(self.nodes[0].getrawmempool()), 0) assert_equal(self.nodes[0].getbalance(), balance) # If the transaction is re-sent the wallet also unabandons it. The # change should be available, and it's child transaction should remain # abandoned. # NOTE: Abandoned transactions are internal to the wallet, and tracked # separately from other indices. self.nodes[0].sendrawtransaction(signed["hex"]) newbalance = self.nodes[0].getbalance() - assert_equal(newbalance, balance - Decimal("20") + Decimal("14.99998")) + assert_equal( + newbalance, + balance - + Decimal("20000000") + + Decimal("14999980")) balance = newbalance # Send child tx again so it is no longer abandoned. self.nodes[0].sendrawtransaction(signed2["hex"]) newbalance = self.nodes[0].getbalance() - assert_equal(newbalance, balance - Decimal("10") - - Decimal("14.99998") + Decimal("24.9996")) + assert_equal(newbalance, balance - Decimal("10000000") + - Decimal("14999980") + Decimal("24999600")) balance = newbalance # Reset to a higher relay fee so that we abandon a transaction - self.restart_node(0, extra_args=["-minrelaytxfee=0.0001"]) + self.restart_node(0, extra_args=["-minrelaytxfee=100"]) assert self.nodes[0].getmempoolinfo()['loaded'] assert_equal(len(self.nodes[0].getrawmempool()), 0) newbalance = self.nodes[0].getbalance() - assert_equal(newbalance, balance - Decimal("24.9996")) + assert_equal(newbalance, balance - Decimal("24999600")) balance = newbalance # Create a double spend of AB1. Spend it again from only A's 10 output. # Mine double spend from node 1. inputs = [] inputs.append({"txid": txA, "vout": nA}) outputs = {} - outputs[self.nodes[1].getnewaddress()] = Decimal("9.9999") + outputs[self.nodes[1].getnewaddress()] = Decimal("9999900") tx = self.nodes[0].createrawtransaction(inputs, outputs) signed = self.nodes[0].signrawtransactionwithwallet(tx) self.nodes[1].sendrawtransaction(signed["hex"]) self.nodes[1].generate(1) connect_nodes(self.nodes[0], self.nodes[1]) self.sync_blocks() # Verify that B and C's 10 BCH outputs are available for spending again # because AB1 is now conflicted newbalance = self.nodes[0].getbalance() - assert_equal(newbalance, balance + Decimal("20")) + assert_equal(newbalance, balance + Decimal("20000000")) balance = newbalance # There is currently a minor bug around this and so this test doesn't work. See Issue #7315 # Invalidate the block with the double spend and B's 10 BCH output should no longer be available # Don't think C's should either self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) newbalance = self.nodes[0].getbalance() # assert_equal(newbalance, balance - Decimal("10")) self.log.info( "If balance has not declined after invalidateblock then out of mempool wallet tx which is no longer") self.log.info( "conflicted has not resumed causing its inputs to be seen as spent. See Issue #7315") self.log.info(str(balance) + " -> " + str(newbalance) + " ?") if __name__ == '__main__': AbandonConflictTest().main() diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py index fd4fdd70a..4105917ab 100755 --- a/test/functional/wallet_address_types.py +++ b/test/functional/wallet_address_types.py @@ -1,303 +1,303 @@ #!/usr/bin/env python3 # Copyright (c) 2017 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test that the wallet can send and receive using all combinations of address types. There are 4 nodes-under-test: - node0 uses legacy addresses - node1 uses legacy addresses - node2 uses legacy addresses - node3 uses legacy addresses node4 exists to generate new blocks. ## Multisig address test Test that adding a multisig address with: - an uncompressed pubkey always gives a legacy address - only compressed pubkeys gives the an `-addresstype` address ## Sending to address types test A series of tests, iterating over node0-node3. In each iteration of the test, one node sends: - 10/101th of its balance to itself (using getrawchangeaddress for single key addresses) - 20/101th to the next node - 30/101th to the node after that - 40/101th to the remaining node - 1/101th remains as fee+change Iterate over each node for single key addresses, and then over each node for multisig addresses. Repeat test. As every node sends coins after receiving, this also verifies that spending coins sent to all these address types works. """ from decimal import Decimal import itertools from test_framework.test_framework import BitcoinTestFramework from test_framework.descriptors import ( descsum_create, descsum_check, ) from test_framework.util import ( assert_equal, assert_greater_than, connect_nodes, ) class AddressTypeTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 5 # whitelist all peers to speed up tx relay / mempool sync self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() def setup_network(self): self.setup_nodes() # Fully mesh-connect nodes for faster mempool sync for i, j in itertools.product(range(self.num_nodes), repeat=2): if i > j: connect_nodes(self.nodes[i], self.nodes[j]) self.sync_all() def get_balances(self, key='trusted'): """Return a list of balances.""" return [self.nodes[i].getbalances()['mine'][key] for i in range(4)] def test_address(self, node, address, multisig, typ): """Run sanity checks on an address.""" self.log.info(address) info = self.nodes[node].getaddressinfo(address) assert(self.nodes[node].validateaddress(address)['isvalid']) assert_equal(info.get('solvable'), True) if not multisig and typ == 'legacy': # P2PKH assert(not info['isscript']) assert('pubkey' in info) elif typ == 'legacy': # P2SH-multisig assert(info['isscript']) assert_equal(info['script'], 'multisig') assert('pubkeys' in info) else: # Unknown type assert(False) def test_desc(self, node, address, multisig, typ, utxo): """Run sanity checks on a descriptor reported by getaddressinfo.""" info = self.nodes[node].getaddressinfo(address) assert('desc' in info) assert_equal(info['desc'], utxo['desc']) assert(self.nodes[node].validateaddress(address)['isvalid']) # Use a ridiculously roundabout way to find the key origin info through # the PSBT logic. However, this does test consistency between the PSBT reported # fingerprints/paths and the descriptor logic. psbt = self.nodes[node].createpsbt( - [{'txid': utxo['txid'], 'vout':utxo['vout']}], [{address: 0.00010000}]) + [{'txid': utxo['txid'], 'vout':utxo['vout']}], [{address: 100.00}]) psbt = self.nodes[node].walletprocesspsbt( psbt, False, "ALL|FORKID", True) decode = self.nodes[node].decodepsbt(psbt['psbt']) key_descs = {} for deriv in decode['inputs'][0]['bip32_derivs']: assert_equal(len(deriv['master_fingerprint']), 8) assert_equal(deriv['path'][0], 'm') key_descs[deriv['pubkey']] = '[' + deriv['master_fingerprint'] + \ deriv['path'][1:] + ']' + deriv['pubkey'] # Verify the descriptor checksum against the Python implementation assert(descsum_check(info['desc'])) # Verify that stripping the checksum and recreating it using Python # roundtrips assert(info['desc'] == descsum_create(info['desc'][:-9])) # Verify that stripping the checksum and feeding it to # getdescriptorinfo roundtrips assert(info['desc'] == self.nodes[0].getdescriptorinfo( info['desc'][:-9])['descriptor']) assert_equal( info['desc'][-8:], self.nodes[0].getdescriptorinfo(info['desc'][:-9])['checksum']) # Verify that keeping the checksum and feeding it to getdescriptorinfo # roundtrips assert info['desc'] == self.nodes[0].getdescriptorinfo(info['desc'])[ 'descriptor'] assert_equal(info['desc'][-8:], self.nodes[0].getdescriptorinfo(info['desc'])['checksum']) if not multisig and typ == 'legacy': # P2PKH assert_equal(info['desc'], descsum_create("pkh({})".format(key_descs[info['pubkey']]))) elif typ == 'legacy': # P2SH-multisig assert_equal(info['desc'], descsum_create("sh(multi(2,{},{}))".format( key_descs[info['pubkeys'][0]], key_descs[info['pubkeys'][1]]))) else: # Unknown type assert(False) def test_change_output_type( self, node_sender, destinations, expected_type): txid = self.nodes[node_sender].sendmany( dummy="", amounts=dict.fromkeys( - destinations, 0.001)) + destinations, 1000.00)) raw_tx = self.nodes[node_sender].getrawtransaction(txid) tx = self.nodes[node_sender].decoderawtransaction(raw_tx) # Make sure the transaction has change: assert_equal(len(tx["vout"]), len(destinations) + 1) # Make sure the destinations are included, and remove them: output_addresses = [vout['scriptPubKey']['addresses'][0] for vout in tx["vout"]] change_addresses = [ d for d in output_addresses if d not in destinations] assert_equal(len(change_addresses), 1) self.log.debug( "Check if change address " + change_addresses[0] + " is " + expected_type) self.test_address( node_sender, change_addresses[0], multisig=False, typ=expected_type) def run_test(self): # Mine 101 blocks on node4 to bring nodes out of IBD and make sure that # no coinbases are maturing for the nodes-under-test during the test self.nodes[4].generate(101) self.sync_blocks() uncompressed_1 = "0496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee" uncompressed_2 = "047211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f03385237d92167c13e236446b417ab79a0fcae412ae3316b77" compressed_1 = "0296b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52" compressed_2 = "037211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073" # addmultisigaddress with at least 1 uncompressed key should return a # legacy address. for node in range(4): self.test_address(node, self.nodes[node].addmultisigaddress( 2, [uncompressed_1, uncompressed_2])['address'], True, 'legacy') self.test_address(node, self.nodes[node].addmultisigaddress( 2, [compressed_1, uncompressed_2])['address'], True, 'legacy') self.test_address(node, self.nodes[node].addmultisigaddress( 2, [uncompressed_1, compressed_2])['address'], True, 'legacy') # addmultisigaddress with all compressed keys should return the # appropriate address type (even when the keys are not ours). self.test_address(0, self.nodes[0].addmultisigaddress( 2, [compressed_1, compressed_2])['address'], True, 'legacy') for multisig, from_node in itertools.product([False, True], range(4)): self.log.info( "Sending from node {} with{} multisig".format(from_node, "" if multisig else "out")) old_balances = self.get_balances() self.log.debug("Old balances are {}".format(old_balances)) to_send = ( old_balances[from_node] / 101).quantize( - Decimal("0.00000001")) + Decimal("0.01")) sends = {} addresses = {} self.log.debug("Prepare sends") for n, to_node in enumerate(range(from_node, from_node + 4)): to_node %= 4 if not multisig: if from_node == to_node: # When sending non-multisig to self, use # getrawchangeaddress address = self.nodes[to_node].getrawchangeaddress() else: address = self.nodes[to_node].getnewaddress() else: addr1 = self.nodes[to_node].getnewaddress() addr2 = self.nodes[to_node].getnewaddress() address = self.nodes[to_node].addmultisigaddress(2, [addr1, addr2])[ 'address'] # Do some sanity checking on the created address typ = 'legacy' self.test_address(to_node, address, multisig, typ) # Output entry sends[address] = to_send * 10 * (1 + n) addresses[to_node] = (address, typ) self.log.debug("Sending: {}".format(sends)) self.nodes[from_node].sendmany("", sends) self.sync_mempools() unconf_balances = self.get_balances('untrusted_pending') self.log.debug( "Check unconfirmed balances: {}".format(unconf_balances)) assert_equal(unconf_balances[from_node], 0) for n, to_node in enumerate(range(from_node + 1, from_node + 4)): to_node %= 4 assert_equal(unconf_balances[to_node], to_send * 10 * (2 + n)) # node4 collects fee and block subsidy to keep accounting simple self.nodes[4].generate(1) self.sync_blocks() # Verify that the receiving wallet contains a UTXO with the # expected address, and expected descriptor for n, to_node in enumerate(range(from_node, from_node + 4)): to_node %= 4 found = False for utxo in self.nodes[to_node].listunspent(): if utxo['address'] == addresses[to_node][0]: found = True self.test_desc( to_node, addresses[to_node][0], multisig, addresses[to_node][1], utxo) break assert found new_balances = self.get_balances() self.log.debug("Check new balances: {}".format(new_balances)) # We don't know what fee was set, so we can only check bounds on # the balance of the sending node assert_greater_than(new_balances[from_node], to_send * 10) assert_greater_than(to_send * 11, new_balances[from_node]) for n, to_node in enumerate(range(from_node + 1, from_node + 4)): to_node %= 4 assert_equal( new_balances[to_node], old_balances[to_node] + to_send * 10 * (2 + n)) # Get addresses from node2 and node3: to_address_2 = self.nodes[2].getnewaddress() to_address_3_1 = self.nodes[3].getnewaddress() to_address_3_2 = self.nodes[3].getnewaddress() self.log.info("Various change output tests") self.test_change_output_type(0, [to_address_3_1], 'legacy') self.test_change_output_type(1, [to_address_2], 'legacy') self.test_change_output_type(1, [to_address_3_1], 'legacy') self.test_change_output_type( 1, [to_address_2, to_address_3_1], 'legacy') self.test_change_output_type( 1, [to_address_3_1, to_address_3_2], 'legacy') self.test_change_output_type(2, [to_address_3_1], 'legacy') self.log.info('Test getrawchangeaddress') self.test_address( 3, self.nodes[3].getrawchangeaddress(), multisig=False, typ='legacy') if __name__ == '__main__': AddressTypeTest().main() diff --git a/test/functional/wallet_avoidreuse.py b/test/functional/wallet_avoidreuse.py index c999e6e82..648629a21 100755 --- a/test/functional/wallet_avoidreuse.py +++ b/test/functional/wallet_avoidreuse.py @@ -1,451 +1,481 @@ #!/usr/bin/env python3 # Copyright (c) 2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the avoid_reuse and setwalletflag features.""" from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_approx, assert_equal, assert_raises_rpc_error, connect_nodes, ) def reset_balance(node, discardaddr): '''Throw away all owned coins by the node so it gets a balance of 0.''' balance = node.getbalance(avoid_reuse=False) - if balance > 0.5: + if balance > 500000: node.sendtoaddress( address=discardaddr, amount=balance, subtractfeefromamount=True, avoid_reuse=False) def count_unspent(node): '''Count the unspent outputs for the given node and return various statistics''' r = { "total": { "count": 0, "sum": 0, }, "reused": { "count": 0, "sum": 0, }, } supports_reused = True for utxo in node.listunspent(minconf=0): r["total"]["count"] += 1 r["total"]["sum"] += utxo["amount"] if supports_reused and "reused" in utxo: if utxo["reused"]: r["reused"]["count"] += 1 r["reused"]["sum"] += utxo["amount"] else: supports_reused = False r["reused"]["supported"] = supports_reused return r def assert_unspent(node, total_count=None, total_sum=None, reused_supported=None, reused_count=None, reused_sum=None): '''Make assertions about a node's unspent output statistics''' stats = count_unspent(node) if total_count is not None: assert_equal(stats["total"]["count"], total_count) if total_sum is not None: - assert_approx(stats["total"]["sum"], total_sum, 0.001) + assert_approx(stats["total"]["sum"], total_sum, 1000) if reused_supported is not None: assert_equal(stats["reused"]["supported"], reused_supported) if reused_count is not None: assert_equal(stats["reused"]["count"], reused_count) if reused_sum is not None: assert_approx(stats["reused"]["sum"], reused_sum, 0.001) def assert_balances(node, mine): '''Make assertions about a node's getbalances output''' got = node.getbalances()["mine"] for k, v in mine.items(): - assert_approx(got[k], v, 0.001) + assert_approx(got[k], v, 1000) class AvoidReuseTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = False self.num_nodes = 2 # This test isn't testing txn relay/timing, so set whitelist on the # peers for instant txn relay. This speeds up the test run time 2-3x. self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): '''Set up initial chain and run tests defined below''' self.test_persistence() self.test_immutable() self.nodes[0].generate(110) self.sync_all() self.test_change_remains_change(self.nodes[1]) reset_balance(self.nodes[1], self.nodes[0].getnewaddress()) self.test_sending_from_reused_address_without_avoid_reuse() reset_balance(self.nodes[1], self.nodes[0].getnewaddress()) self.test_sending_from_reused_address_fails() reset_balance(self.nodes[1], self.nodes[0].getnewaddress()) self.test_getbalances_used() reset_balance(self.nodes[1], self.nodes[0].getnewaddress()) self.test_full_destination_group_is_preferred() reset_balance(self.nodes[1], self.nodes[0].getnewaddress()) self.test_all_destination_groups_are_used() def test_persistence(self): '''Test that wallet files persist the avoid_reuse flag.''' self.log.info("Test wallet files persist avoid_reuse flag") # Configure node 1 to use avoid_reuse self.nodes[1].setwalletflag('avoid_reuse') # Flags should be node1.avoid_reuse=false, node2.avoid_reuse=true assert_equal(self.nodes[0].getwalletinfo()["avoid_reuse"], False) assert_equal(self.nodes[1].getwalletinfo()["avoid_reuse"], True) # Stop and restart node 1 self.restart_node(1) connect_nodes(self.nodes[0], self.nodes[1]) # Flags should still be node1.avoid_reuse=false, node2.avoid_reuse=true assert_equal(self.nodes[0].getwalletinfo()["avoid_reuse"], False) assert_equal(self.nodes[1].getwalletinfo()["avoid_reuse"], True) # Attempting to set flag to its current state should throw assert_raises_rpc_error(-8, "Wallet flag is already set to false", self.nodes[0].setwalletflag, 'avoid_reuse', False) assert_raises_rpc_error(-8, "Wallet flag is already set to true", self.nodes[1].setwalletflag, 'avoid_reuse', True) def test_immutable(self): '''Test immutable wallet flags''' self.log.info("Test immutable wallet flags") # Attempt to set the disable_private_keys flag; this should not work assert_raises_rpc_error(-8, "Wallet flag is immutable", self.nodes[1].setwalletflag, 'disable_private_keys') tempwallet = ".wallet_avoidreuse.py_test_immutable_wallet.dat" # Create a wallet with disable_private_keys set; this should work self.nodes[1].createwallet(tempwallet, True) w = self.nodes[1].get_wallet_rpc(tempwallet) # Attempt to unset the disable_private_keys flag; this should not work assert_raises_rpc_error(-8, "Wallet flag is immutable", w.setwalletflag, 'disable_private_keys', False) # Unload temp wallet self.nodes[1].unloadwallet(tempwallet) def test_change_remains_change(self, node): self.log.info( "Test that change doesn't turn into non-change when spent") reset_balance(node, node.getnewaddress()) addr = node.getnewaddress() - txid = node.sendtoaddress(addr, 1) - out = node.listunspent(minconf=0, query_options={'minimumAmount': 2}) + txid = node.sendtoaddress(addr, 1000000) + out = node.listunspent( + minconf=0, query_options={ + 'minimumAmount': 2000000}) assert_equal(len(out), 1) assert_equal(out[0]['txid'], txid) changeaddr = out[0]['address'] # Make sure it's starting out as change as expected assert node.getaddressinfo(changeaddr)['ischange'] for logical_tx in node.listtransactions(): assert logical_tx.get('address') != changeaddr # Spend it reset_balance(node, node.getnewaddress()) # It should still be change assert node.getaddressinfo(changeaddr)['ischange'] for logical_tx in node.listtransactions(): assert logical_tx.get('address') != changeaddr def test_sending_from_reused_address_without_avoid_reuse(self): ''' Test the same as test_sending_from_reused_address_fails, except send the 10 BCH with the avoid_reuse flag set to false. This means the 10 BTC send should succeed, where it fails in test_sending_from_reused_address_fails. ''' self.log.info( "Test sending from reused address with avoid_reuse=false") fundaddr = self.nodes[1].getnewaddress() retaddr = self.nodes[0].getnewaddress() - self.nodes[0].sendtoaddress(fundaddr, 10) + self.nodes[0].sendtoaddress(fundaddr, 10000000) self.nodes[0].generate(1) self.sync_all() # listunspent should show 1 single, unused 10 BCH output assert_unspent( self.nodes[1], total_count=1, - total_sum=10, + total_sum=10000000, reused_supported=True, reused_count=0) # getbalances should show no used, 10 BCH trusted - assert_balances(self.nodes[1], mine={"used": 0, "trusted": 10}) + assert_balances(self.nodes[1], mine={"used": 0, "trusted": 10000000}) # node 0 should not show a used entry, as it does not enable # avoid_reuse assert("used" not in self.nodes[0].getbalances()["mine"]) - self.nodes[1].sendtoaddress(retaddr, 5) + self.nodes[1].sendtoaddress(retaddr, 5000000) self.nodes[0].generate(1) self.sync_all() # listunspent should show 1 single, unused 5 BCH output assert_unspent( self.nodes[1], total_count=1, - total_sum=5, + total_sum=5000000, reused_supported=True, reused_count=0) # getbalances should show no used, 5 BCH trusted - assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5}) + assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5000000}) - self.nodes[0].sendtoaddress(fundaddr, 10) + self.nodes[0].sendtoaddress(fundaddr, 10000000) self.nodes[0].generate(1) self.sync_all() # listunspent should show 2 total outputs (5, 10 BCH), one unused (5), # one reused (10) assert_unspent( self.nodes[1], total_count=2, - total_sum=15, + total_sum=15000000, reused_count=1, - reused_sum=10) + reused_sum=10000000) # getbalances should show 10 used, 5 BCH trusted - assert_balances(self.nodes[1], mine={"used": 10, "trusted": 5}) + assert_balances( + self.nodes[1], + mine={ + "used": 10000000, + "trusted": 5000000}) self.nodes[1].sendtoaddress( - address=retaddr, amount=10, avoid_reuse=False) + address=retaddr, amount=10000000, avoid_reuse=False) # listunspent should show 1 total outputs (5 BCH), unused assert_unspent( self.nodes[1], total_count=1, - total_sum=5, + total_sum=5000000, reused_count=0) # getbalances should show no used, 5 BCH trusted - assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5}) + assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5000000}) # node 1 should now have about 5 BCH left (for both cases) - assert_approx(self.nodes[1].getbalance(), 5, 0.001) - assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 5, 0.001) + assert_approx(self.nodes[1].getbalance(), 5000000, 1000) + assert_approx( + self.nodes[1].getbalance( + avoid_reuse=False), + 5000000, + 1000) def test_sending_from_reused_address_fails(self): ''' Test the simple case where [1] generates a new address A, then [0] sends 10 BCH to A. [1] spends 5 BCH from A. (leaving roughly 5 BCH useable) [0] sends 10 BCH to A again. [1] tries to spend 10 BCH (fails; dirty). [1] tries to spend 4 BCH (succeeds; change address sufficient) ''' self.log.info("Test sending from reused address fails") fundaddr = self.nodes[1].getnewaddress(label="", address_type="legacy") retaddr = self.nodes[0].getnewaddress() - self.nodes[0].sendtoaddress(fundaddr, 10) + self.nodes[0].sendtoaddress(fundaddr, 10000000) self.nodes[0].generate(1) self.sync_all() # listunspent should show 1 single, unused 10 BCH output assert_unspent( self.nodes[1], total_count=1, - total_sum=10, + total_sum=10000000, reused_supported=True, reused_count=0) # getbalances should show no used, 10 BCH trusted - assert_balances(self.nodes[1], mine={"used": 0, "trusted": 10}) + assert_balances(self.nodes[1], mine={"used": 0, "trusted": 10000000}) - self.nodes[1].sendtoaddress(retaddr, 5) + self.nodes[1].sendtoaddress(retaddr, 5000000) self.nodes[0].generate(1) self.sync_all() # listunspent should show 1 single, unused 5 BCH output assert_unspent( self.nodes[1], total_count=1, - total_sum=5, + total_sum=5000000, reused_supported=True, reused_count=0) # getbalances should show no used, 5 BCH trusted - assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5}) + assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5000000}) # For the second send, we transmute it to a related single-key address # to make sure it's also detected as re-use # NB: this is not very useful for ABC, but we keep the new variable # name for consistency. new_fundaddr = fundaddr - self.nodes[0].sendtoaddress(new_fundaddr, 10) + self.nodes[0].sendtoaddress(new_fundaddr, 10000000) self.nodes[0].generate(1) self.sync_all() # listunspent should show 2 total outputs (5, 10 BCH), one unused (5), # one reused (10) assert_unspent( self.nodes[1], total_count=2, - total_sum=15, + total_sum=15000000, reused_count=1, - reused_sum=10) + reused_sum=10000000) # getbalances should show 10 used, 5 BCH trusted - assert_balances(self.nodes[1], mine={"used": 10, "trusted": 5}) + assert_balances( + self.nodes[1], + mine={ + "used": 10000000, + "trusted": 5000000}) # node 1 should now have a balance of 5 (no dirty) or 15 (including # dirty) - assert_approx(self.nodes[1].getbalance(), 5, 0.001) - assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 15, 0.001) + assert_approx(self.nodes[1].getbalance(), 5000000, 1000) + assert_approx( + self.nodes[1].getbalance( + avoid_reuse=False), + 15000000, + 1000) assert_raises_rpc_error(-6, "Insufficient funds", - self.nodes[1].sendtoaddress, retaddr, 10) + self.nodes[1].sendtoaddress, retaddr, 10000000) - self.nodes[1].sendtoaddress(retaddr, 4) + self.nodes[1].sendtoaddress(retaddr, 4000000) # listunspent should show 2 total outputs (1, 10 BCH), one unused (1), # one reused (10) assert_unspent( self.nodes[1], total_count=2, - total_sum=11, + total_sum=11000000, reused_count=1, - reused_sum=10) + reused_sum=10000000) # getbalances should show 10 used, 1 BCH trusted - assert_balances(self.nodes[1], mine={"used": 10, "trusted": 1}) + assert_balances( + self.nodes[1], + mine={ + "used": 10000000, + "trusted": 1000000}) # node 1 should now have about 1 BCH left (no dirty) and 11 (including # dirty) - assert_approx(self.nodes[1].getbalance(), 1, 0.001) - assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 11, 0.001) + assert_approx(self.nodes[1].getbalance(), 1000000, 1000) + assert_approx( + self.nodes[1].getbalance( + avoid_reuse=False), + 11000000, + 1000) def test_getbalances_used(self): ''' getbalances and listunspent should pick up on reused addresses immediately, even for address reusing outputs created before the first transaction was spending from that address ''' self.log.info("Test getbalances used category") # node under test should be completely empty assert_equal(self.nodes[1].getbalance(avoid_reuse=False), 0) new_addr = self.nodes[1].getnewaddress() ret_addr = self.nodes[0].getnewaddress() # send multiple transactions, reusing one address for _ in range(11): - self.nodes[0].sendtoaddress(new_addr, 1) + self.nodes[0].sendtoaddress(new_addr, 1000000) self.nodes[0].generate(1) self.sync_all() # send transaction that should not use all the available outputs # per the current coin selection algorithm - self.nodes[1].sendtoaddress(ret_addr, 5) + self.nodes[1].sendtoaddress(ret_addr, 5000000) # getbalances and listunspent should show the remaining outputs # in the reused address as used/reused assert_unspent( self.nodes[1], total_count=2, - total_sum=6, + total_sum=6000000, reused_count=1, - reused_sum=1) - assert_balances(self.nodes[1], mine={"used": 1, "trusted": 5}) + reused_sum=1000000) + assert_balances( + self.nodes[1], + mine={ + "used": 1000000, + "trusted": 5000000}) def test_full_destination_group_is_preferred(self): ''' Test the case where [1] only has 11 outputs of 1 BCH in the same reused address and tries to send a small payment of 0.5 BCH. The wallet should use 10 outputs from the reused address as inputs and not a single 1 BCH input, in order to join several outputs from the reused address. ''' self.log.info( "Test that full destination groups are preferred in coin selection") # Node under test should be empty assert_equal(self.nodes[1].getbalance(avoid_reuse=False), 0) new_addr = self.nodes[1].getnewaddress() ret_addr = self.nodes[0].getnewaddress() # Send 11 outputs of 1 BCH to the same, reused address in the wallet for _ in range(11): - self.nodes[0].sendtoaddress(new_addr, 1) + self.nodes[0].sendtoaddress(new_addr, 1000000) self.nodes[0].generate(1) self.sync_all() # Sending a transaction that is smaller than each one of the # available outputs - txid = self.nodes[1].sendtoaddress(address=ret_addr, amount=0.5) + txid = self.nodes[1].sendtoaddress(address=ret_addr, amount=500000) inputs = self.nodes[1].getrawtransaction(txid, 1)["vin"] # The transaction should use 10 inputs exactly assert_equal(len(inputs), 10) def test_all_destination_groups_are_used(self): ''' Test the case where [1] only has 22 outputs of 1 BCH in the same reused address and tries to send a payment of 20.5 BCH. The wallet should use all 22 outputs from the reused address as inputs. ''' self.log.info("Test that all destination groups are used") # Node under test should be empty assert_equal(self.nodes[1].getbalance(avoid_reuse=False), 0) new_addr = self.nodes[1].getnewaddress() ret_addr = self.nodes[0].getnewaddress() # Send 22 outputs of 1 BCH to the same, reused address in the wallet for _ in range(22): - self.nodes[0].sendtoaddress(new_addr, 1) + self.nodes[0].sendtoaddress(new_addr, 1000000) self.nodes[0].generate(1) self.sync_all() # Sending a transaction that needs to use the full groups # of 10 inputs but also the incomplete group of 2 inputs. - txid = self.nodes[1].sendtoaddress(address=ret_addr, amount=20.5) + txid = self.nodes[1].sendtoaddress(address=ret_addr, amount=20500000) inputs = self.nodes[1].getrawtransaction(txid, 1)["vin"] # The transaction should use 22 inputs exactly assert_equal(len(inputs), 22) if __name__ == '__main__': AvoidReuseTest().main() diff --git a/test/functional/wallet_backup.py b/test/functional/wallet_backup.py index fdd6ff9ed..c116f0394 100755 --- a/test/functional/wallet_backup.py +++ b/test/functional/wallet_backup.py @@ -1,249 +1,249 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet backup features. Test case is: 4 nodes. 1 2 and 3 send transactions between each other, fourth node is a miner. 1 2 3 each mine a block to start, then Miner creates 100 blocks so 1 2 3 each have 50 mature coins to spend. Then 5 iterations of 1/2/3 sending coins amongst themselves to get transactions in the wallets, and the miner mining one block. Wallets are backed up using dumpwallet/backupwallet. Then 5 more iterations of transactions and mining a block. Miner then generates 101 more blocks, so any transaction fees paid mature. Sanity check: Sum(1,2,3,4 balances) == 114*50 1/2/3 are shutdown, and their wallets erased. Then restore using wallet.dat backup. And confirm 1/2/3/4 balances are same as before. Shutdown again, restore using importwallet, and confirm again balances are correct. """ from decimal import Decimal import os from random import randint import shutil from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, connect_nodes, ) class WalletBackupTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.setup_clean_chain = True # nodes 1, 2,3 are spenders, let's give them a keypool=100 # whitelist all peers to speed up tx relay / mempool sync self.extra_args = [ ["-whitelist=noban@127.0.0.1", "-keypool=100"], ["-whitelist=noban@127.0.0.1", "-keypool=100"], ["-whitelist=noban@127.0.0.1", "-keypool=100"], ["-whitelist=noban@127.0.0.1"], ] self.rpc_timeout = 120 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def setup_network(self): self.setup_nodes() connect_nodes(self.nodes[0], self.nodes[3]) connect_nodes(self.nodes[1], self.nodes[3]) connect_nodes(self.nodes[2], self.nodes[3]) connect_nodes(self.nodes[2], self.nodes[0]) self.sync_all() def one_send(self, from_node, to_address): if (randint(1, 2) == 1): - amount = Decimal(randint(1, 10)) / Decimal(10) + amount = Decimal(randint(1, 10)) * Decimal(100000) self.nodes[from_node].sendtoaddress(to_address, amount) def do_one_round(self): a0 = self.nodes[0].getnewaddress() a1 = self.nodes[1].getnewaddress() a2 = self.nodes[2].getnewaddress() self.one_send(0, a1) self.one_send(0, a2) self.one_send(1, a0) self.one_send(1, a2) self.one_send(2, a0) self.one_send(2, a1) # Have the miner (node3) mine a block. # Must sync mempools before mining. self.sync_mempools() self.nodes[3].generate(1) self.sync_blocks() # As above, this mirrors the original bash test. def start_three(self): self.start_node(0) self.start_node(1) self.start_node(2) connect_nodes(self.nodes[0], self.nodes[3]) connect_nodes(self.nodes[1], self.nodes[3]) connect_nodes(self.nodes[2], self.nodes[3]) connect_nodes(self.nodes[2], self.nodes[0]) def stop_three(self): self.stop_node(0) self.stop_node(1) self.stop_node(2) def erase_three(self): os.remove(os.path.join( self.nodes[0].datadir, self.chain, 'wallets', 'wallet.dat')) os.remove(os.path.join( self.nodes[1].datadir, self.chain, 'wallets', 'wallet.dat')) os.remove(os.path.join( self.nodes[2].datadir, self.chain, 'wallets', 'wallet.dat')) def run_test(self): self.log.info("Generating initial blockchain") self.nodes[0].generate(1) self.sync_blocks() self.nodes[1].generate(1) self.sync_blocks() self.nodes[2].generate(1) self.sync_blocks() self.nodes[3].generate(100) self.sync_blocks() - assert_equal(self.nodes[0].getbalance(), 50) - assert_equal(self.nodes[1].getbalance(), 50) - assert_equal(self.nodes[2].getbalance(), 50) + assert_equal(self.nodes[0].getbalance(), 50000000) + assert_equal(self.nodes[1].getbalance(), 50000000) + assert_equal(self.nodes[2].getbalance(), 50000000) assert_equal(self.nodes[3].getbalance(), 0) self.log.info("Creating transactions") # Five rounds of sending each other transactions. for i in range(5): self.do_one_round() self.log.info("Backing up") self.nodes[0].backupwallet(os.path.join( self.nodes[0].datadir, 'wallet.bak')) self.nodes[0].dumpwallet(os.path.join( self.nodes[0].datadir, 'wallet.dump')) self.nodes[1].backupwallet(os.path.join( self.nodes[1].datadir, 'wallet.bak')) self.nodes[1].dumpwallet(os.path.join( self.nodes[1].datadir, 'wallet.dump')) self.nodes[2].backupwallet(os.path.join( self.nodes[2].datadir, 'wallet.bak')) self.nodes[2].dumpwallet(os.path.join( self.nodes[2].datadir, 'wallet.dump')) self.log.info("More transactions") for i in range(5): self.do_one_round() # Generate 101 more blocks, so any fees paid mature self.nodes[3].generate(101) self.sync_all() balance0 = self.nodes[0].getbalance() balance1 = self.nodes[1].getbalance() balance2 = self.nodes[2].getbalance() balance3 = self.nodes[3].getbalance() total = balance0 + balance1 + balance2 + balance3 # At this point, there are 214 blocks (103 for setup, then 10 rounds, then 101.) # 114 are mature, so the sum of all wallets should be 114 * 50 = 5700. - assert_equal(total, 5700) + assert_equal(total, 5700000000) ## # Test restoring spender wallets from backups ## self.log.info("Restoring using wallet.dat") self.stop_three() self.erase_three() # Start node2 with no chain shutil.rmtree( os.path.join( self.nodes[2].datadir, self.chain, 'blocks')) shutil.rmtree(os.path.join( self.nodes[2].datadir, self.chain, 'chainstate')) # Restore wallets from backup shutil.copyfile(os.path.join(self.nodes[0].datadir, 'wallet.bak'), os.path.join( self.nodes[0].datadir, self.chain, 'wallets', 'wallet.dat')) shutil.copyfile(os.path.join(self.nodes[1].datadir, 'wallet.bak'), os.path.join( self.nodes[1].datadir, self.chain, 'wallets', 'wallet.dat')) shutil.copyfile(os.path.join(self.nodes[2].datadir, 'wallet.bak'), os.path.join( self.nodes[2].datadir, self.chain, 'wallets', 'wallet.dat')) self.log.info("Re-starting nodes") self.start_three() self.sync_blocks() assert_equal(self.nodes[0].getbalance(), balance0) assert_equal(self.nodes[1].getbalance(), balance1) assert_equal(self.nodes[2].getbalance(), balance2) self.log.info("Restoring using dumped wallet") self.stop_three() self.erase_three() # start node2 with no chain shutil.rmtree( os.path.join( self.nodes[2].datadir, self.chain, 'blocks')) shutil.rmtree(os.path.join( self.nodes[2].datadir, self.chain, 'chainstate')) self.start_three() assert_equal(self.nodes[0].getbalance(), 0) assert_equal(self.nodes[1].getbalance(), 0) assert_equal(self.nodes[2].getbalance(), 0) self.nodes[0].importwallet(os.path.join( self.nodes[0].datadir, 'wallet.dump')) self.nodes[1].importwallet(os.path.join( self.nodes[1].datadir, 'wallet.dump')) self.nodes[2].importwallet(os.path.join( self.nodes[2].datadir, 'wallet.dump')) self.sync_blocks() assert_equal(self.nodes[0].getbalance(), balance0) assert_equal(self.nodes[1].getbalance(), balance1) assert_equal(self.nodes[2].getbalance(), balance2) # Backup to source wallet file must fail sourcePaths = [ os.path.join(self.nodes[0].datadir, self.chain, 'wallets', 'wallet.dat'), os.path.join(self.nodes[0].datadir, self.chain, '.', 'wallets', 'wallet.dat'), os.path.join(self.nodes[0].datadir, self.chain, 'wallets', ''), os.path.join(self.nodes[0].datadir, self.chain, 'wallets')] for sourcePath in sourcePaths: assert_raises_rpc_error(-4, "backup failed", self.nodes[0].backupwallet, sourcePath) if __name__ == '__main__': WalletBackupTest().main() diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py index 70e910828..d5dffee33 100755 --- a/test/functional/wallet_balance.py +++ b/test/functional/wallet_balance.py @@ -1,316 +1,325 @@ #!/usr/bin/env python3 # Copyright (c) 2018-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet balance RPC methods.""" from decimal import Decimal import struct from test_framework.address import ADDRESS_BCHREG_UNSPENDABLE as ADDRESS_WATCHONLY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, connect_nodes, ) def create_transactions(node, address, amt, fees): # Create and sign raw transactions from node to address for amt. # Creates a transaction for each fee and returns an array # of the raw transactions. utxos = [u for u in node.listunspent(0) if u['spendable']] # Create transactions inputs = [] ins_total = 0 for utxo in utxos: inputs.append({"txid": utxo["txid"], "vout": utxo["vout"]}) ins_total += utxo['amount'] if ins_total >= amt + max(fees): break # make sure there was enough utxos assert ins_total >= amt + max(fees) txs = [] for fee in fees: outputs = {address: amt} # prevent 0 change output if ins_total > amt + fee: outputs[node.getrawchangeaddress()] = ins_total - amt - fee raw_tx = node.createrawtransaction(inputs, outputs, 0) raw_tx = node.signrawtransactionwithwallet(raw_tx) assert_equal(raw_tx['complete'], True) txs.append(raw_tx) return txs class WalletTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True self.extra_args = [ # Limit mempool descendants as a hack to have wallet txs rejected # from the mempool ['-limitdescendantcount=3'], [], ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): self.nodes[0].importaddress(ADDRESS_WATCHONLY) # Check that nodes don't own any UTXOs assert_equal(len(self.nodes[0].listunspent()), 0) assert_equal(len(self.nodes[1].listunspent()), 0) self.log.info("Check that only node 0 is watching an address") assert 'watchonly' in self.nodes[0].getbalances() assert 'watchonly' not in self.nodes[1].getbalances() self.log.info("Mining blocks ...") self.nodes[0].generate(1) self.sync_all() self.nodes[1].generate(1) self.nodes[1].generatetoaddress(101, ADDRESS_WATCHONLY) self.sync_all() - assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 50) - assert_equal(self.nodes[0].getwalletinfo()['balance'], 50) - assert_equal(self.nodes[1].getbalances()['mine']['trusted'], 50) + assert_equal(self.nodes[0].getbalances()['mine']['trusted'], 50000000) + assert_equal(self.nodes[0].getwalletinfo()['balance'], 50000000) + assert_equal(self.nodes[1].getbalances()['mine']['trusted'], 50000000) assert_equal(self.nodes[0].getbalances()[ - 'watchonly']['immature'], 5000) + 'watchonly']['immature'], 5000000000) assert 'watchonly' not in self.nodes[1].getbalances() - assert_equal(self.nodes[0].getbalance(), 50) - assert_equal(self.nodes[1].getbalance(), 50) + assert_equal(self.nodes[0].getbalance(), 50000000) + assert_equal(self.nodes[1].getbalance(), 50000000) self.log.info("Test getbalance with different arguments") - assert_equal(self.nodes[0].getbalance("*"), 50) - assert_equal(self.nodes[0].getbalance("*", 1), 50) - assert_equal(self.nodes[0].getbalance("*", 1, True), 100) - assert_equal(self.nodes[0].getbalance(minconf=1), 50) + assert_equal(self.nodes[0].getbalance("*"), 50000000) + assert_equal(self.nodes[0].getbalance("*", 1), 50000000) + assert_equal(self.nodes[0].getbalance("*", 1, True), 100000000) + assert_equal(self.nodes[0].getbalance(minconf=1), 50000000) assert_equal( self.nodes[0].getbalance( minconf=0, include_watchonly=True), - 100) + 100000000) assert_equal( self.nodes[1].getbalance( minconf=0, include_watchonly=True), - 50) + 50000000) # Send 40 BTC from 0 to 1 and 60 BTC from 1 to 0. txs = create_transactions( - self.nodes[0], self.nodes[1].getnewaddress(), 40, [Decimal('0.01')]) + self.nodes[0], self.nodes[1].getnewaddress(), 40000000, [Decimal('10000')]) self.nodes[0].sendrawtransaction(txs[0]['hex']) # sending on both nodes is faster than waiting for propagation self.nodes[1].sendrawtransaction(txs[0]['hex']) self.sync_all() - txs = create_transactions(self.nodes[1], self.nodes[0].getnewaddress(), 60, [ - Decimal('0.01'), Decimal('0.02')]) + txs = create_transactions(self.nodes[1], self.nodes[0].getnewaddress(), 60000000, [ + Decimal('10000'), Decimal('20000')]) self.nodes[1].sendrawtransaction(txs[0]['hex']) # sending on both nodes is faster than waiting for propagation self.nodes[0].sendrawtransaction(txs[0]['hex']) self.sync_all() # First argument of getbalance must be set to "*" assert_raises_rpc_error(-32, "dummy first argument must be excluded or set to \"*\"", self.nodes[1].getbalance, "") self.log.info("Test balances with unconfirmed inputs") # Before `test_balance()`, we have had two nodes with a balance of 50 # each and then we: # # 1) Sent 40 from node A to node B with fee 0.01 # 2) Sent 60 from node B to node A with fee 0.01 # # Then we check the balances: # # 1) As is # 2) With transaction 2 from above with 2x the fee # # Prior to #16766, in this situation, the node would immediately report # a balance of 30 on node B as unconfirmed and trusted. # # After #16766, we show that balance as unconfirmed. # # The balance is indeed "trusted" and "confirmed" insofar as removing # the mempool transactions would return at least that much money. But # the algorithm after #16766 marks it as unconfirmed because the 'taint' # tracking of transaction trust for summing balances doesn't consider # which inputs belong to a user. In this case, the change output in # question could be "destroyed" by replace the 1st transaction above. # # The post #16766 behavior is correct; we shouldn't be treating those # funds as confirmed. If you want to rely on that specific UTXO existing # which has given you that balance, you cannot, as a third party # spending the other input would destroy that unconfirmed. # # For example, if the test transactions were: # # 1) Sent 40 from node A to node B with fee 0.01 # 2) Sent 10 from node B to node A with fee 0.01 # # Then our node would report a confirmed balance of 40 + 50 - 10 = 80 # BTC, which is more than would be available if transaction 1 were # replaced. def test_balances(*, fee_node_1=0): # getbalances - expected_balances_0 = {'mine': {'immature': Decimal('0E-8'), + expected_balances_0 = {'mine': {'immature': Decimal('0E-2'), # change from node 0's send - 'trusted': Decimal('9.99'), - 'untrusted_pending': Decimal('60.0')}, - 'watchonly': {'immature': Decimal('5000'), - 'trusted': Decimal('50.0'), - 'untrusted_pending': Decimal('0E-8')}} - expected_balances_1 = {'mine': {'immature': Decimal('0E-8'), + 'trusted': Decimal('9990000'), + 'untrusted_pending': Decimal('60000000.0')}, + 'watchonly': {'immature': Decimal('5000000000'), + 'trusted': Decimal('50000000.0'), + 'untrusted_pending': Decimal('0E-2')}} + expected_balances_1 = {'mine': {'immature': Decimal('0E-2'), # node 1's send had an unsafe input - 'trusted': Decimal('0E-8'), + 'trusted': Decimal('0E-2'), # Doesn't include output of node # 0's send since it was spent - 'untrusted_pending': Decimal('30.0') - fee_node_1}} + 'untrusted_pending': Decimal('30000000.0') - fee_node_1}} assert_equal(self.nodes[0].getbalances(), expected_balances_0) assert_equal(self.nodes[1].getbalances(), expected_balances_1) # getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions # change from node 0's send - assert_equal(self.nodes[0].getbalance(), Decimal('9.99')) + assert_equal(self.nodes[0].getbalance(), Decimal('9990000')) # node 1's send had an unsafe input assert_equal(self.nodes[1].getbalance(), Decimal('0')) # Same with minconf=0 - assert_equal(self.nodes[0].getbalance(minconf=0), Decimal('9.99')) + assert_equal( + self.nodes[0].getbalance( + minconf=0), + Decimal('9990000')) assert_equal(self.nodes[1].getbalance(minconf=0), Decimal('0')) # getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago # TODO: fix getbalance tracking of coin spentness depth assert_equal(self.nodes[0].getbalance(minconf=1), Decimal('0')) assert_equal(self.nodes[1].getbalance(minconf=1), Decimal('0')) # getunconfirmedbalance # output of node 1's spend - assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal('60')) + assert_equal( + self.nodes[0].getunconfirmedbalance(), + Decimal('60000000')) # Doesn't include output of node 0's send since it was spent assert_equal( self.nodes[1].getunconfirmedbalance(), - Decimal('30') - fee_node_1) + Decimal('30000000') - fee_node_1) # getwalletinfo.unconfirmed_balance assert_equal(self.nodes[0].getwalletinfo()[ - "unconfirmed_balance"], Decimal('60')) + "unconfirmed_balance"], Decimal('60000000')) assert_equal( self.nodes[1].getwalletinfo()["unconfirmed_balance"], - Decimal('30') - fee_node_1) + Decimal('30000000') - fee_node_1) - test_balances(fee_node_1=Decimal('0.01')) + test_balances(fee_node_1=Decimal('10000')) # In the original Core version of this test, Node 1 would've bumped # the fee by 0.01 here to resend, but this is BCH, so it has 0.01 BCH # left to spend on goods and services self.sync_all() self.log.info( "Test getbalance and getbalances.mine.untrusted_pending with conflicted unconfirmed inputs") - test_balances(fee_node_1=Decimal('0.01')) + test_balances(fee_node_1=Decimal('10000')) self.nodes[1].generatetoaddress(1, ADDRESS_WATCHONLY) self.sync_all() # balances are correct after the transactions are confirmed # node 1's send plus change from node 0's send - balance_node0 = Decimal('69.99') + balance_node0 = Decimal('69990000') # change from node 0's send - balance_node1 = Decimal('29.99') + balance_node1 = Decimal('29990000') assert_equal(self.nodes[0].getbalances()[ 'mine']['trusted'], balance_node0) assert_equal(self.nodes[1].getbalances()[ 'mine']['trusted'], balance_node1) assert_equal(self.nodes[0].getbalance(), balance_node0) assert_equal(self.nodes[1].getbalance(), balance_node1) # Send total balance away from node 1 txs = create_transactions(self.nodes[1], self.nodes[0].getnewaddress( - ), Decimal('29.97'), [Decimal('0.01')]) + ), Decimal('29970000'), [Decimal('10000')]) self.nodes[1].sendrawtransaction(txs[0]['hex']) self.nodes[1].generatetoaddress(2, ADDRESS_WATCHONLY) self.sync_all() # getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago # TODO: fix getbalance tracking of coin spentness depth # getbalance with minconf=3 should still show the old balance assert_equal(self.nodes[1].getbalance(minconf=3), Decimal('0')) # getbalance with minconf=2 will show the new balance. - assert_equal(self.nodes[1].getbalance(minconf=2), Decimal('0.01')) + assert_equal(self.nodes[1].getbalance(minconf=2), Decimal('10000')) # check mempool transactions count for wallet unconfirmed balance after # dynamically loading the wallet. before = self.nodes[1].getbalances()['mine']['untrusted_pending'] dst = self.nodes[1].getnewaddress() self.nodes[1].unloadwallet('') - self.nodes[0].sendtoaddress(dst, 0.1) + self.nodes[0].sendtoaddress(dst, 100000) self.sync_all() self.nodes[1].loadwallet('') after = self.nodes[1].getbalances()['mine']['untrusted_pending'] - assert_equal(before + Decimal('0.1'), after) + assert_equal(before + Decimal('100000'), after) # Create 3 more wallet txs, where the last is not accepted to the # mempool because it is the third descendant of the tx above for _ in range(3): # Set amount high enough such that all coins are spent by each tx txid = self.nodes[0].sendtoaddress( - self.nodes[0].getnewaddress(), 99) + self.nodes[0].getnewaddress(), 99000000) self.log.info('Check that wallet txs not in the mempool are untrusted') assert txid not in self.nodes[0].getrawmempool() assert_equal(self.nodes[0].gettransaction(txid)['trusted'], False) assert_equal(self.nodes[0].getbalance(minconf=0), 0) self.log.info("Test replacement and reorg of non-mempool tx") tx_orig = self.nodes[0].gettransaction(txid)['hex'] # Increase fee by 1 coin tx_replace = tx_orig.replace( struct.pack(" 1 for address in output_addresses: ischange = self.nodes[0].getaddressinfo(address)['ischange'] assert_equal(ischange, address != destination) if ischange: change = address self.nodes[0].setlabel(change, 'foobar') assert_equal(self.nodes[0].getaddressinfo(change)['ischange'], False) # Test gettransaction response with different arguments. self.log.info( "Testing gettransaction response with different arguments...") self.nodes[0].setlabel(change, 'baz') baz = self.nodes[0].listtransactions(label="baz", count=1)[0] expected_receive_vout = {"label": "baz", "address": baz["address"], "amount": baz["amount"], "category": baz["category"], "vout": baz["vout"]} expected_fields = frozenset({'amount', 'confirmations', 'details', 'fee', 'hex', 'time', 'timereceived', 'trusted', 'txid', 'walletconflicts'}) verbose_field = "decoded" expected_verbose_fields = expected_fields | {verbose_field} self.log.debug("Testing gettransaction response without verbose") tx = self.nodes[0].gettransaction(txid=txid) assert_equal(set([*tx]), expected_fields) assert_array_result( tx["details"], { "category": "receive"}, expected_receive_vout) self.log.debug( "Testing gettransaction response with verbose set to False") tx = self.nodes[0].gettransaction(txid=txid, verbose=False) assert_equal(set([*tx]), expected_fields) assert_array_result( tx["details"], { "category": "receive"}, expected_receive_vout) self.log.debug( "Testing gettransaction response with verbose set to True") tx = self.nodes[0].gettransaction(txid=txid, verbose=True) assert_equal(set([*tx]), expected_verbose_fields) assert_array_result( tx["details"], { "category": "receive"}, expected_receive_vout) assert_equal( tx[verbose_field], self.nodes[0].decoderawtransaction( tx["hex"])) if __name__ == '__main__': WalletTest().main() diff --git a/test/functional/wallet_create_tx.py b/test/functional/wallet_create_tx.py index 0d1b7437e..719b8d1e4 100755 --- a/test/functional/wallet_create_tx.py +++ b/test/functional/wallet_create_tx.py @@ -1,89 +1,91 @@ #!/usr/bin/env python3 # Copyright (c) 2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, ) from test_framework.blocktools import ( TIME_GENESIS_BLOCK, ) class CreateTxWalletTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): self.log.info('Create some old blocks') self.nodes[0].setmocktime(TIME_GENESIS_BLOCK) self.nodes[0].generate(200) self.nodes[0].setmocktime(0) self.test_anti_fee_sniping() self.test_tx_size_too_large() def test_anti_fee_sniping(self): self.log.info( 'Check that we have some (old) blocks and that anti-fee-sniping is disabled') assert_equal(self.nodes[0].getblockchaininfo()['blocks'], 200) - txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) + txid = self.nodes[0].sendtoaddress( + self.nodes[0].getnewaddress(), 1000000) tx = self.nodes[0].decoderawtransaction( self.nodes[0].gettransaction(txid)['hex']) assert_equal(tx['locktime'], 0) self.log.info( 'Check that anti-fee-sniping is enabled when we mine a recent block') self.nodes[0].generate(1) - txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1) + txid = self.nodes[0].sendtoaddress( + self.nodes[0].getnewaddress(), 1000000) tx = self.nodes[0].decoderawtransaction( self.nodes[0].gettransaction(txid)['hex']) assert 0 < tx['locktime'] <= 201 def test_tx_size_too_large(self): # More than 10kB of outputs, so that we hit -maxtxfee with a high # feerate - outputs = {self.nodes[0].getnewaddress(): 0.000025 for i in range(400)} + outputs = {self.nodes[0].getnewaddress(): 25 for i in range(400)} raw_tx = self.nodes[0].createrawtransaction(inputs=[], outputs=outputs) - for fee_setting in ['-minrelaytxfee=0.01', - '-mintxfee=0.01', '-paytxfee=0.01']: + for fee_setting in ['-minrelaytxfee=10000', + '-mintxfee=10000', '-paytxfee=10000']: self.log.info( 'Check maxtxfee in combination with {}'.format(fee_setting)) self.restart_node(0, extra_args=[fee_setting]) assert_raises_rpc_error( -6, "Fee exceeds maximum configured by -maxtxfee", lambda: self.nodes[0].sendmany(dummy="", amounts=outputs), ) assert_raises_rpc_error( -4, "Fee exceeds maximum configured by -maxtxfee", lambda: self.nodes[0].fundrawtransaction(hexstring=raw_tx), ) self.log.info('Check maxtxfee in combination with settxfee') self.restart_node(0) - self.nodes[0].settxfee(0.01) + self.nodes[0].settxfee(10000) assert_raises_rpc_error( -6, "Fee exceeds maximum configured by -maxtxfee", lambda: self.nodes[0].sendmany(dummy="", amounts=outputs), ) assert_raises_rpc_error( -4, "Fee exceeds maximum configured by -maxtxfee", lambda: self.nodes[0].fundrawtransaction(hexstring=raw_tx), ) self.nodes[0].settxfee(0) if __name__ == '__main__': CreateTxWalletTest().main() diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py index d1821f09a..3b053b9d5 100755 --- a/test/functional/wallet_groups.py +++ b/test/functional/wallet_groups.py @@ -1,143 +1,147 @@ #!/usr/bin/env python3 # Copyright (c) 2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test wallet group functionality.""" from test_framework.test_framework import BitcoinTestFramework from test_framework.messages import CTransaction, FromHex, ToHex from test_framework.util import ( assert_approx, assert_equal, ) class WalletGroupTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 4 self.extra_args = [ - [], [], ['-avoidpartialspends'], ["-maxapsfee=0.0001"]] + [], [], ['-avoidpartialspends'], ["-maxapsfee=100"]] self.rpc_timeout = 120 self.supports_cli = False def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): # Mine some coins self.nodes[0].generate(110) # Get some addresses from the two nodes addr1 = [self.nodes[1].getnewaddress() for i in range(3)] addr2 = [self.nodes[2].getnewaddress() for i in range(3)] addrs = addr1 + addr2 # Send 1 + 0.5 coin to each address - [self.nodes[0].sendtoaddress(addr, 1.0) for addr in addrs] - [self.nodes[0].sendtoaddress(addr, 0.5) for addr in addrs] + [self.nodes[0].sendtoaddress(addr, 1000000) for addr in addrs] + [self.nodes[0].sendtoaddress(addr, 500000) for addr in addrs] self.nodes[0].generate(1) self.sync_all() # For each node, send 0.2 coins back to 0; # - node[1] should pick one 0.5 UTXO and leave the rest # - node[2] should pick one (1.0 + 0.5) UTXO group corresponding to a # given address, and leave the rest - txid1 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 0.2) + txid1 = self.nodes[1].sendtoaddress( + self.nodes[0].getnewaddress(), 200000) tx1 = self.nodes[1].getrawtransaction(txid1, True) # txid1 should have 1 input and 2 outputs assert_equal(1, len(tx1["vin"])) assert_equal(2, len(tx1["vout"])) # one output should be 0.2, the other should be ~0.3 v = sorted([vout["value"] for vout in tx1["vout"]]) - assert_approx(v[0], 0.2) - assert_approx(v[1], 0.3, 0.0001) + assert_approx(v[0], 200000) + assert_approx(v[1], 300000, 100) - txid2 = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 0.2) + txid2 = self.nodes[2].sendtoaddress( + self.nodes[0].getnewaddress(), 200000) tx2 = self.nodes[2].getrawtransaction(txid2, True) # txid2 should have 2 inputs and 2 outputs assert_equal(2, len(tx2["vin"])) assert_equal(2, len(tx2["vout"])) # one output should be 0.2, the other should be ~1.3 v = sorted([vout["value"] for vout in tx2["vout"]]) - assert_approx(v[0], 0.2) - assert_approx(v[1], 1.3, 0.0001) + assert_approx(v[0], 200000) + assert_approx(v[1], 1300000, 100) # Test 'avoid partial if warranted, even if disabled' self.sync_all() self.nodes[0].generate(1) # Nodes 1-2 now have confirmed UTXOs (letters denote destinations): # Node #1: Node #2: # - A 1.0 - D0 1.0 # - B0 1.0 - D1 0.5 # - B1 0.5 - E0 1.0 # - C0 1.0 - E1 0.5 # - C1 0.5 - F ~1.3 # - D ~0.3 - assert_approx(self.nodes[1].getbalance(), 4.3, 0.0001) - assert_approx(self.nodes[2].getbalance(), 4.3, 0.0001) + assert_approx(self.nodes[1].getbalance(), 4300000, 100) + assert_approx(self.nodes[2].getbalance(), 4300000, 100) # Sending 1.4 btc should pick one 1.0 + one more. For node #1, # this could be (A / B0 / C0) + (B1 / C1 / D). We ensure that it is # B0 + B1 or C0 + C1, because this avoids partial spends while not being # detrimental to transaction cost - txid3 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1.4) + txid3 = self.nodes[1].sendtoaddress( + self.nodes[0].getnewaddress(), 1400000) tx3 = self.nodes[1].getrawtransaction(txid3, True) # tx3 should have 2 inputs and 2 outputs assert_equal(2, len(tx3["vin"])) assert_equal(2, len(tx3["vout"])) # the accumulated value should be 1.5, so the outputs should be # ~0.1 and 1.4 and should come from the same destination values = sorted([vout["value"] for vout in tx3["vout"]]) - assert_approx(values[0], 0.1, 0.0001) - assert_approx(values[1], 1.4) + assert_approx(values[0], 100000, 100) + assert_approx(values[1], 1400000) input_txids = [vin["txid"] for vin in tx3["vin"]] input_addrs = [self.nodes[1].gettransaction( txid)['details'][0]['address'] for txid in input_txids] assert_equal(input_addrs[0], input_addrs[1]) # Node 2 enforces avoidpartialspends so needs no checking here # Test wallet option maxapsfee with Node 3 addr_aps = self.nodes[3].getnewaddress() - self.nodes[0].sendtoaddress(addr_aps, 1.0) - self.nodes[0].sendtoaddress(addr_aps, 1.0) + self.nodes[0].sendtoaddress(addr_aps, 1000000) + self.nodes[0].sendtoaddress(addr_aps, 1000000) self.nodes[0].generate(1) self.sync_all() - txid4 = self.nodes[3].sendtoaddress(self.nodes[0].getnewaddress(), 0.1) + txid4 = self.nodes[3].sendtoaddress( + self.nodes[0].getnewaddress(), 100000) tx4 = self.nodes[3].getrawtransaction(txid4, True) # tx4 should have 2 inputs and 2 outputs although one output would # have been enough and the transaction caused higher fees assert_equal(2, len(tx4["vin"])) assert_equal(2, len(tx4["vout"])) # Empty out node2's wallet self.nodes[2].sendtoaddress(address=self.nodes[0].getnewaddress( ), amount=self.nodes[2].getbalance(), subtractfeefromamount=True) self.sync_all() self.nodes[0].generate(1) # Fill node2's wallet with 10000 outputs corresponding to the same # scriptPubKey for i in range(5): raw_tx = self.nodes[0].createrawtransaction( - [{"txid": "0" * 64, "vout": 0}], [{addr2[0]: 0.05}]) + [{"txid": "0" * 64, "vout": 0}], [{addr2[0]: 50000}]) tx = FromHex(CTransaction(), raw_tx) tx.vin = [] tx.vout = [tx.vout[0]] * 2000 funded_tx = self.nodes[0].fundrawtransaction(ToHex(tx)) signed_tx = self.nodes[0].signrawtransactionwithwallet( funded_tx['hex']) self.nodes[0].sendrawtransaction(signed_tx['hex']) self.nodes[0].generate(1) self.sync_all() # Check that we can create a transaction that only requires ~100 of our # utxos, without pulling in all outputs and creating a transaction that # is way too big. - assert self.nodes[2].sendtoaddress(address=addr2[0], amount=5) + assert self.nodes[2].sendtoaddress(address=addr2[0], amount=5000000) if __name__ == '__main__': WalletGroupTest().main() diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py index 91a7f2bc5..72eb4702a 100755 --- a/test/functional/wallet_hd.py +++ b/test/functional/wallet_hd.py @@ -1,319 +1,328 @@ #!/usr/bin/env python3 # Copyright (c) 2016-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test Hierarchical Deterministic wallet function.""" import os import shutil from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, connect_nodes, assert_raises_rpc_error ) class WalletHDTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 self.extra_args = [[], ['-keypool=0']] self.supports_cli = False def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): # Make sure we use hd, keep masterkeyid masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] assert_equal(len(masterkeyid), 40) # create an internal key change_addr = self.nodes[1].getrawchangeaddress() change_addrV = self.nodes[1].getaddressinfo(change_addr) # first internal child key assert_equal(change_addrV["hdkeypath"], "m/0'/1'/0'") # Import a non-HD private key in the HD wallet non_hd_add = self.nodes[0].getnewaddress() self.nodes[1].importprivkey(self.nodes[0].dumpprivkey(non_hd_add)) # This should be enough to keep the master key and the non-HD key self.nodes[1].backupwallet( os.path.join(self.nodes[1].datadir, "hd.bak")) # self.nodes[1].dumpwallet(os.path.join(self.nodes[1].datadir, "hd.dump")) # Derive some HD addresses and remember the last # Also send funds to each add self.nodes[0].generate(101) hd_add = None NUM_HD_ADDS = 10 for i in range(NUM_HD_ADDS): hd_add = self.nodes[1].getnewaddress() hd_info = self.nodes[1].getaddressinfo(hd_add) assert_equal(hd_info["hdkeypath"], "m/0'/0'/" + str(i) + "'") assert_equal(hd_info["hdseedid"], masterkeyid) - self.nodes[0].sendtoaddress(hd_add, 1) + self.nodes[0].sendtoaddress(hd_add, 1000000) self.nodes[0].generate(1) - self.nodes[0].sendtoaddress(non_hd_add, 1) + self.nodes[0].sendtoaddress(non_hd_add, 1000000) self.nodes[0].generate(1) # create an internal key (again) change_addr = self.nodes[1].getrawchangeaddress() change_addrV = self.nodes[1].getaddressinfo(change_addr) # second internal child key assert_equal(change_addrV["hdkeypath"], "m/0'/1'/1'") self.sync_all() - assert_equal(self.nodes[1].getbalance(), NUM_HD_ADDS + 1) + assert_equal( + self.nodes[1].getbalance(), + (NUM_HD_ADDS * 1000000) + 1000000) self.log.info("Restore backup ...") self.stop_node(1) # we need to delete the complete regtest directory # otherwise node1 would auto-recover all funds in flag the keypool keys # as used shutil.rmtree( os.path.join( self.nodes[1].datadir, self.chain, "blocks")) shutil.rmtree(os.path.join( self.nodes[1].datadir, self.chain, "chainstate")) shutil.copyfile(os.path.join(self.nodes[1].datadir, "hd.bak"), os.path.join( self.nodes[1].datadir, self.chain, "wallets", "wallet.dat")) self.start_node(1) # Assert that derivation is deterministic hd_add_2 = None for i in range(NUM_HD_ADDS): hd_add_2 = self.nodes[1].getnewaddress() hd_info_2 = self.nodes[1].getaddressinfo(hd_add_2) assert_equal(hd_info_2["hdkeypath"], "m/0'/0'/" + str(i) + "'") assert_equal(hd_info_2["hdseedid"], masterkeyid) assert_equal(hd_add, hd_add_2) connect_nodes(self.nodes[0], self.nodes[1]) self.sync_all() # Needs rescan self.restart_node(1, extra_args=self.extra_args[1] + ['-rescan']) - assert_equal(self.nodes[1].getbalance(), NUM_HD_ADDS + 1) + assert_equal( + self.nodes[1].getbalance(), + (NUM_HD_ADDS * 1000000) + 1000000) # Try a RPC based rescan self.stop_node(1) shutil.rmtree( os.path.join( self.nodes[1].datadir, self.chain, "blocks")) shutil.rmtree(os.path.join( self.nodes[1].datadir, self.chain, "chainstate")) shutil.copyfile(os.path.join(self.nodes[1].datadir, "hd.bak"), os.path.join( self.nodes[1].datadir, self.chain, "wallets", "wallet.dat")) self.start_node(1, extra_args=self.extra_args[1]) connect_nodes(self.nodes[0], self.nodes[1]) self.sync_all() # Wallet automatically scans blocks older than key on startup - assert_equal(self.nodes[1].getbalance(), NUM_HD_ADDS + 1) + assert_equal( + self.nodes[1].getbalance(), + (NUM_HD_ADDS * 1000000) + 1000000) out = self.nodes[1].rescanblockchain(0, 1) assert_equal(out['start_height'], 0) assert_equal(out['stop_height'], 1) out = self.nodes[1].rescanblockchain(2, 4) assert_equal(out['start_height'], 2) assert_equal(out['stop_height'], 4) out = self.nodes[1].rescanblockchain(3) assert_equal(out['start_height'], 3) assert_equal(out['stop_height'], self.nodes[1].getblockcount()) out = self.nodes[1].rescanblockchain() assert_equal(out['start_height'], 0) assert_equal(out['stop_height'], self.nodes[1].getblockcount()) - assert_equal(self.nodes[1].getbalance(), NUM_HD_ADDS + 1) + assert_equal( + self.nodes[1].getbalance(), + (NUM_HD_ADDS * 1000000) + 1000000) # send a tx and make sure its using the internal chain for the # changeoutput - txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1) + txid = self.nodes[1].sendtoaddress( + self.nodes[0].getnewaddress(), 1000000) outs = self.nodes[1].decoderawtransaction( self.nodes[1].gettransaction(txid)['hex'])['vout'] keypath = "" for out in outs: - if out['value'] != 1: + if out['value'] != 1000000: keypath = self.nodes[1].getaddressinfo( out['scriptPubKey']['addresses'][0])['hdkeypath'] assert_equal(keypath[0:7], "m/0'/1'") # Generate a new HD seed on node 1 and make sure it is set orig_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] self.nodes[1].sethdseed() new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] assert orig_masterkeyid != new_masterkeyid addr = self.nodes[1].getnewaddress() # Make sure the new address is the first from the keypool assert_equal(self.nodes[1].getaddressinfo( addr)['hdkeypath'], 'm/0\'/0\'/0\'') # Fill keypool with 1 key self.nodes[1].keypoolrefill(1) # Set a new HD seed on node 1 without flushing the keypool new_seed = self.nodes[0].dumpprivkey(self.nodes[0].getnewaddress()) orig_masterkeyid = new_masterkeyid self.nodes[1].sethdseed(False, new_seed) new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] assert orig_masterkeyid != new_masterkeyid addr = self.nodes[1].getnewaddress() assert_equal(orig_masterkeyid, self.nodes[1].getaddressinfo( addr)['hdseedid']) # Make sure the new address continues previous keypool assert_equal(self.nodes[1].getaddressinfo( addr)['hdkeypath'], 'm/0\'/0\'/1\'') # Check that the next address is from the new seed self.nodes[1].keypoolrefill(1) next_addr = self.nodes[1].getnewaddress() assert_equal(new_masterkeyid, self.nodes[1].getaddressinfo( next_addr)['hdseedid']) # Make sure the new address is not from previous keypool assert_equal(self.nodes[1].getaddressinfo( next_addr)['hdkeypath'], 'm/0\'/0\'/0\'') assert next_addr != addr # Sethdseed parameter validity assert_raises_rpc_error(-1, 'sethdseed', self.nodes[0].sethdseed, False, new_seed, 0) assert_raises_rpc_error(-5, "Invalid private key", self.nodes[1].sethdseed, False, "not_wif") assert_raises_rpc_error(-1, "JSON value is not a boolean as expected", self.nodes[1].sethdseed, "Not_bool") assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[1].sethdseed, False, True) assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, new_seed) assert_raises_rpc_error(-5, "Already have this key", self.nodes[1].sethdseed, False, self.nodes[1].dumpprivkey(self.nodes[1].getnewaddress())) self.log.info( 'Test sethdseed restoring with keys outside of the initial keypool') self.nodes[0].generate(10) # Restart node 1 with keypool of 3 and a different wallet self.nodes[1].createwallet(wallet_name='origin', blank=True) self.restart_node(1, extra_args=['-keypool=3', '-wallet=origin']) connect_nodes(self.nodes[0], self.nodes[1]) # sethdseed restoring and seeing txs to addresses out of the # keypool origin_rpc = self.nodes[1].get_wallet_rpc('origin') seed = self.nodes[0].dumpprivkey(self.nodes[0].getnewaddress()) origin_rpc.sethdseed(True, seed) self.nodes[1].createwallet(wallet_name='restore', blank=True) restore_rpc = self.nodes[1].get_wallet_rpc('restore') # Set to be the same seed as origin_rpc restore_rpc.sethdseed(True, seed) # Rotate to a new seed, making original `seed` inactive restore_rpc.sethdseed(True) self.nodes[1].createwallet(wallet_name='restore2', blank=True) restore2_rpc = self.nodes[1].get_wallet_rpc('restore2') # Set to be the same seed as origin_rpc restore2_rpc.sethdseed(True, seed) # Rotate to a new seed, making original `seed` inactive restore2_rpc.sethdseed(True) # Check persistence of inactive seed by reloading restore. restore2 # is still loaded to test the case where the wallet is not reloaded restore_rpc.unloadwallet() self.nodes[1].loadwallet('restore') restore_rpc = self.nodes[1].get_wallet_rpc('restore') # Empty origin keypool and get an address that is beyond the # initial keypool origin_rpc.getnewaddress() origin_rpc.getnewaddress() # Last address of initial keypool last_addr = origin_rpc.getnewaddress() # First address beyond initial keypool addr = origin_rpc.getnewaddress() # Check that the restored seed has last_addr but does not have addr info = restore_rpc.getaddressinfo(last_addr) assert_equal(info['ismine'], True) info = restore_rpc.getaddressinfo(addr) assert_equal(info['ismine'], False) info = restore2_rpc.getaddressinfo(last_addr) assert_equal(info['ismine'], True) info = restore2_rpc.getaddressinfo(addr) assert_equal(info['ismine'], False) # Check that the origin seed has addr info = origin_rpc.getaddressinfo(addr) assert_equal(info['ismine'], True) # Send a transaction to addr, which is out of the initial keypool. # The wallet that has set a new seed (restore_rpc) should not # detect this transaction. - txid = self.nodes[0].sendtoaddress(addr, 1) + txid = self.nodes[0].sendtoaddress(addr, 1000000) origin_rpc.sendrawtransaction( self.nodes[0].gettransaction(txid)['hex']) self.nodes[0].generate(1) self.sync_blocks() origin_rpc.gettransaction(txid) assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', restore_rpc.gettransaction, txid) out_of_kp_txid = txid # Send a transaction to last_addr, which is in the initial keypool. # The wallet that has set a new seed (restore_rpc) should detect this # transaction and generate 3 new keys from the initial seed. # The previous transaction (out_of_kp_txid) should still not be # detected as a rescan is required. - txid = self.nodes[0].sendtoaddress(last_addr, 1) + txid = self.nodes[0].sendtoaddress(last_addr, 1000000) origin_rpc.sendrawtransaction( self.nodes[0].gettransaction(txid)['hex']) self.nodes[0].generate(1) self.sync_blocks() origin_rpc.gettransaction(txid) restore_rpc.gettransaction(txid) assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', restore_rpc.gettransaction, out_of_kp_txid) restore2_rpc.gettransaction(txid) assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', restore2_rpc.gettransaction, out_of_kp_txid) # After rescanning, restore_rpc should now see out_of_kp_txid and generate an additional key. # addr should now be part of restore_rpc and be ismine restore_rpc.rescanblockchain() restore_rpc.gettransaction(out_of_kp_txid) info = restore_rpc.getaddressinfo(addr) assert_equal(info['ismine'], True) restore2_rpc.rescanblockchain() restore2_rpc.gettransaction(out_of_kp_txid) info = restore2_rpc.getaddressinfo(addr) assert_equal(info['ismine'], True) # Check again that 3 keys were derived. # Empty keypool and get an address that is beyond the initial # keypool origin_rpc.getnewaddress() origin_rpc.getnewaddress() last_addr = origin_rpc.getnewaddress() addr = origin_rpc.getnewaddress() # Check that the restored seed has last_addr but does not have addr info = restore_rpc.getaddressinfo(last_addr) assert_equal(info['ismine'], True) info = restore_rpc.getaddressinfo(addr) assert_equal(info['ismine'], False) info = restore2_rpc.getaddressinfo(last_addr) assert_equal(info['ismine'], True) info = restore2_rpc.getaddressinfo(addr) assert_equal(info['ismine'], False) if __name__ == '__main__': WalletHDTest().main() diff --git a/test/functional/wallet_import_rescan.py b/test/functional/wallet_import_rescan.py index ca90410d5..797e5708c 100755 --- a/test/functional/wallet_import_rescan.py +++ b/test/functional/wallet_import_rescan.py @@ -1,242 +1,242 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test wallet import RPCs. Test rescan behavior of importaddress, importpubkey, importprivkey, and importmulti RPCs with different types of keys and rescan options. In the first part of the test, node 0 creates an address for each type of import RPC call and node 0 sends BCH to it. Then other nodes import the addresses, and the test makes listtransactions and getbalance calls to confirm that the importing node either did or did not execute rescans picking up the send transactions. In the second part of the test, node 0 sends more BCH to each address, and the test makes more listtransactions and getbalance calls to confirm that the importing nodes pick up the new transactions regardless of whether rescans happened previously. """ from decimal import Decimal import collections import enum import itertools import random from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, connect_nodes, set_node_times, ) Call = enum.Enum("Call", "single multiaddress multiscript") Data = enum.Enum("Data", "address pub priv") Rescan = enum.Enum("Rescan", "no yes late_timestamp") class Variant(collections.namedtuple("Variant", "call data rescan prune")): """Helper for importing one key and verifying scanned transactions.""" def do_import(self, timestamp): """Call one key import RPC.""" rescan = self.rescan == Rescan.yes assert_equal(self.address["solvable"], True) if self.call == Call.single: if self.data == Data.address: response = self.node.importaddress( address=self.address["address"], label=self.label, rescan=rescan) elif self.data == Data.pub: response = self.node.importpubkey( pubkey=self.address["pubkey"], label=self.label, rescan=rescan) elif self.data == Data.priv: response = self.node.importprivkey( privkey=self.key, label=self.label, rescan=rescan) assert_equal(response, None) elif self.call in (Call.multiaddress, Call.multiscript): request = { "scriptPubKey": { "address": self.address["address"] } if self.call == Call.multiaddress else self.address["scriptPubKey"], "timestamp": timestamp + TIMESTAMP_WINDOW + ( 1 if self.rescan == Rescan.late_timestamp else 0), "pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [], "keys": [self.key] if self.data == Data.priv else [], "label": self.label, "watchonly": self.data != Data.priv } response = self.node.importmulti( requests=[request], options={ "rescan": self.rescan in ( Rescan.yes, Rescan.late_timestamp)}, ) assert_equal(response, [{"success": True}]) def check(self, txid=None, amount=None, confirmation_height=None): """Verify that listtransactions/listreceivedbyaddress return expected values.""" txs = self.node.listtransactions( label=self.label, count=10000, include_watchonly=True) current_height = self.node.getblockcount() assert_equal(len(txs), self.expected_txs) addresses = self.node.listreceivedbyaddress( minconf=0, include_watchonly=True, address_filter=self.address['address']) if self.expected_txs: assert_equal(len(addresses[0]["txids"]), self.expected_txs) if txid is not None: tx, = [tx for tx in txs if tx["txid"] == txid] assert_equal(tx["label"], self.label) assert_equal(tx["address"], self.address["address"]) assert_equal(tx["amount"], amount) assert_equal(tx["category"], "receive") assert_equal(tx["label"], self.label) assert_equal(tx["txid"], txid) assert_equal(tx["confirmations"], 1 + current_height - confirmation_height) assert_equal("trusted" not in tx, True) address, = [ad for ad in addresses if txid in ad["txids"]] assert_equal(address["address"], self.address["address"]) assert_equal(address["amount"], self.expected_balance) assert_equal(address["confirmations"], 1 + current_height - confirmation_height) # Verify the transaction is correctly marked watchonly depending on # whether the transaction pays to an imported public key or # imported private key. The test setup ensures that transaction # inputs will not be from watchonly keys (important because # involvesWatchonly will be true if either the transaction output # or inputs are watchonly). if self.data != Data.priv: assert_equal(address["involvesWatchonly"], True) else: assert_equal("involvesWatchonly" not in address, True) # List of Variants for each way a key or address could be imported. IMPORT_VARIANTS = [Variant(*variants) for variants in itertools.product(Call, Data, Rescan, (False, True))] # List of nodes to import keys to. Half the nodes will have pruning disabled, # half will have it enabled. Different nodes will be used for imports that are # expected to cause rescans, and imports that are not expected to cause # rescans, in order to prevent rescans during later imports picking up # transactions associated with earlier imports. This makes it easier to keep # track of expected balances and transactions. ImportNode = collections.namedtuple("ImportNode", "prune rescan") IMPORT_NODES = [ImportNode(*fields) for fields in itertools.product((False, True), repeat=2)] # Rescans start at the earliest block up to 2 hours before the key timestamp. TIMESTAMP_WINDOW = 2 * 60 * 60 -AMOUNT_DUST = 0.00000546 +AMOUNT_DUST = 5.46 def get_rand_amount(): - r = random.uniform(AMOUNT_DUST, 1) - return Decimal(str(round(r, 8))) + r = random.uniform(AMOUNT_DUST, 1000000) + return Decimal(str(round(r, 2))) class ImportRescanTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 + len(IMPORT_NODES) self.supports_cli = False def skip_test_if_missing_module(self): self.skip_if_no_wallet() def setup_network(self): self.extra_args = [[] for _ in range(self.num_nodes)] for i, import_node in enumerate(IMPORT_NODES, 2): if import_node.prune: self.extra_args[i] += ["-prune=1"] self.add_nodes(self.num_nodes, extra_args=self.extra_args) # Import keys with pruning disabled self.start_nodes(extra_args=[[]] * self.num_nodes) for n in self.nodes: n.importprivkey( privkey=n.get_deterministic_priv_key().key, label='coinbase') self.stop_nodes() self.start_nodes() for i in range(1, self.num_nodes): connect_nodes(self.nodes[i], self.nodes[0]) def run_test(self): # Create one transaction on node 0 with a unique amount for # each possible type of wallet import RPC. for i, variant in enumerate(IMPORT_VARIANTS): variant.label = "label {} {}".format(i, variant) variant.address = self.nodes[1].getaddressinfo( self.nodes[1].getnewaddress(label=variant.label)) variant.key = self.nodes[1].dumpprivkey(variant.address["address"]) variant.initial_amount = get_rand_amount() variant.initial_txid = self.nodes[0].sendtoaddress( variant.address["address"], variant.initial_amount) # Generate one block for each send self.nodes[0].generate(1) variant.confirmation_height = self.nodes[0].getblockcount() variant.timestamp = self.nodes[0].getblockheader( self.nodes[0].getbestblockhash())["time"] # Generate a block further in the future (past the rescan window). assert_equal(self.nodes[0].getrawmempool(), []) set_node_times(self.nodes, self.nodes[0].getblockheader( self.nodes[0].getbestblockhash())["time"] + TIMESTAMP_WINDOW + 1) self.nodes[0].generate(1) self.sync_all() # For each variation of wallet key import, invoke the import RPC and # check the results from getbalance and listtransactions. for variant in IMPORT_VARIANTS: self.log.info('Run import for variant {}'.format(variant)) expect_rescan = variant.rescan == Rescan.yes variant.node = self.nodes[2 + IMPORT_NODES.index( ImportNode(variant.prune, expect_rescan))] variant.do_import(variant.timestamp) if expect_rescan: variant.expected_balance = variant.initial_amount variant.expected_txs = 1 variant.check(variant.initial_txid, variant.initial_amount, variant.confirmation_height) else: variant.expected_balance = 0 variant.expected_txs = 0 variant.check() # Create new transactions sending to each address. for i, variant in enumerate(IMPORT_VARIANTS): variant.sent_amount = get_rand_amount() variant.sent_txid = self.nodes[0].sendtoaddress( variant.address["address"], variant.sent_amount) # Generate one block for each send self.nodes[0].generate(1) variant.confirmation_height = self.nodes[0].getblockcount() assert_equal(self.nodes[0].getrawmempool(), []) self.sync_all() # Check the latest results from getbalance and listtransactions. for variant in IMPORT_VARIANTS: self.log.info('Run check for variant {}'.format(variant)) variant.expected_balance += variant.sent_amount variant.expected_txs += 1 variant.check(variant.sent_txid, variant.sent_amount, variant.confirmation_height) if __name__ == "__main__": ImportRescanTest().main() diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py index 67de4f604..33e6fda05 100755 --- a/test/functional/wallet_importdescriptors.py +++ b/test/functional/wallet_importdescriptors.py @@ -1,463 +1,463 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the importdescriptors RPC. Test importdescriptors by generating keys on node0, importing the corresponding descriptors on node1 and then testing the address info for the different address variants. - `get_generate_key()` is called to generate keys and return the privkeys, pubkeys and all variants of scriptPubKey and address. - `test_importdesc()` is called to send an importdescriptors call to node1, test success, and (if unsuccessful) test the error code and error message returned. - `test_address()` is called to call getaddressinfo for an address on node1 and test the values returned.""" from test_framework.test_framework import BitcoinTestFramework from test_framework.descriptors import descsum_create from test_framework.util import ( assert_equal, assert_raises_rpc_error, find_vout_for_address, ) from test_framework.wallet_util import ( get_generate_key, test_address, ) class ImportDescriptorsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.extra_args = [[], ["-keypool=5"]] self.setup_clean_chain = True def skip_test_if_missing_module(self): self.skip_if_no_wallet() def test_importdesc(self, req, success, error_code=None, error_message=None, warnings=None, wallet=None): """Run importdescriptors and assert success""" if warnings is None: warnings = [] wrpc = self.nodes[1].get_wallet_rpc('w1') if wallet is not None: wrpc = wallet result = wrpc.importdescriptors([req]) observed_warnings = [] if 'warnings' in result[0]: observed_warnings = result[0]['warnings'] assert_equal( "\n".join( sorted(warnings)), "\n".join( sorted(observed_warnings))) assert_equal(result[0]['success'], success) if error_code is not None: assert_equal(result[0]['error']['code'], error_code) assert_equal(result[0]['error']['message'], error_message) def run_test(self): self.log.info('Setting up wallets') self.nodes[0].createwallet( wallet_name='w0', disable_private_keys=False) w0 = self.nodes[0].get_wallet_rpc('w0') self.nodes[1].createwallet( wallet_name='w1', disable_private_keys=True, blank=True, descriptors=True) w1 = self.nodes[1].get_wallet_rpc('w1') assert_equal(w1.getwalletinfo()['keypoolsize'], 0) self.nodes[1].createwallet( wallet_name="wpriv", disable_private_keys=False, blank=True, descriptors=True) wpriv = self.nodes[1].get_wallet_rpc("wpriv") assert_equal(wpriv.getwalletinfo()['keypoolsize'], 0) self.log.info('Mining coins') w0.generatetoaddress(101, w0.getnewaddress()) # RPC importdescriptors ----------------------------------------------- # # Test import fails if no descriptor present key = get_generate_key() self.log.info("Import should fail if a descriptor is not provided") self.test_importdesc({"timestamp": "now"}, success=False, error_code=-8, error_message='Descriptor not found.') # # Test importing of a P2PKH descriptor key = get_generate_key() self.log.info("Should import a p2pkh descriptor") self.test_importdesc({"desc": descsum_create("pkh(" + key.pubkey + ")"), "timestamp": "now", "label": "Descriptor import test"}, success=True) test_address(w1, key.p2pkh_addr, solvable=True, ismine=True, labels=["Descriptor import test"]) assert_equal(w1.getwalletinfo()['keypoolsize'], 0) self.log.info("Internal addresses cannot have labels") self.test_importdesc({"desc": descsum_create("pkh(" + key.pubkey + ")"), "timestamp": "now", "internal": True, "label": "Descriptor import test"}, success=False, error_code=-8, error_message="Internal addresses should not have a label") assert_equal(w1.getwalletinfo()['keypoolsize'], 0) test_address(w1, key.p2pkh_addr, ismine=True, solvable=True) # # Test importing of a multisig descriptor key1 = get_generate_key() key2 = get_generate_key() self.log.info("Should import a 1-of-2 bare multisig from descriptor") self.test_importdesc({"desc": descsum_create("multi(1," + key1.pubkey + "," + key2.pubkey + ")"), "timestamp": "now"}, success=True) self.log.info( "Should not treat individual keys from the imported bare multisig as watchonly") test_address(w1, key1.p2pkh_addr, ismine=False) # # Test ranged descriptors xpriv = "tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg" xpub = "tpubD6NzVbkrYhZ4YNXVQbNhMK1WqguFsUXceaVJKbmno2aZ3B6QfbMeraaYvnBSGpV3vxLyTTK9DYT1yoEck4XUScMzXoQ2U2oSmE2JyMedq3H" addresses = [ "2N7yv4p8G8yEaPddJxY41kPihnWvs39qCMf", "2MsHxyb2JS3pAySeNUsJ7mNnurtpeenDzLA"] # hdkeypath=m/0'/0'/0' and 1' # wpkh subscripts corresponding to the above addresses addresses += ["ecregtest:prvn9ycvgr5atuyh49sua3mapskh2mnnzg7t9yp6dt", "ecregtest:pp3n087yx0njv2e5wcvltahfxqst7l66rutz8ceeat"] desc = "sh(pkh(" + xpub + "/0/0/*" + "))" self.log.info("Ranged descriptors cannot have labels") self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [0, 100], "label": "test"}, success=False, error_code=-8, error_message='Ranged descriptors should not have a label') self.log.info("Private keys required for private keys enabled wallet") self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [0, 100]}, success=False, error_code=-4, error_message='Cannot import descriptor without private keys to a wallet with private keys enabled', wallet=wpriv) self.log.info( "Ranged descriptor import should warn without a specified range") self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now"}, success=True, warnings=['Range not given, using default keypool range']) assert_equal(w1.getwalletinfo()['keypoolsize'], 0) # # Test importing of a ranged descriptor with xpriv self.log.info( "Should not import a ranged descriptor that includes xpriv into a watch-only wallet") desc = "sh(pkh(" + xpriv + "/0'/0'/*'" + "))" self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": 1}, success=False, error_code=-4, error_message='Cannot import private keys to a wallet with private keys disabled') for address in addresses: test_address(w1, address, ismine=False, solvable=False) self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": -1}, success=False, error_code=-8, error_message='End of range is too high') self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [-1, 10]}, success=False, error_code=-8, error_message='Range should be greater or equal than 0') self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [(2 << 31 + 1) - 1000000, (2 << 31 + 1)]}, success=False, error_code=-8, error_message='End of range is too high') self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [2, 1]}, success=False, error_code=-8, error_message='Range specified as [begin,end] must not have begin after end') self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [0, 1000001]}, success=False, error_code=-8, error_message='Range is too large') # Make sure ranged imports import keys in order w1 = self.nodes[1].get_wallet_rpc('w1') self.log.info('Key ranges should be imported in order') xpub = "tpubDAXcJ7s7ZwicqjprRaEWdPoHKrCS215qxGYxpusRLLmJuT69ZSicuGdSfyvyKpvUNYBW1s2U3NSrT6vrCYB9e6nZUEvrqnwXPF8ArTCRXMY" addresses = [ 'ecregtest:qp0v86h53rc92hjrlpwzpjtdlgzsxu25svv6g40fpl', # m/0'/0'/0 'ecregtest:qqasy0zlkdleqt4pkn8fs4ehm5gnnz6qpgdcpt90fq', # m/0'/0'/1 'ecregtest:qp0sp4wlhctvprqvdt2dgvqcfdjssu04xgey0l3syw', # m/0'/0'/2 'ecregtest:qrhn24tegn04cptfv4ldhtkduxq55zcwrycjfdj9vr', # m/0'/0'/3 'ecregtest:qzpqhett2uwltq803vrxv7zkqhft5vsnmcjeh50v0p', # m/0'/0'/4 ] self.test_importdesc({'desc': descsum_create('sh(pkh([abcdef12/0h/0h]' + xpub + '/*))'), 'active': True, 'range': [0, 2], 'timestamp': 'now' }, success=True) self.test_importdesc({'desc': descsum_create('pkh([12345678/0h/0h]' + xpub + '/*)'), 'active': True, 'range': [0, 2], 'timestamp': 'now' }, success=True) assert_equal(w1.getwalletinfo()['keypoolsize'], 5) for i, expected_addr in enumerate(addresses): pkh_addr = w1.getnewaddress('') assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress) assert_equal(pkh_addr, expected_addr) pkh_addr_info = w1.getaddressinfo(pkh_addr) assert_equal(pkh_addr_info['desc'][:22], 'pkh([12345678/0\'/0\'/{}]'.format(i)) # After retrieving a key, we don't refill the keypool again, so # it's one less for each address type assert_equal(w1.getwalletinfo()['keypoolsize'], 4) w1.keypoolrefill() assert_equal(w1.getwalletinfo()['keypoolsize'], 5) # Check active=False default self.log.info('Check imported descriptors are not active by default') self.test_importdesc({'desc': descsum_create('pkh([12345678/0h/0h]' + xpub + '/*)'), 'range': [0, 2], 'timestamp': 'now', 'internal': True }, success=True) assert_raises_rpc_error(-4, 'This wallet has no available keys', w1.getrawchangeaddress) # # Test importing a descriptor containing a WIF private key wif_priv = "cTe1f5rdT8A8DFgVWTjyPwACsDPJM9ff4QngFxUixCSvvbg1x6sh" address = "ecregtest:ppn85zpvym8cdccmgw8km6e48jfhnpa435h3hkyfd6" desc = "sh(pkh(" + wif_priv + "))" self.log.info( "Should import a descriptor with a WIF private key as spendable") self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now"}, success=True, wallet=wpriv) test_address(wpriv, address, solvable=True, ismine=True) - txid = w0.sendtoaddress(address, 49.99999600) + txid = w0.sendtoaddress(address, 49999996.00) w0.generatetoaddress(6, w0.getnewaddress()) self.sync_blocks() tx = wpriv.createrawtransaction([{"txid": txid, "vout": 0}], { - w0.getnewaddress(): 49.999}) + w0.getnewaddress(): 49999000}) signed_tx = wpriv.signrawtransactionwithwallet(tx) w1.sendrawtransaction(signed_tx['hex']) # Make sure that we can use import and use multisig as addresses self.log.info( 'Test that multisigs can be imported, signed for, and getnewaddress\'d') self.nodes[1].createwallet( wallet_name="wmulti_priv", disable_private_keys=False, blank=True, descriptors=True) wmulti_priv = self.nodes[1].get_wallet_rpc("wmulti_priv") assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 0) self.test_importdesc({"desc": "sh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/0h/0h/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/0h/0h/*,tprv8ZgxMBicQKsPeonDt8Ka2mrQmHa61hQ5FQCsvWBTpSNzBFgM58cV2EuXNAHF14VawVpznnme3SuTbA62sGriwWyKifJmXntfNeK7zeqMCj1/84h/0h/0h/*))#f5nqn4ax", "active": True, "range": 1000, "next_index": 0, "timestamp": "now"}, success=True, wallet=wmulti_priv) self.test_importdesc({"desc": "sh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/1h/0h/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/1h/0h/*,tprv8ZgxMBicQKsPeonDt8Ka2mrQmHa61hQ5FQCsvWBTpSNzBFgM58cV2EuXNAHF14VawVpznnme3SuTbA62sGriwWyKifJmXntfNeK7zeqMCj1/84h/1h/0h/*))#m4e4s5de", "active": True, "internal": True, "range": 1000, "next_index": 0, "timestamp": "now"}, success=True, wallet=wmulti_priv) # Range end (1000) is inclusive, so 1001 addresses generated assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 1001) addr = wmulti_priv.getnewaddress('') # Derived at m/84'/0'/0'/0 assert_equal( addr, 'ecregtest:pzkcf26dw7np58jcspnpxaupgz9csnc3wsf5wdh2a3') change_addr = wmulti_priv.getrawchangeaddress() assert_equal( change_addr, 'ecregtest:prnkfg7pxe3kpyv3l4v00ft6q3sfseag7vnva0k49n') assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 1000) - txid = w0.sendtoaddress(addr, 10) + txid = w0.sendtoaddress(addr, 10000000) self.nodes[0].generate(6) self.sync_all() self.nodes[1].createwallet( wallet_name="wmulti_pub", disable_private_keys=True, blank=True, descriptors=True) wmulti_pub = self.nodes[1].get_wallet_rpc("wmulti_pub") assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 0) self.test_importdesc({"desc": "sh(multi(2,[7b2d0242/84h/0h/0h]tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*,[59b09cd6/84h/0h/0h]tpubDDBF2BTR6s8drwrfDei8WxtckGuSm1cyoKxYY1QaKSBFbHBYQArWhHPA6eJrzZej6nfHGLSURYSLHr7GuYch8aY5n61tGqgn8b4cXrMuoPH/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))#x75vpsak", "active": True, "range": 1000, "next_index": 0, "timestamp": "now"}, success=True, wallet=wmulti_pub) self.test_importdesc({"desc": "sh(multi(2,[7b2d0242/84h/1h/0h]tpubDCXqdwWZcszwqYJSnZp8eARkxGJfHAk23KDxbztV4BbschfaTfYLTcSkSJ3TN64dRqwa1rnFUScsYormKkGqNbbPwkorQimVevXjxzUV9Gf/*,[59b09cd6/84h/1h/0h]tpubDCYfZY2ceyHzYzMMVPt9MNeiqtQ2T7Uyp9QSFwYXh8Vi9iJFYXcuphJaGXfF3jUQJi5Y3GMNXvM11gaL4txzZgNGK22BFAwMXynnzv4z2Jh/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))#v0t48ucu", "active": True, "internal": True, "range": 1000, "next_index": 0, "timestamp": "now"}, success=True, wallet=wmulti_pub) # The first one was already consumed by previous import and is detected # as used assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 1000) addr = wmulti_pub.getnewaddress('') # Derived at m/84'/0'/0'/1 assert_equal( addr, 'ecregtest:pr5xql8r03jp5dvrep22dns59vf7hhykr5nmaucy2h') change_addr = wmulti_pub.getrawchangeaddress() assert_equal( change_addr, 'ecregtest:prnkfg7pxe3kpyv3l4v00ft6q3sfseag7vnva0k49n') assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 999) - txid = w0.sendtoaddress(addr, 10) + txid = w0.sendtoaddress(addr, 10000000) vout = find_vout_for_address(self.nodes[0], txid, addr) self.nodes[0].generate(6) self.sync_all() assert_equal(wmulti_pub.getbalance(), wmulti_priv.getbalance()) self.log.info("Multisig with distributed keys") self.nodes[1].createwallet( wallet_name="wmulti_priv1", descriptors=True) wmulti_priv1 = self.nodes[1].get_wallet_rpc("wmulti_priv1") res = wmulti_priv1.importdescriptors([ { "desc": descsum_create("sh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/0h/0h/*,[59b09cd6/84h/0h/0h]tpubDDBF2BTR6s8drwrfDei8WxtckGuSm1cyoKxYY1QaKSBFbHBYQArWhHPA6eJrzZej6nfHGLSURYSLHr7GuYch8aY5n61tGqgn8b4cXrMuoPH/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))"), "active": True, "range": 1000, "next_index": 0, "timestamp": "now" }, { "desc": descsum_create("sh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/1h/0h/*,[59b09cd6/84h/1h/0h]tpubDCYfZY2ceyHzYzMMVPt9MNeiqtQ2T7Uyp9QSFwYXh8Vi9iJFYXcuphJaGXfF3jUQJi5Y3GMNXvM11gaL4txzZgNGK22BFAwMXynnzv4z2Jh/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))"), "active": True, "internal": True, "range": 1000, "next_index": 0, "timestamp": "now" }]) assert_equal(res[0]['success'], True) assert_equal( res[0]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors') assert_equal(res[1]['success'], True) assert_equal( res[1]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors') self.nodes[1].createwallet( wallet_name='wmulti_priv2', blank=True, descriptors=True) wmulti_priv2 = self.nodes[1].get_wallet_rpc('wmulti_priv2') res = wmulti_priv2.importdescriptors([ { "desc": descsum_create("sh(multi(2,[7b2d0242/84h/0h/0h]tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/0h/0h/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))"), "active": True, "range": 1000, "next_index": 0, "timestamp": "now" }, { "desc": descsum_create("sh(multi(2,[7b2d0242/84h/1h/0h]tpubDCXqdwWZcszwqYJSnZp8eARkxGJfHAk23KDxbztV4BbschfaTfYLTcSkSJ3TN64dRqwa1rnFUScsYormKkGqNbbPwkorQimVevXjxzUV9Gf/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/1h/0h/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))"), "active": True, "internal": True, "range": 1000, "next_index": 0, "timestamp": "now" }]) assert_equal(res[0]['success'], True) assert_equal( res[0]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors') assert_equal(res[1]['success'], True) assert_equal( res[1]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors') rawtx = self.nodes[1].createrawtransaction( - [{'txid': txid, 'vout': vout}], {w0.getnewaddress(): 9.999}) + [{'txid': txid, 'vout': vout}], {w0.getnewaddress(): 9999000}) tx_signed_1 = wmulti_priv1.signrawtransactionwithwallet(rawtx) assert_equal(tx_signed_1['complete'], False) tx_signed_2 = wmulti_priv2.signrawtransactionwithwallet( tx_signed_1['hex']) assert_equal(tx_signed_2['complete'], True) self.nodes[1].sendrawtransaction(tx_signed_2['hex']) self.log.info("Combo descriptors cannot be active") self.test_importdesc({"desc": descsum_create("combo(tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*)"), "active": True, "range": 1, "timestamp": "now"}, success=False, error_code=-4, error_message="Combo descriptors cannot be set to active") self.log.info("Descriptors with no type cannot be active") self.test_importdesc({"desc": descsum_create("pk(tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*)"), "active": True, "range": 1, "timestamp": "now"}, success=True, warnings=["Unknown output type, cannot set descriptor to active."]) if __name__ == '__main__': ImportDescriptorsTest().main() diff --git a/test/functional/wallet_importprunedfunds.py b/test/functional/wallet_importprunedfunds.py index e94db7771..20647084b 100755 --- a/test/functional/wallet_importprunedfunds.py +++ b/test/functional/wallet_importprunedfunds.py @@ -1,126 +1,126 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2016 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the importprunedfunds and removeprunedfunds RPCs.""" from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error class ImportPrunedFundsTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): self.log.info("Mining blocks...") self.nodes[0].generate(101) self.sync_all() # address address1 = self.nodes[0].getnewaddress() # pubkey address2 = self.nodes[0].getnewaddress() # privkey address3 = self.nodes[0].getnewaddress() # Using privkey address3_privkey = self.nodes[0].dumpprivkey(address3) # Check only one address address_info = self.nodes[0].getaddressinfo(address1) assert_equal(address_info['ismine'], True) self.sync_all() # Node 1 sync test assert_equal(self.nodes[1].getblockcount(), 101) # Address Test - before import address_info = self.nodes[1].getaddressinfo(address1) assert_equal(address_info['iswatchonly'], False) assert_equal(address_info['ismine'], False) address_info = self.nodes[1].getaddressinfo(address2) assert_equal(address_info['iswatchonly'], False) assert_equal(address_info['ismine'], False) address_info = self.nodes[1].getaddressinfo(address3) assert_equal(address_info['iswatchonly'], False) assert_equal(address_info['ismine'], False) # Send funds to self - txnid1 = self.nodes[0].sendtoaddress(address1, 0.1) + txnid1 = self.nodes[0].sendtoaddress(address1, 100000) self.nodes[0].generate(1) rawtxn1 = self.nodes[0].gettransaction(txnid1)['hex'] proof1 = self.nodes[0].gettxoutproof([txnid1]) - txnid2 = self.nodes[0].sendtoaddress(address2, 0.05) + txnid2 = self.nodes[0].sendtoaddress(address2, 50000) self.nodes[0].generate(1) rawtxn2 = self.nodes[0].gettransaction(txnid2)['hex'] proof2 = self.nodes[0].gettxoutproof([txnid2]) - txnid3 = self.nodes[0].sendtoaddress(address3, 0.025) + txnid3 = self.nodes[0].sendtoaddress(address3, 25000) self.nodes[0].generate(1) rawtxn3 = self.nodes[0].gettransaction(txnid3)['hex'] proof3 = self.nodes[0].gettxoutproof([txnid3]) self.sync_all() # Import with no affiliated address assert_raises_rpc_error( -5, "No addresses", self.nodes[1].importprunedfunds, rawtxn1, proof1) balance1 = self.nodes[1].getbalance() assert_equal(balance1, Decimal(0)) # Import with affiliated address with no rescan self.nodes[1].importaddress(address=address2, rescan=False) self.nodes[1].importprunedfunds( rawtransaction=rawtxn2, txoutproof=proof2) assert [tx for tx in self.nodes[1].listtransactions( include_watchonly=True) if tx['txid'] == txnid2] # Import with private key with no rescan self.nodes[1].importprivkey(privkey=address3_privkey, rescan=False) self.nodes[1].importprunedfunds(rawtxn3, proof3) assert [tx for tx in self.nodes[1].listtransactions() if tx['txid'] == txnid3] balance3 = self.nodes[1].getbalance() - assert_equal(balance3, Decimal('0.025')) + assert_equal(balance3, Decimal('25000')) # Addresses Test - after import address_info = self.nodes[1].getaddressinfo(address1) assert_equal(address_info['iswatchonly'], False) assert_equal(address_info['ismine'], False) address_info = self.nodes[1].getaddressinfo(address2) assert_equal(address_info['iswatchonly'], True) assert_equal(address_info['ismine'], False) address_info = self.nodes[1].getaddressinfo(address3) assert_equal(address_info['iswatchonly'], False) assert_equal(address_info['ismine'], True) # Remove transactions assert_raises_rpc_error( -8, "Transaction does not exist in wallet.", self.nodes[1].removeprunedfunds, txnid1) assert not [tx for tx in self.nodes[1].listtransactions( include_watchonly=True) if tx['txid'] == txnid1] self.nodes[1].removeprunedfunds(txnid2) assert not [tx for tx in self.nodes[1].listtransactions( include_watchonly=True) if tx['txid'] == txnid2] self.nodes[1].removeprunedfunds(txnid3) assert not [tx for tx in self.nodes[1].listtransactions( include_watchonly=True) if tx['txid'] == txnid3] if __name__ == '__main__': ImportPrunedFundsTest().main() diff --git a/test/functional/wallet_keypool.py b/test/functional/wallet_keypool.py index 828a7b14c..9d6c3ebcf 100755 --- a/test/functional/wallet_keypool.py +++ b/test/functional/wallet_keypool.py @@ -1,170 +1,170 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet keypool and interaction with wallet encryption/locking.""" import time from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error class KeyPoolTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): nodes = self.nodes addr_before_encrypting = nodes[0].getnewaddress() addr_before_encrypting_data = nodes[ 0].getaddressinfo(addr_before_encrypting) wallet_info_old = nodes[0].getwalletinfo() assert addr_before_encrypting_data[ 'hdseedid'] == wallet_info_old['hdseedid'] # Encrypt wallet and wait to terminate nodes[0].encryptwallet('test') # Keep creating keys addr = nodes[0].getnewaddress() addr_data = nodes[0].getaddressinfo(addr) wallet_info = nodes[0].getwalletinfo() assert addr_before_encrypting_data[ 'hdseedid'] != wallet_info['hdseedid'] assert addr_data['hdseedid'] == wallet_info['hdseedid'] assert_raises_rpc_error( -12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress) # put six (plus 2) new keys in the keypool (100% external-, +100% # internal-keys, 1 in min) nodes[0].walletpassphrase('test', 12000) nodes[0].keypoolrefill(6) nodes[0].walletlock() wi = nodes[0].getwalletinfo() assert_equal(wi['keypoolsize_hd_internal'], 6) assert_equal(wi['keypoolsize'], 6) # drain the internal keys nodes[0].getrawchangeaddress() nodes[0].getrawchangeaddress() nodes[0].getrawchangeaddress() nodes[0].getrawchangeaddress() nodes[0].getrawchangeaddress() nodes[0].getrawchangeaddress() addr = set() # the next one should fail assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].getrawchangeaddress) # drain the external keys addr.add(nodes[0].getnewaddress()) addr.add(nodes[0].getnewaddress()) addr.add(nodes[0].getnewaddress()) addr.add(nodes[0].getnewaddress()) addr.add(nodes[0].getnewaddress()) addr.add(nodes[0].getnewaddress()) assert len(addr) == 6 # the next one should fail assert_raises_rpc_error( -12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress) # refill keypool with three new addresses nodes[0].walletpassphrase('test', 1) nodes[0].keypoolrefill(3) # test walletpassphrase timeout time.sleep(1.1) assert_equal(nodes[0].getwalletinfo()["unlocked_until"], 0) # drain the keypool for _ in range(3): nodes[0].getnewaddress() assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].getnewaddress) nodes[0].walletpassphrase('test', 100) nodes[0].keypoolrefill(100) wi = nodes[0].getwalletinfo() assert_equal(wi['keypoolsize_hd_internal'], 100) assert_equal(wi['keypoolsize'], 100) # create a blank wallet nodes[0].createwallet(wallet_name='w2', blank=True) w2 = nodes[0].get_wallet_rpc('w2') # refer to initial wallet as w1 w1 = nodes[0].get_wallet_rpc('') # import private key and fund it address = addr.pop() privkey = w1.dumpprivkey(address) res = w2.importmulti( [{'scriptPubKey': {'address': address}, 'keys': [privkey], 'timestamp': 'now'}]) assert_equal(res[0]['success'], True) w1.walletpassphrase('test', 100) - res = w1.sendtoaddress(address=address, amount=0.00010000) + res = w1.sendtoaddress(address=address, amount=100.00) nodes[0].generate(1) destination = addr.pop() # Using a fee rate (10 sat / byte) well above the minimum relay rate # creating a 5,000 sat transaction with change should not be possible assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.", w2.walletcreatefundedpsbt, inputs=[], - outputs=[{addr.pop(): 0.00005000}], + outputs=[{addr.pop(): 50.00}], options={"subtractFeeFromOutputs": [0], - "feeRate": 0.00010}) + "feeRate": 100}) # creating a 10,000 sat transaction without change, with a manual # input, should still be possible res = w2.walletcreatefundedpsbt( inputs=w2.listunspent(), - outputs=[{destination: 0.00010000}], - options={"subtractFeeFromOutputs": [0], "feeRate": 0.00010}) + outputs=[{destination: 100.00}], + options={"subtractFeeFromOutputs": [0], "feeRate": 100}) assert_equal("psbt" in res, True) # creating a 10,000 sat transaction without change should still be # possible res = w2.walletcreatefundedpsbt( inputs=[], - outputs=[{destination: 0.00010000}], - options={"subtractFeeFromOutputs": [0], "feeRate": 0.00010}) + outputs=[{destination: 100.00}], + options={"subtractFeeFromOutputs": [0], "feeRate": 100}) assert_equal("psbt" in res, True) # should work without subtractFeeFromOutputs if the exact fee is # subtracted from the amount res = w2.walletcreatefundedpsbt(inputs=[], - outputs=[{destination: 0.00008000}], - options={"feeRate": 0.00010}) + outputs=[{destination: 80.00}], + options={"feeRate": 100}) assert_equal("psbt" in res, True) # dust change should be removed res = w2.walletcreatefundedpsbt(inputs=[], - outputs=[{destination: 0.00007900}], - options={"feeRate": 0.00010}) + outputs=[{destination: 79.00}], + options={"feeRate": 100}) assert_equal("psbt" in res, True) # create a transaction without change at the maximum fee rate, such # that the output is still spendable: res = w2.walletcreatefundedpsbt( inputs=[], - outputs=[{destination: 0.00010000}], - options={"subtractFeeFromOutputs": [0], "feeRate": 0.0004949}) + outputs=[{destination: 100.00}], + options={"subtractFeeFromOutputs": [0], "feeRate": 494.90}) assert_equal("psbt" in res, True) - assert_equal(res["fee"], Decimal("0.00009453")) + assert_equal(res["fee"], Decimal("94.53")) # creating a 10,000 sat transaction with a manual change address should # be possible res = w2.walletcreatefundedpsbt(inputs=[], - outputs=[{destination: 0.00010000}], + outputs=[{destination: 100.00}], options={"subtractFeeFromOutputs": [0], - "feeRate": 0.00010, + "feeRate": 100, "changeAddress": addr.pop()}) assert_equal("psbt" in res, True) if __name__ == '__main__': KeyPoolTest().main() diff --git a/test/functional/wallet_keypool_topup.py b/test/functional/wallet_keypool_topup.py index f1945ba27..49961f355 100755 --- a/test/functional/wallet_keypool_topup.py +++ b/test/functional/wallet_keypool_topup.py @@ -1,74 +1,74 @@ #!/usr/bin/env python3 # Copyright (c) 2017-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test HD Wallet keypool restore function. Two nodes. Node1 is under test. Node0 is providing transactions and generating blocks. - Start node1, shutdown and backup wallet. - Generate 110 keys (enough to drain the keypool). Store key 90 (in the initial keypool) and key 110 (beyond the initial keypool). Send funds to key 90 and key 110. - Stop node1, clear the datadir, move wallet file back into the datadir and restart node1. - connect node1 to node0. Verify that they sync and node1 receives its funds.""" import os import shutil from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, connect_nodes, ) class KeypoolRestoreTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 self.extra_args = [[], ['-keypool=100']] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): wallet_path = os.path.join( self.nodes[1].datadir, self.chain, "wallets", "wallet.dat") wallet_backup_path = os.path.join(self.nodes[1].datadir, "wallet.bak") self.nodes[0].generate(101) self.log.info("Make backup of wallet") self.stop_node(1) shutil.copyfile(wallet_path, wallet_backup_path) self.start_node(1, self.extra_args[1]) connect_nodes(self.nodes[0], self.nodes[1]) self.log.info("Generate keys for wallet") for _ in range(90): addr_oldpool = self.nodes[1].getnewaddress() for _ in range(20): addr_extpool = self.nodes[1].getnewaddress() self.log.info("Send funds to wallet") - self.nodes[0].sendtoaddress(addr_oldpool, 10) + self.nodes[0].sendtoaddress(addr_oldpool, 10000000) self.nodes[0].generate(1) - self.nodes[0].sendtoaddress(addr_extpool, 5) + self.nodes[0].sendtoaddress(addr_extpool, 5000000) self.nodes[0].generate(1) self.sync_blocks() self.log.info("Restart node with wallet backup") self.stop_node(1) shutil.copyfile(wallet_backup_path, wallet_path) self.start_node(1, self.extra_args[1]) connect_nodes(self.nodes[0], self.nodes[1]) self.sync_all() self.log.info("Verify keypool is restored and balance is correct") - assert_equal(self.nodes[1].getbalance(), 15) + assert_equal(self.nodes[1].getbalance(), 15000000) assert_equal(self.nodes[1].listtransactions() [0]['category'], "receive") # Check that we have marked all keys up to the used keypool key as used assert_equal(self.nodes[1].getaddressinfo( self.nodes[1].getnewaddress())['hdkeypath'], "m/0'/0'/110'") if __name__ == '__main__': KeypoolRestoreTest().main() diff --git a/test/functional/wallet_labels.py b/test/functional/wallet_labels.py index ddfb103dd..1ab3c8954 100755 --- a/test/functional/wallet_labels.py +++ b/test/functional/wallet_labels.py @@ -1,189 +1,189 @@ #!/usr/bin/env python3 # Copyright (c) 2016-2017 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test label RPCs. RPCs tested are: - getaddressesbylabel - listaddressgroupings - setlabel """ from collections import defaultdict from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error from test_framework.wallet_util import test_address class WalletLabelsTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): # Check that there's no UTXO on the node node = self.nodes[0] assert_equal(len(node.listunspent()), 0) # Note each time we call generate, all generated coins go into # the same address, so we call twice to get two addresses w/50 each node.generatetoaddress( nblocks=1, address=node.getnewaddress( label='coinbase')) node.generatetoaddress( nblocks=101, address=node.getnewaddress( label='coinbase')) - assert_equal(node.getbalance(), 100) + assert_equal(node.getbalance(), 100000000) # there should be 2 address groups # each with 1 address with a balance of 50 Bitcoins address_groups = node.listaddressgroupings() assert_equal(len(address_groups), 2) # the addresses aren't linked now, but will be after we send to the # common address linked_addresses = set() for address_group in address_groups: assert_equal(len(address_group), 1) assert_equal(len(address_group[0]), 3) - assert_equal(address_group[0][1], 50) + assert_equal(address_group[0][1], 50000000) assert_equal(address_group[0][2], 'coinbase') linked_addresses.add(address_group[0][0]) # send 50 from each address to a third address not in this wallet common_address = "msf4WtN1YQKXvNtvdFYt9JBnUD2FB41kjr" node.sendmany( - amounts={common_address: 100}, + amounts={common_address: 100000000}, subtractfeefrom=[common_address], minconf=1, ) # there should be 1 address group, with the previously # unlinked addresses now linked (they both have 0 balance) address_groups = node.listaddressgroupings() assert_equal(len(address_groups), 1) assert_equal(len(address_groups[0]), 2) assert_equal(set([a[0] for a in address_groups[0]]), linked_addresses) assert_equal([a[1] for a in address_groups[0]], [0, 0]) node.generate(1) # we want to reset so that the "" label has what's expected. # otherwise we're off by exactly the fee amount as that's mined # and matures in the next 100 blocks - amount_to_send = 1.0 + amount_to_send = 1000000 # Create labels and make sure subsequent label API calls # recognize the label/address associations. labels = [Label(name) for name in ("a", "b", "c", "d", "e")] for label in labels: address = node.getnewaddress(label.name) label.add_receive_address(address) label.verify(node) # Check all labels are returned by listlabels. assert_equal(node.listlabels(), sorted( ['coinbase'] + [label.name for label in labels])) # Send a transaction to each label. for label in labels: node.sendtoaddress(label.addresses[0], amount_to_send) label.verify(node) # Check the amounts received. node.generate(1) for label in labels: assert_equal( node.getreceivedbyaddress(label.addresses[0]), amount_to_send) assert_equal(node.getreceivedbylabel(label.name), amount_to_send) for i, label in enumerate(labels): to_label = labels[(i + 1) % len(labels)] node.sendtoaddress(to_label.addresses[0], amount_to_send) node.generate(1) for label in labels: address = node.getnewaddress(label.name) label.add_receive_address(address) label.verify(node) - assert_equal(node.getreceivedbylabel(label.name), 2) + assert_equal(node.getreceivedbylabel(label.name), 2000000) label.verify(node) node.generate(101) # Check that setlabel can assign a label to a new unused address. for label in labels: address = node.getnewaddress() node.setlabel(address, label.name) label.add_address(address) label.verify(node) assert_raises_rpc_error(-11, "No addresses with label", node.getaddressesbylabel, "") # Check that addmultisigaddress can assign labels. for label in labels: addresses = [] for x in range(10): addresses.append(node.getnewaddress()) multisig_address = node.addmultisigaddress( 5, addresses, label.name)['address'] label.add_address(multisig_address) label.purpose[multisig_address] = "send" label.verify(node) node.generate(101) # Check that setlabel can change the label of an address from a # different label. change_label(node, labels[0].addresses[0], labels[0], labels[1]) # Check that setlabel can set the label of an address already # in the label. This is a no-op. change_label(node, labels[2].addresses[0], labels[2], labels[2]) class Label: def __init__(self, name): # Label name self.name = name # Current receiving address associated with this label. self.receive_address = None # List of all addresses assigned with this label self.addresses = [] # Map of address to address purpose self.purpose = defaultdict(lambda: "receive") def add_address(self, address): assert_equal(address not in self.addresses, True) self.addresses.append(address) def add_receive_address(self, address): self.add_address(address) def verify(self, node): if self.receive_address is not None: assert self.receive_address in self.addresses for address in self.addresses: test_address(node, address, labels=[self.name]) assert self.name in node.listlabels() assert_equal( node.getaddressesbylabel(self.name), {address: {"purpose": self.purpose[address]} for address in self.addresses}) def change_label(node, address, old_label, new_label): assert_equal(address in old_label.addresses, True) node.setlabel(address, new_label.name) old_label.addresses.remove(address) new_label.add_address(address) old_label.verify(node) new_label.verify(node) if __name__ == '__main__': WalletLabelsTest().main() diff --git a/test/functional/wallet_listreceivedby.py b/test/functional/wallet_listreceivedby.py index 4c33f1069..c4bab5ba3 100755 --- a/test/functional/wallet_listreceivedby.py +++ b/test/functional/wallet_listreceivedby.py @@ -1,194 +1,194 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the listreceivedbyaddress RPC.""" from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_array_result, assert_equal, assert_raises_rpc_error, ) from test_framework.wallet_util import test_address class ReceivedByTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 def skip_test_if_missing_module(self): self.skip_if_no_wallet() self.skip_if_no_cli() def run_test(self): # Generate block to get out of IBD self.nodes[0].generate(1) self.sync_blocks() # save the number of coinbase reward addresses so far num_cb_reward_addresses = len( self.nodes[1].listreceivedbyaddress( minconf=0, include_empty=True, include_watchonly=True)) self.log.info("listreceivedbyaddress Test") # Send from node 0 to 1 addr = self.nodes[1].getnewaddress() - txid = self.nodes[0].sendtoaddress(addr, 0.1) + txid = self.nodes[0].sendtoaddress(addr, 100000) self.sync_all() # Check not listed in listreceivedbyaddress because has 0 confirmations assert_array_result(self.nodes[1].listreceivedbyaddress(), {"address": addr}, {}, True) # Bury Tx under 10 block so it will be returned by # listreceivedbyaddress self.nodes[1].generate(10) self.sync_all() assert_array_result(self.nodes[1].listreceivedbyaddress(), {"address": addr}, - {"address": addr, "label": "", "amount": Decimal("0.1"), "confirmations": 10, "txids": [txid, ]}) + {"address": addr, "label": "", "amount": Decimal("100000"), "confirmations": 10, "txids": [txid, ]}) # With min confidence < 10 assert_array_result(self.nodes[1].listreceivedbyaddress(5), {"address": addr}, - {"address": addr, "label": "", "amount": Decimal("0.1"), "confirmations": 10, "txids": [txid, ]}) + {"address": addr, "label": "", "amount": Decimal("100000"), "confirmations": 10, "txids": [txid, ]}) # With min confidence > 10, should not find Tx assert_array_result(self.nodes[1].listreceivedbyaddress(11), { "address": addr}, {}, True) # Empty Tx empty_addr = self.nodes[1].getnewaddress() assert_array_result(self.nodes[1].listreceivedbyaddress(0, True), {"address": empty_addr}, {"address": empty_addr, "label": "", "amount": 0, "confirmations": 0, "txids": []}) # Test Address filtering # Only on addr expected = {"address": addr, "label": "", "amount": Decimal( - "0.1"), "confirmations": 10, "txids": [txid, ]} + "100000"), "confirmations": 10, "txids": [txid, ]} res = self.nodes[1].listreceivedbyaddress( minconf=0, include_empty=True, include_watchonly=True, address_filter=addr) assert_array_result(res, {"address": addr}, expected) assert_equal(len(res), 1) # Test for regression on CLI calls with address string (#14173) cli_res = self.nodes[1].cli.listreceivedbyaddress(0, True, True, addr) assert_array_result(cli_res, {"address": addr}, expected) assert_equal(len(cli_res), 1) # Error on invalid address assert_raises_rpc_error(-4, "address_filter parameter was invalid", self.nodes[1].listreceivedbyaddress, minconf=0, include_empty=True, include_watchonly=True, address_filter="bamboozling") # Another address receive money res = self.nodes[1].listreceivedbyaddress(0, True, True) # Right now 2 entries assert_equal(len(res), 2 + num_cb_reward_addresses) other_addr = self.nodes[1].getnewaddress() - txid2 = self.nodes[0].sendtoaddress(other_addr, 0.1) + txid2 = self.nodes[0].sendtoaddress(other_addr, 100000) self.nodes[0].generate(1) self.sync_all() # Same test as above should still pass expected = {"address": addr, "label": "", "amount": Decimal( - "0.1"), "confirmations": 11, "txids": [txid, ]} + "100000"), "confirmations": 11, "txids": [txid, ]} res = self.nodes[1].listreceivedbyaddress(0, True, True, addr) assert_array_result(res, {"address": addr}, expected) assert_equal(len(res), 1) # Same test as above but with other_addr should still pass expected = {"address": other_addr, "label": "", "amount": Decimal( - "0.1"), "confirmations": 1, "txids": [txid2, ]} + "100000"), "confirmations": 1, "txids": [txid2, ]} res = self.nodes[1].listreceivedbyaddress(0, True, True, other_addr) assert_array_result(res, {"address": other_addr}, expected) assert_equal(len(res), 1) # Should be two entries though without filter res = self.nodes[1].listreceivedbyaddress(0, True, True) # Became 3 entries assert_equal(len(res), 3 + num_cb_reward_addresses) # Not on random addr # note on node[0]! just a random addr other_addr = self.nodes[0].getnewaddress() res = self.nodes[1].listreceivedbyaddress(0, True, True, other_addr) assert_equal(len(res), 0) self.log.info("getreceivedbyaddress Test") # Send from node 0 to 1 addr = self.nodes[1].getnewaddress() - txid = self.nodes[0].sendtoaddress(addr, 0.1) + txid = self.nodes[0].sendtoaddress(addr, 100000) self.sync_all() # Check balance is 0 because of 0 confirmations balance = self.nodes[1].getreceivedbyaddress(addr) assert_equal(balance, Decimal("0.0")) # Check balance is 0.1 balance = self.nodes[1].getreceivedbyaddress(addr, 0) - assert_equal(balance, Decimal("0.1")) + assert_equal(balance, Decimal("100000")) # Bury Tx under 10 block so it will be returned by the default # getreceivedbyaddress self.nodes[1].generate(10) self.sync_all() balance = self.nodes[1].getreceivedbyaddress(addr) - assert_equal(balance, Decimal("0.1")) + assert_equal(balance, Decimal("100000")) # Trying to getreceivedby for an address the wallet doesn't own should # return an error assert_raises_rpc_error(-4, "Address not found in wallet", self.nodes[0].getreceivedbyaddress, addr) self.log.info("listreceivedbylabel + getreceivedbylabel Test") # set pre-state label = '' address = self.nodes[1].getnewaddress() test_address(self.nodes[1], address, labels=[label]) received_by_label_json = [ r for r in self.nodes[1].listreceivedbylabel() if r["label"] == label][0] balance_by_label = self.nodes[1].getreceivedbylabel(label) - txid = self.nodes[0].sendtoaddress(addr, 0.1) + txid = self.nodes[0].sendtoaddress(addr, 100000) self.sync_all() # listreceivedbylabel should return received_by_label_json because of 0 # confirmations assert_array_result(self.nodes[1].listreceivedbylabel(), {"label": label}, received_by_label_json) # getreceivedbyaddress should return same balance because of 0 # confirmations balance = self.nodes[1].getreceivedbylabel(label) assert_equal(balance, balance_by_label) self.nodes[1].generate(10) self.sync_all() # listreceivedbylabel should return updated received list assert_array_result(self.nodes[1].listreceivedbylabel(), {"label": label}, - {"label": received_by_label_json["label"], "amount": (received_by_label_json["amount"] + Decimal("0.1"))}) + {"label": received_by_label_json["label"], "amount": (received_by_label_json["amount"] + Decimal("100000"))}) # getreceivedbylabel should return updated receive total balance = self.nodes[1].getreceivedbylabel(label) - assert_equal(balance, balance_by_label + Decimal("0.1")) + assert_equal(balance, balance_by_label + Decimal("100000")) # Create a new label named "mynewlabel" that has a 0 balance address = self.nodes[1].getnewaddress() self.nodes[1].setlabel(address, "mynewlabel") received_by_label_json = [r for r in self.nodes[1].listreceivedbylabel( 0, True) if r["label"] == "mynewlabel"][0] # Test includeempty of listreceivedbylabel assert_equal(received_by_label_json["amount"], Decimal("0.0")) # Test getreceivedbylabel for 0 amount labels balance = self.nodes[1].getreceivedbylabel("mynewlabel") assert_equal(balance, Decimal("0.0")) if __name__ == '__main__': ReceivedByTest().main() diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py index 780baf6fe..2d2bfabf5 100755 --- a/test/functional/wallet_listsinceblock.py +++ b/test/functional/wallet_listsinceblock.py @@ -1,315 +1,317 @@ #!/usr/bin/env python3 # Copyright (c) 2017-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the listsinceblock RPC.""" from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_array_result, assert_equal, assert_raises_rpc_error, connect_nodes, ) class ListSinceBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.setup_clean_chain = True self.extra_args = [["-noparkdeepreorg"], ["-noparkdeepreorg"], [], []] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): # All nodes are in IBD from genesis, so they'll need the miner (node2) to be an outbound connection, or have # only one connection. (See fPreferredDownload in net_processing) connect_nodes(self.nodes[1], self.nodes[2]) self.nodes[2].generate(101) self.sync_all() self.test_no_blockhash() self.test_invalid_blockhash() self.test_reorg() self.test_double_spend() self.test_double_send() def test_no_blockhash(self): self.log.info("Test no blockhash") - txid = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 1) + txid = self.nodes[2].sendtoaddress( + self.nodes[0].getnewaddress(), 1000000) blockhash, = self.nodes[2].generate(1) blockheight = self.nodes[2].getblockheader(blockhash)['height'] self.sync_all() txs = self.nodes[0].listtransactions() assert_array_result(txs, {"txid": txid}, { "category": "receive", - "amount": 1, + "amount": 1000000, "blockhash": blockhash, "blockheight": blockheight, "confirmations": 1, }) assert_equal( self.nodes[0].listsinceblock(), {"lastblock": blockhash, "removed": [], "transactions": txs}) assert_equal( self.nodes[0].listsinceblock(""), {"lastblock": blockhash, "removed": [], "transactions": txs}) def test_invalid_blockhash(self): self.log.info("Test invalid blockhash") assert_raises_rpc_error(-5, "Block not found", self.nodes[0].listsinceblock, "42759cde25462784395a337460bde75f58e73d3f08bd31fdc3507cbac856a2c4") assert_raises_rpc_error(-5, "Block not found", self.nodes[0].listsinceblock, "0000000000000000000000000000000000000000000000000000000000000000") assert_raises_rpc_error(-8, "blockhash must be of length 64 (not 11, for 'invalid-hex')", self.nodes[0].listsinceblock, "invalid-hex") assert_raises_rpc_error(-8, "blockhash must be hexadecimal string (not 'Z000000000000000000000000000000000000000000000000000000000000000')", self.nodes[0].listsinceblock, "Z000000000000000000000000000000000000000000000000000000000000000") def test_reorg(self): ''' `listsinceblock` did not behave correctly when handed a block that was no longer in the main chain: ab0 / \ aa1 [tx0] bb1 | | aa2 bb2 | | aa3 bb3 | bb4 Consider a client that has only seen block `aa3` above. It asks the node to `listsinceblock aa3`. But at some point prior the main chain switched to the bb chain. Previously: listsinceblock would find height=4 for block aa3 and compare this to height=5 for the tip of the chain (bb4). It would then return results restricted to bb3-bb4. Now: listsinceblock finds the fork at ab0 and returns results in the range bb1-bb4. This test only checks that [tx0] is present. ''' self.log.info("Test reorg") # Split network into two self.split_network() # send to nodes[0] from nodes[2] - senttx = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 1) + senttx = self.nodes[2].sendtoaddress( + self.nodes[0].getnewaddress(), 1000000) # generate on both sides nodes1_last_blockhash = self.nodes[1].generate(6)[-1] nodes2_first_blockhash = self.nodes[2].generate(7)[0] self.log.debug( "nodes[1] last blockhash = {}".format(nodes1_last_blockhash)) self.log.debug( "nodes[2] first blockhash = {}".format(nodes2_first_blockhash)) self.sync_all(self.nodes[:2]) self.sync_all(self.nodes[2:]) self.join_network() # listsinceblock(nodes1_last_blockhash) should now include tx as seen from nodes[0] # and return the block height which listsinceblock now exposes since # rABC6098a1cb2b25. transactions = self.nodes[0].listsinceblock( nodes1_last_blockhash)['transactions'] found = next(tx for tx in transactions if tx['txid'] == senttx) assert_equal( found['blockheight'], self.nodes[0].getblockheader(nodes2_first_blockhash)['height']) def test_double_spend(self): ''' This tests the case where the same UTXO is spent twice on two separate blocks as part of a reorg. ab0 / \ aa1 [tx1] bb1 [tx2] | | aa2 bb2 | | aa3 bb3 | bb4 Problematic case: 1. User 1 receives BCH in tx1 from utxo1 in block aa1. 2. User 2 receives BCH in tx2 from utxo1 (same) in block bb1 3. User 1 sees 2 confirmations at block aa3. 4. Reorg into bb chain. 5. User 1 asks `listsinceblock aa3` and does not see that tx1 is now invalidated. Currently the solution to this is to detect that a reorg'd block is asked for in listsinceblock, and to iterate back over existing blocks up until the fork point, and to include all transactions that relate to the node wallet. ''' self.log.info("Test double spend") self.sync_all() # Split network into two self.split_network() # share utxo between nodes[1] and nodes[2] utxos = self.nodes[2].listunspent() utxo = utxos[0] privkey = self.nodes[2].dumpprivkey(utxo['address']) self.nodes[1].importprivkey(privkey) # send from nodes[1] using utxo to nodes[0] - change = '{:.8f}'.format(float(utxo['amount']) - 1.0003) + change = '{:.2f}'.format(float(utxo['amount']) - 1000300.00) recipient_dict = { - self.nodes[0].getnewaddress(): 1, + self.nodes[0].getnewaddress(): 1000000, self.nodes[1].getnewaddress(): change, } utxo_dicts = [{ 'txid': utxo['txid'], 'vout': utxo['vout'], }] txid1 = self.nodes[1].sendrawtransaction( self.nodes[1].signrawtransactionwithwallet( self.nodes[1].createrawtransaction(utxo_dicts, recipient_dict))['hex']) # send from nodes[2] using utxo to nodes[3] recipient_dict2 = { - self.nodes[3].getnewaddress(): 1, + self.nodes[3].getnewaddress(): 1000000, self.nodes[2].getnewaddress(): change, } self.nodes[2].sendrawtransaction( self.nodes[2].signrawtransactionwithwallet( self.nodes[2].createrawtransaction(utxo_dicts, recipient_dict2))['hex']) # generate on both sides lastblockhash = self.nodes[1].generate(3)[2] self.nodes[2].generate(4) self.join_network() self.sync_all() # gettransaction should work for txid1 assert self.nodes[0].gettransaction( txid1)['txid'] == txid1, "gettransaction failed to find txid1" # listsinceblock(lastblockhash) should now include txid1, as seen from # nodes[0] lsbres = self.nodes[0].listsinceblock(lastblockhash) assert any(tx['txid'] == txid1 for tx in lsbres['removed']) # but it should not include 'removed' if include_removed=false lsbres2 = self.nodes[0].listsinceblock( blockhash=lastblockhash, include_removed=False) assert 'removed' not in lsbres2 def test_double_send(self): ''' This tests the case where the same transaction is submitted twice on two separate blocks as part of a reorg. The former will vanish and the latter will appear as the true transaction (with confirmations dropping as a result). ab0 / \ aa1 [tx1] bb1 | | aa2 bb2 | | aa3 bb3 [tx1] | bb4 Asserted: 1. tx1 is listed in listsinceblock. 2. It is included in 'removed' as it was removed, even though it is now present in a different block. 3. It is listed with a confirmation count of 2 (bb3, bb4), not 3 (aa1, aa2, aa3). ''' self.log.info("Test double send") self.sync_all() # Split network into two self.split_network() # create and sign a transaction utxos = self.nodes[2].listunspent() utxo = utxos[0] - change = '{:.8f}'.format(float(utxo['amount']) - 1.0003) + change = '{:.2f}'.format(float(utxo['amount']) - 1000300.00) recipient_dict = { - self.nodes[0].getnewaddress(): 1, + self.nodes[0].getnewaddress(): 1000000, self.nodes[2].getnewaddress(): change, } utxo_dicts = [{ 'txid': utxo['txid'], 'vout': utxo['vout'], }] signedtxres = self.nodes[2].signrawtransactionwithwallet( self.nodes[2].createrawtransaction(utxo_dicts, recipient_dict)) assert signedtxres['complete'] signedtx = signedtxres['hex'] # send from nodes[1]; this will end up in aa1 txid1 = self.nodes[1].sendrawtransaction(signedtx) # generate bb1-bb2 on right side self.nodes[2].generate(2) # send from nodes[2]; this will end up in bb3 txid2 = self.nodes[2].sendrawtransaction(signedtx) assert_equal(txid1, txid2) # generate on both sides lastblockhash = self.nodes[1].generate(3)[2] self.nodes[2].generate(2) self.join_network() self.sync_all() # gettransaction should work for txid1 tx1 = self.nodes[0].gettransaction(txid1) assert_equal( tx1['blockheight'], self.nodes[0].getblockheader( tx1['blockhash'])['height']) # listsinceblock(lastblockhash) should now include txid1 in transactions # as well as in removed lsbres = self.nodes[0].listsinceblock(lastblockhash) assert any(tx['txid'] == txid1 for tx in lsbres['transactions']) assert any(tx['txid'] == txid1 for tx in lsbres['removed']) # find transaction and ensure confirmations is valid for tx in lsbres['transactions']: if tx['txid'] == txid1: assert_equal(tx['confirmations'], 2) # the same check for the removed array; confirmations should STILL be 2 for tx in lsbres['removed']: if tx['txid'] == txid1: assert_equal(tx['confirmations'], 2) if __name__ == '__main__': ListSinceBlockTest().main() diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py index 98a5466d4..597feae8b 100755 --- a/test/functional/wallet_listtransactions.py +++ b/test/functional/wallet_listtransactions.py @@ -1,110 +1,117 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the listtransactions API.""" from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_array_result, assert_equal, ) class ListTransactionsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): # Leave IBD self.nodes[0].generate(1) # Simple send, 0 to 1: - txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1) + txid = self.nodes[0].sendtoaddress( + self.nodes[1].getnewaddress(), 100000) self.sync_all() assert_array_result(self.nodes[0].listtransactions(), {"txid": txid}, - {"category": "send", "amount": Decimal("-0.1"), "confirmations": 0}) + {"category": "send", "amount": Decimal("-100000"), "confirmations": 0}) assert_array_result(self.nodes[1].listtransactions(), {"txid": txid}, - {"category": "receive", "amount": Decimal("0.1"), "confirmations": 0}) + {"category": "receive", "amount": Decimal("100000"), "confirmations": 0}) # mine a block, confirmations should change: blockhash = self.nodes[0].generate(1)[0] blockheight = self.nodes[0].getblockheader(blockhash)['height'] self.sync_all() assert_array_result(self.nodes[0].listtransactions(), {"txid": txid}, - {"category": "send", "amount": Decimal("-0.1"), "confirmations": 1, "blockhash": blockhash, "blockheight": blockheight}) + {"category": "send", "amount": Decimal("-100000"), "confirmations": 1, "blockhash": blockhash, "blockheight": blockheight}) assert_array_result(self.nodes[1].listtransactions(), {"txid": txid}, - {"category": "receive", "amount": Decimal("0.1"), "confirmations": 1, "blockhash": blockhash, "blockheight": blockheight}) + {"category": "receive", "amount": Decimal("100000"), "confirmations": 1, "blockhash": blockhash, "blockheight": blockheight}) # send-to-self: - txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 0.2) + txid = self.nodes[0].sendtoaddress( + self.nodes[0].getnewaddress(), 200000) assert_array_result(self.nodes[0].listtransactions(), {"txid": txid, "category": "send"}, - {"amount": Decimal("-0.2")}) + {"amount": Decimal("-200000")}) assert_array_result(self.nodes[0].listtransactions(), {"txid": txid, "category": "receive"}, - {"amount": Decimal("0.2")}) + {"amount": Decimal("200000")}) # sendmany from node1: twice to self, twice to node2: - send_to = {self.nodes[0].getnewaddress(): 0.11, - self.nodes[1].getnewaddress(): 0.22, - self.nodes[0].getnewaddress(): 0.33, - self.nodes[1].getnewaddress(): 0.44} + send_to = {self.nodes[0].getnewaddress(): 110000, + self.nodes[1].getnewaddress(): 220000, + self.nodes[0].getnewaddress(): 330000, + self.nodes[1].getnewaddress(): 440000} txid = self.nodes[1].sendmany("", send_to) self.sync_all() assert_array_result(self.nodes[1].listtransactions(), - {"category": "send", "amount": Decimal("-0.11")}, + {"category": "send", "amount": Decimal("-110000")}, {"txid": txid}) assert_array_result(self.nodes[0].listtransactions(), - {"category": "receive", "amount": Decimal("0.11")}, + {"category": "receive", + "amount": Decimal("110000")}, {"txid": txid}) assert_array_result(self.nodes[1].listtransactions(), - {"category": "send", "amount": Decimal("-0.22")}, + {"category": "send", "amount": Decimal("-220000")}, {"txid": txid}) assert_array_result(self.nodes[1].listtransactions(), - {"category": "receive", "amount": Decimal("0.22")}, + {"category": "receive", + "amount": Decimal("220000")}, {"txid": txid}) assert_array_result(self.nodes[1].listtransactions(), - {"category": "send", "amount": Decimal("-0.33")}, + {"category": "send", "amount": Decimal("-330000")}, {"txid": txid}) assert_array_result(self.nodes[0].listtransactions(), - {"category": "receive", "amount": Decimal("0.33")}, + {"category": "receive", + "amount": Decimal("330000")}, {"txid": txid}) assert_array_result(self.nodes[1].listtransactions(), - {"category": "send", "amount": Decimal("-0.44")}, + {"category": "send", "amount": Decimal("-440000")}, {"txid": txid}) assert_array_result(self.nodes[1].listtransactions(), - {"category": "receive", "amount": Decimal("0.44")}, + {"category": "receive", + "amount": Decimal("440000")}, {"txid": txid}) pubkey = self.nodes[1].getaddressinfo( self.nodes[1].getnewaddress())['pubkey'] multisig = self.nodes[1].createmultisig(1, [pubkey]) self.nodes[0].importaddress( multisig["redeemScript"], "watchonly", False, True) - txid = self.nodes[1].sendtoaddress(multisig["address"], 0.1) + txid = self.nodes[1].sendtoaddress(multisig["address"], 100000) self.nodes[1].generate(1) self.sync_all() assert_equal(len(self.nodes[0].listtransactions( label="watchonly", include_watchonly=True)), 1) assert_equal(len(self.nodes[0].listtransactions( dummy="watchonly", include_watchonly=True)), 1) assert len( self.nodes[0].listtransactions( label="watchonly", count=100, include_watchonly=False)) == 0 assert_array_result(self.nodes[0].listtransactions(label="watchonly", count=100, include_watchonly=True), - {"category": "receive", "amount": Decimal("0.1")}, + {"category": "receive", + "amount": Decimal("100000")}, {"txid": txid, "label": "watchonly"}) if __name__ == '__main__': ListTransactionsTest().main() diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index 299863ab8..cb09a1c39 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -1,403 +1,403 @@ #!/usr/bin/env python3 # Copyright (c) 2017-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test multiwallet. Verify that a bitcoind node can load multiple wallet files """ from decimal import Decimal import os import shutil import time from test_framework.test_framework import BitcoinTestFramework from test_framework.test_node import ErrorMatch from test_framework.util import ( assert_equal, assert_raises_rpc_error, ) FEATURE_LATEST = 200300 class MultiWalletTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def add_options(self, parser): parser.add_argument( '--data_wallets_dir', default=os.path.join( os.path.dirname( os.path.realpath(__file__)), 'data/wallets/'), help='Test data with wallet directories (default: %(default)s)', ) def run_test(self): node = self.nodes[0] def data_dir(*p): return os.path.join(node.datadir, self.chain, *p) def wallet_dir(*p): return data_dir('wallets', *p) def wallet(name): return node.get_wallet_rpc(name) def wallet_file(name): if os.path.isdir(wallet_dir(name)): return wallet_dir(name, "wallet.dat") return wallet_dir(name) assert_equal(self.nodes[0].listwalletdir(), {'wallets': [{'name': ''}]}) # check wallet.dat is created self.stop_nodes() assert_equal(os.path.isfile(wallet_dir('wallet.dat')), True) # create symlink to verify wallet directory path can be referenced # through symlink if os.name != 'nt': os.mkdir(wallet_dir('w7')) os.symlink('w7', wallet_dir('w7_symlink')) # rename wallet.dat to make sure plain wallet file paths (as opposed to # directory paths) can be loaded os.rename(wallet_dir("wallet.dat"), wallet_dir("w8")) # create another dummy wallet for use in testing backups later self.start_node(0, []) self.stop_nodes() empty_wallet = os.path.join(self.options.tmpdir, 'empty.dat') os.rename(wallet_dir("wallet.dat"), empty_wallet) # restart node with a mix of wallet names: # w1, w2, w3 - to verify new wallets created when non-existing paths specified # w - to verify wallet name matching works when one wallet path is prefix of another # sub/w5 - to verify relative wallet path is created correctly # extern/w6 - to verify absolute wallet path is created correctly # w7_symlink - to verify symlinked wallet path is initialized correctly # w8 - to verify existing wallet file is loaded correctly # '' - to verify default wallet file is created correctly wallet_names = ['w1', 'w2', 'w3', 'w', 'sub/w5', os.path.join(self.options.tmpdir, 'extern/w6'), 'w7_symlink', 'w8', ''] if os.name == 'nt': wallet_names.remove('w7_symlink') extra_args = ['-wallet={}'.format(n) for n in wallet_names] self.start_node(0, extra_args) assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), [ '', os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8']) assert_equal(set(node.listwallets()), set(wallet_names)) # check that all requested wallets were created self.stop_node(0) for wallet_name in wallet_names: assert_equal(os.path.isfile(wallet_file(wallet_name)), True) # should not initialize if wallet path can't be created exp_stderr = "boost::filesystem::create_directory:" self.nodes[0].assert_start_raises_init_error( ['-wallet=wallet.dat/bad'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX) self.nodes[0].assert_start_raises_init_error( ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" does not exist') self.nodes[0].assert_start_raises_init_error( ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" is a relative path', cwd=data_dir()) self.nodes[0].assert_start_raises_init_error( ['-walletdir=debug.log'], 'Error: Specified -walletdir "debug.log" is not a directory', cwd=data_dir()) # should not initialize if there are duplicate wallets self.nodes[0].assert_start_raises_init_error( ['-wallet=w1', '-wallet=w1'], 'Error: Error loading wallet w1. Duplicate -wallet filename specified.') # should not initialize if one wallet is a copy of another shutil.copyfile(wallet_dir('w8'), wallet_dir('w8_copy')) exp_stderr = r"BerkeleyBatch: Can't open database w8_copy \(duplicates fileid \w+ from w8\)" self.nodes[0].assert_start_raises_init_error( ['-wallet=w8', '-wallet=w8_copy'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX) # should not initialize if wallet file is a symlink if os.name != 'nt': os.symlink('w8', wallet_dir('w8_symlink')) self.nodes[0].assert_start_raises_init_error( ['-wallet=w8_symlink'], r'Error: Invalid -wallet path \'w8_symlink\'\. .*', match=ErrorMatch.FULL_REGEX) # should not initialize if the specified walletdir does not exist self.nodes[0].assert_start_raises_init_error( ['-walletdir=bad'], 'Error: Specified -walletdir "bad" does not exist') # should not initialize if the specified walletdir is not a directory not_a_dir = wallet_dir('notadir') open(not_a_dir, 'a', encoding="utf8").close() self.nodes[0].assert_start_raises_init_error( ['-walletdir=' + not_a_dir], 'Error: Specified -walletdir "' + not_a_dir + '" is not a directory') self.log.info("Do not allow -zapwallettxes with multiwallet") self.nodes[0].assert_start_raises_init_error( [ '-zapwallettxes', '-wallet=w1', '-wallet=w2'], "Error: -zapwallettxes is only allowed with a single wallet file") self.nodes[0].assert_start_raises_init_error( [ '-zapwallettxes=1', '-wallet=w1', '-wallet=w2'], "Error: -zapwallettxes is only allowed with a single wallet file") self.nodes[0].assert_start_raises_init_error( [ '-zapwallettxes=2', '-wallet=w1', '-wallet=w2'], "Error: -zapwallettxes is only allowed with a single wallet file") # if wallets/ doesn't exist, datadir should be the default wallet dir wallet_dir2 = data_dir('walletdir') os.rename(wallet_dir(), wallet_dir2) self.start_node(0, ['-wallet=w4', '-wallet=w5']) assert_equal(set(node.listwallets()), {"w4", "w5"}) w5 = wallet("w5") node.generatetoaddress(nblocks=1, address=w5.getnewaddress()) # now if wallets/ exists again, but the rootdir is specified as the # walletdir, w4 and w5 should still be loaded os.rename(wallet_dir2, wallet_dir()) self.restart_node(0, ['-wallet=w4', '-wallet=w5', '-walletdir=' + data_dir()]) assert_equal(set(node.listwallets()), {"w4", "w5"}) w5 = wallet("w5") w5_info = w5.getwalletinfo() - assert_equal(w5_info['immature_balance'], 50) + assert_equal(w5_info['immature_balance'], 50000000) competing_wallet_dir = os.path.join( self.options.tmpdir, 'competing_walletdir') os.mkdir(competing_wallet_dir) self.restart_node(0, ['-walletdir=' + competing_wallet_dir]) exp_stderr = r"Error: Error initializing wallet database environment \"\S+competing_walletdir\"!" self.nodes[1].assert_start_raises_init_error( ['-walletdir=' + competing_wallet_dir], exp_stderr, match=ErrorMatch.PARTIAL_REGEX) self.restart_node(0, extra_args) assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), [ '', os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8', 'w8_copy']) wallets = [wallet(w) for w in wallet_names] wallet_bad = wallet("bad") # check wallet names and balances node.generatetoaddress(nblocks=1, address=wallets[0].getnewaddress()) for wallet_name, wallet in zip(wallet_names, wallets): info = wallet.getwalletinfo() assert_equal(info['immature_balance'], - 50 if wallet is wallets[0] else 0) + 50000000 if wallet is wallets[0] else 0) assert_equal(info['walletname'], wallet_name) # accessing invalid wallet fails assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", wallet_bad.getwalletinfo) # accessing wallet RPC without using wallet endpoint fails assert_raises_rpc_error(-19, "Wallet file not specified (must request wallet RPC through /wallet/ uri-path).", node.getwalletinfo) w1, w2, w3, w4, *_ = wallets node.generatetoaddress(nblocks=101, address=w1.getnewaddress()) - assert_equal(w1.getbalance(), 100) + assert_equal(w1.getbalance(), 100000000) assert_equal(w2.getbalance(), 0) assert_equal(w3.getbalance(), 0) assert_equal(w4.getbalance(), 0) - w1.sendtoaddress(w2.getnewaddress(), 1) - w1.sendtoaddress(w3.getnewaddress(), 2) - w1.sendtoaddress(w4.getnewaddress(), 3) + w1.sendtoaddress(w2.getnewaddress(), 1000000) + w1.sendtoaddress(w3.getnewaddress(), 2000000) + w1.sendtoaddress(w4.getnewaddress(), 3000000) node.generatetoaddress(nblocks=1, address=w1.getnewaddress()) - assert_equal(w2.getbalance(), 1) - assert_equal(w3.getbalance(), 2) - assert_equal(w4.getbalance(), 3) + assert_equal(w2.getbalance(), 1000000) + assert_equal(w3.getbalance(), 2000000) + assert_equal(w4.getbalance(), 3000000) batch = w1.batch([w1.getblockchaininfo.get_request(), w1.getwalletinfo.get_request()]) assert_equal(batch[0]["result"]["chain"], self.chain) assert_equal(batch[1]["result"]["walletname"], "w1") self.log.info('Check for per-wallet settxfee call') assert_equal(w1.getwalletinfo()['paytxfee'], 0) assert_equal(w2.getwalletinfo()['paytxfee'], 0) - w2.settxfee(0.001) + w2.settxfee(1000) assert_equal(w1.getwalletinfo()['paytxfee'], 0) - assert_equal(w2.getwalletinfo()['paytxfee'], Decimal('0.00100000')) + assert_equal(w2.getwalletinfo()['paytxfee'], Decimal('1000.00')) self.log.info("Test dynamic wallet loading") self.restart_node(0, ['-nowallet']) assert_equal(node.listwallets(), []) assert_raises_rpc_error(-32601, "Method not found", node.getwalletinfo) self.log.info("Load first wallet") loadwallet_name = node.loadwallet(wallet_names[0]) assert_equal(loadwallet_name['name'], wallet_names[0]) assert_equal(node.listwallets(), wallet_names[0:1]) node.getwalletinfo() w1 = node.get_wallet_rpc(wallet_names[0]) w1.getwalletinfo() self.log.info("Load second wallet") loadwallet_name = node.loadwallet(wallet_names[1]) assert_equal(loadwallet_name['name'], wallet_names[1]) assert_equal(node.listwallets(), wallet_names[0:2]) assert_raises_rpc_error(-19, "Wallet file not specified", node.getwalletinfo) w2 = node.get_wallet_rpc(wallet_names[1]) w2.getwalletinfo() self.log.info("Load remaining wallets") for wallet_name in wallet_names[2:]: loadwallet_name = self.nodes[0].loadwallet(wallet_name) assert_equal(loadwallet_name['name'], wallet_name) assert_equal(set(self.nodes[0].listwallets()), set(wallet_names)) # Fail to load if wallet doesn't exist assert_raises_rpc_error(-18, 'Wallet wallets not found.', self.nodes[0].loadwallet, 'wallets') # Fail to load duplicate wallets assert_raises_rpc_error( -4, 'Wallet file verification failed. Error loading wallet w1. Duplicate -wallet filename specified.', self.nodes[0].loadwallet, wallet_names[0]) # Fail to load duplicate wallets by different ways (directory and # filepath) assert_raises_rpc_error( -4, "Wallet file verification failed. Error loading wallet wallet.dat. Duplicate -wallet filename specified.", self.nodes[0].loadwallet, 'wallet.dat') # Fail to load if one wallet is a copy of another assert_raises_rpc_error(-4, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy') # Fail to load if one wallet is a copy of another. # Test this twice to make sure that we don't re-introduce # https://github.com/bitcoin/bitcoin/issues/14304 assert_raises_rpc_error(-4, "BerkeleyBatch: Can't open database w8_copy (duplicates fileid", self.nodes[0].loadwallet, 'w8_copy') # Fail to load if wallet file is a symlink if os.name != 'nt': assert_raises_rpc_error( -4, "Wallet file verification failed. Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, 'w8_symlink') # Fail to load if a directory is specified that doesn't contain a # wallet os.mkdir(wallet_dir('empty_wallet_dir')) assert_raises_rpc_error(-18, "Directory empty_wallet_dir does not contain a wallet.dat file", self.nodes[0].loadwallet, 'empty_wallet_dir') self.log.info("Test dynamic wallet creation.") # Fail to create a wallet if it already exists. assert_raises_rpc_error(-4, "Wallet w2 already exists.", self.nodes[0].createwallet, 'w2') # Successfully create a wallet with a new name loadwallet_name = self.nodes[0].createwallet('w9') assert_equal(loadwallet_name['name'], 'w9') w9 = node.get_wallet_rpc('w9') assert_equal(w9.getwalletinfo()['walletname'], 'w9') assert 'w9' in self.nodes[0].listwallets() # Successfully create a wallet using a full path new_wallet_dir = os.path.join(self.options.tmpdir, 'new_walletdir') new_wallet_name = os.path.join(new_wallet_dir, 'w10') loadwallet_name = self.nodes[0].createwallet(new_wallet_name) assert_equal(loadwallet_name['name'], new_wallet_name) w10 = node.get_wallet_rpc(new_wallet_name) assert_equal(w10.getwalletinfo()['walletname'], new_wallet_name) assert new_wallet_name in self.nodes[0].listwallets() self.log.info("Test dynamic wallet unloading") # Test `unloadwallet` errors assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[0].unloadwallet) assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", self.nodes[0].unloadwallet, "dummy") assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", node.get_wallet_rpc("dummy").unloadwallet) assert_raises_rpc_error(-8, "Cannot unload the requested wallet", w1.unloadwallet, "w2"), # Successfully unload the specified wallet name self.nodes[0].unloadwallet("w1") assert 'w1' not in self.nodes[0].listwallets() # Successfully unload the wallet referenced by the request endpoint # Also ensure unload works during walletpassphrase timeout w2.encryptwallet('test') w2.walletpassphrase('test', 1) w2.unloadwallet() time.sleep(1.1) assert 'w2' not in self.nodes[0].listwallets() # Successfully unload all wallets for wallet_name in self.nodes[0].listwallets(): self.nodes[0].unloadwallet(wallet_name) assert_equal(self.nodes[0].listwallets(), []) assert_raises_rpc_error(-32601, "Method not found (wallet method is disabled because no wallet is loaded)", self.nodes[0].getwalletinfo) # Successfully load a previously unloaded wallet self.nodes[0].loadwallet('w1') assert_equal(self.nodes[0].listwallets(), ['w1']) assert_equal(w1.getwalletinfo()['walletname'], 'w1') assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), [ '', os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8', 'w8_copy', 'w9']) # Test backing up and restoring wallets self.log.info("Test wallet backup") self.restart_node(0, ['-nowallet']) for wallet_name in wallet_names: self.nodes[0].loadwallet(wallet_name) for wallet_name in wallet_names: rpc = self.nodes[0].get_wallet_rpc(wallet_name) addr = rpc.getnewaddress() backup = os.path.join(self.options.tmpdir, 'backup.dat') rpc.backupwallet(backup) self.nodes[0].unloadwallet(wallet_name) shutil.copyfile(empty_wallet, wallet_file(wallet_name)) self.nodes[0].loadwallet(wallet_name) assert_equal(rpc.getaddressinfo(addr)['ismine'], False) self.nodes[0].unloadwallet(wallet_name) shutil.copyfile(backup, wallet_file(wallet_name)) self.nodes[0].loadwallet(wallet_name) assert_equal(rpc.getaddressinfo(addr)['ismine'], True) # Test .walletlock file is closed self.start_node(1) wallet = os.path.join(self.options.tmpdir, 'my_wallet') self.nodes[0].createwallet(wallet) assert_raises_rpc_error(-4, "Error initializing wallet database environment", self.nodes[1].loadwallet, wallet) self.nodes[0].unloadwallet(wallet) self.nodes[1].loadwallet(wallet) if __name__ == '__main__': MultiWalletTest().main() diff --git a/test/functional/wallet_reorgsrestore.py b/test/functional/wallet_reorgsrestore.py index 7da94242d..c396b9aca 100755 --- a/test/functional/wallet_reorgsrestore.py +++ b/test/functional/wallet_reorgsrestore.py @@ -1,127 +1,127 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test tx status in case of reorgs while wallet being shutdown. Wallet txn status rely on block connection/disconnection for its accuracy. In case of reorgs happening while wallet being shutdown block updates are not going to be received. At wallet loading, we check against chain if confirmed txn are still in chain and change their status if block in which they have been included has been disconnected. """ from decimal import Decimal import os import shutil from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, connect_nodes, disconnect_nodes, ) class ReorgsRestoreTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 3 self.extra_args = [["-noparkdeepreorg"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): # Send a tx from which to conflict outputs later txid_conflict_from = self.nodes[0].sendtoaddress( - self.nodes[0].getnewaddress(), Decimal("10")) + self.nodes[0].getnewaddress(), Decimal("10000000")) self.nodes[0].generate(1) self.sync_blocks() # Disconnect node1 from others to reorg its chain later disconnect_nodes(self.nodes[0], self.nodes[1]) disconnect_nodes(self.nodes[1], self.nodes[2]) connect_nodes(self.nodes[0], self.nodes[2]) # Send a tx to be unconfirmed later txid = self.nodes[0].sendtoaddress( - self.nodes[0].getnewaddress(), Decimal("10")) + self.nodes[0].getnewaddress(), Decimal("10000000")) tx = self.nodes[0].gettransaction(txid) self.nodes[0].generate(4) tx_before_reorg = self.nodes[0].gettransaction(txid) assert_equal(tx_before_reorg["confirmations"], 4) # Disconnect node0 from node2 to broadcast a conflict on their # respective chains disconnect_nodes(self.nodes[0], self.nodes[2]) nA = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction( - txid_conflict_from)["details"] if tx_out["amount"] == Decimal("10")) + txid_conflict_from)["details"] if tx_out["amount"] == Decimal("10000000")) inputs = [] inputs.append({"txid": txid_conflict_from, "vout": nA}) outputs_1 = {} outputs_2 = {} # Create a conflicted tx broadcast on node0 chain and conflicting tx # broadcast on node1 chain. Both spend from txid_conflict_from - outputs_1[self.nodes[0].getnewaddress()] = Decimal("9.99998") - outputs_2[self.nodes[0].getnewaddress()] = Decimal("9.99998") + outputs_1[self.nodes[0].getnewaddress()] = Decimal("9999980") + outputs_2[self.nodes[0].getnewaddress()] = Decimal("9999980") conflicted = self.nodes[0].signrawtransactionwithwallet( self.nodes[0].createrawtransaction(inputs, outputs_1)) conflicting = self.nodes[0].signrawtransactionwithwallet( self.nodes[0].createrawtransaction(inputs, outputs_2)) conflicted_txid = self.nodes[0].sendrawtransaction(conflicted["hex"]) self.nodes[0].generate(1) conflicting_txid = self.nodes[2].sendrawtransaction(conflicting["hex"]) self.nodes[2].generate(9) # Reconnect node0 and node2 and check that conflicted_txid is # effectively conflicted connect_nodes(self.nodes[0], self.nodes[2]) self.sync_blocks([self.nodes[0], self.nodes[2]]) conflicted = self.nodes[0].gettransaction(conflicted_txid) conflicting = self.nodes[0].gettransaction(conflicting_txid) assert_equal(conflicted["confirmations"], -9) assert_equal(conflicted["walletconflicts"][0], conflicting["txid"]) # Node0 wallet is shutdown self.restart_node(0) # The block chain re-orgs and the tx is included in a different block self.nodes[1].generate(9) self.nodes[1].sendrawtransaction(tx["hex"]) self.nodes[1].generate(1) self.nodes[1].sendrawtransaction(conflicted["hex"]) self.nodes[1].generate(1) # Node0 wallet file is loaded on longest sync'ed node1 self.stop_node(1) self.nodes[0].backupwallet( os.path.join( self.nodes[0].datadir, 'wallet.bak')) shutil.copyfile( os.path.join( self.nodes[0].datadir, 'wallet.bak'), os.path.join( self.nodes[1].datadir, self.chain, 'wallet.dat')) self.start_node(1) tx_after_reorg = self.nodes[1].gettransaction(txid) # Check that normal confirmed tx is confirmed again but with different # blockhash assert_equal(tx_after_reorg["confirmations"], 2) assert(tx_before_reorg["blockhash"] != tx_after_reorg["blockhash"]) conflicted_after_reorg = self.nodes[1].gettransaction(conflicted_txid) # Check that conflicted tx is confirmed again with blockhash different # than previously conflicting tx assert_equal(conflicted_after_reorg["confirmations"], 1) assert(conflicting["blockhash"] != conflicted_after_reorg["blockhash"]) if __name__ == '__main__': ReorgsRestoreTest().main() diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py index 9ec550d32..de5764791 100755 --- a/test/functional/wallet_resendwallettransactions.py +++ b/test/functional/wallet_resendwallettransactions.py @@ -1,84 +1,84 @@ #!/usr/bin/env python3 # Copyright (c) 2017-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test that the wallet resends transactions periodically.""" import time from test_framework.blocktools import create_block, create_coinbase from test_framework.messages import ToHex from test_framework.p2p import P2PTxInvStore, p2p_lock from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, wait_until class ResendWalletTransactionsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): node = self.nodes[0] # alias node.add_p2p_connection(P2PTxInvStore()) self.log.info("Create a new transaction and wait until it's broadcast") - txid = int(node.sendtoaddress(node.getnewaddress(), 1), 16) + txid = int(node.sendtoaddress(node.getnewaddress(), 1000000), 16) # Wallet rebroadcast is first scheduled 1 sec after startup (see # nNextResend in ResendWalletTransactions()). Sleep for just over a # second to be certain that it has been called before the first # setmocktime call below. time.sleep(1.1) # Can take a few seconds due to transaction trickling wait_until( lambda: node.p2p.tx_invs_received[txid] >= 1, lock=p2p_lock) # Add a second peer since txs aren't rebroadcast to the same peer (see # filterInventoryKnown) node.add_p2p_connection(P2PTxInvStore()) self.log.info("Create a block") # Create and submit a block without the transaction. # Transactions are only rebroadcast if there has been a block at least five minutes # after the last time we tried to broadcast. Use mocktime and give an # extra minute to be sure. block_time = int(time.time()) + 6 * 60 node.setmocktime(block_time) block = create_block(int(node.getbestblockhash(), 16), create_coinbase( node.getblockcount() + 1), block_time) block.rehash() block.solve() node.submitblock(ToHex(block)) node.syncwithvalidationinterfacequeue() now = int(time.time()) # Transaction should not be rebroadcast within first 12 hours # Leave 2 mins for buffer twelve_hrs = 12 * 60 * 60 two_min = 2 * 60 node.setmocktime(now + twelve_hrs - two_min) # ensure enough time has passed for rebroadcast attempt to occur time.sleep(2) assert_equal(txid in node.p2ps[1].get_invs(), False) self.log.info("Bump time & check that transaction is rebroadcast") # Transaction should be rebroadcast approximately 24 hours in the future, # but can range from 12-36. So bump 36 hours to be sure. node.setmocktime(now + 36 * 60 * 60) # Tell scheduler to call MaybeResendWalletTxn now. node.mockscheduler(1) # Give some time for trickle to occur node.setmocktime(now + 36 * 60 * 60 + 600) wait_until( lambda: node.p2ps[1].tx_invs_received[txid] >= 1, lock=p2p_lock) if __name__ == '__main__': ResendWalletTransactionsTest().main() diff --git a/test/functional/wallet_txn_clone.py b/test/functional/wallet_txn_clone.py index 65c4a8bc8..080554bd3 100755 --- a/test/functional/wallet_txn_clone.py +++ b/test/functional/wallet_txn_clone.py @@ -1,150 +1,150 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet accounts properly when there are cloned transactions with malleated scriptsigs.""" import io from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, connect_nodes, disconnect_nodes, ) -from test_framework.messages import CTransaction, COIN +from test_framework.messages import CTransaction, XEC class TxnMallTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.extra_args = [["-noparkdeepreorg"], ["-noparkdeepreorg"], [], []] self.supports_cli = False def skip_test_if_missing_module(self): self.skip_if_no_wallet() def add_options(self, parser): parser.add_argument("--mineblock", dest="mine_block", default=False, action="store_true", help="Test double-spend of 1-confirmed transaction") def setup_network(self): # Start with split network: super().setup_network() disconnect_nodes(self.nodes[1], self.nodes[2]) def run_test(self): output_type = "legacy" # All nodes should start with 1,250 BCH: - starting_balance = 1250 + starting_balance = 1250000000 for i in range(4): assert_equal(self.nodes[i].getbalance(), starting_balance) # bug workaround, coins generated assigned to first getnewaddress! self.nodes[i].getnewaddress() - self.nodes[0].settxfee(.001) + self.nodes[0].settxfee(1000) node0_address1 = self.nodes[0].getnewaddress(address_type=output_type) - node0_txid1 = self.nodes[0].sendtoaddress(node0_address1, 1219) + node0_txid1 = self.nodes[0].sendtoaddress(node0_address1, 1219000000) node0_tx1 = self.nodes[0].gettransaction(node0_txid1) node0_address2 = self.nodes[0].getnewaddress(address_type=output_type) - node0_txid2 = self.nodes[0].sendtoaddress(node0_address2, 29) + node0_txid2 = self.nodes[0].sendtoaddress(node0_address2, 29000000) node0_tx2 = self.nodes[0].gettransaction(node0_txid2) assert_equal(self.nodes[0].getbalance(), starting_balance + node0_tx1["fee"] + node0_tx2["fee"]) # Coins are sent to node1_address node1_address = self.nodes[1].getnewaddress() # Send tx1, and another transaction tx2 that won't be cloned - txid1 = self.nodes[0].sendtoaddress(node1_address, 40) - txid2 = self.nodes[0].sendtoaddress(node1_address, 20) + txid1 = self.nodes[0].sendtoaddress(node1_address, 40000000) + txid2 = self.nodes[0].sendtoaddress(node1_address, 20000000) # Construct a clone of tx1, to be malleated rawtx1 = self.nodes[0].getrawtransaction(txid1, 1) clone_inputs = [{"txid": rawtx1["vin"][0]["txid"], "vout": rawtx1["vin"][0]["vout"], "sequence": rawtx1["vin"][0]["sequence"]}] clone_outputs = {rawtx1["vout"][0]["scriptPubKey"]["addresses"][0]: rawtx1["vout"][0]["value"], rawtx1["vout"][1]["scriptPubKey"]["addresses"][0]: rawtx1["vout"][1]["value"]} clone_locktime = rawtx1["locktime"] clone_raw = self.nodes[0].createrawtransaction( clone_inputs, clone_outputs, clone_locktime) # createrawtransaction randomizes the order of its outputs, so swap # them if necessary. clone_tx = CTransaction() clone_tx.deserialize(io.BytesIO(bytes.fromhex(clone_raw))) - if (rawtx1["vout"][0]["value"] == 40 and - clone_tx.vout[0].nValue != 40 * COIN or - rawtx1["vout"][0]["value"] != 40 and - clone_tx.vout[0].nValue == 40 * COIN): + if (rawtx1["vout"][0]["value"] == 40000000 and + clone_tx.vout[0].nValue != 40000000 * XEC or + rawtx1["vout"][0]["value"] != 40000000 and + clone_tx.vout[0].nValue == 40000000 * XEC): (clone_tx.vout[0], clone_tx.vout[1]) = (clone_tx.vout[1], clone_tx.vout[0]) # Use a different signature hash type to sign. This creates an equivalent but malleated clone. # Don't send the clone anywhere yet tx1_clone = self.nodes[0].signrawtransactionwithwallet( clone_tx.serialize().hex(), None, "ALL|FORKID|ANYONECANPAY") assert_equal(tx1_clone["complete"], True) # Have node0 mine a block, if requested: if (self.options.mine_block): self.nodes[0].generate(1) self.sync_blocks(self.nodes[0:2]) tx1 = self.nodes[0].gettransaction(txid1) tx2 = self.nodes[0].gettransaction(txid2) # Node0's balance should be starting balance, plus 50BTC for another # matured block, minus tx1 and tx2 amounts, and minus transaction fees: expected = starting_balance + node0_tx1["fee"] + node0_tx2["fee"] if self.options.mine_block: - expected += 50 + expected += 50000000 expected += tx1["amount"] + tx1["fee"] expected += tx2["amount"] + tx2["fee"] assert_equal(self.nodes[0].getbalance(), expected) if self.options.mine_block: assert_equal(tx1["confirmations"], 1) assert_equal(tx2["confirmations"], 1) else: assert_equal(tx1["confirmations"], 0) assert_equal(tx2["confirmations"], 0) # Send clone and its parent to miner self.nodes[2].sendrawtransaction(node0_tx1["hex"]) txid1_clone = self.nodes[2].sendrawtransaction(tx1_clone["hex"]) # ... mine a block... self.nodes[2].generate(1) # Reconnect the split network, and sync chain: connect_nodes(self.nodes[1], self.nodes[2]) self.nodes[2].sendrawtransaction(node0_tx2["hex"]) self.nodes[2].sendrawtransaction(tx2["hex"]) self.nodes[2].generate(1) # Mine another block to make sure we sync self.sync_blocks() # Re-fetch transaction info: tx1 = self.nodes[0].gettransaction(txid1) tx1_clone = self.nodes[0].gettransaction(txid1_clone) tx2 = self.nodes[0].gettransaction(txid2) # Verify expected confirmations assert_equal(tx1["confirmations"], -2) assert_equal(tx1_clone["confirmations"], 2) assert_equal(tx2["confirmations"], 1) # Check node0's total balance; should be same as before the clone, + 100 BCH for 2 matured, # less possible orphaned matured subsidy - expected += 100 + expected += 100000000 if (self.options.mine_block): - expected -= 50 + expected -= 50000000 assert_equal(self.nodes[0].getbalance(), expected) if __name__ == '__main__': TxnMallTest().main() diff --git a/test/functional/wallet_txn_doublespend.py b/test/functional/wallet_txn_doublespend.py index d7f157eeb..a2d3414b5 100755 --- a/test/functional/wallet_txn_doublespend.py +++ b/test/functional/wallet_txn_doublespend.py @@ -1,150 +1,154 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet accounts properly when there is a double-spend conflict.""" from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, connect_nodes, disconnect_nodes, find_output, ) class TxnMallTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.extra_args = [["-noparkdeepreorg"], ["-noparkdeepreorg"], [], []] self.supports_cli = False def skip_test_if_missing_module(self): self.skip_if_no_wallet() def add_options(self, parser): parser.add_argument("--mineblock", dest="mine_block", default=False, action="store_true", help="Test double-spend of 1-confirmed transaction") def setup_network(self): # Start with split network: super().setup_network() disconnect_nodes(self.nodes[1], self.nodes[2]) def run_test(self): # All nodes should start with 1,250 BCH: - starting_balance = 1250 + starting_balance = 1250000000 # All nodes should be out of IBD. # If the nodes are not all out of IBD, that can interfere with # blockchain sync later in the test when nodes are connected, due to # timing issues. for n in self.nodes: assert n.getblockchaininfo()["initialblockdownload"] is False for i in range(4): assert_equal(self.nodes[i].getbalance(), starting_balance) # bug workaround, coins generated assigned to first getnewaddress! self.nodes[i].getnewaddress("") # Assign coins to foo and bar addresses: node0_address_foo = self.nodes[0].getnewaddress() - fund_foo_txid = self.nodes[0].sendtoaddress(node0_address_foo, 1219) + fund_foo_txid = self.nodes[0].sendtoaddress( + node0_address_foo, 1219000000) fund_foo_tx = self.nodes[0].gettransaction(fund_foo_txid) node0_address_bar = self.nodes[0].getnewaddress() - fund_bar_txid = self.nodes[0].sendtoaddress(node0_address_bar, 29) + fund_bar_txid = self.nodes[0].sendtoaddress( + node0_address_bar, 29000000) fund_bar_tx = self.nodes[0].gettransaction(fund_bar_txid) assert_equal(self.nodes[0].getbalance(), starting_balance + fund_foo_tx["fee"] + fund_bar_tx["fee"]) # Coins are sent to node1_address node1_address = self.nodes[1].getnewaddress() # First: use raw transaction API to send 1240 BCH to node1_address, # but don't broadcast: - doublespend_fee = Decimal('-.02') + doublespend_fee = Decimal('-20000') rawtx_input_0 = {} rawtx_input_0["txid"] = fund_foo_txid - rawtx_input_0["vout"] = find_output(self.nodes[0], fund_foo_txid, 1219) + rawtx_input_0["vout"] = find_output( + self.nodes[0], fund_foo_txid, 1219000000) rawtx_input_1 = {} rawtx_input_1["txid"] = fund_bar_txid - rawtx_input_1["vout"] = find_output(self.nodes[0], fund_bar_txid, 29) + rawtx_input_1["vout"] = find_output( + self.nodes[0], fund_bar_txid, 29000000) inputs = [rawtx_input_0, rawtx_input_1] change_address = self.nodes[0].getnewaddress() outputs = {} - outputs[node1_address] = 1240 - outputs[change_address] = 1248 - 1240 + doublespend_fee + outputs[node1_address] = 1240000000 + outputs[change_address] = 1248000000 - 1240000000 + doublespend_fee rawtx = self.nodes[0].createrawtransaction(inputs, outputs) doublespend = self.nodes[0].signrawtransactionwithwallet(rawtx) assert_equal(doublespend["complete"], True) # Create two spends using 1 50 BCH coin each - txid1 = self.nodes[0].sendtoaddress(node1_address, 40) - txid2 = self.nodes[0].sendtoaddress(node1_address, 20) + txid1 = self.nodes[0].sendtoaddress(node1_address, 40000000) + txid2 = self.nodes[0].sendtoaddress(node1_address, 20000000) # Have node0 mine a block: if (self.options.mine_block): self.nodes[0].generate(1) self.sync_blocks(self.nodes[0:2]) tx1 = self.nodes[0].gettransaction(txid1) tx2 = self.nodes[0].gettransaction(txid2) # Node0's balance should be starting balance, plus 50BTC for another # matured block, minus 40, minus 20, and minus transaction fees: expected = starting_balance + fund_foo_tx["fee"] + fund_bar_tx["fee"] if self.options.mine_block: - expected += 50 + expected += 50000000 expected += tx1["amount"] + tx1["fee"] expected += tx2["amount"] + tx2["fee"] assert_equal(self.nodes[0].getbalance(), expected) if self.options.mine_block: assert_equal(tx1["confirmations"], 1) assert_equal(tx2["confirmations"], 1) # Node1's balance should be both transaction amounts: assert_equal(self.nodes[1].getbalance( ), starting_balance - tx1["amount"] - tx2["amount"]) else: assert_equal(tx1["confirmations"], 0) assert_equal(tx2["confirmations"], 0) # Now give doublespend and its parents to miner: self.nodes[2].sendrawtransaction(fund_foo_tx["hex"]) self.nodes[2].sendrawtransaction(fund_bar_tx["hex"]) doublespend_txid = self.nodes[2].sendrawtransaction(doublespend["hex"]) # ... mine a block... self.nodes[2].generate(1) # Reconnect the split network, and sync chain: connect_nodes(self.nodes[1], self.nodes[2]) self.nodes[2].generate(1) # Mine another block to make sure we sync self.sync_blocks() assert_equal(self.nodes[0].gettransaction( doublespend_txid)["confirmations"], 2) # Re-fetch transaction info: tx1 = self.nodes[0].gettransaction(txid1) tx2 = self.nodes[0].gettransaction(txid2) # Both transactions should be conflicted assert_equal(tx1["confirmations"], -2) assert_equal(tx2["confirmations"], -2) # Node0's total balance should be starting balance, plus 100BTC for # two more matured blocks, minus 1240 for the double-spend, plus fees (which are # negative): - expected = starting_balance + 100 - 1240 + \ + expected = starting_balance + 100000000 - 1240000000 + \ fund_foo_tx["fee"] + fund_bar_tx["fee"] + doublespend_fee assert_equal(self.nodes[0].getbalance(), expected) # Node1's balance should be its initial balance (1250 for 25 block # rewards) plus the doublespend: - assert_equal(self.nodes[1].getbalance(), 1250 + 1240) + assert_equal(self.nodes[1].getbalance(), 1250000000 + 1240000000) if __name__ == '__main__': TxnMallTest().main() diff --git a/test/functional/wallet_watchonly.py b/test/functional/wallet_watchonly.py index 7406f7d43..28b30eb2d 100755 --- a/test/functional/wallet_watchonly.py +++ b/test/functional/wallet_watchonly.py @@ -1,119 +1,119 @@ #!/usr/bin/env python3 # Copyright (c) 2018-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test createwallet arguments. """ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error ) class CreateWalletWatchonlyTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = False self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): node = self.nodes[0] self.nodes[0].createwallet(wallet_name='default') def_wallet = node.get_wallet_rpc('default') a1 = def_wallet.getnewaddress() wo_change = def_wallet.getnewaddress() wo_addr = def_wallet.getnewaddress() self.nodes[0].createwallet(wallet_name='wo', disable_private_keys=True) wo_wallet = node.get_wallet_rpc('wo') wo_wallet.importpubkey( pubkey=def_wallet.getaddressinfo(wo_addr)['pubkey']) wo_wallet.importpubkey( pubkey=def_wallet.getaddressinfo(wo_change)['pubkey']) # generate some btc for testing node.generatetoaddress(101, a1) # send 1 btc to our watch-only address - txid = def_wallet.sendtoaddress(wo_addr, 1) + txid = def_wallet.sendtoaddress(wo_addr, 1000000) self.nodes[0].generate(1) # getbalance self.log.info( 'include_watchonly should default to true for watch-only wallets') self.log.info('Testing getbalance watch-only defaults') - assert_equal(wo_wallet.getbalance(), 1) + assert_equal(wo_wallet.getbalance(), 1000000) assert_equal(len(wo_wallet.listtransactions()), 1) assert_equal(wo_wallet.getbalance(include_watchonly=False), 0) self.log.info('Testing listreceivedbyaddress watch-only defaults') result = wo_wallet.listreceivedbyaddress() assert_equal(len(result), 1) assert_equal(result[0]["involvesWatchonly"], True) result = wo_wallet.listreceivedbyaddress(include_watchonly=False) assert_equal(len(result), 0) self.log.info('Testing listreceivedbylabel watch-only defaults') result = wo_wallet.listreceivedbylabel() assert_equal(len(result), 1) assert_equal(result[0]["involvesWatchonly"], True) result = wo_wallet.listreceivedbylabel(include_watchonly=False) assert_equal(len(result), 0) self.log.info('Testing listtransactions watch-only defaults') result = wo_wallet.listtransactions() assert_equal(len(result), 1) assert_equal(result[0]["involvesWatchonly"], True) result = wo_wallet.listtransactions(include_watchonly=False) assert_equal(len(result), 0) self.log.info('Testing listsinceblock watch-only defaults') result = wo_wallet.listsinceblock() assert_equal(len(result["transactions"]), 1) assert_equal(result["transactions"][0]["involvesWatchonly"], True) result = wo_wallet.listsinceblock(include_watchonly=False) assert_equal(len(result["transactions"]), 0) self.log.info('Testing gettransaction watch-only defaults') result = wo_wallet.gettransaction(txid) assert_equal(result["details"][0]["involvesWatchonly"], True) result = wo_wallet.gettransaction(txid=txid, include_watchonly=False) assert_equal(len(result["details"]), 0) self.log.info('Testing walletcreatefundedpsbt watch-only defaults') inputs = [] - outputs = [{a1: 0.5}] + outputs = [{a1: 500000}] options = {'changeAddress': wo_change} no_wo_options = {'changeAddress': wo_change, 'includeWatching': False} result = wo_wallet.walletcreatefundedpsbt( inputs=inputs, outputs=outputs, options=options) assert_equal("psbt" in result, True) assert_raises_rpc_error(-4, "Insufficient funds", wo_wallet.walletcreatefundedpsbt, inputs, outputs, 0, no_wo_options) self.log.info('Testing fundrawtransaction watch-only defaults') rawtx = wo_wallet.createrawtransaction(inputs=inputs, outputs=outputs) result = wo_wallet.fundrawtransaction(hexstring=rawtx, options=options) assert_equal("hex" in result, True) assert_raises_rpc_error(-4, "Insufficient funds", wo_wallet.fundrawtransaction, rawtx, no_wo_options) if __name__ == '__main__': CreateWalletWatchonlyTest().main()