diff options
Diffstat (limited to 'test/functional')
79 files changed, 1596 insertions, 910 deletions
diff --git a/test/functional/data/invalid_txs.py b/test/functional/data/invalid_txs.py index 33d6282961..3747b2a98d 100644 --- a/test/functional/data/invalid_txs.py +++ b/test/functional/data/invalid_txs.py @@ -28,6 +28,7 @@ from test_framework.messages import ( CTxIn, CTxOut, MAX_MONEY, + SEQUENCE_FINAL, ) from test_framework.blocktools import create_tx_with_script, MAX_BLOCK_SIGOPS from test_framework.script import ( @@ -77,7 +78,7 @@ class BadTxTemplate: def __init__(self, *, spend_tx=None, spend_block=None): self.spend_tx = spend_block.vtx[0] if spend_block else spend_tx self.spend_avail = sum(o.nValue for o in self.spend_tx.vout) - self.valid_txin = CTxIn(COutPoint(self.spend_tx.sha256, 0), b"", 0xffffffff) + self.valid_txin = CTxIn(COutPoint(self.spend_tx.sha256, 0), b"", SEQUENCE_FINAL) @abc.abstractmethod def get_tx(self, *args, **kwargs): @@ -137,7 +138,7 @@ class BadInputOutpointIndex(BadTxTemplate): bad_idx = num_indices + 100 tx = CTransaction() - tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256, bad_idx), b"", 0xffffffff)) + tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256, bad_idx), b"", SEQUENCE_FINAL)) tx.vout.append(CTxOut(0, basic_p2sh)) tx.calc_sha256() return tx @@ -175,7 +176,7 @@ class NonexistentInput(BadTxTemplate): def get_tx(self): tx = CTransaction() - tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256 + 1, 0), b"", 0xffffffff)) + tx.vin.append(CTxIn(COutPoint(self.spend_tx.sha256 + 1, 0), b"", SEQUENCE_FINAL)) tx.vin.append(self.valid_txin) tx.vout.append(CTxOut(1, basic_p2sh)) tx.calc_sha256() diff --git a/test/functional/data/rpc_decodescript.json b/test/functional/data/rpc_decodescript.json index d1aa9ab00d..8903f5efac 100644 --- a/test/functional/data/rpc_decodescript.json +++ b/test/functional/data/rpc_decodescript.json @@ -4,6 +4,7 @@ { "asm": "1 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "address": "bcrt1pamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqz6nvlh", + "desc": "addr(bcrt1pamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqz6nvlh)#v52jnujz", "type": "witness_v1_taproot" } ], @@ -12,6 +13,7 @@ { "asm": "1 -28398", "address": "bcrt1pamhqk96edn", + "desc": "addr(bcrt1pamhqk96edn)#vkh8uj5a", "type": "witness_unknown" } ], @@ -20,6 +22,7 @@ { "asm": "0 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "address": "bcrt1qamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqgdn98t", + "desc": "addr(bcrt1qamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhwamhqgdn98t)#afaecevx", "type": "witness_v0_scripthash", "p2sh": "2MwGk8mw1GBP6U9D5X8gTvgvXpuknmAK3fo" } @@ -29,6 +32,7 @@ { "asm": "OP_HASH160 eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee OP_EQUAL", "address": "2NF2b3KS8xXb9XHvbRMXdZh8s5g92rUZHtp", + "desc": "addr(2NF2b3KS8xXb9XHvbRMXdZh8s5g92rUZHtp)#ywfcpmh9", "type": "scripthash" } ], @@ -36,6 +40,7 @@ "6a00", { "asm": "OP_RETURN 0", + "desc": "raw(6a00)#ncfmkl43", "type": "nulldata" } ], @@ -43,6 +48,7 @@ "6aee", { "asm": "OP_RETURN OP_UNKNOWN", + "desc": "raw(6aee)#vsyzgqdt", "type": "nonstandard" } ], @@ -50,6 +56,7 @@ "6a02ee", { "asm": "OP_RETURN [error]", + "desc": "raw(6a02ee)#gvdwnlzl", "type": "nonstandard" } ], @@ -57,10 +64,12 @@ "02eeee", { "asm": "-28398", + "desc": "raw(02eeee)#5xzck7pr", "type": "nonstandard", "p2sh": "2N34iiGoUUkVSPiaaTFpJjB1FR9TXQu3PGM", "segwit": { "asm": "0 96c2368fc30514a438a8bd909f93c49a1549d77198ccbdb792043b666cb24f42", + "desc": "addr(bcrt1qjmprdr7rq522gw9ghkgfly7yng25n4m3nrxtmdujqsakvm9jfapqk795l5)#5akkdska", "hex": "002096c2368fc30514a438a8bd909f93c49a1549d77198ccbdb792043b666cb24f42", "address": "bcrt1qjmprdr7rq522gw9ghkgfly7yng25n4m3nrxtmdujqsakvm9jfapqk795l5", "type": "witness_v0_scripthash", @@ -72,6 +81,7 @@ "ba", { "asm": "OP_CHECKSIGADD", + "desc": "raw(ba)#yy0eg44l", "type": "nonstandard" } ], @@ -79,6 +89,7 @@ "50", { "asm": "OP_RESERVED", + "desc": "raw(50)#a7tu03xf", "type": "nonstandard" } ] diff --git a/test/functional/data/rpc_psbt.json b/test/functional/data/rpc_psbt.json index 0f6cd97fd8..8672400a92 100644 --- a/test/functional/data/rpc_psbt.json +++ b/test/functional/data/rpc_psbt.json @@ -19,6 +19,7 @@ "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", @@ -34,7 +35,15 @@ "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" ], "creator" : [ { @@ -136,4 +145,4 @@ "result" : "0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000" } ] -}
\ No newline at end of file +} diff --git a/test/functional/feature_addrman.py b/test/functional/feature_addrman.py index 14a4f8abb7..0fdefaa9c3 100755 --- a/test/functional/feature_addrman.py +++ b/test/functional/feature_addrman.py @@ -68,17 +68,16 @@ class AddrmanTest(BitcoinTestFramework): self.start_node(0, extra_args=["-checkaddrman=1"]) assert_equal(self.nodes[0].getnodeaddresses(), []) - self.log.info("Check that addrman from future cannot be read") + self.log.info("Check that addrman from future is overwritten with new addrman") self.stop_node(0) write_addrman(peers_dat, lowest_compatible=111) - self.nodes[0].assert_start_raises_init_error( - expected_msg=init_error( - "Unsupported format of addrman database: 1. It is compatible with " - "formats >=111, but the maximum supported by this version of " - f"{self.config['environment']['PACKAGE_NAME']} is 4.: (.+)" - ), - match=ErrorMatch.FULL_REGEX, - ) + assert_equal(os.path.exists(peers_dat + ".bak"), False) + with self.nodes[0].assert_debug_log([ + f'Creating new peers.dat because the file version was not compatible ("{peers_dat}"). Original backed up to peers.dat.bak', + ]): + self.start_node(0) + assert_equal(self.nodes[0].getnodeaddresses(), []) + assert_equal(os.path.exists(peers_dat + ".bak"), True) self.log.info("Check that corrupt addrman cannot be read (EOF)") self.stop_node(0) diff --git a/test/functional/feature_backwards_compatibility.py b/test/functional/feature_backwards_compatibility.py index 476a6a0c14..a7fb3184a6 100755 --- a/test/functional/feature_backwards_compatibility.py +++ b/test/functional/feature_backwards_compatibility.py @@ -34,15 +34,18 @@ from test_framework.util import ( class BackwardsCompatibilityTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True - self.num_nodes = 6 + self.num_nodes = 9 # Add new version after each release: self.extra_args = [ - ["-addresstype=bech32"], # Pre-release: use to mine blocks - ["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # Pre-release: use to receive coins, swap wallets, etc - ["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.19.1 - ["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.18.1 - ["-nowallet", "-walletrbf=1", "-addresstype=bech32"], # v0.17.2 - ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-wallet=wallet.dat"], # v0.16.3 + ["-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # Pre-release: use to mine blocks. noban for immediate tx relay + ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # Pre-release: use to receive coins, swap wallets, etc + ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v22.0 + ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.21.0 + ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.20.1 + ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=noban@127.0.0.1"], # v0.19.1 + ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=127.0.0.1"], # v0.18.1 + ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=127.0.0.1"], # v0.17.2 + ["-nowallet", "-walletrbf=1", "-addresstype=bech32", "-whitelist=127.0.0.1", "-wallet=wallet.dat"], # v0.16.3 ] self.wallet_names = [self.default_wallet_name] @@ -54,6 +57,9 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): self.add_nodes(self.num_nodes, extra_args=self.extra_args, versions=[ None, None, + 220000, + 210000, + 200100, 190100, 180100, 170200, @@ -63,19 +69,27 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): self.start_nodes() self.import_deterministic_coinbase_privkeys() - def run_test(self): - self.generatetoaddress(self.nodes[0], COINBASE_MATURITY + 1, self.nodes[0].getnewaddress()) - - # Sanity check the test framework: - res = self.nodes[self.num_nodes - 1].getblockchaininfo() - assert_equal(res['blocks'], COINBASE_MATURITY + 1) + def nodes_wallet_dir(self, node): + if node.version < 170000: + return os.path.join(node.datadir, "regtest") + return os.path.join(node.datadir, "regtest/wallets") - node_master = self.nodes[self.num_nodes - 5] + def run_test(self): + node_miner = self.nodes[0] + node_master = self.nodes[1] node_v19 = self.nodes[self.num_nodes - 4] node_v18 = self.nodes[self.num_nodes - 3] node_v17 = self.nodes[self.num_nodes - 2] node_v16 = self.nodes[self.num_nodes - 1] + legacy_nodes = self.nodes[2:] + + self.generatetoaddress(node_miner, COINBASE_MATURITY + 1, node_miner.getnewaddress()) + + # Sanity check the test framework: + res = node_v16.getblockchaininfo() + assert_equal(res['blocks'], COINBASE_MATURITY + 1) + self.log.info("Test wallet backwards compatibility...") # Create a number of wallets and open them in older versions: @@ -88,21 +102,21 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): assert info['keypoolsize'] > 0 # Create a confirmed transaction, receiving coins address = wallet.getnewaddress() - self.nodes[0].sendtoaddress(address, 10) + node_miner.sendtoaddress(address, 10) self.sync_mempools() - self.generate(self.nodes[0], 1) + self.generate(node_miner, 1) # Create a conflicting transaction using RBF - return_address = self.nodes[0].getnewaddress() - tx1_id = self.nodes[1].sendtoaddress(return_address, 1) - tx2_id = self.nodes[1].bumpfee(tx1_id)["txid"] + return_address = node_miner.getnewaddress() + tx1_id = node_master.sendtoaddress(return_address, 1) + tx2_id = node_master.bumpfee(tx1_id)["txid"] # Confirm the transaction self.sync_mempools() - self.generate(self.nodes[0], 1) + self.generate(node_miner, 1) # Create another conflicting transaction using RBF - tx3_id = self.nodes[1].sendtoaddress(return_address, 1) - tx4_id = self.nodes[1].bumpfee(tx3_id)["txid"] + tx3_id = node_master.sendtoaddress(return_address, 1) + tx4_id = node_master.bumpfee(tx3_id)["txid"] # Abandon transaction, but don't confirm - self.nodes[1].abandontransaction(tx3_id) + node_master.abandontransaction(tx3_id) # w1_v19: regular wallet, created with v0.19 node_v19.rpc.createwallet(wallet_name="w1_v19") @@ -113,6 +127,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): # Use addmultisigaddress (see #18075) address_18075 = wallet.rpc.addmultisigaddress(1, ["0296b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52", "037211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073"], "", "legacy")["address"] assert wallet.getaddressinfo(address_18075)["solvable"] + node_v19.unloadwallet("w1_v19") # w1_v18: regular wallet, created with v0.18 node_v18.rpc.createwallet(wallet_name="w1_v18") @@ -130,20 +145,6 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): assert info['private_keys_enabled'] == False assert info['keypoolsize'] == 0 - # w2_v19: wallet with private keys disabled, created with v0.19 - node_v19.rpc.createwallet(wallet_name="w2_v19", disable_private_keys=True) - wallet = node_v19.get_wallet_rpc("w2_v19") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] == False - assert info['keypoolsize'] == 0 - - # w2_v18: wallet with private keys disabled, created with v0.18 - node_v18.rpc.createwallet(wallet_name="w2_v18", disable_private_keys=True) - wallet = node_v18.get_wallet_rpc("w2_v18") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] == False - assert info['keypoolsize'] == 0 - # w3: blank wallet, created on master: update this # test when default blank wallets can no longer be opened by older versions. node_master.createwallet(wallet_name="w3", blank=True) @@ -152,170 +153,72 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): assert info['private_keys_enabled'] assert info['keypoolsize'] == 0 - # w3_v19: blank wallet, created with v0.19 - node_v19.rpc.createwallet(wallet_name="w3_v19", blank=True) - wallet = node_v19.get_wallet_rpc("w3_v19") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] - assert info['keypoolsize'] == 0 - - # w3_v18: blank wallet, created with v0.18 - node_v18.rpc.createwallet(wallet_name="w3_v18", blank=True) - wallet = node_v18.get_wallet_rpc("w3_v18") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] - assert info['keypoolsize'] == 0 - - # Copy the wallets to older nodes: + # Unload wallets and copy to older nodes: node_master_wallets_dir = os.path.join(node_master.datadir, "regtest/wallets") node_v19_wallets_dir = os.path.join(node_v19.datadir, "regtest/wallets") - node_v18_wallets_dir = os.path.join(node_v18.datadir, "regtest/wallets") node_v17_wallets_dir = os.path.join(node_v17.datadir, "regtest/wallets") node_v16_wallets_dir = os.path.join(node_v16.datadir, "regtest") node_master.unloadwallet("w1") node_master.unloadwallet("w2") - node_v19.unloadwallet("w1_v19") - node_v19.unloadwallet("w2_v19") - node_v18.unloadwallet("w1_v18") - node_v18.unloadwallet("w2_v18") - - # Copy wallets to v0.16 - for wallet in os.listdir(node_master_wallets_dir): - shutil.copytree( - os.path.join(node_master_wallets_dir, wallet), - os.path.join(node_v16_wallets_dir, wallet) - ) + node_master.unloadwallet("w3") - # Copy wallets to v0.17 - for wallet in os.listdir(node_master_wallets_dir): - shutil.copytree( - os.path.join(node_master_wallets_dir, wallet), - os.path.join(node_v17_wallets_dir, wallet) - ) - for wallet in os.listdir(node_v18_wallets_dir): - shutil.copytree( - os.path.join(node_v18_wallets_dir, wallet), - os.path.join(node_v17_wallets_dir, wallet) - ) - - # Copy wallets to v0.18 - for wallet in os.listdir(node_master_wallets_dir): - shutil.copytree( - os.path.join(node_master_wallets_dir, wallet), - os.path.join(node_v18_wallets_dir, wallet) - ) - - # Copy wallets to v0.19 - for wallet in os.listdir(node_master_wallets_dir): - shutil.copytree( - os.path.join(node_master_wallets_dir, wallet), - os.path.join(node_v19_wallets_dir, wallet) - ) + for node in legacy_nodes: + # Copy wallets to previous version + for wallet in os.listdir(node_master_wallets_dir): + shutil.copytree( + os.path.join(node_master_wallets_dir, wallet), + os.path.join(self.nodes_wallet_dir(node), wallet) + ) if not self.options.descriptors: # Descriptor wallets break compatibility, only run this test for legacy wallet - # Open the wallets in v0.19 - node_v19.loadwallet("w1") - wallet = node_v19.get_wallet_rpc("w1") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] - assert info['keypoolsize'] > 0 - txs = wallet.listtransactions() - assert_equal(len(txs), 5) - assert_equal(txs[1]["txid"], tx1_id) - assert_equal(txs[2]["walletconflicts"], [tx1_id]) - assert_equal(txs[1]["replaced_by_txid"], tx2_id) - assert not(txs[1]["abandoned"]) - assert_equal(txs[1]["confirmations"], -1) - assert_equal(txs[2]["blockindex"], 1) - assert txs[3]["abandoned"] - assert_equal(txs[4]["walletconflicts"], [tx3_id]) - assert_equal(txs[3]["replaced_by_txid"], tx4_id) - assert not(hasattr(txs[3], "blockindex")) - - node_v19.loadwallet("w2") - wallet = node_v19.get_wallet_rpc("w2") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] == False - assert info['keypoolsize'] == 0 - - node_v19.loadwallet("w3") - wallet = node_v19.get_wallet_rpc("w3") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] - assert info['keypoolsize'] == 0 - - # Open the wallets in v0.18 - node_v18.loadwallet("w1") - wallet = node_v18.get_wallet_rpc("w1") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] - assert info['keypoolsize'] > 0 - txs = wallet.listtransactions() - assert_equal(len(txs), 5) - assert_equal(txs[1]["txid"], tx1_id) - assert_equal(txs[2]["walletconflicts"], [tx1_id]) - assert_equal(txs[1]["replaced_by_txid"], tx2_id) - assert not(txs[1]["abandoned"]) - assert_equal(txs[1]["confirmations"], -1) - assert_equal(txs[2]["blockindex"], 1) - assert txs[3]["abandoned"] - assert_equal(txs[4]["walletconflicts"], [tx3_id]) - assert_equal(txs[3]["replaced_by_txid"], tx4_id) - assert not(hasattr(txs[3], "blockindex")) - - node_v18.loadwallet("w2") - wallet = node_v18.get_wallet_rpc("w2") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] == False - assert info['keypoolsize'] == 0 - - node_v18.loadwallet("w3") - wallet = node_v18.get_wallet_rpc("w3") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] - assert info['keypoolsize'] == 0 - - node_v17.loadwallet("w1") - wallet = node_v17.get_wallet_rpc("w1") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] - assert info['keypoolsize'] > 0 - - node_v17.loadwallet("w2") - wallet = node_v17.get_wallet_rpc("w2") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] == False - assert info['keypoolsize'] == 0 + # Load modern wallet with older nodes + for node in legacy_nodes: + for wallet_name in ["w1", "w2", "w3"]: + if node.version < 170000: + # loadwallet was introduced in v0.17.0 + continue + if node.version < 180000 and wallet_name == "w3": + # Blank wallets were introduced in v0.18.0. We test the loading error below. + continue + node.loadwallet(wallet_name) + wallet = node.get_wallet_rpc(wallet_name) + info = wallet.getwalletinfo() + if wallet_name == "w1": + assert info['private_keys_enabled'] == True + assert info['keypoolsize'] > 0 + txs = wallet.listtransactions() + assert_equal(len(txs), 5) + assert_equal(txs[1]["txid"], tx1_id) + assert_equal(txs[2]["walletconflicts"], [tx1_id]) + assert_equal(txs[1]["replaced_by_txid"], tx2_id) + assert not(txs[1]["abandoned"]) + assert_equal(txs[1]["confirmations"], -1) + assert_equal(txs[2]["blockindex"], 1) + assert txs[3]["abandoned"] + assert_equal(txs[4]["walletconflicts"], [tx3_id]) + assert_equal(txs[3]["replaced_by_txid"], tx4_id) + assert not(hasattr(txs[3], "blockindex")) + elif wallet_name == "w2": + assert(info['private_keys_enabled'] == False) + assert info['keypoolsize'] == 0 + else: + assert(info['private_keys_enabled'] == True) + assert info['keypoolsize'] == 0 else: - # Descriptor wallets appear to be corrupted wallets to old software - assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v19.loadwallet, "w1") - assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v19.loadwallet, "w2") - assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v19.loadwallet, "w3") - assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v18.loadwallet, "w1") - assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v18.loadwallet, "w2") - assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node_v18.loadwallet, "w3") - - # Open the wallets in v0.17 - node_v17.loadwallet("w1_v18") - wallet = node_v17.get_wallet_rpc("w1_v18") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] - assert info['keypoolsize'] > 0 - - node_v17.loadwallet("w2_v18") - wallet = node_v17.get_wallet_rpc("w2_v18") - info = wallet.getwalletinfo() - assert info['private_keys_enabled'] == False - assert info['keypoolsize'] == 0 + for node in legacy_nodes: + # Descriptor wallets appear to be corrupted wallets to old software + # and loadwallet is introduced in v0.17.0 + if node.version >= 170000 and node.version < 210000: + for wallet_name in ["w1", "w2", "w3"]: + assert_raises_rpc_error(-4, "Wallet file verification failed: wallet.dat corrupt, salvage failed", node.loadwallet, wallet_name) # RPC loadwallet failure causes bitcoind to exit, in addition to the RPC # call failure, so the following test won't work: - # assert_raises_rpc_error(-4, "Wallet loading failed.", node_v17.loadwallet, 'w3_v18') + # assert_raises_rpc_error(-4, "Wallet loading failed.", node_v17.loadwallet, 'w3') # Instead, we stop node and try to launch it with the wallet: - self.stop_node(4) - node_v17.assert_start_raises_init_error(["-wallet=w3_v18"], "Error: Error loading w3_v18: Wallet requires newer version of Bitcoin Core") + self.stop_node(node_v17.index) if self.options.descriptors: # Descriptor wallets appear to be corrupted wallets to old software node_v17.assert_start_raises_init_error(["-wallet=w1"], "Error: wallet.dat corrupt, salvage failed") @@ -323,23 +226,23 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): node_v17.assert_start_raises_init_error(["-wallet=w3"], "Error: wallet.dat corrupt, salvage failed") else: node_v17.assert_start_raises_init_error(["-wallet=w3"], "Error: Error loading w3: Wallet requires newer version of Bitcoin Core") - self.start_node(4) + self.start_node(node_v17.index) if not self.options.descriptors: # Descriptor wallets break compatibility, only run this test for legacy wallets # Open most recent wallet in v0.16 (no loadwallet RPC) - self.restart_node(5, extra_args=["-wallet=w2"]) + self.restart_node(node_v16.index, extra_args=["-wallet=w2"]) wallet = node_v16.get_wallet_rpc("w2") info = wallet.getwalletinfo() assert info['keypoolsize'] == 1 # Create upgrade wallet in v0.16 - self.restart_node(-1, extra_args=["-wallet=u1_v16"]) + self.restart_node(node_v16.index, extra_args=["-wallet=u1_v16"]) wallet = node_v16.get_wallet_rpc("u1_v16") v16_addr = wallet.getnewaddress('', "bech32") v16_info = wallet.validateaddress(v16_addr) v16_pubkey = v16_info['pubkey'] - self.stop_node(-1) + self.stop_node(node_v16.index) self.log.info("Test wallet upgrade path...") # u1: regular wallet, created with v0.17 @@ -371,7 +274,7 @@ class BackwardsCompatibilityTest(BitcoinTestFramework): os.path.join(node_master_wallets_dir, "u1_v16"), os.path.join(node_v16_wallets_dir, "wallets/u1_v16") ) - self.start_node(-1, extra_args=["-wallet=u1_v16"]) + self.start_node(node_v16.index, extra_args=["-wallet=u1_v16"]) wallet = node_v16.get_wallet_rpc("u1_v16") info = wallet.validateaddress(v16_addr) assert_equal(info, v16_info) diff --git a/test/functional/feature_bind_extra.py b/test/functional/feature_bind_extra.py index af26f94033..6802da8d48 100755 --- a/test/functional/feature_bind_extra.py +++ b/test/functional/feature_bind_extra.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2014-2020 The Bitcoin Core developers +# Copyright (c) 2014-2021 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index a3253763bd..462deeae32 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -23,6 +23,7 @@ from test_framework.messages import ( CTxIn, CTxOut, MAX_BLOCK_WEIGHT, + SEQUENCE_FINAL, uint256_from_compact, uint256_from_str, ) @@ -50,9 +51,13 @@ from test_framework.script_util import ( script_to_p2sh_script, ) from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal +from test_framework.util import ( + assert_equal, + assert_greater_than, +) from data import invalid_txs + # Use this class for tests that require behavior other than normal p2p behavior. # For now, it is used to serialize a bloated varint (b64). class CBrokenBlock(CBlock): @@ -801,7 +806,7 @@ class FullBlockTest(BitcoinTestFramework): b58 = self.next_block(58, spend=out[17]) tx = CTransaction() assert len(out[17].vout) < 42 - tx.vin.append(CTxIn(COutPoint(out[17].sha256, 42), CScript([OP_TRUE]), 0xffffffff)) + tx.vin.append(CTxIn(COutPoint(out[17].sha256, 42), CScript([OP_TRUE]), SEQUENCE_FINAL)) tx.vout.append(CTxOut(0, b"")) tx.calc_sha256() b58 = self.update_block(58, [tx]) @@ -876,7 +881,7 @@ class FullBlockTest(BitcoinTestFramework): tx.nLockTime = 0xffffffff # this locktime is non-final tx.vin.append(CTxIn(COutPoint(out[18].sha256, 0))) # don't set nSequence tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) - assert tx.vin[0].nSequence < 0xffffffff + assert_greater_than(SEQUENCE_FINAL, tx.vin[0].nSequence) tx.calc_sha256() b62 = self.update_block(62, [tx]) self.send_blocks([b62], success=False, reject_reason='bad-txns-nonfinal', reconnect=True) @@ -1024,7 +1029,7 @@ class FullBlockTest(BitcoinTestFramework): bogus_tx = CTransaction() bogus_tx.sha256 = uint256_from_str(b"23c70ed7c0506e9178fc1a987f40a33946d4ad4c962b5ae3a52546da53af0c5c") tx = CTransaction() - tx.vin.append(CTxIn(COutPoint(bogus_tx.sha256, 0), b"", 0xffffffff)) + tx.vin.append(CTxIn(COutPoint(bogus_tx.sha256, 0), b"", SEQUENCE_FINAL)) tx.vout.append(CTxOut(1, b"")) b70 = self.update_block(70, [tx]) self.send_blocks([b70], success=False, reject_reason='bad-txns-inputs-missingorspent', reconnect=True) diff --git a/test/functional/feature_blockfilterindex_prune.py b/test/functional/feature_blockfilterindex_prune.py index 39eb700b4f..2451988135 100755 --- a/test/functional/feature_blockfilterindex_prune.py +++ b/test/functional/feature_blockfilterindex_prune.py @@ -24,13 +24,13 @@ class FeatureBlockfilterindexPruneTest(BitcoinTestFramework): self.log.info("check if we can access a blockfilter when pruning is enabled but no blocks are actually pruned") self.sync_index(height=200) assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getbestblockhash())['filter']), 0) - # Mine two batches of blocks to avoid hitting NODE_NETWORK_LIMITED_MIN_BLOCKS disconnection - self.generate(self.nodes[0], 250) - self.generate(self.nodes[0], 250) + self.generate(self.nodes[0], 500) self.sync_index(height=700) self.log.info("prune some blocks") pruneheight = self.nodes[0].pruneblockchain(400) + # the prune heights used here and below are magic numbers that are determined by the + # thresholds at which block files wrap, so they depend on disk serialization and default block file size. assert_equal(pruneheight, 248) self.log.info("check if we can access the tips blockfilter when we have pruned some blocks") @@ -39,16 +39,29 @@ class FeatureBlockfilterindexPruneTest(BitcoinTestFramework): self.log.info("check if we can access the blockfilter of a pruned block") assert_greater_than(len(self.nodes[0].getblockfilter(self.nodes[0].getblockhash(2))['filter']), 0) + # mine and sync index up to a height that will later be the pruneheight + self.generate(self.nodes[0], 298) + self.sync_index(height=998) + self.log.info("start node without blockfilterindex") self.restart_node(0, extra_args=["-fastprune", "-prune=1"]) self.log.info("make sure accessing the blockfilters throws an error") assert_raises_rpc_error(-1, "Index is not enabled for filtertype basic", self.nodes[0].getblockfilter, self.nodes[0].getblockhash(2)) - self.generate(self.nodes[0], 1000) + self.generate(self.nodes[0], 502) + + self.log.info("prune exactly up to the blockfilterindexes best block while blockfilters are disabled") + pruneheight_2 = self.nodes[0].pruneblockchain(1000) + assert_equal(pruneheight_2, 998) + self.restart_node(0, extra_args=["-fastprune", "-prune=1", "-blockfilterindex=1"]) + self.log.info("make sure that we can continue with the partially synced index after having pruned up to the index height") + self.sync_index(height=1500) self.log.info("prune below the blockfilterindexes best block while blockfilters are disabled") - pruneheight_new = self.nodes[0].pruneblockchain(1000) - assert_greater_than(pruneheight_new, pruneheight) + self.restart_node(0, extra_args=["-fastprune", "-prune=1"]) + self.generate(self.nodes[0], 1000) + pruneheight_3 = self.nodes[0].pruneblockchain(2000) + assert_greater_than(pruneheight_3, pruneheight_2) self.stop_node(0) self.log.info("make sure we get an init error when starting the node again with block filters") diff --git a/test/functional/feature_cltv.py b/test/functional/feature_cltv.py index eb90b2c598..9d32749a08 100755 --- a/test/functional/feature_cltv.py +++ b/test/functional/feature_cltv.py @@ -13,6 +13,7 @@ from test_framework.blocktools import ( ) from test_framework.messages import ( CTransaction, + SEQUENCE_FINAL, msg_block, ) from test_framework.p2p import P2PInterface @@ -53,7 +54,7 @@ def cltv_invalidate(tx, failure_reason): # 3) the lock-time type (height vs. timestamp) of the top stack item and the # nLockTime field are not the same # 4) the top stack item is greater than the transaction's nLockTime field - # 5) the nSequence field of the txin is 0xffffffff + # 5) the nSequence field of the txin is 0xffffffff (SEQUENCE_FINAL) assert failure_reason in range(5) scheme = [ # | Script to prepend to scriptSig | nSequence | nLockTime | @@ -62,7 +63,7 @@ def cltv_invalidate(tx, failure_reason): [[OP_1NEGATE, OP_CHECKLOCKTIMEVERIFY, OP_DROP], None, None], [[CScriptNum(100), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0, 1296688602], # timestamp of genesis block [[CScriptNum(100), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0, 50], - [[CScriptNum(50), OP_CHECKLOCKTIMEVERIFY, OP_DROP], 0xffffffff, 50], + [[CScriptNum(50), OP_CHECKLOCKTIMEVERIFY, OP_DROP], SEQUENCE_FINAL, 50], ][failure_reason] cltv_modify_tx(tx, prepend_scriptsig=scheme[0], nsequence=scheme[1], nlocktime=scheme[2]) @@ -91,7 +92,7 @@ class BIP65Test(BitcoinTestFramework): self.rpc_timeout = 480 def test_cltv_info(self, *, is_active): - assert_equal(self.nodes[0].getblockchaininfo()['softforks']['bip65'], { + assert_equal(self.nodes[0].getdeploymentinfo()['deployments']['bip65'], { "active": is_active, "height": CLTV_HEIGHT, "type": "buried", @@ -114,7 +115,7 @@ class BIP65Test(BitcoinTestFramework): # create one invalid tx per CLTV failure reason (5 in total) and collect them invalid_cltv_txs = [] for i in range(5): - spendtx = wallet.create_self_transfer(from_node=self.nodes[0])['tx'] + spendtx = wallet.create_self_transfer()['tx'] cltv_invalidate(spendtx, i) invalid_cltv_txs.append(spendtx) @@ -145,7 +146,7 @@ class BIP65Test(BitcoinTestFramework): # create and test one invalid tx per CLTV failure reason (5 in total) for i in range(5): - spendtx = wallet.create_self_transfer(from_node=self.nodes[0])['tx'] + spendtx = wallet.create_self_transfer()['tx'] cltv_invalidate(spendtx, i) expected_cltv_reject_reason = [ diff --git a/test/functional/feature_coinstatsindex.py b/test/functional/feature_coinstatsindex.py index 8afd0ff74a..c70f8a83db 100755 --- a/test/functional/feature_coinstatsindex.py +++ b/test/functional/feature_coinstatsindex.py @@ -40,7 +40,9 @@ class CoinStatsIndexTest(BitcoinTestFramework): self.num_nodes = 2 self.supports_cli = False self.extra_args = [ - [], + # Explicitly set the output type in order to have consistent tx vsize / fees + # for both legacy and descriptor wallets (disables the change address type detection algorithm) + ["-addresstype=bech32", "-changetype=bech32"], ["-coinstatsindex"] ] diff --git a/test/functional/feature_csv_activation.py b/test/functional/feature_csv_activation.py index c200445e81..6470c1c5eb 100755 --- a/test/functional/feature_csv_activation.py +++ b/test/functional/feature_csv_activation.py @@ -104,7 +104,7 @@ class BIP68_112_113Test(BitcoinTestFramework): def create_self_transfer_from_utxo(self, input_tx): utxo = self.miniwallet.get_utxo(txid=input_tx.rehash(), mark_as_spent=False) - tx = self.miniwallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo)['tx'] + tx = self.miniwallet.create_self_transfer(utxo_to_spend=utxo)['tx'] return tx def create_bip112special(self, input, txversion): diff --git a/test/functional/feature_dersig.py b/test/functional/feature_dersig.py index b7cb32c842..9a46839969 100755 --- a/test/functional/feature_dersig.py +++ b/test/functional/feature_dersig.py @@ -57,10 +57,10 @@ class BIP66Test(BitcoinTestFramework): def create_tx(self, input_txid): utxo_to_spend = self.miniwallet.get_utxo(txid=input_txid, mark_as_spent=False) - return self.miniwallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_to_spend)['tx'] + return self.miniwallet.create_self_transfer(utxo_to_spend=utxo_to_spend)['tx'] def test_dersig_info(self, *, is_active): - assert_equal(self.nodes[0].getblockchaininfo()['softforks']['bip66'], + assert_equal(self.nodes[0].getdeploymentinfo()['deployments']['bip66'], { "active": is_active, "height": DERSIG_HEIGHT, diff --git a/test/functional/feature_dirsymlinks.py b/test/functional/feature_dirsymlinks.py new file mode 100755 index 0000000000..288754c04c --- /dev/null +++ b/test/functional/feature_dirsymlinks.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 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 successful startup with symlinked directories. +""" + +import os + +from test_framework.test_framework import BitcoinTestFramework + + +def rename_and_link(*, from_name, to_name): + os.rename(from_name, to_name) + os.symlink(to_name, from_name) + assert os.path.islink(from_name) and os.path.isdir(from_name) + + +class SymlinkTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + + def run_test(self): + dir_new_blocks = self.nodes[0].chain_path / "new_blocks" + dir_new_chainstate = self.nodes[0].chain_path / "new_chainstate" + self.stop_node(0) + + rename_and_link( + from_name=self.nodes[0].chain_path / "blocks", + to_name=dir_new_blocks, + ) + rename_and_link( + from_name=self.nodes[0].chain_path / "chainstate", + to_name=dir_new_chainstate, + ) + + self.start_node(0) + + +if __name__ == "__main__": + SymlinkTest().main() diff --git a/test/functional/feature_fee_estimation.py b/test/functional/feature_fee_estimation.py index 46d5bcf1a6..233ffd60da 100755 --- a/test/functional/feature_fee_estimation.py +++ b/test/functional/feature_fee_estimation.py @@ -17,7 +17,6 @@ from test_framework.messages import ( from test_framework.script import ( CScript, OP_1, - OP_2, OP_DROP, OP_TRUE, ) @@ -36,16 +35,14 @@ from test_framework.util import ( # Construct 2 trivial P2SH's and the ScriptSigs that spend them # So we can create many transactions without needing to spend # time signing. -REDEEM_SCRIPT_1 = CScript([OP_1, OP_DROP]) -REDEEM_SCRIPT_2 = CScript([OP_2, OP_DROP]) -P2SH_1 = script_to_p2sh_script(REDEEM_SCRIPT_1) -P2SH_2 = script_to_p2sh_script(REDEEM_SCRIPT_2) +SCRIPT = CScript([OP_1, OP_DROP]) +P2SH = script_to_p2sh_script(SCRIPT) +REDEEM_SCRIPT = CScript([OP_TRUE, SCRIPT]) -# Associated ScriptSig's to spend satisfy P2SH_1 and P2SH_2 -SCRIPT_SIG = [CScript([OP_TRUE, REDEEM_SCRIPT_1]), CScript([OP_TRUE, REDEEM_SCRIPT_2])] - -def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee_increment): +def small_txpuzzle_randfee( + from_node, conflist, unconflist, amount, min_fee, fee_increment +): """Create and send a transaction with a random fee. The transaction pays to a trivial P2SH script, and assumes that its inputs @@ -66,20 +63,15 @@ def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee while total_in <= (amount + fee) and len(conflist) > 0: t = conflist.pop(0) total_in += t["amount"] - tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), b"")) + tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), REDEEM_SCRIPT)) + while total_in <= (amount + fee) and len(unconflist) > 0: + t = unconflist.pop(0) + total_in += t["amount"] + tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), REDEEM_SCRIPT)) if total_in <= amount + fee: - while total_in <= (amount + fee) and len(unconflist) > 0: - t = unconflist.pop(0) - total_in += t["amount"] - tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), b"")) - if total_in <= amount + fee: - raise RuntimeError(f"Insufficient funds: need {amount + fee}, have {total_in}") - tx.vout.append(CTxOut(int((total_in - amount - fee) * COIN), P2SH_1)) - tx.vout.append(CTxOut(int(amount * COIN), P2SH_2)) - # These transactions don't need to be signed, but we still have to insert - # the ScriptSig that will satisfy the ScriptPubKey. - for inp in tx.vin: - inp.scriptSig = SCRIPT_SIG[inp.prevout.n] + raise RuntimeError(f"Insufficient funds: need {amount + fee}, have {total_in}") + tx.vout.append(CTxOut(int((total_in - amount - fee) * COIN), P2SH)) + tx.vout.append(CTxOut(int(amount * COIN), P2SH)) txid = from_node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0) unconflist.append({"txid": txid, "vout": 0, "amount": total_in - amount - fee}) unconflist.append({"txid": txid, "vout": 1, "amount": amount}) @@ -87,34 +79,6 @@ def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee return (tx.serialize().hex(), fee) -def split_inputs(from_node, txins, txouts, initial_split=False): - """Generate a lot of inputs so we can generate a ton of transactions. - - This function takes an input from txins, and creates and sends a transaction - which splits the value into 2 outputs which are appended to txouts. - Previously this was designed to be small inputs so they wouldn't have - a high coin age when the notion of priority still existed.""" - - prevtxout = txins.pop() - tx = CTransaction() - tx.vin.append(CTxIn(COutPoint(int(prevtxout["txid"], 16), prevtxout["vout"]), b"")) - - half_change = satoshi_round(prevtxout["amount"] / 2) - rem_change = prevtxout["amount"] - half_change - Decimal("0.00001000") - tx.vout.append(CTxOut(int(half_change * COIN), P2SH_1)) - tx.vout.append(CTxOut(int(rem_change * COIN), P2SH_2)) - - # If this is the initial split we actually need to sign the transaction - # Otherwise we just need to insert the proper ScriptSig - if (initial_split): - completetx = from_node.signrawtransactionwithwallet(tx.serialize().hex())["hex"] - else: - tx.vin[0].scriptSig = SCRIPT_SIG[prevtxout["vout"]] - completetx = tx.serialize().hex() - txid = from_node.sendrawtransaction(hexstring=completetx, maxfeerate=0) - txouts.append({"txid": txid, "vout": 0, "amount": half_change}) - txouts.append({"txid": txid, "vout": 1, "amount": rem_change}) - def check_raw_estimates(node, fees_seen): """Call estimaterawfee and verify that the estimates meet certain invariants.""" @@ -125,7 +89,10 @@ def check_raw_estimates(node, fees_seen): assert_greater_than(feerate, 0) if feerate + delta < min(fees_seen) or feerate - delta > max(fees_seen): - raise AssertionError(f"Estimated fee ({feerate}) out of range ({min(fees_seen)},{max(fees_seen)})") + raise AssertionError( + f"Estimated fee ({feerate}) out of range ({min(fees_seen)},{max(fees_seen)})" + ) + def check_smart_estimates(node, fees_seen): """Call estimatesmartfee and verify that the estimates meet certain invariants.""" @@ -133,8 +100,8 @@ def check_smart_estimates(node, fees_seen): delta = 1.0e-6 # account for rounding error last_feerate = float(max(fees_seen)) all_smart_estimates = [node.estimatesmartfee(i) for i in range(1, 26)] - mempoolMinFee = node.getmempoolinfo()['mempoolminfee'] - minRelaytxFee = node.getmempoolinfo()['minrelaytxfee'] + mempoolMinFee = node.getmempoolinfo()["mempoolminfee"] + minRelaytxFee = node.getmempoolinfo()["minrelaytxfee"] for i, e in enumerate(all_smart_estimates): # estimate is for i+1 feerate = float(e["feerate"]) assert_greater_than(feerate, 0) @@ -142,9 +109,13 @@ def check_smart_estimates(node, fees_seen): assert_greater_than_or_equal(feerate, float(minRelaytxFee)) if feerate + delta < min(fees_seen) or feerate - delta > max(fees_seen): - raise AssertionError(f"Estimated fee ({feerate}) out of range ({min(fees_seen)},{max(fees_seen)})") + raise AssertionError( + f"Estimated fee ({feerate}) out of range ({min(fees_seen)},{max(fees_seen)})" + ) if feerate - delta > last_feerate: - raise AssertionError(f"Estimated fee ({feerate}) larger than last fee ({last_feerate}) for lower number of confirms") + raise AssertionError( + f"Estimated fee ({feerate}) larger than last fee ({last_feerate}) for lower number of confirms" + ) last_feerate = feerate if i == 0: @@ -152,6 +123,7 @@ def check_smart_estimates(node, fees_seen): else: assert_greater_than_or_equal(i + 1, e["blocks"]) + def check_estimates(node, fees_seen): check_raw_estimates(node, fees_seen) check_smart_estimates(node, fees_seen) @@ -159,27 +131,25 @@ def check_estimates(node, fees_seen): def send_tx(node, utxo, feerate): """Broadcast a 1in-1out transaction with a specific input and feerate (sat/vb).""" - overhead, op, scriptsig, nseq, value, spk = 10, 36, 5, 4, 8, 24 - tx_size = overhead + op + scriptsig + nseq + value + spk - fee = tx_size * feerate - tx = CTransaction() - tx.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), SCRIPT_SIG[utxo["vout"]])] - tx.vout = [CTxOut(int(utxo["amount"] * COIN) - fee, P2SH_1)] - txid = node.sendrawtransaction(tx.serialize().hex()) + tx.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), REDEEM_SCRIPT)] + tx.vout = [CTxOut(int(utxo["amount"] * COIN), P2SH)] + + # vbytes == bytes as we are using legacy transactions + fee = tx.get_vsize() * feerate + tx.vout[0].nValue -= fee - return txid + return node.sendrawtransaction(tx.serialize().hex()) class EstimateFeeTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 3 - # mine non-standard txs (e.g. txs with "dust" outputs) # Force fSendTrickle to true (via whitelist.noban) self.extra_args = [ - ["-acceptnonstdtxn", "-whitelist=noban@127.0.0.1"], - ["-acceptnonstdtxn", "-whitelist=noban@127.0.0.1", "-blockmaxweight=68000"], - ["-acceptnonstdtxn", "-whitelist=noban@127.0.0.1", "-blockmaxweight=32000"], + ["-whitelist=noban@127.0.0.1"], + ["-whitelist=noban@127.0.0.1", "-blockmaxweight=68000"], + ["-whitelist=noban@127.0.0.1", "-blockmaxweight=32000"], ] def skip_test_if_missing_module(self): @@ -212,11 +182,17 @@ class EstimateFeeTest(BitcoinTestFramework): random.shuffle(self.confutxo) for _ in range(random.randrange(100 - 50, 100 + 50)): from_index = random.randint(1, 2) - (txhex, fee) = small_txpuzzle_randfee(self.nodes[from_index], self.confutxo, - self.memutxo, Decimal("0.005"), min_fee, min_fee) + (txhex, fee) = small_txpuzzle_randfee( + self.nodes[from_index], + self.confutxo, + self.memutxo, + Decimal("0.005"), + min_fee, + min_fee, + ) tx_kbytes = (len(txhex) // 2) / 1000.0 self.fees_per_kb.append(float(fee) / tx_kbytes) - self.sync_mempools(wait=.1) + self.sync_mempools(wait=0.1) mined = mining_node.getblock(self.generate(mining_node, 1)[0], True)["tx"] # update which txouts are confirmed newmem = [] @@ -229,46 +205,45 @@ class EstimateFeeTest(BitcoinTestFramework): def initial_split(self, node): """Split two coinbase UTxOs into many small coins""" - self.txouts = [] - self.txouts2 = [] - # Split a coinbase into two transaction puzzle outputs - split_inputs(node, node.listunspent(0), self.txouts, True) - - # Mine + utxo_count = 2048 + self.confutxo = [] + splitted_amount = Decimal("0.04") + fee = Decimal("0.1") + change = Decimal("100") - splitted_amount * utxo_count - fee + tx = CTransaction() + tx.vin = [ + CTxIn(COutPoint(int(cb["txid"], 16), cb["vout"])) + for cb in node.listunspent()[:2] + ] + tx.vout = [CTxOut(int(splitted_amount * COIN), P2SH) for _ in range(utxo_count)] + tx.vout.append(CTxOut(int(change * COIN), P2SH)) + txhex = node.signrawtransactionwithwallet(tx.serialize().hex())["hex"] + txid = node.sendrawtransaction(txhex) + self.confutxo = [ + {"txid": txid, "vout": i, "amount": splitted_amount} + for i in range(utxo_count) + ] while len(node.getrawmempool()) > 0: self.generate(node, 1, sync_fun=self.no_op) - # Repeatedly split those 2 outputs, doubling twice for each rep - # Use txouts to monitor the available utxo, since these won't be tracked in wallet - reps = 0 - while reps < 5: - # Double txouts to txouts2 - while len(self.txouts) > 0: - split_inputs(node, self.txouts, self.txouts2) - while len(node.getrawmempool()) > 0: - self.generate(node, 1, sync_fun=self.no_op) - # Double txouts2 to txouts - while len(self.txouts2) > 0: - split_inputs(node, self.txouts2, self.txouts) - while len(node.getrawmempool()) > 0: - self.generate(node, 1, sync_fun=self.no_op) - reps += 1 - def sanity_check_estimates_range(self): """Populate estimation buckets, assert estimates are in a sane range and are strictly increasing as the target decreases.""" self.fees_per_kb = [] self.memutxo = [] - self.confutxo = self.txouts # Start with the set of confirmed txouts after splitting self.log.info("Will output estimates for 1/2/3/6/15/25 blocks") for _ in range(2): - self.log.info("Creating transactions and mining them with a block size that can't keep up") + self.log.info( + "Creating transactions and mining them with a block size that can't keep up" + ) # Create transactions and mine 10 small blocks with node 2, but create txs faster than we can mine self.transact_and_mine(10, self.nodes[2]) check_estimates(self.nodes[1], self.fees_per_kb) - self.log.info("Creating transactions and mining them at a block size that is just big enough") + self.log.info( + "Creating transactions and mining them at a block size that is just big enough" + ) # Generate transactions while mining 10 more blocks, this time with node1 # which mines blocks with capacity just above the rate that transactions are being created self.transact_and_mine(10, self.nodes[1]) @@ -277,12 +252,13 @@ class EstimateFeeTest(BitcoinTestFramework): # Finish by mining a normal-sized block: while len(self.nodes[1].getrawmempool()) > 0: self.generate(self.nodes[1], 1) + self.log.info("Final estimates after emptying mempools") check_estimates(self.nodes[1], self.fees_per_kb) def test_feerate_mempoolminfee(self): - high_val = 3*self.nodes[1].estimatesmartfee(1)['feerate'] - self.restart_node(1, extra_args=[f'-minrelaytxfee={high_val}']) + high_val = 3 * self.nodes[1].estimatesmartfee(1)["feerate"] + self.restart_node(1, extra_args=[f"-minrelaytxfee={high_val}"]) check_estimates(self.nodes[1], self.fees_per_kb) self.restart_node(1) @@ -303,7 +279,7 @@ class EstimateFeeTest(BitcoinTestFramework): utxos_to_respend = [] txids_to_replace = [] - assert len(utxos) >= 250 + assert_greater_than_or_equal(len(utxos), 250) for _ in range(5): # Broadcast 45 low fee transactions that will need to be RBF'd for _ in range(45): @@ -315,27 +291,24 @@ class EstimateFeeTest(BitcoinTestFramework): for _ in range(5): send_tx(node, utxos.pop(0), low_feerate) # Mine the transactions on another node - self.sync_mempools(wait=.1, nodes=[node, miner]) + self.sync_mempools(wait=0.1, nodes=[node, miner]) for txid in txids_to_replace: miner.prioritisetransaction(txid=txid, fee_delta=-COIN) self.generate(miner, 1) # RBF the low-fee transactions - while True: - try: - u = utxos_to_respend.pop(0) - send_tx(node, u, high_feerate) - except IndexError: - break + while len(utxos_to_respend) > 0: + u = utxos_to_respend.pop(0) + send_tx(node, u, high_feerate) # Mine the last replacement txs - self.sync_mempools(wait=.1, nodes=[node, miner]) + self.sync_mempools(wait=0.1, nodes=[node, miner]) self.generate(miner, 1) # Only 10% of the transactions were really confirmed with a low feerate, # the rest needed to be RBF'd. We must return the 90% conf rate feerate. - high_feerate_kvb = Decimal(high_feerate) / COIN * 10**3 + high_feerate_kvb = Decimal(high_feerate) / COIN * 10 ** 3 est_feerate = node.estimatesmartfee(2)["feerate"] - assert est_feerate == high_feerate_kvb + assert_equal(est_feerate, high_feerate_kvb) def run_test(self): self.log.info("This test is time consuming, please be patient") @@ -359,7 +332,9 @@ class EstimateFeeTest(BitcoinTestFramework): self.sanity_check_estimates_range() # check that the effective feerate is greater than or equal to the mempoolminfee even for high mempoolminfee - self.log.info("Test fee rate estimation after restarting node with high MempoolMinFee") + self.log.info( + "Test fee rate estimation after restarting node with high MempoolMinFee" + ) self.test_feerate_mempoolminfee() self.log.info("Restarting node with fresh estimation") @@ -375,9 +350,10 @@ class EstimateFeeTest(BitcoinTestFramework): self.log.info("Testing that fee estimation is disabled in blocksonly.") self.restart_node(0, ["-blocksonly"]) - assert_raises_rpc_error(-32603, "Fee estimation disabled", - self.nodes[0].estimatesmartfee, 2) + assert_raises_rpc_error( + -32603, "Fee estimation disabled", self.nodes[0].estimatesmartfee, 2 + ) -if __name__ == '__main__': +if __name__ == "__main__": EstimateFeeTest().main() diff --git a/test/functional/feature_init.py b/test/functional/feature_init.py index cffbd40271..d0cb1e10e2 100755 --- a/test/functional/feature_init.py +++ b/test/functional/feature_init.py @@ -3,8 +3,6 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Stress tests related to node initialization.""" -import random -import time import os from pathlib import Path @@ -26,7 +24,6 @@ class InitStressTest(BitcoinTestFramework): def run_test(self): """ - test terminating initialization after seeing a certain log line. - - test terminating init after seeing a random number of log lines. - test removing certain essential files to test startup error paths. """ # TODO: skip Windows for now since it isn't clear how to SIGTERM. @@ -44,97 +41,44 @@ class InitStressTest(BitcoinTestFramework): def sigterm_node(): node.process.terminate() node.process.wait() - node.debug_log_path.unlink() - node.debug_log_path.touch() def check_clean_start(): """Ensure that node restarts successfully after various interrupts.""" - # TODO: add -txindex=1 to fully test index initiatlization. - # See https://github.com/bitcoin/bitcoin/pull/23289#discussion_r735159180 for - # a discussion of the related bug. node.start() node.wait_for_rpc_connection() assert_equal(200, node.getblockcount()) lines_to_terminate_after = [ + 'Validating signatures for all blocks', 'scheduler thread start', + 'Starting HTTP server', 'Loading P2P addresses', 'Loading banlist', 'Loading block index', 'Switching active chainstate', + 'Checking all blk files are present', 'Loaded best chain:', 'init message: Verifying blocks', - 'loadblk thread start', - # TODO: reenable - see above TODO - # 'txindex thread start', + 'init message: Starting network threads', 'net thread start', 'addcon thread start', + 'loadblk thread start', + 'txindex thread start', + 'block filter index thread start', + 'coinstatsindex thread start', 'msghand thread start', + 'net thread start', + 'addcon thread start', ] if self.is_wallet_compiled(): lines_to_terminate_after.append('Verifying wallet') for terminate_line in lines_to_terminate_after: self.log.info(f"Starting node and will exit after line '{terminate_line}'") - node.start( - # TODO: add -txindex=1 to fully test index initiatlization. - # extra_args=['-txindex=1'], - ) - logfile = open(node.debug_log_path, 'r', encoding='utf8') - - MAX_SECS_TO_WAIT = 30 - start = time.time() - num_lines = 0 - - while True: - line = logfile.readline() - if line: - num_lines += 1 - - if line and terminate_line.lower() in line.lower(): - self.log.debug(f"Terminating node after {num_lines} log lines seen") - sigterm_node() - break - - if (time.time() - start) > MAX_SECS_TO_WAIT: - raise AssertionError( - f"missed line {terminate_line}; terminating now after {num_lines} lines") - - if node.process.poll() is not None: - raise AssertionError(f"node failed to start (line: '{terminate_line}')") - - check_clean_start() - num_total_logs = len(node.debug_log_path.read_text().splitlines()) - self.stop_node(0) - - self.log.info( - f"Terminate at some random point in the init process (max logs: {num_total_logs})") - - for _ in range(40): - terminate_after = random.randint(1, num_total_logs) - self.log.debug(f"Starting node and will exit after {terminate_after} lines") - node.start( - # TODO: add -txindex=1 to fully test index initiatlization. - # extra_args=['-txindex=1'], - ) - logfile = open(node.debug_log_path, 'r', encoding='utf8') - - MAX_SECS_TO_WAIT = 10 - start = time.time() - num_lines = 0 - - while True: - line = logfile.readline() - if line: - num_lines += 1 - - if num_lines >= terminate_after or (time.time() - start) > MAX_SECS_TO_WAIT: - self.log.debug(f"Terminating node after {num_lines} log lines seen") - sigterm_node() - break - - if node.process.poll() is not None: - raise AssertionError("node failed to start") + with node.wait_for_debug_log([terminate_line], ignore_case=True): + node.start(extra_args=['-txindex=1', '-blockfilterindex=1', '-coinstatsindex=1']) + self.log.debug("Terminating node after terminate line was found") + sigterm_node() check_clean_start() self.stop_node(0) @@ -148,11 +92,12 @@ class InitStressTest(BitcoinTestFramework): } for file_patt, err_fragment in files_to_disturb.items(): - target_file = list(node.chain_path.glob(file_patt))[0] + target_files = list(node.chain_path.glob(file_patt)) - self.log.info(f"Tweaking file to ensure failure {target_file}") - bak_path = str(target_file) + ".bak" - target_file.rename(bak_path) + for target_file in target_files: + self.log.info(f"Tweaking file to ensure failure {target_file}") + bak_path = str(target_file) + ".bak" + target_file.rename(bak_path) # TODO: at some point, we should test perturbing the files instead of removing # them, e.g. @@ -166,14 +111,16 @@ class InitStressTest(BitcoinTestFramework): # investigate doing this later. node.assert_start_raises_init_error( - # TODO: add -txindex=1 to fully test index initiatlization. - # extra_args=['-txindex=1'], + extra_args=['-txindex=1', '-blockfilterindex=1', '-coinstatsindex=1'], expected_msg=err_fragment, match=ErrorMatch.PARTIAL_REGEX, ) - self.log.info(f"Restoring file from {bak_path} and restarting") - Path(bak_path).rename(target_file) + for target_file in target_files: + bak_path = str(target_file) + ".bak" + self.log.debug(f"Restoring file from {bak_path} and restarting") + Path(bak_path).rename(target_file) + check_clean_start() self.stop_node(0) diff --git a/test/functional/feature_maxtipage.py b/test/functional/feature_maxtipage.py new file mode 100755 index 0000000000..87f9d6962d --- /dev/null +++ b/test/functional/feature_maxtipage.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 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 logic for setting nMaxTipAge on command line. + +Nodes don't consider themselves out of "initial block download" as long as +their best known block header time is more than nMaxTipAge in the past. +""" + +import time + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import assert_equal + + +DEFAULT_MAX_TIP_AGE = 24 * 60 * 60 + + +class MaxTipAgeTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 2 + + def test_maxtipage(self, maxtipage, set_parameter=True): + node_miner = self.nodes[0] + node_ibd = self.nodes[1] + + self.restart_node(1, [f'-maxtipage={maxtipage}'] if set_parameter else None) + self.connect_nodes(0, 1) + + # tips older than maximum age -> stay in IBD + cur_time = int(time.time()) + node_ibd.setmocktime(cur_time) + for delta in [5, 4, 3, 2, 1]: + node_miner.setmocktime(cur_time - maxtipage - delta) + self.generate(node_miner, 1) + assert_equal(node_ibd.getblockchaininfo()['initialblockdownload'], True) + + # tip within maximum age -> leave IBD + node_miner.setmocktime(cur_time - maxtipage) + self.generate(node_miner, 1) + assert_equal(node_ibd.getblockchaininfo()['initialblockdownload'], False) + + def run_test(self): + self.log.info("Test IBD with maximum tip age of 24 hours (default).") + self.test_maxtipage(DEFAULT_MAX_TIP_AGE, set_parameter=False) + + for hours in [20, 10, 5, 2, 1]: + maxtipage = hours * 60 * 60 + self.log.info(f"Test IBD with maximum tip age of {hours} hours (-maxtipage={maxtipage}).") + self.test_maxtipage(maxtipage) + + +if __name__ == '__main__': + MaxTipAgeTest().main() diff --git a/test/functional/feature_maxuploadtarget.py b/test/functional/feature_maxuploadtarget.py index b4e0df8a11..24f79dda67 100755 --- a/test/functional/feature_maxuploadtarget.py +++ b/test/functional/feature_maxuploadtarget.py @@ -36,7 +36,7 @@ class MaxUploadTest(BitcoinTestFramework): self.setup_clean_chain = True self.num_nodes = 1 self.extra_args = [[ - "-maxuploadtarget=800", + "-maxuploadtarget=800M", "-acceptnonstdtxn=1", ]] self.supports_cli = False diff --git a/test/functional/feature_proxy.py b/test/functional/feature_proxy.py index 7d9e5b70fc..fb0f6d7cb7 100755 --- a/test/functional/feature_proxy.py +++ b/test/functional/feature_proxy.py @@ -280,7 +280,7 @@ class ProxyTest(BitcoinTestFramework): n3 = networks_dict(self.nodes[3].getnetworkinfo()) assert_equal(NETWORKS, n3.keys()) for net in NETWORKS: - if net == NET_I2P: + if net == NET_I2P or net == NET_ONION: expected_proxy = '' else: expected_proxy = f'[{self.conf3.addr[0]}]:{self.conf3.addr[1]}' diff --git a/test/functional/feature_pruning.py b/test/functional/feature_pruning.py index 0edf1d66c8..ba3c5053cb 100755 --- a/test/functional/feature_pruning.py +++ b/test/functional/feature_pruning.py @@ -96,9 +96,6 @@ class PruneTest(BitcoinTestFramework): ] self.rpc_timeout = 120 - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def setup_network(self): self.setup_nodes() @@ -114,7 +111,8 @@ class PruneTest(BitcoinTestFramework): def setup_nodes(self): self.add_nodes(self.num_nodes, self.extra_args) self.start_nodes() - self.import_deterministic_coinbase_privkeys() + if self.is_wallet_compiled(): + self.import_deterministic_coinbase_privkeys() def create_big_chain(self): # Start by creating some coinbases we can spend later @@ -277,7 +275,7 @@ class PruneTest(BitcoinTestFramework): self.start_node(node_number) node = self.nodes[node_number] assert_equal(node.getblockcount(), 995) - assert_raises_rpc_error(-1, "not in prune mode", node.pruneblockchain, 500) + assert_raises_rpc_error(-1, "Cannot prune blocks because node is not in prune mode", node.pruneblockchain, 500) # now re-start in manual pruning mode self.restart_node(node_number, extra_args=["-prune=1"]) @@ -308,11 +306,18 @@ class PruneTest(BitcoinTestFramework): self.generate(node, 6, sync_fun=self.no_op) assert_equal(node.getblockchaininfo()["blocks"], 1001) + # prune parameter in the future (block or timestamp) should raise an exception + future_parameter = height(1001) + 5 + if use_timestamp: + assert_raises_rpc_error(-8, "Could not find block with at least the specified timestamp", node.pruneblockchain, future_parameter) + else: + assert_raises_rpc_error(-8, "Blockchain is shorter than the attempted prune height", node.pruneblockchain, future_parameter) + # Pruned block should still know the number of transactions assert_equal(node.getblockheader(node.getblockhash(1))["nTx"], block1_details["nTx"]) # negative heights should raise an exception - assert_raises_rpc_error(-8, "Negative", node.pruneblockchain, -10) + assert_raises_rpc_error(-8, "Negative block height", node.pruneblockchain, -10) # height=100 too low to prune first block file so this is a no-op prune(100) @@ -467,8 +472,9 @@ class PruneTest(BitcoinTestFramework): self.log.info("Test manual pruning with timestamps") self.manual_test(4, use_timestamp=True) - self.log.info("Test wallet re-scan") - self.wallet_test() + if self.is_wallet_compiled(): + self.log.info("Test wallet re-scan") + self.wallet_test() self.log.info("Test invalid pruning command line options") self.test_invalid_command_line_options() diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index e540cc1574..f0ed914461 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -14,6 +14,7 @@ from test_framework.messages import ( CTransaction, CTxIn, CTxOut, + SEQUENCE_FINAL, ) from test_framework.script import CScript, OP_DROP from test_framework.test_framework import BitcoinTestFramework @@ -114,7 +115,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): """Simple doublespend""" # we use MiniWallet to create a transaction template with inputs correctly set, # and modify the output (amount, scriptPubKey) according to our needs - tx_template = self.wallet.create_self_transfer(from_node=self.nodes[0])['tx'] + tx_template = self.wallet.create_self_transfer()['tx'] tx1a = deepcopy(tx_template) tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] @@ -402,7 +403,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # Create a non-opting in transaction tx1a = CTransaction() - tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0xffffffff)] + tx1a.vin = [CTxIn(tx0_outpoint, nSequence=SEQUENCE_FINAL)] tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)] tx1a_hex = tx1a.serialize().hex() tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0) @@ -445,7 +446,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): tx2a_txid = int(tx2a_txid, 16) tx3a = CTransaction() - tx3a.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0xffffffff), + tx3a.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=SEQUENCE_FINAL), CTxIn(COutPoint(tx2a_txid, 0), nSequence=0xfffffffd)] tx3a.vout = [CTxOut(int(0.9 * COIN), CScript([b'c'])), CTxOut(int(0.9 * COIN), CScript([b'd']))] tx3a_hex = tx3a.serialize().hex() @@ -538,7 +539,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): assert_equal(json0["vin"][0]["sequence"], 4294967293) assert_equal(json1["vin"][0]["sequence"], 4294967295) - if self.is_wallet_compiled(): + if self.is_specified_wallet_compiled(): self.init_wallet(node=0) rawtx2 = self.nodes[0].createrawtransaction([], outs) frawtx2a = self.nodes[0].fundrawtransaction(rawtx2, {"replaceable": True}) @@ -562,7 +563,6 @@ class ReplaceByFeeTest(BitcoinTestFramework): assert_equal(True, self.nodes[0].getmempoolentry(optin_parent_tx['txid'])['bip125-replaceable']) replacement_parent_tx = self.wallet.create_self_transfer( - from_node=self.nodes[0], utxo_to_spend=confirmed_utxo, sequence=BIP125_SEQUENCE_NUMBER, fee_rate=Decimal('0.02'), @@ -579,7 +579,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): optout_child_tx = self.wallet.send_self_transfer( from_node=self.nodes[0], utxo_to_spend=parent_utxo, - sequence=0xffffffff, + sequence=SEQUENCE_FINAL, fee_rate=Decimal('0.01'), ) @@ -587,9 +587,8 @@ class ReplaceByFeeTest(BitcoinTestFramework): assert_equal(True, self.nodes[0].getmempoolentry(optout_child_tx['txid'])['bip125-replaceable']) replacement_child_tx = self.wallet.create_self_transfer( - from_node=self.nodes[0], utxo_to_spend=parent_utxo, - sequence=0xffffffff, + sequence=SEQUENCE_FINAL, fee_rate=Decimal('0.02'), mempool_valid=False, ) @@ -608,7 +607,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): replacement_parent_tx = self.wallet.send_self_transfer( from_node=self.nodes[0], utxo_to_spend=confirmed_utxo, - sequence=0xffffffff, + sequence=SEQUENCE_FINAL, fee_rate=Decimal('0.03'), ) # Check that child is removed and update wallet utxo state diff --git a/test/functional/feature_startupnotify.py b/test/functional/feature_startupnotify.py new file mode 100755 index 0000000000..c6aa837768 --- /dev/null +++ b/test/functional/feature_startupnotify.py @@ -0,0 +1,41 @@ +#!/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 -startupnotify.""" + +import os + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import ( + assert_equal, +) + +NODE_DIR = "node0" +FILE_NAME = "test.txt" + + +class StartupNotifyTest(BitcoinTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.disable_syscall_sandbox = True + + def run_test(self): + tmpdir_file = os.path.join(self.options.tmpdir, NODE_DIR, FILE_NAME) + assert not os.path.exists(tmpdir_file) + + self.log.info("Test -startupnotify command is run when node starts") + self.restart_node(0, extra_args=[f"-startupnotify=echo '{FILE_NAME}' >> {NODE_DIR}/{FILE_NAME}"]) + self.wait_until(lambda: os.path.exists(tmpdir_file)) + + self.log.info("Test -startupnotify is executed once") + with open(tmpdir_file, "r", encoding="utf8") as f: + file_content = f.read() + assert_equal(file_content.count(FILE_NAME), 1) + + self.log.info("Test node is fully started") + assert_equal(self.nodes[0].getblockcount(), 200) + + +if __name__ == '__main__': + StartupNotifyTest().main() diff --git a/test/functional/feature_syscall_sandbox.py b/test/functional/feature_syscall_sandbox.py index caf7f1e7fc..e430542845 100755 --- a/test/functional/feature_syscall_sandbox.py +++ b/test/functional/feature_syscall_sandbox.py @@ -14,7 +14,7 @@ class SyscallSandboxTest(BitcoinTestFramework): def skip_test_if_missing_module(self): if not self.is_syscall_sandbox_compiled(): raise SkipTest("bitcoind has not been built with syscall sandbox enabled.") - if self.options.nosandbox: + if self.disable_syscall_sandbox: raise SkipTest("--nosandbox passed to test runner.") def run_test(self): diff --git a/test/functional/feature_taproot.py b/test/functional/feature_taproot.py index e9da6edaf6..3e3d4b3c77 100755 --- a/test/functional/feature_taproot.py +++ b/test/functional/feature_taproot.py @@ -19,6 +19,7 @@ from test_framework.messages import ( CTxIn, CTxInWitness, CTxOut, + SEQUENCE_FINAL, ) from test_framework.script import ( ANNEX_TAG, @@ -1516,7 +1517,7 @@ class TaprootTest(BitcoinTestFramework): assert self.nodes[1].getblockcount() == 0 coinbase = CTransaction() coinbase.nVersion = 1 - coinbase.vin = [CTxIn(COutPoint(0, 0xffffffff), CScript([OP_1, OP_1]), 0xffffffff)] + coinbase.vin = [CTxIn(COutPoint(0, 0xffffffff), CScript([OP_1, OP_1]), SEQUENCE_FINAL)] coinbase.vout = [CTxOut(5000000000, CScript([OP_1]))] coinbase.nLockTime = 0 coinbase.rehash() @@ -1604,7 +1605,7 @@ class TaprootTest(BitcoinTestFramework): val = 42000000 * (i + 7) tx = CTransaction() tx.nVersion = 1 - tx.vin = [CTxIn(COutPoint(lasttxid, i & 1), CScript([]), 0xffffffff)] + tx.vin = [CTxIn(COutPoint(lasttxid, i & 1), CScript([]), SEQUENCE_FINAL)] tx.vout = [CTxOut(val, spk), CTxOut(amount - val, CScript([OP_1]))] if i & 1: tx.vout = list(reversed(tx.vout)) @@ -1664,7 +1665,7 @@ class TaprootTest(BitcoinTestFramework): tx.vin = [] inputs = [] input_spks = [tap_spks[0], tap_spks[1], old_spks[0], tap_spks[2], tap_spks[5], old_spks[2], tap_spks[6], tap_spks[3], tap_spks[4]] - sequences = [0, 0xffffffff, 0xffffffff, 0xfffffffe, 0xfffffffe, 0, 0, 0xffffffff, 0xffffffff] + sequences = [0, SEQUENCE_FINAL, SEQUENCE_FINAL, 0xfffffffe, 0xfffffffe, 0, 0, SEQUENCE_FINAL, SEQUENCE_FINAL] hashtypes = [SIGHASH_SINGLE, SIGHASH_SINGLE|SIGHASH_ANYONECANPAY, SIGHASH_ALL, SIGHASH_ALL, SIGHASH_DEFAULT, SIGHASH_ALL, SIGHASH_NONE, SIGHASH_NONE|SIGHASH_ANYONECANPAY, SIGHASH_ALL|SIGHASH_ANYONECANPAY] for i, spk in enumerate(input_spks): tx.vin.append(CTxIn(spend_info[spk]['prevout'], CScript(), sequences[i])) diff --git a/test/functional/feature_txindex_compatibility.py b/test/functional/feature_txindex_compatibility.py index bbe1d1b537..20b023d82c 100755 --- a/test/functional/feature_txindex_compatibility.py +++ b/test/functional/feature_txindex_compatibility.py @@ -78,7 +78,7 @@ class MempoolCompatibilityTest(BitcoinTestFramework): self.stop_nodes() - self.log.info("Check migrated txindex can not be read by legacy node") + self.log.info("Check migrated txindex cannot be read by legacy node") err_msg = f": You need to rebuild the database using -reindex to change -txindex.{os.linesep}Please restart with -reindex or -reindex-chainstate to recover." shutil.rmtree(legacy_chain_dir) shutil.copytree(migrate_chain_dir, legacy_chain_dir) diff --git a/test/functional/feature_utxo_set_hash.py b/test/functional/feature_utxo_set_hash.py index 33b7615aea..75180e62a2 100755 --- a/test/functional/feature_utxo_set_hash.py +++ b/test/functional/feature_utxo_set_hash.py @@ -69,8 +69,8 @@ class UTXOSetHashTest(BitcoinTestFramework): assert_equal(finalized[::-1].hex(), node_muhash) self.log.info("Test deterministic UTXO set hash results") - assert_equal(node.gettxoutsetinfo()['hash_serialized_2'], "221f245cf4c9010eeb7f5183d342c002ae6c1c27e98aa357dccb788c21d98049") - assert_equal(node.gettxoutsetinfo("muhash")['muhash'], "7c0890c68501f7630d36aeb3999dc924e63af084ae1bbfba11dd462144637635") + assert_equal(node.gettxoutsetinfo()['hash_serialized_2'], "3a570529b4c32e77268de1f81b903c75cc2da53c48df0d125c1e697ba7c8c7b7") + assert_equal(node.gettxoutsetinfo("muhash")['muhash'], "a13e0e70eb8acc786549596e3bc154623f1a5a622ba2f70715f6773ec745f435") def run_test(self): self.test_muhash_implementation() diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py index 8a1edb7eb2..db5564ac50 100755 --- a/test/functional/interface_bitcoin_cli.py +++ b/test/functional/interface_bitcoin_cli.py @@ -69,7 +69,7 @@ class TestBitcoinCli(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 - if self.is_wallet_compiled(): + if self.is_specified_wallet_compiled(): self.requires_wallet = True def skip_test_if_missing_module(self): @@ -113,7 +113,7 @@ class TestBitcoinCli(BitcoinTestFramework): assert_raises_process_error(1, "Invalid value for -color option. Valid values: always, auto, never.", self.nodes[0].cli('-getinfo', '-color=foo').send_cli) self.log.info("Test -getinfo returns expected network and blockchain info") - if self.is_wallet_compiled(): + if self.is_specified_wallet_compiled(): self.nodes[0].encryptwallet(password) cli_get_info_string = self.nodes[0].cli('-getinfo').send_cli() cli_get_info = cli_get_info_string_to_dict(cli_get_info_string) @@ -138,9 +138,9 @@ class TestBitcoinCli(BitcoinTestFramework): cli_get_info = cli_get_info_string_to_dict(cli_get_info_string) assert_equal(cli_get_info["Proxies"], "127.0.0.1:9050 (ipv4, ipv6, onion, cjdns), 127.0.0.1:7656 (i2p)") - if self.is_wallet_compiled(): + if self.is_specified_wallet_compiled(): self.log.info("Test -getinfo and bitcoin-cli getwalletinfo return expected wallet info") - # Explicitely set the output type in order to have constintent tx vsize / fees + # Explicitly set the output type in order to have consistent tx vsize / fees # for both legacy and descriptor wallets (disables the change address type detection algorithm) self.restart_node(0, extra_args=["-addresstype=bech32", "-changetype=bech32"]) assert_equal(Decimal(cli_get_info['Balance']), BALANCE) diff --git a/test/functional/interface_rest.py b/test/functional/interface_rest.py index a2f84573da..a3d949c6a8 100755 --- a/test/functional/interface_rest.py +++ b/test/functional/interface_rest.py @@ -6,21 +6,32 @@ from decimal import Decimal from enum import Enum +import http.client from io import BytesIO import json from struct import pack, unpack - -import http.client import urllib.parse + +from test_framework.messages import ( + BLOCK_HEADER_SIZE, + COIN, +) from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_greater_than, assert_greater_than_or_equal, ) +from test_framework.wallet import ( + MiniWallet, + getnewdestination, +) + + +INVALID_PARAM = "abc" +UNKNOWN_PARAM = "0000000000000000000000000000000000000000000000000000000000000000" -from test_framework.messages import BLOCK_HEADER_SIZE class ReqType(Enum): JSON = 1 @@ -39,14 +50,13 @@ def filter_output_indices_by_value(vouts, value): class RESTTest (BitcoinTestFramework): def set_test_params(self): - self.setup_clean_chain = True self.num_nodes = 2 - self.extra_args = [["-rest"], []] + self.extra_args = [["-rest", "-blockfilterindex=1"], []] + # whitelist peers to speed up tx relay / mempool sync + for args in self.extra_args: + args.append("-whitelist=noban@127.0.0.1") 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: @@ -75,17 +85,11 @@ class RESTTest (BitcoinTestFramework): def run_test(self): self.url = urllib.parse.urlparse(self.nodes[0].url) - self.log.info("Mine blocks and send Bitcoin to node 1") + self.wallet = MiniWallet(self.nodes[0]) + self.wallet.rescan_utxos() - # Random address so node1's balance doesn't increase - not_related_address = "2MxqoHEdNQTyYeX1mHcbrrpzgojbosTpCvJ" - - self.generate(self.nodes[0], 1) - self.generatetoaddress(self.nodes[1], 100, not_related_address) - - assert_equal(self.nodes[0].getbalance(), 50) - - txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1) + self.log.info("Broadcast test transaction and sync nodes") + txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=getnewdestination()[1], amount=int(0.1 * COIN)) self.sync_all() self.log.info("Test the /tx URI") @@ -103,13 +107,17 @@ class RESTTest (BitcoinTestFramework): n, = filter_output_indices_by_value(json_obj['vout'], Decimal('0.1')) spending = (txid, n) + # Test /tx with an invalid and an unknown txid + resp = self.test_rest_request(uri=f"/tx/{INVALID_PARAM}", ret_type=RetType.OBJ, status=400) + assert_equal(resp.read().decode('utf-8').rstrip(), f"Invalid hash: {INVALID_PARAM}") + resp = self.test_rest_request(uri=f"/tx/{UNKNOWN_PARAM}", ret_type=RetType.OBJ, status=404) + assert_equal(resp.read().decode('utf-8').rstrip(), f"{UNKNOWN_PARAM} not found") + self.log.info("Query an unspent TXO using the /getutxos URI") - self.generatetoaddress(self.nodes[1], 1, not_related_address) + self.generate(self.wallet, 1) bb_hash = self.nodes[0].getbestblockhash() - assert_equal(self.nodes[1].getbalance(), Decimal("0.1")) - # Check chainTip response json_obj = self.test_rest_request(f"/getutxos/{spending[0]}-{spending[1]}") assert_equal(json_obj['chaintipHash'], bb_hash) @@ -151,7 +159,7 @@ class RESTTest (BitcoinTestFramework): response_hash = output.read(32)[::-1].hex() assert_equal(bb_hash, response_hash) # check if getutxo's chaintip during calculation was fine - assert_equal(chain_height, 102) # chain height must be 102 + assert_equal(chain_height, 201) # chain height must be 201 (pre-mined chain [200] + generated block [1]) self.log.info("Test the /getutxos URI with and without /checkmempool") # Create a transaction, check that it's found with /checkmempool, but @@ -159,7 +167,7 @@ class RESTTest (BitcoinTestFramework): # found with or without /checkmempool. # do a tx and don't sync - txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 0.1) + txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=getnewdestination()[1], amount=int(0.1 * COIN)) json_obj = self.test_rest_request(f"/tx/{txid}") # get the spent output to later check for utxo (should be spent by then) spent = (json_obj['vin'][0]['txid'], json_obj['vin'][0]['vout']) @@ -205,8 +213,8 @@ class RESTTest (BitcoinTestFramework): bb_hash = self.nodes[0].getbestblockhash() # Check result if block does not exists - assert_equal(self.test_rest_request('/headers/1/0000000000000000000000000000000000000000000000000000000000000000'), []) - self.test_rest_request('/block/0000000000000000000000000000000000000000000000000000000000000000', status=404, ret_type=RetType.OBJ) + assert_equal(self.test_rest_request(f"/headers/1/{UNKNOWN_PARAM}"), []) + self.test_rest_request(f"/block/{UNKNOWN_PARAM}", status=404, ret_type=RetType.OBJ) # Check result if block is not in the active chain self.nodes[0].invalidateblock(bb_hash) @@ -250,8 +258,8 @@ class RESTTest (BitcoinTestFramework): assert_equal(blockhash, bb_hash) # Check invalid blockhashbyheight requests - resp = self.test_rest_request("/blockhashbyheight/abc", ret_type=RetType.OBJ, status=400) - assert_equal(resp.read().decode('utf-8').rstrip(), "Invalid height: abc") + resp = self.test_rest_request(f"/blockhashbyheight/{INVALID_PARAM}", ret_type=RetType.OBJ, status=400) + assert_equal(resp.read().decode('utf-8').rstrip(), f"Invalid height: {INVALID_PARAM}") resp = self.test_rest_request("/blockhashbyheight/1000000", ret_type=RetType.OBJ, status=404) assert_equal(resp.read().decode('utf-8').rstrip(), "Block height out of range") resp = self.test_rest_request("/blockhashbyheight/-1", ret_type=RetType.OBJ, status=400) @@ -272,21 +280,32 @@ class RESTTest (BitcoinTestFramework): self.generate(self.nodes[1], 5) json_obj = self.test_rest_request(f"/headers/5/{bb_hash}") assert_equal(len(json_obj), 5) # now we should have 5 header objects + json_obj = self.test_rest_request(f"/blockfilterheaders/basic/5/{bb_hash}") + first_filter_header = json_obj[0] + assert_equal(len(json_obj), 5) # now we should have 5 filter header objects + json_obj = self.test_rest_request(f"/blockfilter/basic/{bb_hash}") + + # Compare with normal RPC blockfilter response + rpc_blockfilter = self.nodes[0].getblockfilter(bb_hash) + assert_equal(first_filter_header, rpc_blockfilter['header']) + assert_equal(json_obj['filter'], rpc_blockfilter['filter']) # Test number parsing for num in ['5a', '-5', '0', '2001', '99999999999999999999999999999999999']: assert_equal( - bytes(f'Header count out of range: {num}\r\n', 'ascii'), + bytes(f'Header count is invalid or out of acceptable range (1-2000): {num}\r\n', 'ascii'), self.test_rest_request(f"/headers/{num}/{bb_hash}", ret_type=RetType.BYTES, status=400), ) self.log.info("Test tx inclusion in the /mempool and /block URIs") - # Make 3 tx and mine them on node 1 + # Make 3 chained txs and mine them on node 1 txs = [] - txs.append(self.nodes[0].sendtoaddress(not_related_address, 11)) - txs.append(self.nodes[0].sendtoaddress(not_related_address, 11)) - txs.append(self.nodes[0].sendtoaddress(not_related_address, 11)) + input_txid = txid + for _ in range(3): + utxo_to_spend = self.wallet.get_utxo(txid=input_txid) + txs.append(self.wallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_to_spend)['txid']) + input_txid = txs[-1] self.sync_all() # Check that there are exactly 3 transactions in the TX memory pool before generating the block @@ -332,5 +351,6 @@ class RESTTest (BitcoinTestFramework): json_obj = self.test_rest_request("/chaininfo") assert_equal(json_obj['bestblockhash'], bb_hash) + if __name__ == '__main__': RESTTest().main() diff --git a/test/functional/mempool_accept.py b/test/functional/mempool_accept.py index 44db8bb00a..d3961e753d 100755 --- a/test/functional/mempool_accept.py +++ b/test/functional/mempool_accept.py @@ -4,6 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test mempool acceptance of raw transactions.""" +from copy import deepcopy from decimal import Decimal import math @@ -17,6 +18,7 @@ from test_framework.messages import ( CTxOut, MAX_BLOCK_WEIGHT, MAX_MONEY, + SEQUENCE_FINAL, tx_from_hex, ) from test_framework.script import ( @@ -33,6 +35,7 @@ from test_framework.util import ( assert_equal, assert_raises_rpc_error, ) +from test_framework.wallet import MiniWallet class MempoolAcceptanceTest(BitcoinTestFramework): @@ -43,9 +46,6 @@ class MempoolAcceptanceTest(BitcoinTestFramework): ]] * self.num_nodes self.supports_cli = False - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def check_mempool_result(self, result_expected, *args, **kwargs): """Wrapper to check result of testmempoolaccept on node_0's mempool""" result_test = self.nodes[0].testmempoolaccept(*args, **kwargs) @@ -56,12 +56,13 @@ class MempoolAcceptanceTest(BitcoinTestFramework): def run_test(self): node = self.nodes[0] + self.wallet = MiniWallet(node) + self.wallet.rescan_utxos() self.log.info('Start with empty mempool, and 200 blocks') self.mempool_size = 0 assert_equal(node.getblockcount(), 200) assert_equal(node.getmempoolinfo()['size'], self.mempool_size) - coins = node.listunspent() self.log.info('Should not accept garbage to testmempoolaccept') assert_raises_rpc_error(-3, 'Expected type array, got string', lambda: node.testmempoolaccept(rawtxs='ff00baar')) @@ -70,12 +71,12 @@ class MempoolAcceptanceTest(BitcoinTestFramework): assert_raises_rpc_error(-22, 'TX decode failed', lambda: node.testmempoolaccept(rawtxs=['ff00baar'])) self.log.info('A transaction already in the blockchain') - coin = coins.pop() # Pick a random coin(base) to spend - raw_tx_in_block = node.signrawtransactionwithwallet(node.createrawtransaction( - inputs=[{'txid': coin['txid'], 'vout': coin['vout']}], - outputs=[{node.getnewaddress(): 0.3}, {node.getnewaddress(): 49}], - ))['hex'] - txid_in_block = node.sendrawtransaction(hexstring=raw_tx_in_block, maxfeerate=0) + tx = self.wallet.create_self_transfer()['tx'] # Pick a random coin(base) to spend + tx.vout.append(deepcopy(tx.vout[0])) + tx.vout[0].nValue = int(0.3 * COIN) + tx.vout[1].nValue = int(49 * COIN) + raw_tx_in_block = tx.serialize().hex() + txid_in_block = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_in_block, maxfeerate=0) self.generate(node, 1) self.mempool_size = 0 self.check_mempool_result( @@ -85,11 +86,10 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.log.info('A transaction not in the mempool') fee = Decimal('0.000007') - raw_tx_0 = node.signrawtransactionwithwallet(node.createrawtransaction( - inputs=[{"txid": txid_in_block, "vout": 0, "sequence": BIP125_SEQUENCE_NUMBER}], # RBF is used later - outputs=[{node.getnewaddress(): Decimal('0.3') - fee}], - ))['hex'] - tx = tx_from_hex(raw_tx_0) + utxo_to_spend = self.wallet.get_utxo(txid=txid_in_block) # use 0.3 BTC UTXO + tx = self.wallet.create_self_transfer(utxo_to_spend=utxo_to_spend, sequence=BIP125_SEQUENCE_NUMBER)['tx'] + tx.vout[0].nValue = int((Decimal('0.3') - fee) * COIN) + raw_tx_0 = tx.serialize().hex() txid_0 = tx.rehash() self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee}}], @@ -97,15 +97,15 @@ class MempoolAcceptanceTest(BitcoinTestFramework): ) self.log.info('A final transaction not in the mempool') - coin = coins.pop() # Pick a random coin(base) to spend output_amount = Decimal('0.025') - raw_tx_final = node.signrawtransactionwithwallet(node.createrawtransaction( - inputs=[{'txid': coin['txid'], 'vout': coin['vout'], "sequence": 0xffffffff}], # SEQUENCE_FINAL - outputs=[{node.getnewaddress(): output_amount}], + tx = self.wallet.create_self_transfer( + sequence=SEQUENCE_FINAL, locktime=node.getblockcount() + 2000, # Can be anything - ))['hex'] + )['tx'] + tx.vout[0].nValue = int(output_amount * COIN) + raw_tx_final = tx.serialize().hex() tx = tx_from_hex(raw_tx_final) - fee_expected = coin['amount'] - output_amount + fee_expected = Decimal('50.0') - output_amount self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': fee_expected}}], rawtxs=[tx.serialize().hex()], @@ -126,8 +126,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): tx = tx_from_hex(raw_tx_0) tx.vout[0].nValue -= int(fee * COIN) # Double the fee tx.vin[0].nSequence = BIP125_SEQUENCE_NUMBER + 1 # Now, opt out of RBF - raw_tx_0 = node.signrawtransactionwithwallet(tx.serialize().hex())['hex'] - tx = tx_from_hex(raw_tx_0) + raw_tx_0 = tx.serialize().hex() txid_0 = tx.rehash() self.check_mempool_result( result_expected=[{'txid': txid_0, 'allowed': True, 'vsize': tx.get_vsize(), 'fees': {'base': (2 * fee)}}], @@ -140,7 +139,6 @@ class MempoolAcceptanceTest(BitcoinTestFramework): # take original raw_tx_0 tx = tx_from_hex(raw_tx_0) tx.vout[0].nValue -= int(4 * fee * COIN) # Set more fee - # skip re-signing the tx self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'txn-mempool-conflict'}], rawtxs=[tx.serialize().hex()], @@ -150,7 +148,6 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.log.info('A transaction with missing inputs, that never existed') tx = tx_from_hex(raw_tx_0) tx.vin[0].prevout = COutPoint(hash=int('ff' * 32, 16), n=14) - # skip re-signing the tx self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'missing-inputs'}], rawtxs=[tx.serialize().hex()], @@ -159,17 +156,17 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.log.info('A transaction with missing inputs, that existed once in the past') tx = tx_from_hex(raw_tx_0) tx.vin[0].prevout.n = 1 # Set vout to 1, to spend the other outpoint (49 coins) of the in-chain-tx we want to double spend - raw_tx_1 = node.signrawtransactionwithwallet(tx.serialize().hex())['hex'] + raw_tx_1 = tx.serialize().hex() txid_1 = node.sendrawtransaction(hexstring=raw_tx_1, maxfeerate=0) # Now spend both to "clearly hide" the outputs, ie. remove the coins from the utxo set by spending them - raw_tx_spend_both = node.signrawtransactionwithwallet(node.createrawtransaction( - inputs=[ - {'txid': txid_0, 'vout': 0}, - {'txid': txid_1, 'vout': 0}, - ], - outputs=[{node.getnewaddress(): 0.1}] - ))['hex'] - txid_spend_both = node.sendrawtransaction(hexstring=raw_tx_spend_both, maxfeerate=0) + tx = self.wallet.create_self_transfer()['tx'] + tx.vin.append(deepcopy(tx.vin[0])) + tx.wit.vtxinwit.append(deepcopy(tx.wit.vtxinwit[0])) + tx.vin[0].prevout = COutPoint(hash=int(txid_0, 16), n=0) + tx.vin[1].prevout = COutPoint(hash=int(txid_1, 16), n=0) + tx.vout[0].nValue = int(0.1 * COIN) + raw_tx_spend_both = tx.serialize().hex() + txid_spend_both = self.wallet.sendrawtransaction(from_node=node, tx_hex=raw_tx_spend_both, maxfeerate=0) self.generate(node, 1) self.mempool_size = 0 # Now see if we can add the coins back to the utxo set by sending the exact txs again @@ -182,12 +179,11 @@ class MempoolAcceptanceTest(BitcoinTestFramework): rawtxs=[raw_tx_1], ) - self.log.info('Create a signed "reference" tx for later use') - raw_tx_reference = node.signrawtransactionwithwallet(node.createrawtransaction( - inputs=[{'txid': txid_spend_both, 'vout': 0}], - outputs=[{node.getnewaddress(): 0.05}], - ))['hex'] - tx = tx_from_hex(raw_tx_reference) + self.log.info('Create a "reference" tx for later use') + utxo_to_spend = self.wallet.get_utxo(txid=txid_spend_both) + tx = self.wallet.create_self_transfer(utxo_to_spend=utxo_to_spend, sequence=SEQUENCE_FINAL)['tx'] + tx.vout[0].nValue = int(0.05 * COIN) + raw_tx_reference = tx.serialize().hex() # Reference tx should be valid on itself self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': True, 'vsize': tx.get_vsize(), 'fees': { 'base': Decimal('0.1') - Decimal('0.05')}}], @@ -198,8 +194,6 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.log.info('A transaction with no outputs') tx = tx_from_hex(raw_tx_reference) tx.vout = [] - # Skip re-signing the transaction for context independent checks from now on - # tx = tx_from_hex(node.signrawtransactionwithwallet(tx.serialize().hex())['hex']) self.check_mempool_result( result_expected=[{'txid': tx.rehash(), 'allowed': False, 'reject-reason': 'bad-txns-vout-empty'}], rawtxs=[tx.serialize().hex()], @@ -256,7 +250,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework): ) self.log.info('A coinbase transaction') - # Pick the input of the first tx we signed, so it has to be a coinbase tx + # Pick the input of the first tx we created, so it has to be a coinbase tx raw_tx_coinbase_spent = node.getrawtransaction(txid=node.decoderawtransaction(hexstring=raw_tx_in_block)['vin'][0]['txid']) tx = tx_from_hex(raw_tx_coinbase_spent) self.check_mempool_result( @@ -333,7 +327,6 @@ class MempoolAcceptanceTest(BitcoinTestFramework): self.log.info('A transaction that is locked by BIP68 sequence logic') tx = tx_from_hex(raw_tx_reference) tx.vin[0].nSequence = 2 # We could include it in the second block mined from now, but not the very next one - # 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()], diff --git a/test/functional/mempool_reorg.py b/test/functional/mempool_reorg.py index 7e940fa3ca..91f2d0051a 100755 --- a/test/functional/mempool_reorg.py +++ b/test/functional/mempool_reorg.py @@ -45,14 +45,13 @@ class MempoolCoinbaseTest(BitcoinTestFramework): utxo_2 = wallet.get_utxo(txid=coinbase_txids[2]) utxo_3 = wallet.get_utxo(txid=coinbase_txids[3]) self.log.info("Create three transactions spending from coinbase utxos: spend_1, spend_2, spend_3") - spend_1 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_1) - spend_2 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_2) - spend_3 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_3) + spend_1 = wallet.create_self_transfer(utxo_to_spend=utxo_1) + spend_2 = wallet.create_self_transfer(utxo_to_spend=utxo_2) + spend_3 = wallet.create_self_transfer(utxo_to_spend=utxo_3) self.log.info("Create another transaction which is time-locked to two blocks in the future") utxo = wallet.get_utxo(txid=coinbase_txids[0]) timelock_tx = wallet.create_self_transfer( - from_node=self.nodes[0], utxo_to_spend=utxo, mempool_valid=False, locktime=self.nodes[0].getblockcount() + 2 @@ -71,9 +70,9 @@ class MempoolCoinbaseTest(BitcoinTestFramework): self.log.info("Create spend_2_1 and spend_3_1") spend_2_utxo = wallet.get_utxo(txid=spend_2['txid']) - spend_2_1 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=spend_2_utxo) + spend_2_1 = wallet.create_self_transfer(utxo_to_spend=spend_2_utxo) spend_3_utxo = wallet.get_utxo(txid=spend_3['txid']) - spend_3_1 = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=spend_3_utxo) + spend_3_1 = wallet.create_self_transfer(utxo_to_spend=spend_3_utxo) self.log.info("Broadcast and mine spend_3_1") spend_3_1_id = self.nodes[0].sendrawtransaction(spend_3_1['hex']) diff --git a/test/functional/mempool_spend_coinbase.py b/test/functional/mempool_spend_coinbase.py index 5afa6be925..9c43ddaf6f 100755 --- a/test/functional/mempool_spend_coinbase.py +++ b/test/functional/mempool_spend_coinbase.py @@ -40,7 +40,7 @@ class MempoolSpendCoinbaseTest(BitcoinTestFramework): spend_mature_id = wallet.send_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_mature)["txid"] # other coinbase should be too immature to spend - immature_tx = wallet.create_self_transfer(from_node=self.nodes[0], utxo_to_spend=utxo_immature, mempool_valid=False) + immature_tx = wallet.create_self_transfer(utxo_to_spend=utxo_immature, mempool_valid=False) assert_raises_rpc_error(-26, "bad-txns-premature-spend-of-coinbase", lambda: self.nodes[0].sendrawtransaction(immature_tx['hex'])) diff --git a/test/functional/mempool_updatefromblock.py b/test/functional/mempool_updatefromblock.py index 16c15e3f74..51de582ce0 100755 --- a/test/functional/mempool_updatefromblock.py +++ b/test/functional/mempool_updatefromblock.py @@ -17,7 +17,7 @@ from test_framework.util import assert_equal class MempoolUpdateFromBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000']] + self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000', '-limitancestorcount=100']] def skip_test_if_missing_module(self): self.skip_if_no_wallet() diff --git a/test/functional/mining_basic.py b/test/functional/mining_basic.py index f8d002e664..9c64bb1945 100755 --- a/test/functional/mining_basic.py +++ b/test/functional/mining_basic.py @@ -29,6 +29,8 @@ from test_framework.util import ( assert_equal, assert_raises_rpc_error, ) +from test_framework.wallet import MiniWallet + VERSIONBITS_TOP_BITS = 0x20000000 VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 28 @@ -51,14 +53,11 @@ class MiningTest(BitcoinTestFramework): self.setup_clean_chain = True self.supports_cli = False - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def mine_chain(self): self.log.info('Create some old blocks') for t in range(TIME_GENESIS_BLOCK, TIME_GENESIS_BLOCK + 200 * 600, 600): self.nodes[0].setmocktime(t) - self.generate(self.nodes[0], 1, sync_fun=self.no_op) + self.generate(self.wallet, 1, sync_fun=self.no_op) mining_info = self.nodes[0].getmininginfo() assert_equal(mining_info['blocks'], 200) assert_equal(mining_info['currentblocktx'], 0) @@ -75,8 +74,9 @@ class MiningTest(BitcoinTestFramework): self.connect_nodes(0, 1) def run_test(self): - self.mine_chain() node = self.nodes[0] + self.wallet = MiniWallet(node) + self.mine_chain() def assert_submitblock(block, result_str_1, result_str_2=None): block.solve() @@ -95,7 +95,7 @@ class MiningTest(BitcoinTestFramework): assert_equal(mining_info['pooledtx'], 0) self.log.info("getblocktemplate: Test default witness commitment") - txid = int(node.sendtoaddress(node.getnewaddress(), 1), 16) + txid = int(self.wallet.send_self_transfer(from_node=node)['wtxid'], 16) tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) # Check that default_witness_commitment is present. diff --git a/test/functional/mocks/invalid_signer.py b/test/functional/mocks/invalid_signer.py new file mode 100755 index 0000000000..e30cc9e20b --- /dev/null +++ b/test/functional/mocks/invalid_signer.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python3 +# Copyright (c) 2018-2021 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import os +import sys +import argparse +import json + +def perform_pre_checks(): + mock_result_path = os.path.join(os.getcwd(), "mock_result") + if(os.path.isfile(mock_result_path)): + with open(mock_result_path, "r", encoding="utf8") as f: + mock_result = f.read() + if mock_result[0]: + sys.stdout.write(mock_result[2:]) + sys.exit(int(mock_result[0])) + +def enumerate(args): + sys.stdout.write(json.dumps([{"fingerprint": "b3c19bfc", "type": "trezor", "model": "trezor_t"}, {"fingerprint": "00000002"}])) + +def getdescriptors(args): + xpub_pkh = "xpub6CRhJvXV8x2AKWvqi1ZSMFU6cbxzQiYrv3dxSUXCawjMJ1JzpqVsveH4way1yCmJm29KzH1zrVZmVwes4Qo6oXVE1HFn4fdiKrYJngqFFc6" + xpub_sh = "xpub6CoNoq3Tg4tGSpom2BSwL42gy864KHo3TXkHxLxBbhvCkgmdVXADQmiHbLZhX3Me1cYhRx7s25Lpm4LnT5zu395ANHsXB2QvT9tqJDAibTN" + xpub_wpkh = "xpub6DUcLgY1DfgDy2RV6q4djwwsLitaoZDumbribqrR8mP78fEtgZa1XEsqT5MWQ7gwLwKsTQPT28XLoVE5A97rDNTwMXjmzPaNijoCApCbWvp" + + sys.stdout.write(json.dumps({ + "receive": [ + "pkh([b3c19bfc/44'/1'/" + args.account + "']" + xpub_pkh + "/0/*)#h26nxtl9", + "sh(wpkh([b3c19bfc/49'/1'/" + args.account + "']" + xpub_sh + "/0/*))#32ry02yp", + "wpkh([b3c19bfc/84'/1'/" + args.account + "']" + xpub_wpkh + "/0/*)#jftn8ppv" + ], + "internal": [ + "pkh([b3c19bfc/44'/1'/" + args.account + "']" + xpub_pkh + "/1/*)#x7ljm70a", + "sh(wpkh([b3c19bfc/49'/1'/" + args.account + "']" + xpub_sh + "/1/*))#ytdjh437", + "wpkh([b3c19bfc/84'/1'/" + args.account + "']" + xpub_wpkh + "/1/*)#rawj6535" + ] + })) + +parser = argparse.ArgumentParser(prog='./invalid_signer.py', description='External invalid signer mock') +parser.add_argument('--fingerprint') +parser.add_argument('--chain', default='main') +parser.add_argument('--stdin', action='store_true') + +subparsers = parser.add_subparsers(description='Commands', dest='command') +subparsers.required = True + +parser_enumerate = subparsers.add_parser('enumerate', help='list available signers') +parser_enumerate.set_defaults(func=enumerate) + +parser_getdescriptors = subparsers.add_parser('getdescriptors') +parser_getdescriptors.set_defaults(func=getdescriptors) +parser_getdescriptors.add_argument('--account', metavar='account') + +if not sys.stdin.isatty(): + buffer = sys.stdin.read() + if buffer and buffer.rstrip() != "": + sys.argv.extend(buffer.rstrip().split(" ")) + +args = parser.parse_args() + +perform_pre_checks() + +args.func(args) diff --git a/test/functional/p2p_add_connections.py b/test/functional/p2p_add_connections.py index b86502dc85..f4462673f2 100755 --- a/test/functional/p2p_add_connections.py +++ b/test/functional/p2p_add_connections.py @@ -1,13 +1,23 @@ #!/usr/bin/env python3 -# Copyright (c) 2020 The Bitcoin Core developers +# Copyright (c) 2020-2021 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 add_outbound_p2p_connection test framework functionality""" from test_framework.p2p import P2PInterface from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import check_node_connections - +from test_framework.util import ( + assert_equal, + check_node_connections, +) + +class P2PFeelerReceiver(P2PInterface): + def on_version(self, message): + # The bitcoind node closes feeler connections as soon as a version + # message is received from the test framework. Don't send any responses + # to the node's version message since the connection will already be + # closed. + pass class P2PAddConnections(BitcoinTestFramework): def set_test_params(self): @@ -85,6 +95,16 @@ class P2PAddConnections(BitcoinTestFramework): check_node_connections(node=self.nodes[1], num_in=5, num_out=10) + self.log.info("Add 1 feeler connection to node 0") + feeler_conn = self.nodes[0].add_outbound_p2p_connection(P2PFeelerReceiver(), p2p_idx=6, connection_type="feeler") + + # Feeler connection is closed + assert not feeler_conn.is_connected + + # Verify version message received + assert_equal(feeler_conn.message_count["version"], 1) + # Feeler connections do not request tx relay + assert_equal(feeler_conn.last_message["version"].relay, 0) if __name__ == '__main__': P2PAddConnections().main() diff --git a/test/functional/p2p_addr_relay.py b/test/functional/p2p_addr_relay.py index 5532056dbe..3218a9b14a 100755 --- a/test/functional/p2p_addr_relay.py +++ b/test/functional/p2p_addr_relay.py @@ -44,7 +44,7 @@ class AddrReceiver(P2PInterface): assert_equal(addr.nServices, 9) if not 8333 <= addr.port < 8343: raise AssertionError("Invalid addr.port of {} (8333-8342 expected)".format(addr.port)) - assert addr.ip.startswith('123.123.123.') + assert addr.ip.startswith('123.123.') def on_getaddr(self, message): # When the node sends us a getaddr, it increments the addr relay tokens for the connection by 1000 @@ -91,37 +91,31 @@ class AddrTest(BitcoinTestFramework): self.blocksonly_mode_tests() self.rate_limit_tests() - def setup_addr_msg(self, num): + def setup_addr_msg(self, num, sequential_ips=True): addrs = [] for i in range(num): addr = CAddress() - addr.time = self.mocktime + i + addr.time = self.mocktime + random.randrange(-100, 100) addr.nServices = P2P_SERVICES - addr.ip = f"123.123.123.{self.counter % 256}" + if sequential_ips: + assert self.counter < 256 ** 2 # Don't allow the returned ip addresses to wrap. + addr.ip = f"123.123.{self.counter // 256}.{self.counter % 256}" + self.counter += 1 + else: + addr.ip = f"{random.randrange(128,169)}.{random.randrange(1,255)}.{random.randrange(1,255)}.{random.randrange(1,255)}" addr.port = 8333 + i addrs.append(addr) - self.counter += 1 msg = msg_addr() msg.addrs = addrs return msg - def setup_rand_addr_msg(self, num): - addrs = [] - for i in range(num): - addr = CAddress() - addr.time = self.mocktime + i - addr.nServices = P2P_SERVICES - addr.ip = f"{random.randrange(128,169)}.{random.randrange(1,255)}.{random.randrange(1,255)}.{random.randrange(1,255)}" - addr.port = 8333 - addrs.append(addr) - msg = msg_addr() - msg.addrs = addrs - return msg - def send_addr_msg(self, source, msg, receivers): source.send_and_ping(msg) - # pop m_next_addr_send timer + # invoke m_next_addr_send timer: + # `addr` messages are sent on an exponential distribution with mean interval of 30s. + # Setting the mocktime 600s forward gives a probability of (1 - e^-(600/30)) that + # the event will occur (i.e. this fails once in ~500 million repeats). self.mocktime += 10 * 60 self.nodes[0].setmocktime(self.mocktime) for peer in receivers: @@ -282,7 +276,8 @@ class AddrTest(BitcoinTestFramework): block_relay_peer.send_and_ping(msg_getaddr()) inbound_peer.send_and_ping(msg_getaddr()) - self.mocktime += 5 * 60 + # invoke m_next_addr_send timer, see under send_addr_msg() function for rationale + self.mocktime += 10 * 60 self.nodes[0].setmocktime(self.mocktime) inbound_peer.wait_until(lambda: inbound_peer.addr_received() is True) @@ -313,7 +308,7 @@ class AddrTest(BitcoinTestFramework): def send_addrs_and_test_rate_limiting(self, peer, no_relay, *, new_addrs, total_addrs): """Send an addr message and check that the number of addresses processed and rate-limited is as expected""" - peer.send_and_ping(self.setup_rand_addr_msg(new_addrs)) + peer.send_and_ping(self.setup_addr_msg(new_addrs, sequential_ips=False)) peerinfo = self.nodes[0].getpeerinfo()[0] addrs_processed = peerinfo['addr_processed'] diff --git a/test/functional/p2p_blocksonly.py b/test/functional/p2p_blocksonly.py index 6e48341259..6f142f23f2 100755 --- a/test/functional/p2p_blocksonly.py +++ b/test/functional/p2p_blocksonly.py @@ -102,7 +102,7 @@ class P2PBlocksOnly(BitcoinTestFramework): def check_p2p_tx_violation(self): self.log.info('Check that txs from P2P are rejected and result in disconnect') - spendtx = self.miniwallet.create_self_transfer(from_node=self.nodes[0]) + spendtx = self.miniwallet.create_self_transfer() with self.nodes[0].assert_debug_log(['transaction sent in violation of protocol peer=0']): self.nodes[0].p2ps[0].send_message(msg_tx(spendtx['tx'])) diff --git a/test/functional/p2p_compactblocks.py b/test/functional/p2p_compactblocks.py index a314e8edfd..364e806e18 100755 --- a/test/functional/p2p_compactblocks.py +++ b/test/functional/p2p_compactblocks.py @@ -65,6 +65,8 @@ from test_framework.util import ( assert_equal, softfork_active, ) +from test_framework.wallet import MiniWallet + # TestP2PConn: A peer we use to send messages to bitcoind, and store responses. class TestP2PConn(P2PInterface): @@ -150,9 +152,6 @@ class CompactBlocksTest(BitcoinTestFramework): ]] self.utxos = [] - def skip_test_if_missing_module(self): - self.skip_if_no_wallet() - def build_block_on_tip(self, node, segwit=False): block = create_block(tmpl=node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)) if segwit: @@ -165,7 +164,7 @@ class CompactBlocksTest(BitcoinTestFramework): block = self.build_block_on_tip(self.nodes[0]) self.segwit_node.send_and_ping(msg_no_witness_block(block)) assert int(self.nodes[0].getbestblockhash(), 16) == block.sha256 - self.generatetoaddress(self.nodes[0], COINBASE_MATURITY, self.nodes[0].getnewaddress(address_type="bech32")) + self.generate(self.wallet, COINBASE_MATURITY) total_value = block.vtx[0].vout[0].nValue out_value = total_value // 10 @@ -296,12 +295,10 @@ class CompactBlocksTest(BitcoinTestFramework): # Generate a bunch of transactions. self.generate(node, COINBASE_MATURITY + 1) num_transactions = 25 - address = node.getnewaddress() segwit_tx_generated = False for _ in range(num_transactions): - txid = node.sendtoaddress(address, 0.1) - hex_tx = node.gettransaction(txid)["hex"] + hex_tx = self.wallet.send_self_transfer(from_node=self.nodes[0])['hex'] tx = tx_from_hex(hex_tx) if not tx.wit.is_null(): segwit_tx_generated = True @@ -843,8 +840,7 @@ class CompactBlocksTest(BitcoinTestFramework): assert_highbandwidth_states(self.nodes[0], hb_to=True, hb_from=False) def run_test(self): - # Get the nodes out of IBD - self.generate(self.nodes[0], 1) + self.wallet = MiniWallet(self.nodes[0]) # Setup the p2p connections self.segwit_node = self.nodes[0].add_p2p_connection(TestP2PConn(cmpct_version=2)) diff --git a/test/functional/p2p_disconnect_ban.py b/test/functional/p2p_disconnect_ban.py index fb8529ad2b..7284ecde83 100755 --- a/test/functional/p2p_disconnect_ban.py +++ b/test/functional/p2p_disconnect_ban.py @@ -57,11 +57,11 @@ class DisconnectBanTest(BitcoinTestFramework): assert_equal(len(self.nodes[1].listbanned()), 0) self.log.info("setban: test persistence across node restart") - self.nodes[1].setban("127.0.0.0/32", "add") - self.nodes[1].setban("127.0.0.0/24", "add") # Set the mocktime so we can control when bans expire old_time = int(time.time()) self.nodes[1].setmocktime(old_time) + self.nodes[1].setban("127.0.0.0/32", "add") + self.nodes[1].setban("127.0.0.0/24", "add") self.nodes[1].setban("192.168.0.1", "add", 1) # ban for 1 seconds self.nodes[1].setban("2001:4d48:ac57:400:cacf:e9ff:fe1d:9c63/19", "add", 1000) # ban for 1000 seconds listBeforeShutdown = self.nodes[1].listbanned() @@ -70,6 +70,15 @@ class DisconnectBanTest(BitcoinTestFramework): self.nodes[1].setmocktime(old_time + 3) assert_equal(len(self.nodes[1].listbanned()), 3) + self.log.info("Test ban_duration and time_remaining") + for ban in self.nodes[1].listbanned(): + if ban["address"] in ["127.0.0.0/32", "127.0.0.0/24"]: + assert_equal(ban["ban_duration"], 86400) + assert_equal(ban["time_remaining"], 86397) + elif ban["address"] == "2001:4d48:ac57:400:cacf:e9ff:fe1d:9c63/19": + assert_equal(ban["ban_duration"], 1000) + assert_equal(ban["time_remaining"], 997) + self.restart_node(1) listAfterShutdown = self.nodes[1].listbanned() diff --git a/test/functional/p2p_filter.py b/test/functional/p2p_filter.py index 2192363a89..3cf92b0316 100755 --- a/test/functional/p2p_filter.py +++ b/test/functional/p2p_filter.py @@ -31,7 +31,7 @@ from test_framework.script import MAX_SCRIPT_ELEMENT_SIZE from test_framework.test_framework import BitcoinTestFramework from test_framework.wallet import ( MiniWallet, - random_p2wpkh, + getnewdestination, ) @@ -169,14 +169,14 @@ class FilterTest(BitcoinTestFramework): self.log.info('Check that we only receive a merkleblock if the filter does not match a tx in a block') filter_peer.tx_received = False - block_hash = self.generatetoscriptpubkey(random_p2wpkh()) + block_hash = self.generatetoscriptpubkey(getnewdestination()[1]) filter_peer.wait_for_merkleblock(block_hash) assert not filter_peer.tx_received self.log.info('Check that we not receive a tx if the filter does not match a mempool tx') filter_peer.merkleblock_received = False filter_peer.tx_received = False - self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=random_p2wpkh(), amount=7 * COIN) + self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=getnewdestination()[1], amount=7 * COIN) filter_peer.sync_send_with_ping() assert not filter_peer.merkleblock_received assert not filter_peer.tx_received @@ -190,14 +190,14 @@ class FilterTest(BitcoinTestFramework): self.log.info('Check that after deleting filter all txs get relayed again') filter_peer.send_and_ping(msg_filterclear()) for _ in range(5): - txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=random_p2wpkh(), amount=7 * COIN) + txid, _ = self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=getnewdestination()[1], amount=7 * COIN) filter_peer.wait_for_tx(txid) self.log.info('Check that request for filtered blocks is ignored if no filter is set') filter_peer.merkleblock_received = False filter_peer.tx_received = False with self.nodes[0].assert_debug_log(expected_msgs=['received getdata']): - block_hash = self.generatetoscriptpubkey(random_p2wpkh()) + block_hash = self.generatetoscriptpubkey(getnewdestination()[1]) filter_peer.wait_for_inv([CInv(MSG_BLOCK, int(block_hash, 16))]) filter_peer.sync_with_ping() assert not filter_peer.merkleblock_received diff --git a/test/functional/p2p_timeouts.py b/test/functional/p2p_timeouts.py index a7e240dcfa..f0abbc7d8b 100755 --- a/test/functional/p2p_timeouts.py +++ b/test/functional/p2p_timeouts.py @@ -1,16 +1,15 @@ #!/usr/bin/env python3 -# Copyright (c) 2016-2020 The Bitcoin Core developers +# Copyright (c) 2016-2021 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 various net timeouts. -- Create three bitcoind nodes: +- Create three peers: no_verack_node - we never send a verack in response to their version no_version_node - we never send a version (only a ping) no_send_node - we never send any P2P message. -- Start all three nodes - Wait 1 second - Assert that we're connected - Send a ping to no_verack_node and no_version_node @@ -21,12 +20,12 @@ - Assert that we're no longer connected (timeout to receive version/verack is 3 seconds) """ -from time import sleep - from test_framework.messages import msg_ping from test_framework.p2p import P2PInterface from test_framework.test_framework import BitcoinTestFramework +import time + class TestP2PConn(P2PInterface): def on_version(self, message): @@ -41,17 +40,27 @@ class TimeoutsTest(BitcoinTestFramework): # set timeout to receive version/verack to 3 seconds self.extra_args = [["-peertimeout=3"]] + def mock_forward(self, delta): + self.mock_time += delta + self.nodes[0].setmocktime(self.mock_time) + def run_test(self): - # Setup the p2p connections - no_verack_node = self.nodes[0].add_p2p_connection(TestP2PConn(), wait_for_verack=False) - no_version_node = self.nodes[0].add_p2p_connection(TestP2PConn(), send_version=False, wait_for_verack=False) - no_send_node = self.nodes[0].add_p2p_connection(TestP2PConn(), send_version=False, wait_for_verack=False) + self.mock_time = int(time.time()) + self.mock_forward(0) + + # Setup the p2p connections, making sure the connections are established before the mocktime is bumped + with self.nodes[0].assert_debug_log(['Added connection peer=0']): + no_verack_node = self.nodes[0].add_p2p_connection(TestP2PConn(), wait_for_verack=False) + with self.nodes[0].assert_debug_log(['Added connection peer=1']): + no_version_node = self.nodes[0].add_p2p_connection(TestP2PConn(), send_version=False, wait_for_verack=False) + with self.nodes[0].assert_debug_log(['Added connection peer=2']): + no_send_node = self.nodes[0].add_p2p_connection(TestP2PConn(), send_version=False, wait_for_verack=False) # Wait until we got the verack in response to the version. Though, don't wait for the other node to receive the # verack, since we never sent one no_verack_node.wait_for_verack() - sleep(1) + self.mock_forward(1) assert no_verack_node.is_connected assert no_version_node.is_connected @@ -62,7 +71,7 @@ class TimeoutsTest(BitcoinTestFramework): with self.nodes[0].assert_debug_log(['non-version message before version handshake. Message "ping" from peer=1']): no_version_node.send_message(msg_ping()) - sleep(1) + self.mock_forward(1) assert "version" in no_verack_node.last_message @@ -80,14 +89,10 @@ class TimeoutsTest(BitcoinTestFramework): ] with self.nodes[0].assert_debug_log(expected_msgs=expected_timeout_logs): - sleep(3) - # By now, we waited a total of 5 seconds. Off-by-two for two - # reasons: - # * The internal precision is one second - # * Account for network delay - assert not no_verack_node.is_connected - assert not no_version_node.is_connected - assert not no_send_node.is_connected + self.mock_forward(2) + no_verack_node.wait_for_disconnect(timeout=1) + no_version_node.wait_for_disconnect(timeout=1) + no_send_node.wait_for_disconnect(timeout=1) if __name__ == '__main__': diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index 8f7d5114fa..b264f23fb5 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -6,6 +6,7 @@ Test the following RPCs: - getblockchaininfo + - getdeploymentinfo - getchaintxstats - gettxoutsetinfo - getblockheader @@ -68,7 +69,14 @@ class BlockchainTest(BitcoinTestFramework): self.wallet = MiniWallet(self.nodes[0]) self.mine_chain() self._test_max_future_block_time() - self.restart_node(0, extra_args=['-stopatheight=207', '-prune=1']) # Set extra args with pruning after rescan is complete + self.restart_node( + 0, + extra_args=[ + "-stopatheight=207", + "-checkblocks=-1", # Check all blocks + "-prune=1", # Set pruning after rescan is complete + ], + ) self._test_getblockchaininfo() self._test_getchaintxstats() @@ -79,6 +87,7 @@ class BlockchainTest(BitcoinTestFramework): self._test_stopatheight() self._test_waitforblockheight() self._test_getblock() + self._test_getdeploymentinfo() assert self.nodes[0].verifychain(4, 0) def mine_chain(self): @@ -115,7 +124,6 @@ class BlockchainTest(BitcoinTestFramework): 'mediantime', 'pruned', 'size_on_disk', - 'softforks', 'time', 'verificationprogress', 'warnings', @@ -159,11 +167,6 @@ class BlockchainTest(BitcoinTestFramework): self.start_node(0, extra_args=[ '-stopatheight=207', '-prune=550', - '-testactivationheight=bip34@2', - '-testactivationheight=dersig@3', - '-testactivationheight=cltv@4', - '-testactivationheight=csv@5', - '-testactivationheight=segwit@6', ]) res = self.nodes[0].getblockchaininfo() @@ -177,7 +180,13 @@ class BlockchainTest(BitcoinTestFramework): assert_equal(res['prune_target_size'], 576716800) assert_greater_than(res['size_on_disk'], 0) - assert_equal(res['softforks'], { + def check_signalling_deploymentinfo_result(self, gdi_result, height, blockhash, status_next): + assert height >= 144 and height <= 287 + + assert_equal(gdi_result, { + "hash": blockhash, + "height": height, + "deployments": { 'bip34': {'type': 'buried', 'active': True, 'height': 2}, 'bip66': {'type': 'buried', 'active': True, 'height': 3}, 'bip65': {'type': 'buried', 'active': True, 'height': 4}, @@ -186,36 +195,65 @@ class BlockchainTest(BitcoinTestFramework): 'testdummy': { 'type': 'bip9', 'bip9': { - 'status': 'started', 'bit': 28, 'start_time': 0, 'timeout': 0x7fffffffffffffff, # testdummy does not have a timeout so is set to the max int64 value + 'min_activation_height': 0, + 'status': 'started', + 'status-next': status_next, 'since': 144, 'statistics': { 'period': 144, 'threshold': 108, - 'elapsed': HEIGHT - 143, - 'count': HEIGHT - 143, + 'elapsed': height - 143, + 'count': height - 143, 'possible': True, }, - 'min_activation_height': 0, + 'signalling': '#'*(height-143), }, 'active': False }, 'taproot': { 'type': 'bip9', 'bip9': { - 'status': 'active', 'start_time': -1, 'timeout': 9223372036854775807, - 'since': 0, 'min_activation_height': 0, + 'status': 'active', + 'status-next': 'active', + 'since': 0, }, 'height': 0, 'active': True } + } }) + def _test_getdeploymentinfo(self): + # Note: continues past -stopatheight height, so must be invoked + # after _test_stopatheight + + self.log.info("Test getdeploymentinfo") + self.stop_node(0) + self.start_node(0, extra_args=[ + '-testactivationheight=bip34@2', + '-testactivationheight=dersig@3', + '-testactivationheight=cltv@4', + '-testactivationheight=csv@5', + '-testactivationheight=segwit@6', + ]) + + gbci207 = self.nodes[0].getblockchaininfo() + self.check_signalling_deploymentinfo_result(self.nodes[0].getdeploymentinfo(), gbci207["blocks"], gbci207["bestblockhash"], "started") + + # block just prior to lock in + self.generate(self.wallet, 287 - gbci207["blocks"]) + gbci287 = self.nodes[0].getblockchaininfo() + self.check_signalling_deploymentinfo_result(self.nodes[0].getdeploymentinfo(), gbci287["blocks"], gbci287["bestblockhash"], "locked_in") + + # calling with an explicit hash works + self.check_signalling_deploymentinfo_result(self.nodes[0].getdeploymentinfo(gbci207["bestblockhash"]), gbci207["blocks"], gbci207["bestblockhash"], "started") + def _test_getchaintxstats(self): self.log.info("Test getchaintxstats") @@ -329,7 +367,7 @@ class BlockchainTest(BitcoinTestFramework): assert 'muhash' not in r # Unknown hash_type raises an error - assert_raises_rpc_error(-8, "foohash is not a valid hash_type", node.gettxoutsetinfo, "foohash") + assert_raises_rpc_error(-8, "'foo hash' is not a valid hash_type", node.gettxoutsetinfo, "foo hash") def _test_getblockheader(self): self.log.info("Test getblockheader") diff --git a/test/functional/rpc_createmultisig.py b/test/functional/rpc_createmultisig.py index f56e71ae7a..1a3d14100f 100755 --- a/test/functional/rpc_createmultisig.py +++ b/test/functional/rpc_createmultisig.py @@ -75,13 +75,19 @@ class RpcCreateMultiSigTest(BitcoinTestFramework): for keys in itertools.permutations([pk0, pk1, pk2]): # Results should be the same as this legacy one legacy_addr = node0.createmultisig(2, keys, 'legacy')['address'] - assert_equal(legacy_addr, wmulti0.addmultisigaddress(2, keys, '', 'legacy')['address']) + result = wmulti0.addmultisigaddress(2, keys, '', 'legacy') + assert_equal(legacy_addr, result['address']) + assert 'warnings' not in result # Generate addresses with the segwit types. These should all make legacy addresses - assert_equal(legacy_addr, wmulti0.createmultisig(2, keys, 'bech32')['address']) - assert_equal(legacy_addr, wmulti0.createmultisig(2, keys, 'p2sh-segwit')['address']) - assert_equal(legacy_addr, wmulti0.addmultisigaddress(2, keys, '', 'bech32')['address']) - assert_equal(legacy_addr, wmulti0.addmultisigaddress(2, keys, '', 'p2sh-segwit')['address']) + for addr_type in ['bech32', 'p2sh-segwit']: + result = wmulti0.createmultisig(2, keys, addr_type) + assert_equal(legacy_addr, result['address']) + assert_equal(result['warnings'], ["Unable to make chosen address type, please ensure no uncompressed public keys are present."]) + + result = wmulti0.addmultisigaddress(2, keys, '', addr_type) + assert_equal(legacy_addr, result['address']) + assert_equal(result['warnings'], ["Unable to make chosen address type, please ensure no uncompressed public keys are present."]) 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: diff --git a/test/functional/rpc_decodescript.py b/test/functional/rpc_decodescript.py index 56f596d419..343cb73989 100755 --- a/test/functional/rpc_decodescript.py +++ b/test/functional/rpc_decodescript.py @@ -52,7 +52,7 @@ class DecodeScriptTest(BitcoinTestFramework): rpc_result = self.nodes[0].decodescript('5100') assert_equal('1 0', rpc_result['asm']) - # null data scriptSig - no such thing because null data scripts can not be spent. + # null data scriptSig - no such thing because null data scripts cannot be spent. # thus, no test case for that standard transaction type is here. def decodescript_script_pub_key(self): diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index 17d11a3ceb..759e43194b 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -4,8 +4,10 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the fundrawtransaction RPC.""" + from decimal import Decimal from itertools import product +from math import ceil from test_framework.descriptors import descsum_create from test_framework.key import ECKey @@ -571,12 +573,12 @@ class RawTransactionsTest(BitcoinTestFramework): if self.options.descriptors: self.nodes[1].walletpassphrase('test', 10) self.nodes[1].importdescriptors([{ - 'desc': descsum_create('tr(tprv8ZgxMBicQKsPdYeeZbPSKd2KYLmeVKtcFA7kqCxDvDR13MQ6us8HopUR2wLcS2ZKPhLyKsqpDL2FtL73LMHcgoCL7DXsciA8eX8nbjCR2eG/0h/*h)'), + 'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPdYeeZbPSKd2KYLmeVKtcFA7kqCxDvDR13MQ6us8HopUR2wLcS2ZKPhLyKsqpDL2FtL73LMHcgoCL7DXsciA8eX8nbjCR2eG/0h/*h)'), 'timestamp': 'now', 'active': True }, { - 'desc': descsum_create('tr(tprv8ZgxMBicQKsPdYeeZbPSKd2KYLmeVKtcFA7kqCxDvDR13MQ6us8HopUR2wLcS2ZKPhLyKsqpDL2FtL73LMHcgoCL7DXsciA8eX8nbjCR2eG/1h/*h)'), + 'desc': descsum_create('wpkh(tprv8ZgxMBicQKsPdYeeZbPSKd2KYLmeVKtcFA7kqCxDvDR13MQ6us8HopUR2wLcS2ZKPhLyKsqpDL2FtL73LMHcgoCL7DXsciA8eX8nbjCR2eG/1h/*h)'), 'timestamp': 'now', 'active': True, 'internal': True @@ -778,18 +780,11 @@ class RawTransactionsTest(BitcoinTestFramework): for param, zero_value in product(["fee_rate", "feeRate"], [0, 0.000, 0.00000000, "0", "0.000", "0.00000000"]): assert_equal(self.nodes[3].fundrawtransaction(rawtx, {param: zero_value})["fee"], 0) - if self.options.descriptors: - # With no arguments passed, expect fee of 153 satoshis as descriptor wallets now have a taproot output. - assert_approx(node.fundrawtransaction(rawtx)["fee"], vexp=0.00000153, vspan=0.00000001) - # Expect fee to be 10,000x higher when an explicit fee rate 10,000x greater is specified. - result = node.fundrawtransaction(rawtx, {"fee_rate": 10000}) - assert_approx(result["fee"], vexp=0.0153, vspan=0.0001) - else: - # With no arguments passed, expect fee of 141 satoshis as legacy wallets only support up to segwit v0. - assert_approx(node.fundrawtransaction(rawtx)["fee"], vexp=0.00000141, vspan=0.00000001) - # Expect fee to be 10,000x higher when an explicit fee rate 10,000x greater is specified. - result = node.fundrawtransaction(rawtx, {"fee_rate": 10000}) - assert_approx(result["fee"], vexp=0.0141, vspan=0.0001) + # With no arguments passed, expect fee of 141 satoshis. + assert_approx(node.fundrawtransaction(rawtx)["fee"], vexp=0.00000141, vspan=0.00000001) + # Expect fee to be 10,000x higher when an explicit fee rate 10,000x greater is specified. + result = node.fundrawtransaction(rawtx, {"fee_rate": 10000}) + assert_approx(result["fee"], vexp=0.0141, vspan=0.0001) self.log.info("Test fundrawtxn with invalid estimate_mode settings") for k, v in {"number": 42, "object": {"foo": "bar"}}.items(): @@ -1010,7 +1005,7 @@ class RawTransactionsTest(BitcoinTestFramework): ext_utxo = self.nodes[0].listunspent(addresses=[addr])[0] # An external input without solving data should result in an error - raw_tx = wallet.createrawtransaction([ext_utxo], {self.nodes[0].getnewaddress(): 15}) + raw_tx = wallet.createrawtransaction([ext_utxo], {self.nodes[0].getnewaddress(): ext_utxo["amount"] / 2}) assert_raises_rpc_error(-4, "Insufficient funds", wallet.fundrawtransaction, raw_tx) # Error conditions @@ -1018,6 +1013,12 @@ class RawTransactionsTest(BitcoinTestFramework): assert_raises_rpc_error(-5, "'01234567890a0b0c0d0e0f' is not a valid public key", wallet.fundrawtransaction, raw_tx, {"solving_data": {"pubkeys":["01234567890a0b0c0d0e0f"]}}) assert_raises_rpc_error(-5, "'not a script' is not hex", wallet.fundrawtransaction, raw_tx, {"solving_data": {"scripts":["not a script"]}}) assert_raises_rpc_error(-8, "Unable to parse descriptor 'not a descriptor'", wallet.fundrawtransaction, raw_tx, {"solving_data": {"descriptors":["not a descriptor"]}}) + assert_raises_rpc_error(-8, "Invalid parameter, missing vout key", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"]}]}) + assert_raises_rpc_error(-8, "Invalid parameter, vout cannot be negative", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": -1}]}) + assert_raises_rpc_error(-8, "Invalid parameter, missing weight key", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"]}]}) + assert_raises_rpc_error(-8, "Invalid parameter, weight cannot be less than 165", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 164}]}) + assert_raises_rpc_error(-8, "Invalid parameter, weight cannot be less than 165", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": -1}]}) + assert_raises_rpc_error(-8, "Invalid parameter, weight cannot be greater than", wallet.fundrawtransaction, raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 400001}]}) # But funding should work when the solving data is provided funded_tx = wallet.fundrawtransaction(raw_tx, {"solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]}}) @@ -1027,10 +1028,45 @@ class RawTransactionsTest(BitcoinTestFramework): assert signed_tx['complete'] funded_tx = wallet.fundrawtransaction(raw_tx, {"solving_data": {"descriptors": [desc]}}) - signed_tx = wallet.signrawtransactionwithwallet(funded_tx['hex']) - assert not signed_tx['complete'] - signed_tx = self.nodes[0].signrawtransactionwithwallet(signed_tx['hex']) - assert signed_tx['complete'] + signed_tx1 = wallet.signrawtransactionwithwallet(funded_tx['hex']) + assert not signed_tx1['complete'] + signed_tx2 = self.nodes[0].signrawtransactionwithwallet(signed_tx1['hex']) + assert signed_tx2['complete'] + + unsigned_weight = self.nodes[0].decoderawtransaction(signed_tx1["hex"])["weight"] + signed_weight = self.nodes[0].decoderawtransaction(signed_tx2["hex"])["weight"] + # Input's weight is difference between weight of signed and unsigned, + # and the weight of stuff that didn't change (prevout, sequence, 1 byte of scriptSig) + input_weight = signed_weight - unsigned_weight + (41 * 4) + low_input_weight = input_weight // 2 + high_input_weight = input_weight * 2 + + # Funding should also work if the input weight is provided + funded_tx = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": input_weight}]}) + signed_tx = wallet.signrawtransactionwithwallet(funded_tx["hex"]) + signed_tx = self.nodes[0].signrawtransactionwithwallet(signed_tx["hex"]) + assert_equal(self.nodes[0].testmempoolaccept([signed_tx["hex"]])[0]["allowed"], True) + assert_equal(signed_tx["complete"], True) + # Reducing the weight should have a lower fee + funded_tx2 = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": low_input_weight}]}) + assert_greater_than(funded_tx["fee"], funded_tx2["fee"]) + # Increasing the weight should have a higher fee + funded_tx2 = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}]}) + assert_greater_than(funded_tx2["fee"], funded_tx["fee"]) + # The provided weight should override the calculated weight when solving data is provided + funded_tx3 = wallet.fundrawtransaction(raw_tx, {"solving_data": {"descriptors": [desc]}, "input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}]}) + assert_equal(funded_tx2["fee"], funded_tx3["fee"]) + # The feerate should be met + funded_tx4 = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}], "fee_rate": 10}) + input_add_weight = high_input_weight - (41 * 4) + tx4_weight = wallet.decoderawtransaction(funded_tx4["hex"])["weight"] + input_add_weight + tx4_vsize = int(ceil(tx4_weight / 4)) + assert_fee_amount(funded_tx4["fee"], tx4_vsize, Decimal(0.0001)) + + # Funding with weight at csuint boundaries should not cause problems + funded_tx = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 255}]}) + funded_tx = wallet.fundrawtransaction(raw_tx, {"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 65539}]}) + self.nodes[2].unloadwallet("extfund") def test_include_unsafe(self): @@ -1080,7 +1116,7 @@ class RawTransactionsTest(BitcoinTestFramework): # Make sure the default wallet will not be loaded when restarted with a high minrelaytxfee self.nodes[0].unloadwallet(self.default_wallet_name, False) feerate = Decimal("0.1") - self.restart_node(0, [f"-minrelaytxfee={feerate}", "-discardfee=0", "-changetype=bech32", "-addresstype=bech32"]) # Set high minrelayfee, set discardfee to 0 for easier calculation + self.restart_node(0, [f"-minrelaytxfee={feerate}", "-discardfee=0"]) # Set high minrelayfee, set discardfee to 0 for easier calculation self.nodes[0].loadwallet(self.default_wallet_name, True) funds = self.nodes[0].get_wallet_rpc(self.default_wallet_name) diff --git a/test/functional/rpc_generate.py b/test/functional/rpc_generate.py index e55f2e6d12..47d7814da3 100755 --- a/test/functional/rpc_generate.py +++ b/test/functional/rpc_generate.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2020 The Bitcoin Core developers +# Copyright (c) 2020-2021 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 generate RPC.""" @@ -17,9 +17,9 @@ class RPCGenerateTest(BitcoinTestFramework): def run_test(self): message = ( - "generate\n" + "generate\n\n" "has been replaced by the -generate " - "cli option. Refer to -help for more information." + "cli option. Refer to -help for more information.\n" ) self.log.info("Test rpc generate raises with message to use cli option") diff --git a/test/functional/rpc_generateblock.py b/test/functional/rpc_generateblock.py index 7aede0e947..7eeb745817 100755 --- a/test/functional/rpc_generateblock.py +++ b/test/functional/rpc_generateblock.py @@ -63,7 +63,7 @@ class GenerateBlockTest(BitcoinTestFramework): assert_equal(block['tx'][1], txid) self.log.info('Generate block with raw tx') - rawtx = miniwallet.create_self_transfer(from_node=node)['hex'] + rawtx = miniwallet.create_self_transfer()['hex'] hash = self.generateblock(node, address, [rawtx])['hash'] block = node.getblock(hash, 1) @@ -74,7 +74,7 @@ class GenerateBlockTest(BitcoinTestFramework): self.log.info('Fail to generate block with out of order txs') txid1 = miniwallet.send_self_transfer(from_node=node)['txid'] utxo1 = miniwallet.get_utxo(txid=txid1) - rawtx2 = miniwallet.create_self_transfer(from_node=node, utxo_to_spend=utxo1)['hex'] + rawtx2 = miniwallet.create_self_transfer(utxo_to_spend=utxo1)['hex'] assert_raises_rpc_error(-25, 'TestBlockValidity failed: bad-txns-inputs-missingorspent', self.generateblock, node, address, [rawtx2, txid1]) self.log.info('Fail to generate block with txid not in mempool') diff --git a/test/functional/rpc_getblockfrompeer.py b/test/functional/rpc_getblockfrompeer.py index e841cba70e..b65322d920 100755 --- a/test/functional/rpc_getblockfrompeer.py +++ b/test/functional/rpc_getblockfrompeer.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2020 The Bitcoin Core developers +# Copyright (c) 2020-2021 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 getblockfrompeer RPC.""" @@ -40,12 +40,8 @@ class GetBlockFromPeerTest(BitcoinTestFramework): self.sync_blocks() self.log.info("Node 0 should only have the header for node 1's block 3") - for x in self.nodes[0].getchaintips(): - if x['hash'] == short_tip: - assert_equal(x['status'], "headers-only") - break - else: - raise AssertionError("short tip not synced") + x = next(filter(lambda x: x['hash'] == short_tip, self.nodes[0].getchaintips())) + assert_equal(x['status'], "headers-only") assert_raises_rpc_error(-1, "Block not found on disk", self.nodes[0].getblock, short_tip) self.log.info("Fetch block from node 1") @@ -60,17 +56,15 @@ class GetBlockFromPeerTest(BitcoinTestFramework): assert_raises_rpc_error(-1, "Block header missing", self.nodes[0].getblockfrompeer, "00" * 32, 0) self.log.info("Non-existent peer generates error") - assert_raises_rpc_error(-1, f"Peer nodeid {peer_0_peer_1_id + 1} does not exist", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id + 1) + assert_raises_rpc_error(-1, "Peer does not exist", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id + 1) self.log.info("Successful fetch") result = self.nodes[0].getblockfrompeer(short_tip, peer_0_peer_1_id) self.wait_until(lambda: self.check_for_block(short_tip), timeout=1) - assert(not "warnings" in result) + assert_equal(result, {}) self.log.info("Don't fetch blocks we already have") - result = self.nodes[0].getblockfrompeer(short_tip, peer_0_peer_1_id) - assert("warnings" in result) - assert_equal(result["warnings"], "Block already downloaded") + assert_raises_rpc_error(-1, "Block already downloaded", self.nodes[0].getblockfrompeer, short_tip, peer_0_peer_1_id) if __name__ == '__main__': GetBlockFromPeerTest().main() diff --git a/test/functional/rpc_getblockstats.py b/test/functional/rpc_getblockstats.py index 8c08d2ced5..1ea1ee5659 100755 --- a/test/functional/rpc_getblockstats.py +++ b/test/functional/rpc_getblockstats.py @@ -142,17 +142,17 @@ class GetblockstatsTest(BitcoinTestFramework): inv_sel_stat = 'asdfghjkl' inv_stats = [ [inv_sel_stat], - ['minfee' , 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 %s' % inv_sel_stat, + assert_raises_rpc_error(-8, f"Invalid selected statistic '{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 - assert_raises_rpc_error(-8, 'Invalid selected statistic aaa%s' % inv_sel_stat, - self.nodes[0].getblockstats, hash_or_height=1, stats=['minfee' , 'aaa%s' % inv_sel_stat]) + assert_raises_rpc_error(-8, f"Invalid selected statistic 'aaa{inv_sel_stat}'", + self.nodes[0].getblockstats, hash_or_height=1, stats=['minfee', f'aaa{inv_sel_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') diff --git a/test/functional/rpc_getdescriptorinfo.py b/test/functional/rpc_getdescriptorinfo.py index 563f54c5e7..5e6fd66aab 100755 --- a/test/functional/rpc_getdescriptorinfo.py +++ b/test/functional/rpc_getdescriptorinfo.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2019-2020 The Bitcoin Core developers +# Copyright (c) 2019-2022 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 getdescriptorinfo RPC. @@ -30,7 +30,7 @@ class DescriptorTest(BitcoinTestFramework): def run_test(self): assert_raises_rpc_error(-1, 'getdescriptorinfo', self.nodes[0].getdescriptorinfo) assert_raises_rpc_error(-3, 'Expected type string', self.nodes[0].getdescriptorinfo, 1) - assert_raises_rpc_error(-5, 'is not a valid descriptor function', self.nodes[0].getdescriptorinfo, '') + assert_raises_rpc_error(-5, "'' is not a valid descriptor function", self.nodes[0].getdescriptorinfo, "") # P2PK output with the specified public key. self.test_desc('pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)', isrange=False, issolvable=True, hasprivatekeys=False) diff --git a/test/functional/rpc_help.py b/test/functional/rpc_help.py index ccb380e25b..3b6413d4a6 100755 --- a/test/functional/rpc_help.py +++ b/test/functional/rpc_help.py @@ -100,7 +100,7 @@ class HelpRpcTest(BitcoinTestFramework): # command titles titles = [line[3:-3] for line in node.help().splitlines() if line.startswith('==')] - components = ['Blockchain', 'Control', 'Generating', 'Mining', 'Network', 'Rawtransactions', 'Util'] + components = ['Blockchain', 'Control', 'Mining', 'Network', 'Rawtransactions', 'Util'] if self.is_wallet_compiled(): components.append('Wallet') diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py index a8034849cc..b037807b53 100755 --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -457,7 +457,7 @@ class PSBTTest(BitcoinTestFramework): 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'] + signed_tx = wrpc.walletprocesspsbt(signer['psbt'], True, "ALL")['psbt'] assert_equal(signed_tx, signer['result']) # Combiner test @@ -606,11 +606,15 @@ class PSBTTest(BitcoinTestFramework): assert_raises_rpc_error(-25, 'Inputs missing or spent', self.nodes[0].walletprocesspsbt, 'cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA==') - # Test that we can fund psbts with external inputs specified + self.log.info("Test that we can fund psbts with external inputs specified") + eckey = ECKey() eckey.generate() privkey = bytes_to_wif(eckey.get_bytes()) + self.nodes[1].createwallet("extfund") + wallet = self.nodes[1].get_wallet_rpc("extfund") + # Make a weird but signable script. sh(pkh()) descriptor accomplishes this desc = descsum_create("sh(pkh({}))".format(privkey)) if self.options.descriptors: @@ -622,26 +626,97 @@ class PSBTTest(BitcoinTestFramework): addr_info = self.nodes[0].getaddressinfo(addr) self.nodes[0].sendtoaddress(addr, 10) + self.nodes[0].sendtoaddress(wallet.getnewaddress(), 10) self.generate(self.nodes[0], 6) ext_utxo = self.nodes[0].listunspent(addresses=[addr])[0] # An external input without solving data should result in an error - assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[1].walletcreatefundedpsbt, [ext_utxo], {self.nodes[0].getnewaddress(): 10 + ext_utxo['amount']}, 0, {'add_inputs': True}) + assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, [ext_utxo], {self.nodes[0].getnewaddress(): 15}) # But funding should work when the solving data is provided - psbt = self.nodes[1].walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {'add_inputs': True, "solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]}}) - signed = self.nodes[1].walletprocesspsbt(psbt['psbt']) + psbt = wallet.walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {"add_inputs": True, "solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"]]}}) + signed = wallet.walletprocesspsbt(psbt['psbt']) assert not signed['complete'] signed = self.nodes[0].walletprocesspsbt(signed['psbt']) assert signed['complete'] self.nodes[0].finalizepsbt(signed['psbt']) - psbt = self.nodes[1].walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {'add_inputs': True, "solving_data":{"descriptors": [desc]}}) - signed = self.nodes[1].walletprocesspsbt(psbt['psbt']) + psbt = wallet.walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {"add_inputs": True, "solving_data":{"descriptors": [desc]}}) + signed = wallet.walletprocesspsbt(psbt['psbt']) assert not signed['complete'] signed = self.nodes[0].walletprocesspsbt(signed['psbt']) assert signed['complete'] - self.nodes[0].finalizepsbt(signed['psbt']) + final = self.nodes[0].finalizepsbt(signed['psbt'], False) + + dec = self.nodes[0].decodepsbt(signed["psbt"]) + for i, txin in enumerate(dec["tx"]["vin"]): + if txin["txid"] == ext_utxo["txid"] and txin["vout"] == ext_utxo["vout"]: + input_idx = i + break + psbt_in = dec["inputs"][input_idx] + # Calculate the input weight + # (prevout + sequence + length of scriptSig + 2 bytes buffer) * 4 + len of scriptwitness + len_scriptsig = len(psbt_in["final_scriptSig"]["hex"]) // 2 if "final_scriptSig" in psbt_in else 0 + len_scriptwitness = len(psbt_in["final_scriptwitness"]["hex"]) // 2 if "final_scriptwitness" in psbt_in else 0 + input_weight = ((41 + len_scriptsig + 2) * 4) + len_scriptwitness + low_input_weight = input_weight // 2 + high_input_weight = input_weight * 2 + + # Input weight error conditions + assert_raises_rpc_error( + -8, + "Input weights should be specified in inputs rather than in options.", + wallet.walletcreatefundedpsbt, + inputs=[ext_utxo], + outputs={self.nodes[0].getnewaddress(): 15}, + options={"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 1000}]} + ) + + # Funding should also work if the input weight is provided + psbt = wallet.walletcreatefundedpsbt( + inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": input_weight}], + outputs={self.nodes[0].getnewaddress(): 15}, + options={"add_inputs": True} + ) + signed = wallet.walletprocesspsbt(psbt["psbt"]) + signed = self.nodes[0].walletprocesspsbt(signed["psbt"]) + final = self.nodes[0].finalizepsbt(signed["psbt"]) + assert self.nodes[0].testmempoolaccept([final["hex"]])[0]["allowed"] + # Reducing the weight should have a lower fee + psbt2 = wallet.walletcreatefundedpsbt( + inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": low_input_weight}], + outputs={self.nodes[0].getnewaddress(): 15}, + options={"add_inputs": True} + ) + assert_greater_than(psbt["fee"], psbt2["fee"]) + # Increasing the weight should have a higher fee + psbt2 = wallet.walletcreatefundedpsbt( + inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}], + outputs={self.nodes[0].getnewaddress(): 15}, + options={"add_inputs": True} + ) + assert_greater_than(psbt2["fee"], psbt["fee"]) + # The provided weight should override the calculated weight when solving data is provided + psbt3 = wallet.walletcreatefundedpsbt( + inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}], + outputs={self.nodes[0].getnewaddress(): 15}, + options={'add_inputs': True, "solving_data":{"descriptors": [desc]}} + ) + assert_equal(psbt2["fee"], psbt3["fee"]) + + # Import the external utxo descriptor so that we can sign for it from the test wallet + if self.options.descriptors: + res = wallet.importdescriptors([{"desc": desc, "timestamp": "now"}]) + else: + res = wallet.importmulti([{"desc": desc, "timestamp": "now"}]) + assert res[0]["success"] + # The provided weight should override the calculated weight for a wallet input + psbt3 = wallet.walletcreatefundedpsbt( + inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}], + outputs={self.nodes[0].getnewaddress(): 15}, + options={"add_inputs": True} + ) + assert_equal(psbt2["fee"], psbt3["fee"]) if __name__ == '__main__': PSBTTest().main() diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 96691b2686..a839af0288 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -99,25 +99,36 @@ class RawTransactionsTest(BitcoinTestFramework): rawTx = self.nodes[1].createrawtransaction([{'txid': txid, 'vout': vout}], {self.nodes[1].getnewaddress(): 9.999}) rawTxSigned = self.nodes[1].signrawtransactionwithwallet(rawTx) txId = self.nodes[1].sendrawtransaction(rawTxSigned['hex']) - self.generate(self.nodes[0], 1) + self.generateblock(self.nodes[0], output=self.nodes[0].getnewaddress(), transactions=[rawTxSigned['hex']]) + err_msg = ( + "No such mempool transaction. Use -txindex or provide a block hash to enable" + " blockchain transaction queries. Use gettransaction for wallet transactions." + ) for n in [0, 3]: self.log.info(f"Test getrawtransaction {'with' if n == 0 else 'without'} -txindex") - # 1. valid parameters - only supply txid - assert_equal(self.nodes[n].getrawtransaction(txId), rawTxSigned['hex']) - # 2. valid parameters - supply txid and 0 for non-verbose - assert_equal(self.nodes[n].getrawtransaction(txId, 0), rawTxSigned['hex']) + if n == 0: + # With -txindex. + # 1. valid parameters - only supply txid + assert_equal(self.nodes[n].getrawtransaction(txId), rawTxSigned['hex']) - # 3. valid parameters - supply txid and False for non-verbose - assert_equal(self.nodes[n].getrawtransaction(txId, False), rawTxSigned['hex']) + # 2. valid parameters - supply txid and 0 for non-verbose + assert_equal(self.nodes[n].getrawtransaction(txId, 0), 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[n].getrawtransaction(txId, 1)["hex"], rawTxSigned['hex']) + # 3. valid parameters - supply txid and False for non-verbose + assert_equal(self.nodes[n].getrawtransaction(txId, False), rawTxSigned['hex']) - # 5. valid parameters - supply txid and True for non-verbose - assert_equal(self.nodes[n].getrawtransaction(txId, True)["hex"], 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[n].getrawtransaction(txId, 1)["hex"], rawTxSigned['hex']) + + # 5. valid parameters - supply txid and True for non-verbose + assert_equal(self.nodes[n].getrawtransaction(txId, True)["hex"], rawTxSigned['hex']) + else: + # Without -txindex, expect to raise. + for verbose in [None, 0, False, 1, True]: + assert_raises_rpc_error(-5, err_msg, self.nodes[n].getrawtransaction, txId, verbose) # 6. invalid parameters - supply txid and invalid boolean values (strings) for verbose for value in ["True", "False"]: @@ -145,10 +156,6 @@ class RawTransactionsTest(BitcoinTestFramework): assert 'in_active_chain' not in gottx else: self.log.info("Test getrawtransaction without -txindex, without blockhash: expect the call to raise") - err_msg = ( - "No such mempool transaction. Use -txindex or provide a block hash to enable" - " blockchain transaction queries. Use gettransaction for wallet transactions." - ) assert_raises_rpc_error(-5, err_msg, self.nodes[n].getrawtransaction, txid=tx, verbose=True) # We should not get the tx if we provide an unrelated block assert_raises_rpc_error(-5, "No such transaction found", self.nodes[n].getrawtransaction, txid=tx, blockhash=block2) diff --git a/test/functional/rpc_scantxoutset.py b/test/functional/rpc_scantxoutset.py index 8703bfab8e..acb6d3ea4a 100755 --- a/test/functional/rpc_scantxoutset.py +++ b/test/functional/rpc_scantxoutset.py @@ -3,62 +3,60 @@ # 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.""" +from test_framework.messages import COIN from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error +from test_framework.wallet import ( + MiniWallet, + address_to_scriptpubkey, + getnewdestination, +) from decimal import Decimal -import shutil -import os + 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 sendtodestination(self, destination, amount): + # interpret strings as addresses, assume scriptPubKey otherwise + if isinstance(destination, str): + destination = address_to_scriptpubkey(destination) + self.wallet.send_to(from_node=self.nodes[0], scriptPubKey=destination, amount=int(COIN * amount)) def run_test(self): - self.log.info("Mining blocks...") - self.generate(self.nodes[0], 110) - - addr_P2SH_SEGWIT = self.nodes[0].getnewaddress("", "p2sh-segwit") - pubk1 = self.nodes[0].getaddressinfo(addr_P2SH_SEGWIT)['pubkey'] - addr_LEGACY = self.nodes[0].getnewaddress("", "legacy") - pubk2 = self.nodes[0].getaddressinfo(addr_LEGACY)['pubkey'] - addr_BECH32 = self.nodes[0].getnewaddress("", "bech32") - pubk3 = self.nodes[0].getaddressinfo(addr_BECH32)['pubkey'] - self.nodes[0].sendtoaddress(addr_P2SH_SEGWIT, 0.001) - self.nodes[0].sendtoaddress(addr_LEGACY, 0.002) - self.nodes[0].sendtoaddress(addr_BECH32, 0.004) + self.wallet = MiniWallet(self.nodes[0]) + self.wallet.rescan_utxos() - #send to child keys of tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK - self.nodes[0].sendtoaddress("mkHV1C6JLheLoUSSZYk7x3FH5tnx9bu7yc", 0.008) # (m/0'/0'/0') - self.nodes[0].sendtoaddress("mipUSRmJAj2KrjSvsPQtnP8ynUon7FhpCR", 0.016) # (m/0'/0'/1') - self.nodes[0].sendtoaddress("n37dAGe6Mq1HGM9t4b6rFEEsDGq7Fcgfqg", 0.032) # (m/0'/0'/1500') - self.nodes[0].sendtoaddress("mqS9Rpg8nNLAzxFExsgFLCnzHBsoQ3PRM6", 0.064) # (m/0'/0'/0) - self.nodes[0].sendtoaddress("mnTg5gVWr3rbhHaKjJv7EEEc76ZqHgSj4S", 0.128) # (m/0'/0'/1) - self.nodes[0].sendtoaddress("mketCd6B9U9Uee1iCsppDJJBHfvi6U6ukC", 0.256) # (m/0'/0'/1500) - self.nodes[0].sendtoaddress("mj8zFzrbBcdaWXowCQ1oPZ4qioBVzLzAp7", 0.512) # (m/1/1/0') - self.nodes[0].sendtoaddress("mfnKpKQEftniaoE1iXuMMePQU3PUpcNisA", 1.024) # (m/1/1/1') - self.nodes[0].sendtoaddress("mou6cB1kaP1nNJM1sryW6YRwnd4shTbXYQ", 2.048) # (m/1/1/1500') - self.nodes[0].sendtoaddress("mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", 4.096) # (m/1/1/0) - self.nodes[0].sendtoaddress("mxp7w7j8S1Aq6L8StS2PqVvtt4HGxXEvdy", 8.192) # (m/1/1/1) - self.nodes[0].sendtoaddress("mpQ8rokAhp1TAtJQR6F6TaUmjAWkAWYYBq", 16.384) # (m/1/1/1500) + self.log.info("Create UTXOs...") + pubk1, spk_P2SH_SEGWIT, addr_P2SH_SEGWIT = getnewdestination("p2sh-segwit") + pubk2, spk_LEGACY, addr_LEGACY = getnewdestination("legacy") + pubk3, spk_BECH32, addr_BECH32 = getnewdestination("bech32") + self.sendtodestination(spk_P2SH_SEGWIT, 0.001) + self.sendtodestination(spk_LEGACY, 0.002) + self.sendtodestination(spk_BECH32, 0.004) + #send to child keys of tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK + self.sendtodestination("mkHV1C6JLheLoUSSZYk7x3FH5tnx9bu7yc", 0.008) # (m/0'/0'/0') + self.sendtodestination("mipUSRmJAj2KrjSvsPQtnP8ynUon7FhpCR", 0.016) # (m/0'/0'/1') + self.sendtodestination("n37dAGe6Mq1HGM9t4b6rFEEsDGq7Fcgfqg", 0.032) # (m/0'/0'/1500') + self.sendtodestination("mqS9Rpg8nNLAzxFExsgFLCnzHBsoQ3PRM6", 0.064) # (m/0'/0'/0) + self.sendtodestination("mnTg5gVWr3rbhHaKjJv7EEEc76ZqHgSj4S", 0.128) # (m/0'/0'/1) + self.sendtodestination("mketCd6B9U9Uee1iCsppDJJBHfvi6U6ukC", 0.256) # (m/0'/0'/1500) + self.sendtodestination("mj8zFzrbBcdaWXowCQ1oPZ4qioBVzLzAp7", 0.512) # (m/1/1/0') + self.sendtodestination("mfnKpKQEftniaoE1iXuMMePQU3PUpcNisA", 1.024) # (m/1/1/1') + self.sendtodestination("mou6cB1kaP1nNJM1sryW6YRwnd4shTbXYQ", 2.048) # (m/1/1/1500') + self.sendtodestination("mtfUoUax9L4tzXARpw1oTGxWyoogp52KhJ", 4.096) # (m/1/1/0) + self.sendtodestination("mxp7w7j8S1Aq6L8StS2PqVvtt4HGxXEvdy", 8.192) # (m/1/1/1) + self.sendtodestination("mpQ8rokAhp1TAtJQR6F6TaUmjAWkAWYYBq", 16.384) # (m/1/1/1500) self.generate(self.nodes[0], 1) - 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, ['-nowallet']) - self.import_deterministic_coinbase_privkeys() - self.generate(self.nodes[0], 110) - scan = self.nodes[0].scantxoutset("start", []) info = self.nodes[0].gettxoutsetinfo() assert_equal(scan['success'], True) @@ -66,56 +64,55 @@ class ScantxoutsetTest(BitcoinTestFramework): 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(" + pubk1 + ")", "pkh(" + pubk2 + ")", "pkh(" + pubk3 + ")"])['total_amount'], Decimal("0.002")) - assert_equal(self.nodes[0].scantxoutset("start", [ "wpkh(" + pubk1 + ")", "wpkh(" + pubk2 + ")", "wpkh(" + pubk3 + ")"])['total_amount'], Decimal("0.004")) - assert_equal(self.nodes[0].scantxoutset("start", [ "sh(wpkh(" + pubk1 + "))", "sh(wpkh(" + pubk2 + "))", "sh(wpkh(" + pubk3 + "))"])['total_amount'], Decimal("0.001")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo(" + pubk1 + ")", "combo(" + pubk2 + ")", "combo(" + pubk3 + ")"])['total_amount'], Decimal("0.007")) - assert_equal(self.nodes[0].scantxoutset("start", [ "addr(" + addr_P2SH_SEGWIT + ")", "addr(" + addr_LEGACY + ")", "addr(" + addr_BECH32 + ")"])['total_amount'], Decimal("0.007")) - assert_equal(self.nodes[0].scantxoutset("start", [ "addr(" + addr_P2SH_SEGWIT + ")", "addr(" + addr_LEGACY + ")", "combo(" + pubk3 + ")"])['total_amount'], Decimal("0.007")) + assert_equal(self.nodes[0].scantxoutset("start", ["pkh(" + pubk1.hex() + ")", "pkh(" + pubk2.hex() + ")", "pkh(" + pubk3.hex() + ")"])['total_amount'], Decimal("0.002")) + assert_equal(self.nodes[0].scantxoutset("start", ["wpkh(" + pubk1.hex() + ")", "wpkh(" + pubk2.hex() + ")", "wpkh(" + pubk3.hex() + ")"])['total_amount'], Decimal("0.004")) + assert_equal(self.nodes[0].scantxoutset("start", ["sh(wpkh(" + pubk1.hex() + "))", "sh(wpkh(" + pubk2.hex() + "))", "sh(wpkh(" + pubk3.hex() + "))"])['total_amount'], Decimal("0.001")) + assert_equal(self.nodes[0].scantxoutset("start", ["combo(" + pubk1.hex() + ")", "combo(" + pubk2.hex() + ")", "combo(" + pubk3.hex() + ")"])['total_amount'], Decimal("0.007")) + assert_equal(self.nodes[0].scantxoutset("start", ["addr(" + addr_P2SH_SEGWIT + ")", "addr(" + addr_LEGACY + ")", "addr(" + addr_BECH32 + ")"])['total_amount'], Decimal("0.007")) + assert_equal(self.nodes[0].scantxoutset("start", ["addr(" + addr_P2SH_SEGWIT + ")", "addr(" + addr_LEGACY + ")", "combo(" + pubk3.hex() + ")"])['total_amount'], Decimal("0.007")) self.log.info("Test range validation.") - assert_raises_rpc_error(-8, "End of range is too high", self.nodes[0].scantxoutset, "start", [ {"desc": "desc", "range": -1}]) - assert_raises_rpc_error(-8, "Range should be greater or equal than 0", self.nodes[0].scantxoutset, "start", [ {"desc": "desc", "range": [-1, 10]}]) - assert_raises_rpc_error(-8, "End of range is too high", self.nodes[0].scantxoutset, "start", [ {"desc": "desc", "range": [(2 << 31 + 1) - 1000000, (2 << 31 + 1)]}]) - assert_raises_rpc_error(-8, "Range specified as [begin,end] must not have begin after end", self.nodes[0].scantxoutset, "start", [ {"desc": "desc", "range": [2, 1]}]) - assert_raises_rpc_error(-8, "Range is too large", self.nodes[0].scantxoutset, "start", [ {"desc": "desc", "range": [0, 1000001]}]) + 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")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0'/1h)"])['total_amount'], Decimal("0.016")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/1500')"])['total_amount'], Decimal("0.032")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0h/0)"])['total_amount'], Decimal("0.064")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/1)"])['total_amount'], Decimal("0.128")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/1500)"])['total_amount'], Decimal("0.256")) - assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/*h)", "range": 1499}])['total_amount'], Decimal("0.024")) - assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0'/*h)", "range": 1500}])['total_amount'], Decimal("0.056")) - assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/*)", "range": 1499}])['total_amount'], Decimal("0.192")) - assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/*)", "range": 1500}])['total_amount'], Decimal("0.448")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0')"])['total_amount'], Decimal("0.512")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1')"])['total_amount'], Decimal("1.024")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1500h)"])['total_amount'], Decimal("2.048")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"])['total_amount'], Decimal("4.096")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1)"])['total_amount'], Decimal("8.192")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1500)"])['total_amount'], Decimal("16.384")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0)"])['total_amount'], Decimal("4.096")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo([abcdef88/1/2'/3/4h]tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)"])['total_amount'], Decimal("8.192")) - assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1500)"])['total_amount'], Decimal("16.384")) - assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')", "range": 1499}])['total_amount'], Decimal("1.536")) - assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')", "range": 1500}])['total_amount'], Decimal("3.584")) - assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", "range": 1499}])['total_amount'], Decimal("12.288")) - assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672")) - assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1499}])['total_amount'], Decimal("12.288")) - assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672")) - assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": [1500,1500]}])['total_amount'], Decimal("16.384")) + assert_equal(self.nodes[0].scantxoutset("start", ["combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/0h)"])['total_amount'], Decimal("0.008")) + assert_equal(self.nodes[0].scantxoutset("start", ["combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0'/1h)"])['total_amount'], Decimal("0.016")) + assert_equal(self.nodes[0].scantxoutset("start", ["combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/1500')"])['total_amount'], Decimal("0.032")) + assert_equal(self.nodes[0].scantxoutset("start", ["combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0h/0)"])['total_amount'], Decimal("0.064")) + assert_equal(self.nodes[0].scantxoutset("start", ["combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/1)"])['total_amount'], Decimal("0.128")) + assert_equal(self.nodes[0].scantxoutset("start", ["combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/1500)"])['total_amount'], Decimal("0.256")) + assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/*h)", "range": 1499}])['total_amount'], Decimal("0.024")) + assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0'/*h)", "range": 1500}])['total_amount'], Decimal("0.056")) + assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/*)", "range": 1499}])['total_amount'], Decimal("0.192")) + assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/*)", "range": 1500}])['total_amount'], Decimal("0.448")) + assert_equal(self.nodes[0].scantxoutset("start", ["combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0')"])['total_amount'], Decimal("0.512")) + assert_equal(self.nodes[0].scantxoutset("start", ["combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1')"])['total_amount'], Decimal("1.024")) + assert_equal(self.nodes[0].scantxoutset("start", ["combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1500h)"])['total_amount'], Decimal("2.048")) + assert_equal(self.nodes[0].scantxoutset("start", ["combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"])['total_amount'], Decimal("4.096")) + assert_equal(self.nodes[0].scantxoutset("start", ["combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1)"])['total_amount'], Decimal("8.192")) + assert_equal(self.nodes[0].scantxoutset("start", ["combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1500)"])['total_amount'], Decimal("16.384")) + assert_equal(self.nodes[0].scantxoutset("start", ["combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0)"])['total_amount'], Decimal("4.096")) + assert_equal(self.nodes[0].scantxoutset("start", ["combo([abcdef88/1/2'/3/4h]tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)"])['total_amount'], Decimal("8.192")) + assert_equal(self.nodes[0].scantxoutset("start", ["combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1500)"])['total_amount'], Decimal("16.384")) + assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')", "range": 1499}])['total_amount'], Decimal("1.536")) + assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')", "range": 1500}])['total_amount'], Decimal("3.584")) + assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", "range": 1499}])['total_amount'], Decimal("12.288")) + assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672")) + assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1499}])['total_amount'], Decimal("12.288")) + assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672")) + assert_equal(self.nodes[0].scantxoutset("start", [{"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": [1500, 1500]}])['total_amount'], Decimal("16.384")) # Test the reported descriptors for a few matches - assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/*)", "range": 1499}])), ["pkh([0c5f9a1e/0'/0'/0]026dbd8b2315f296d36e6b6920b1579ca75569464875c7ebe869b536a7d9503c8c)#dzxw429x", "pkh([0c5f9a1e/0'/0'/1]033e6f25d76c00bedb3a8993c7d5739ee806397f0529b1b31dda31ef890f19a60c)#43rvceed"]) - assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"])), ["pkh([0c5f9a1e/1/1/0]03e1c5b6e650966971d7e71ef2674f80222752740fc1dfd63bbbd220d2da9bd0fb)#cxmct4w8"]) - assert_equal(descriptors(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])), ['pkh([0c5f9a1e/1/1/0]03e1c5b6e650966971d7e71ef2674f80222752740fc1dfd63bbbd220d2da9bd0fb)#cxmct4w8', 'pkh([0c5f9a1e/1/1/1500]03832901c250025da2aebae2bfb38d5c703a57ab66ad477f9c578bfbcd78abca6f)#vchwd07g', 'pkh([0c5f9a1e/1/1/1]030d820fc9e8211c4169be8530efbc632775d8286167afd178caaf1089b77daba7)#z2t3ypsa']) + 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) @@ -124,5 +121,6 @@ class ScantxoutsetTest(BitcoinTestFramework): # 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__': + +if __name__ == "__main__": ScantxoutsetTest().main() diff --git a/test/functional/rpc_signrawtransaction.py b/test/functional/rpc_signrawtransaction.py index e648040278..a2091b4ece 100755 --- a/test/functional/rpc_signrawtransaction.py +++ b/test/functional/rpc_signrawtransaction.py @@ -270,7 +270,7 @@ class SignRawTransactionsTest(BitcoinTestFramework): getcontext().prec = 8 # Make sure CSV is active - assert self.nodes[0].getblockchaininfo()['softforks']['csv']['active'] + assert self.nodes[0].getdeploymentinfo()['deployments']['csv']['active'] # Create a P2WSH script with CSV script = CScript([1, OP_CHECKSEQUENCEVERIFY, OP_DROP]) @@ -305,7 +305,7 @@ class SignRawTransactionsTest(BitcoinTestFramework): getcontext().prec = 8 # Make sure CLTV is active - assert self.nodes[0].getblockchaininfo()['softforks']['bip65']['active'] + assert self.nodes[0].getdeploymentinfo()['deployments']['bip65']['active'] # Create a P2WSH script with CLTV script = CScript([100, OP_CHECKLOCKTIMEVERIFY, OP_DROP]) diff --git a/test/functional/rpc_uptime.py b/test/functional/rpc_uptime.py index 1a82d1fa41..024e8aec1a 100755 --- a/test/functional/rpc_uptime.py +++ b/test/functional/rpc_uptime.py @@ -23,7 +23,7 @@ class UptimeTest(BitcoinTestFramework): self._test_uptime() def _test_negative_time(self): - assert_raises_rpc_error(-8, "Mocktime can not be negative: -1.", self.nodes[0].setmocktime, -1) + assert_raises_rpc_error(-8, "Mocktime cannot be negative: -1.", self.nodes[0].setmocktime, -1) def _test_uptime(self): wait_time = 10 diff --git a/test/functional/test_framework/address.py b/test/functional/test_framework/address.py index 013522a5e1..c7fbf679b6 100644 --- a/test/functional/test_framework/address.py +++ b/test/functional/test_framework/address.py @@ -55,17 +55,15 @@ def create_deterministic_address_bcrt1_p2tr_op_true(): def byte_to_base58(b, version): result = '' - str = b.hex() - str = chr(version).encode('latin-1').hex() + str - checksum = hash256(bytes.fromhex(str)).hex() - str += checksum[:8] - value = int('0x' + str, 0) + b = bytes([version]) + b # prepend version + b += hash256(b)[:4] # append checksum + value = int.from_bytes(b, 'big') while value > 0: result = chars[value % 58] + result value //= 58 - while (str[:2] == '00'): + while b[0] == 0: result = chars[0] + result - str = str[2:] + b = b[1:] return result diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index caa9b86969..40fcbf7761 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -22,6 +22,7 @@ from .messages import ( CTxIn, CTxInWitness, CTxOut, + SEQUENCE_FINAL, hash256, ser_uint256, tx_from_hex, @@ -128,7 +129,7 @@ def create_coinbase(height, pubkey=None, extra_output_script=None, fees=0, nValu If extra_output_script is given, make a 0-value output to that script. This is useful to pad block weight/sigops as needed. """ coinbase = CTransaction() - coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), 0xffffffff)) + coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), SEQUENCE_FINAL)) coinbaseoutput = CTxOut() coinbaseoutput.nValue = nValue * COIN if nValue == 50: @@ -156,7 +157,7 @@ def create_tx_with_script(prevtx, n, script_sig=b"", *, amount, script_pub_key=C """ tx = CTransaction() assert n < len(prevtx.vout) - tx.vin.append(CTxIn(COutPoint(prevtx.sha256, n), script_sig, 0xffffffff)) + tx.vin.append(CTxIn(COutPoint(prevtx.sha256, n), script_sig, SEQUENCE_FINAL)) tx.vout.append(CTxOut(amount, script_pub_key)) tx.calc_sha256() return tx diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 71ac5c5bfd..f57b6e7494 100755 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -40,6 +40,7 @@ COIN = 100000000 # 1 btc in satoshis MAX_MONEY = 21000000 * COIN BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is rbf-opt-in (BIP 125) and csv-opt-out (BIP 68) +SEQUENCE_FINAL = 0xffffffff # Sequence number that disables nLockTime if set for every input of a tx MAX_PROTOCOL_MESSAGE_LENGTH = 4000000 # Maximum length of incoming protocol messages MAX_HEADERS_RESULTS = 2000 # Number of headers sent in one getheaders result @@ -64,13 +65,15 @@ FILTER_TYPE_BASIC = 0 WITNESS_SCALE_FACTOR = 4 -# Serialization/deserialization tools + def sha256(s): - return hashlib.new('sha256', s).digest() + return hashlib.sha256(s).digest() + def hash256(s): return sha256(sha256(s)) + def ser_compact_size(l): r = b"" if l < 253: @@ -506,7 +509,7 @@ class CTransaction: def __init__(self, tx=None): if tx is None: - self.nVersion = 1 + self.nVersion = 2 self.vin = [] self.vout = [] self.wit = CTxWitness() diff --git a/test/functional/test_framework/netutil.py b/test/functional/test_framework/netutil.py index 174dc44a2a..b64f66e69b 100644 --- a/test/functional/test_framework/netutil.py +++ b/test/functional/test_framework/netutil.py @@ -144,7 +144,6 @@ def test_ipv6_local(): ''' Check for (local) IPv6 support. ''' - import socket # By using SOCK_DGRAM this will not actually make a connection, but it will # fail if there is no route to IPv6 localhost. have_ipv6 = True diff --git a/test/functional/test_framework/ripemd160.py b/test/functional/test_framework/ripemd160.py new file mode 100644 index 0000000000..12801364b4 --- /dev/null +++ b/test/functional/test_framework/ripemd160.py @@ -0,0 +1,130 @@ +# Copyright (c) 2021 Pieter Wuille +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Test-only pure Python RIPEMD160 implementation.""" + +import unittest + +# Message schedule indexes for the left path. +ML = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 +] + +# Message schedule indexes for the right path. +MR = [ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 +] + +# Rotation counts for the left path. +RL = [ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 +] + +# Rotation counts for the right path. +RR = [ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 +] + +# K constants for the left path. +KL = [0, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e] + +# K constants for the right path. +KR = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0] + + +def fi(x, y, z, i): + """The f1, f2, f3, f4, and f5 functions from the specification.""" + if i == 0: + return x ^ y ^ z + elif i == 1: + return (x & y) | (~x & z) + elif i == 2: + return (x | ~y) ^ z + elif i == 3: + return (x & z) | (y & ~z) + elif i == 4: + return x ^ (y | ~z) + else: + assert False + + +def rol(x, i): + """Rotate the bottom 32 bits of x left by i bits.""" + return ((x << i) | ((x & 0xffffffff) >> (32 - i))) & 0xffffffff + + +def compress(h0, h1, h2, h3, h4, block): + """Compress state (h0, h1, h2, h3, h4) with block.""" + # Left path variables. + al, bl, cl, dl, el = h0, h1, h2, h3, h4 + # Right path variables. + ar, br, cr, dr, er = h0, h1, h2, h3, h4 + # Message variables. + x = [int.from_bytes(block[4*i:4*(i+1)], 'little') for i in range(16)] + + # Iterate over the 80 rounds of the compression. + for j in range(80): + rnd = j >> 4 + # Perform left side of the transformation. + al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el + al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl + # Perform right side of the transformation. + ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er + ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr + + # Compose old state, left transform, and right transform into new state. + return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr + + +def ripemd160(data): + """Compute the RIPEMD-160 hash of data.""" + # Initialize state. + state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0) + # Process full 64-byte blocks in the input. + for b in range(len(data) >> 6): + state = compress(*state, data[64*b:64*(b+1)]) + # Construct final blocks (with padding and size). + pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63) + fin = data[len(data) & ~63:] + pad + (8 * len(data)).to_bytes(8, 'little') + # Process final blocks. + for b in range(len(fin) >> 6): + state = compress(*state, fin[64*b:64*(b+1)]) + # Produce output. + return b"".join((h & 0xffffffff).to_bytes(4, 'little') for h in state) + + +class TestFrameworkKey(unittest.TestCase): + def test_ripemd160(self): + """RIPEMD-160 test vectors.""" + # See https://homes.esat.kuleuven.be/~bosselae/ripemd160.html + for msg, hexout in [ + (b"", "9c1185a5c5e9fc54612808977ee8f548b2258d31"), + (b"a", "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"), + (b"abc", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"), + (b"message digest", "5d0689ef49d2fae572b881b123a85ffa21595f36"), + (b"abcdefghijklmnopqrstuvwxyz", + "f71c27109c692c1b56bbdceb5b9d2865b3708dbc"), + (b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "12a053384a9c0c88e405a06c27dcf49ada62eb2b"), + (b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", + "b0e20b6e3116640286ed3a87a5713079b21f5189"), + (b"1234567890" * 8, "9b752e45573d4b39f4dbd3323cab82bf63326bfb"), + (b"a" * 1000000, "52783243c1697bdbe16d37f97f68f08325dc1528") + ]: + self.assertEqual(ripemd160(msg).hex(), hexout) diff --git a/test/functional/test_framework/script.py b/test/functional/test_framework/script.py index 947a1f9808..7791ae5392 100644 --- a/test/functional/test_framework/script.py +++ b/test/functional/test_framework/script.py @@ -8,7 +8,6 @@ This file is modified from python-bitcoinlib. """ from collections import namedtuple -import hashlib import struct import unittest from typing import List, Dict @@ -25,6 +24,8 @@ from .messages import ( uint256_from_str, ) +from .ripemd160 import ripemd160 + MAX_SCRIPT_ELEMENT_SIZE = 520 LOCKTIME_THRESHOLD = 500000000 ANNEX_TAG = 0x50 @@ -32,7 +33,7 @@ ANNEX_TAG = 0x50 LEAF_VERSION_TAPSCRIPT = 0xc0 def hash160(s): - return hashlib.new('ripemd160', sha256(s)).digest() + return ripemd160(sha256(s)) def bn2vch(v): """Convert number to bitcoin-specific little endian format.""" diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 6746fbf9f9..ecdc3bf424 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -447,11 +447,15 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): def get_bin_from_version(version, bin_name, bin_default): if not version: return bin_default + if version > 219999: + # Starting at client version 220000 the first two digits represent + # the major version, e.g. v22.0 instead of v0.22.0. + version *= 100 return os.path.join( self.options.previous_releases_path, re.sub( - r'\.0$', - '', # remove trailing .0 for point releases + r'\.0$' if version <= 219999 else r'(\.0){1,2}$', + '', # Remove trailing dot for point releases, after 22.0 also remove double trailing dot. 'v{}.{}.{}.{}'.format( (version % 100000000) // 1000000, (version % 1000000) // 10000, @@ -473,7 +477,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): versions = [None] * num_nodes if self.is_syscall_sandbox_compiled() and not self.disable_syscall_sandbox: for i in range(len(extra_args)): - if versions[i] is None or versions[i] >= 219900: + # The -sandbox argument is not present in the v22.0 release. + if versions[i] is None or versions[i] >= 229900: extra_args[i] = extra_args[i] + ["-sandbox=log-and-abort"] if binary is None: binary = [get_bin_from_version(v, 'bitcoind', self.options.bitcoind) for v in versions] @@ -886,6 +891,14 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass): """Checks whether the wallet module was compiled.""" return self.config["components"].getboolean("ENABLE_WALLET") + def is_specified_wallet_compiled(self): + """Checks whether wallet support for the specified type + (legacy or descriptor wallet) was compiled.""" + if self.options.descriptors: + return self.is_sqlite_compiled() + else: + return self.is_bdb_compiled() + def is_wallet_tool_compiled(self): """Checks whether bitcoin-wallet was compiled.""" return self.config["components"].getboolean("ENABLE_WALLET_TOOL") diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index e3ceb0b118..e56d4aa492 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -389,14 +389,17 @@ class TestNode(): def debug_log_path(self) -> Path: return self.chain_path / 'debug.log' + def debug_log_bytes(self) -> int: + with open(self.debug_log_path, encoding='utf-8') as dl: + dl.seek(0, 2) + return dl.tell() + @contextlib.contextmanager def assert_debug_log(self, expected_msgs, unexpected_msgs=None, timeout=2): if unexpected_msgs is None: unexpected_msgs = [] time_end = time.time() + timeout * self.timeout_factor - with open(self.debug_log_path, encoding='utf-8') as dl: - dl.seek(0, 2) - prev_size = dl.tell() + prev_size = self.debug_log_bytes() yield @@ -420,6 +423,43 @@ class TestNode(): self._raise_assertion_error('Expected messages "{}" does not partially match log:\n\n{}\n\n'.format(str(expected_msgs), print_log)) @contextlib.contextmanager + def wait_for_debug_log(self, expected_msgs, timeout=60, ignore_case=False): + """ + Block until we see a particular debug log message fragment or until we exceed the timeout. + Return: + the number of log lines we encountered when matching + """ + time_end = time.time() + timeout * self.timeout_factor + prev_size = self.debug_log_bytes() + re_flags = re.MULTILINE | (re.IGNORECASE if ignore_case else 0) + + yield + + while True: + found = True + with open(self.debug_log_path, encoding='utf-8') as dl: + dl.seek(prev_size) + log = dl.read() + + for expected_msg in expected_msgs: + if re.search(re.escape(expected_msg), log, flags=re_flags) is None: + found = False + + if found: + return + + if time.time() >= time_end: + print_log = " - " + "\n - ".join(log.splitlines()) + break + + # No sleep here because we want to detect the message fragment as fast as + # possible. + + 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: str): """ Context manager that allows easy profiling of node activity using `perf`. @@ -578,7 +618,7 @@ class TestNode(): def add_outbound_p2p_connection(self, p2p_conn, *, p2p_idx, connection_type="outbound-full-relay", **kwargs): """Add an outbound p2p connection from node. Must be an - "outbound-full-relay", "block-relay-only" or "addr-fetch" connection. + "outbound-full-relay", "block-relay-only", "addr-fetch" or "feeler" connection. This method adds the p2p connection to the self.p2ps list and returns the connection to the caller. @@ -590,11 +630,16 @@ class TestNode(): p2p_conn.peer_accept_connection(connect_cb=addconnection_callback, connect_id=p2p_idx + 1, net=self.chain, timeout_factor=self.timeout_factor, **kwargs)() - p2p_conn.wait_for_connect() - self.p2ps.append(p2p_conn) + if connection_type == "feeler": + # feeler connections are closed as soon as the node receives a `version` message + p2p_conn.wait_until(lambda: p2p_conn.message_count["version"] == 1, check_connected=False) + p2p_conn.wait_until(lambda: not p2p_conn.is_connected, check_connected=False) + else: + p2p_conn.wait_for_connect() + self.p2ps.append(p2p_conn) - p2p_conn.wait_for_verack() - p2p_conn.sync_with_ping() + p2p_conn.wait_for_verack() + p2p_conn.sync_with_ping() return p2p_conn diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index c3a9029d55..210025104e 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -36,6 +36,7 @@ def assert_approx(v, vexp, vspan=0.00001): def assert_fee_amount(fee, tx_size, feerate_BTC_kvB): """Assert the fee is in range.""" + assert isinstance(tx_size, int) target_fee = get_fee(tx_size, feerate_BTC_kvB) if fee < target_fee: raise AssertionError("Fee of %s BTC too low! (Should be %s BTC)" % (str(fee), str(target_fee))) @@ -219,7 +220,13 @@ def str_to_b64str(string): def ceildiv(a, b): - """Divide 2 ints and round up to next int rather than round down""" + """ + Divide 2 ints and round up to next int rather than round down + Implementation requires python integers, which have a // operator that does floor division. + Other types like decimal.Decimal whose // operator truncates towards 0 will not work. + """ + assert isinstance(a, int) + assert isinstance(b, int) return -(-a // b) @@ -227,7 +234,7 @@ def get_fee(tx_size, feerate_btc_kvb): """Calculate the fee in BTC given a feerate is BTC/kvB. Reflects CFeeRate::GetFee""" feerate_sat_kvb = int(feerate_btc_kvb * Decimal(1e8)) # Fee in sat/kvb as an int to avoid float precision errors target_fee_sat = ceildiv(feerate_sat_kvb * tx_size, 1000) # Round calculated fee up to nearest sat - return satoshi_round(target_fee_sat / Decimal(1e8)) # Truncate BTC result to nearest sat + return target_fee_sat / Decimal(1e8) # Return result in BTC def satoshi_round(amount): @@ -379,10 +386,10 @@ def write_config(config_path, *, n, chain, extra_config="", disable_autoconnect= f.write("fixedseeds=0\n") f.write("listenonion=0\n") # Increase peertimeout to avoid disconnects while using mocktime. - # peertimeout is measured in wall clock time, so setting it to the - # duration of the longest test is sufficient. It can be overridden in - # tests. - f.write("peertimeout=999999\n") + # peertimeout is measured in mock time, so setting it large enough to + # cover any duration in mock time is sufficient. It can be overridden + # in tests. + f.write("peertimeout=999999999\n") f.write("printtoconsole=0\n") f.write("upnp=0\n") f.write("natpmp=0\n") @@ -438,7 +445,7 @@ def delete_cookie_file(datadir, chain): def softfork_active(node, key): """Return whether a softfork is active.""" - return node.getblockchaininfo()['softforks'][key]['active'] + return node.getdeploymentinfo()['deployments'][key]['active'] def set_node_times(nodes, t): diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py index f724cb2af3..dd41a740ae 100644 --- a/test/functional/test_framework/wallet.py +++ b/test/functional/test_framework/wallet.py @@ -9,7 +9,13 @@ from decimal import Decimal from enum import Enum from random import choice from typing import Optional -from test_framework.address import create_deterministic_address_bcrt1_p2tr_op_true +from test_framework.address import ( + base58_to_byte, + create_deterministic_address_bcrt1_p2tr_op_true, + key_to_p2pkh, + key_to_p2sh_p2wpkh, + key_to_p2wpkh, +) from test_framework.descriptors import descsum_create from test_framework.key import ECKey from test_framework.messages import ( @@ -31,7 +37,11 @@ from test_framework.script import ( ) from test_framework.script_util import ( key_to_p2pk_script, + key_to_p2pkh_script, + key_to_p2sh_p2wpkh_script, key_to_p2wpkh_script, + keyhash_to_p2pkh_script, + scripthash_to_p2sh_script, ) from test_framework.util import ( assert_equal, @@ -169,8 +179,9 @@ class MiniWallet: txid = self.sendrawtransaction(from_node=from_node, tx_hex=tx.serialize().hex()) return txid, 1 - def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, mempool_valid=True, locktime=0, sequence=0): + def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node=None, utxo_to_spend=None, mempool_valid=True, locktime=0, sequence=0): """Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed.""" + from_node = from_node or self._test_node utxo_to_spend = utxo_to_spend or self.get_utxo() if self._priv_key is None: vsize = Decimal(104) # anyone-can-spend @@ -203,18 +214,46 @@ class MiniWallet: assert_equal(tx_info['fees']['base'], utxo_to_spend['value'] - Decimal(send_value) / COIN) return {'txid': tx_info['txid'], 'wtxid': tx_info['wtxid'], 'hex': tx_hex, 'tx': tx} - def sendrawtransaction(self, *, from_node, tx_hex): - txid = from_node.sendrawtransaction(tx_hex) + def sendrawtransaction(self, *, from_node, tx_hex, **kwargs): + txid = from_node.sendrawtransaction(hexstring=tx_hex, **kwargs) self.scan_tx(from_node.decoderawtransaction(tx_hex)) return txid -def random_p2wpkh(): - """Generate a random P2WPKH scriptPubKey. Can be used when a random destination is needed, - but no compiled wallet is available (e.g. as replacement to the getnewaddress RPC).""" +def getnewdestination(address_type='bech32'): + """Generate a random destination of the specified type and return the + corresponding public key, scriptPubKey and address. Supported types are + 'legacy', 'p2sh-segwit' and 'bech32'. Can be used when a random + destination is needed, but no compiled wallet is available (e.g. as + replacement to the getnewaddress/getaddressinfo RPCs).""" key = ECKey() key.generate() - return key_to_p2wpkh_script(key.get_pubkey().get_bytes()) + pubkey = key.get_pubkey().get_bytes() + if address_type == 'legacy': + scriptpubkey = key_to_p2pkh_script(pubkey) + address = key_to_p2pkh(pubkey) + elif address_type == 'p2sh-segwit': + scriptpubkey = key_to_p2sh_p2wpkh_script(pubkey) + address = key_to_p2sh_p2wpkh(pubkey) + elif address_type == 'bech32': + scriptpubkey = key_to_p2wpkh_script(pubkey) + address = key_to_p2wpkh(pubkey) + # TODO: also support bech32m (need to generate x-only-pubkey) + else: + assert False + return pubkey, scriptpubkey, address + + +def address_to_scriptpubkey(address): + """Converts a given address to the corresponding output script (scriptPubKey).""" + payload, version = base58_to_byte(address) + if version == 111: # testnet pubkey hash + return keyhash_to_p2pkh_script(payload) + elif version == 196: # testnet script hash + return scripthash_to_p2sh_script(payload) + # TODO: also support other address formats + else: + assert False def make_chain(node, address, privkeys, parent_txid, parent_value, n=0, parent_locking_script=None, fee=DEFAULT_FEE): diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 0cd0c7c3a5..d15edcedd2 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -198,6 +198,7 @@ BASE_SCRIPTS = [ 'wallet_keypool.py --legacy-wallet', 'wallet_keypool.py --descriptors', 'wallet_descriptor.py --descriptors', + 'feature_maxtipage.py', 'p2p_nobloomfilter_messages.py', 'p2p_filter.py', 'rpc_setban.py', @@ -257,6 +258,7 @@ BASE_SCRIPTS = [ 'wallet_bumpfee.py --descriptors', 'wallet_implicitsegwit.py --legacy-wallet', 'rpc_named_arguments.py', + 'feature_startupnotify.py', 'wallet_listsinceblock.py --legacy-wallet', 'wallet_listsinceblock.py --descriptors', 'wallet_listdescriptors.py --descriptors', @@ -305,8 +307,10 @@ BASE_SCRIPTS = [ 'feature_txindex_compatibility.py', 'feature_logging.py', 'feature_anchors.py', - 'feature_coinstatsindex.py', + 'feature_coinstatsindex.py --legacy-wallet', + 'feature_coinstatsindex.py --descriptors', 'wallet_orphanedreward.py', + 'wallet_timelock.py', 'p2p_node_network_limited.py', 'p2p_permissions.py', 'feature_blocksdir.py', @@ -318,6 +322,7 @@ BASE_SCRIPTS = [ 'rpc_getdescriptorinfo.py', 'rpc_mempool_entry_fee_fields_deprecation.py', 'rpc_help.py', + 'feature_dirsymlinks.py', 'feature_help.py', 'feature_shutdown.py', 'p2p_ibd_txrelay.py', @@ -529,33 +534,39 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage= max_len_name = len(max(test_list, key=len)) test_count = len(test_list) - for i in range(test_count): - test_result, testdir, stdout, stderr = job_queue.get_next() - test_results.append(test_result) - done_str = "{}/{} - {}{}{}".format(i + 1, test_count, BOLD[1], test_result.name, BOLD[0]) - if test_result.status == "Passed": - logging.debug("%s passed, Duration: %s s" % (done_str, test_result.time)) - elif test_result.status == "Skipped": - logging.debug("%s skipped" % (done_str)) - else: - print("%s failed, Duration: %s s\n" % (done_str, test_result.time)) - print(BOLD[1] + 'stdout:\n' + BOLD[0] + stdout + '\n') - print(BOLD[1] + 'stderr:\n' + BOLD[0] + stderr + '\n') - if combined_logs_len and os.path.isdir(testdir): - # Print the final `combinedlogslen` lines of the combined logs - print('{}Combine the logs and print the last {} lines ...{}'.format(BOLD[1], combined_logs_len, BOLD[0])) - print('\n============') - print('{}Combined log for {}:{}'.format(BOLD[1], testdir, BOLD[0])) - print('============\n') - combined_logs_args = [sys.executable, os.path.join(tests_dir, 'combine_logs.py'), testdir] - if BOLD[0]: - combined_logs_args += ['--color'] - combined_logs, _ = subprocess.Popen(combined_logs_args, universal_newlines=True, stdout=subprocess.PIPE).communicate() - print("\n".join(deque(combined_logs.splitlines(), combined_logs_len))) - - if failfast: - logging.debug("Early exiting after test failure") - break + all_passed = True + i = 0 + while i < test_count: + if failfast and not all_passed: + break + for test_result, testdir, stdout, stderr in job_queue.get_next(): + test_results.append(test_result) + i += 1 + done_str = "{}/{} - {}{}{}".format(i, test_count, BOLD[1], test_result.name, BOLD[0]) + if test_result.status == "Passed": + logging.debug("%s passed, Duration: %s s" % (done_str, test_result.time)) + elif test_result.status == "Skipped": + logging.debug("%s skipped" % (done_str)) + else: + all_passed = False + print("%s failed, Duration: %s s\n" % (done_str, test_result.time)) + print(BOLD[1] + 'stdout:\n' + BOLD[0] + stdout + '\n') + print(BOLD[1] + 'stderr:\n' + BOLD[0] + stderr + '\n') + if combined_logs_len and os.path.isdir(testdir): + # Print the final `combinedlogslen` lines of the combined logs + print('{}Combine the logs and print the last {} lines ...{}'.format(BOLD[1], combined_logs_len, BOLD[0])) + print('\n============') + print('{}Combined log for {}:{}'.format(BOLD[1], testdir, BOLD[0])) + print('============\n') + combined_logs_args = [sys.executable, os.path.join(tests_dir, 'combine_logs.py'), testdir] + if BOLD[0]: + combined_logs_args += ['--color'] + combined_logs, _ = subprocess.Popen(combined_logs_args, universal_newlines=True, stdout=subprocess.PIPE).communicate() + print("\n".join(deque(combined_logs.splitlines(), combined_logs_len))) + + if failfast: + logging.debug("Early exiting after test failure") + break print_results(test_results, max_len_name, (int(time.time() - start_time))) @@ -571,7 +582,7 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage= if not os.listdir(tmpdir): os.rmdir(tmpdir) - all_passed = all(map(lambda test_result: test_result.was_successful, test_results)) and coverage_passed + all_passed = all_passed and coverage_passed # Clean up dangling processes if any. This may only happen with --failfast option. # Killing the process group will also terminate the current process but that is @@ -649,8 +660,9 @@ class TestHandler: dot_count = 0 while True: - # Return first proc that finishes + # Return all procs that have finished, if any. Otherwise sleep until there is one. time.sleep(.5) + ret = [] for job in self.jobs: (name, start_time, proc, testdir, log_out, log_err) = job if proc.poll() is not None: @@ -669,7 +681,9 @@ class TestHandler: clearline = '\r' + (' ' * dot_count) + '\r' print(clearline, end='', flush=True) dot_count = 0 - return TestResult(name, status, int(time.time() - start_time)), testdir, stdout, stderr + ret.append((TestResult(name, status, int(time.time() - start_time)), testdir, stdout, stderr)) + if ret: + return ret if self.use_term_control: print('.', end='', flush=True) dot_count += 1 diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py index afe4dba7b4..2cb9dc4523 100755 --- a/test/functional/tool_wallet.py +++ b/test/functional/tool_wallet.py @@ -390,7 +390,11 @@ class ToolWalletTest(BitcoinTestFramework): bad_sum_wallet_dump = os.path.join(self.nodes[0].datadir, "wallet-bad_sum3.dump") dump_data["checksum"] = "2" * 10 self.write_dump(dump_data, bad_sum_wallet_dump) - self.assert_raises_tool_error('Error: Dumpfile checksum does not match. Computed {}, expected {}{}'.format(checksum, "2" * 10, "0" * 54), '-wallet=badload', '-dumpfile={}'.format(bad_sum_wallet_dump), 'createfromdump') + self.assert_raises_tool_error('Error: Checksum is not the correct size', '-wallet=badload', '-dumpfile={}'.format(bad_sum_wallet_dump), 'createfromdump') + assert not os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest/wallets", "badload")) + dump_data["checksum"] = "3" * 66 + self.write_dump(dump_data, bad_sum_wallet_dump) + self.assert_raises_tool_error('Error: Checksum is not the correct size', '-wallet=badload', '-dumpfile={}'.format(bad_sum_wallet_dump), 'createfromdump') assert not os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest/wallets", "badload")) diff --git a/test/functional/wallet_abandonconflict.py b/test/functional/wallet_abandonconflict.py index 27d9d8da88..36fcdb36d6 100755 --- a/test/functional/wallet_abandonconflict.py +++ b/test/functional/wallet_abandonconflict.py @@ -29,20 +29,25 @@ class AbandonConflictTest(BitcoinTestFramework): self.skip_if_no_wallet() def run_test(self): + # create two wallets to tests conflicts from both sender's and receiver's sides + alice = self.nodes[0].get_wallet_rpc(self.default_wallet_name) + self.nodes[0].createwallet(wallet_name="bob") + bob = self.nodes[0].get_wallet_rpc("bob") + self.generate(self.nodes[1], COINBASE_MATURITY) - balance = self.nodes[0].getbalance() - txA = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) - txB = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) - txC = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10")) + balance = alice.getbalance() + txA = alice.sendtoaddress(alice.getnewaddress(), Decimal("10")) + txB = alice.sendtoaddress(alice.getnewaddress(), Decimal("10")) + txC = alice.sendtoaddress(alice.getnewaddress(), Decimal("10")) self.sync_mempools() self.generate(self.nodes[1], 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)) + assert_raises_rpc_error(-5, 'Invalid or non-wallet transaction id', lambda: alice.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)) + assert_raises_rpc_error(-5, 'Transaction not eligible for abandonment', lambda: alice.abandontransaction(txid=txA)) - newbalance = self.nodes[0].getbalance() + newbalance = alice.getbalance() assert balance - newbalance < Decimal("0.001") #no more than fees lost balance = newbalance @@ -50,9 +55,9 @@ class AbandonConflictTest(BitcoinTestFramework): self.disconnect_nodes(0, 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")) - nB = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txB)["details"] if tx_out["amount"] == Decimal("10")) - nC = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction(txC)["details"] if tx_out["amount"] == Decimal("10")) + nA = next(tx_out["vout"] for tx_out in alice.gettransaction(txA)["details"] if tx_out["amount"] == Decimal("10")) + nB = next(tx_out["vout"] for tx_out in alice.gettransaction(txB)["details"] if tx_out["amount"] == Decimal("10")) + nC = next(tx_out["vout"] for tx_out in alice.gettransaction(txC)["details"] if tx_out["amount"] == Decimal("10")) inputs = [] # spend 10btc outputs from txA and txB @@ -60,39 +65,40 @@ class AbandonConflictTest(BitcoinTestFramework): inputs.append({"txid": txB, "vout": nB}) outputs = {} - outputs[self.nodes[0].getnewaddress()] = Decimal("14.99998") - outputs[self.nodes[1].getnewaddress()] = Decimal("5") - signed = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs)) + outputs[alice.getnewaddress()] = Decimal("14.99998") + outputs[bob.getnewaddress()] = Decimal("5") + signed = alice.signrawtransactionwithwallet(alice.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")) + nAB = next(tx_out["vout"] for tx_out in alice.gettransaction(txAB1)["details"] if tx_out["amount"] == Decimal("14.99998")) #Create a child tx spending AB1 and C inputs = [] inputs.append({"txid": txAB1, "vout": nAB}) inputs.append({"txid": txC, "vout": nC}) outputs = {} - outputs[self.nodes[0].getnewaddress()] = Decimal("24.9996") - signed2 = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs)) + outputs[alice.getnewaddress()] = Decimal("24.9996") + signed2 = alice.signrawtransactionwithwallet(alice.createrawtransaction(inputs, outputs)) txABC2 = self.nodes[0].sendrawtransaction(signed2["hex"]) # Create a child tx spending ABC2 signed3_change = Decimal("24.999") inputs = [{"txid": txABC2, "vout": 0}] - outputs = {self.nodes[0].getnewaddress(): signed3_change} - signed3 = self.nodes[0].signrawtransactionwithwallet(self.nodes[0].createrawtransaction(inputs, outputs)) + outputs = {alice.getnewaddress(): signed3_change} + signed3 = alice.signrawtransactionwithwallet(alice.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() + newbalance = alice.getbalance() assert_equal(newbalance, balance - Decimal("30") + 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"]) + alice = self.nodes[0].get_wallet_rpc(self.default_wallet_name) assert self.nodes[0].getmempoolinfo()['loaded'] # Verify txs no longer in either node's mempool @@ -101,25 +107,25 @@ class AbandonConflictTest(BitcoinTestFramework): # Not in mempool txs from self should only reduce balance # inputs are still spent, but change not received - newbalance = self.nodes[0].getbalance() + newbalance = alice.getbalance() assert_equal(newbalance, balance - signed3_change) # Unconfirmed received funds that are not in mempool, also shouldn't show # up in unconfirmed balance - balances = self.nodes[0].getbalances()['mine'] + balances = alice.getbalances()['mine'] assert_equal(balances['untrusted_pending'] + balances['trusted'], newbalance) # Also shouldn't show up in listunspent - assert not txABC2 in [utxo["txid"] for utxo in self.nodes[0].listunspent(0)] + assert not txABC2 in [utxo["txid"] for utxo in alice.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() + alice.abandontransaction(txAB1) + newbalance = alice.getbalance() assert_equal(newbalance, balance + Decimal("30")) balance = newbalance self.log.info("Check abandoned transactions in listsinceblock") - listsinceblock = self.nodes[0].listsinceblock() + listsinceblock = alice.listsinceblock() txAB1_listsinceblock = [d for d in listsinceblock['transactions'] if d['txid'] == txAB1 and d['category'] == 'send'] for tx in txAB1_listsinceblock: assert_equal(tx['abandoned'], True) @@ -128,49 +134,53 @@ class AbandonConflictTest(BitcoinTestFramework): # Verify that even with a low min relay fee, the tx is not reaccepted from wallet on startup once abandoned self.restart_node(0, extra_args=["-minrelaytxfee=0.00001"]) + alice = self.nodes[0].get_wallet_rpc(self.default_wallet_name) assert self.nodes[0].getmempoolinfo()['loaded'] assert_equal(len(self.nodes[0].getrawmempool()), 0) - assert_equal(self.nodes[0].getbalance(), balance) + assert_equal(alice.getbalance(), balance) # But if it is received again then it is unabandoned # And since now in mempool, the change is available # But its child tx remains abandoned self.nodes[0].sendrawtransaction(signed["hex"]) - newbalance = self.nodes[0].getbalance() + newbalance = alice.getbalance() assert_equal(newbalance, balance - Decimal("20") + Decimal("14.99998")) balance = newbalance # Send child tx again so it is unabandoned self.nodes[0].sendrawtransaction(signed2["hex"]) - newbalance = self.nodes[0].getbalance() + newbalance = alice.getbalance() assert_equal(newbalance, balance - Decimal("10") - Decimal("14.99998") + Decimal("24.9996")) balance = newbalance # Remove using high relay fee again self.restart_node(0, extra_args=["-minrelaytxfee=0.0001"]) + alice = self.nodes[0].get_wallet_rpc(self.default_wallet_name) assert self.nodes[0].getmempoolinfo()['loaded'] assert_equal(len(self.nodes[0].getrawmempool()), 0) - newbalance = self.nodes[0].getbalance() + newbalance = alice.getbalance() assert_equal(newbalance, balance - Decimal("24.9996")) balance = newbalance self.log.info("Test transactions conflicted by a double spend") + self.nodes[0].loadwallet("bob") + bob = self.nodes[0].get_wallet_rpc("bob") + # Create a double spend of AB1 by spending 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") - tx = self.nodes[0].createrawtransaction(inputs, outputs) - signed = self.nodes[0].signrawtransactionwithwallet(tx) - self.nodes[1].sendrawtransaction(signed["hex"]) - self.generate(self.nodes[1], 1, sync_fun=self.no_op) - + outputs[self.nodes[1].getnewaddress()] = Decimal("3.9999") + outputs[bob.getnewaddress()] = Decimal("5.9999") + tx = alice.createrawtransaction(inputs, outputs) + signed = alice.signrawtransactionwithwallet(tx) + double_spend_txid = self.nodes[1].sendrawtransaction(signed["hex"]) self.connect_nodes(0, 1) - self.sync_blocks() + self.generate(self.nodes[1], 1) - tx_list = self.nodes[0].listtransactions() + tx_list = alice.listtransactions() conflicted = [tx for tx in tx_list if tx["confirmations"] < 0] assert_equal(4, len(conflicted)) @@ -179,7 +189,7 @@ class AbandonConflictTest(BitcoinTestFramework): assert_equal(2, len(wallet_conflicts)) double_spends = [tx for tx in tx_list if tx["walletconflicts"] and tx["confirmations"] > 0] - assert_equal(1, len(double_spends)) + assert_equal(2, len(double_spends)) # one for each output double_spend = double_spends[0] # Test the properties of the conflicted transactions, i.e. with confirmations < 0. @@ -198,8 +208,19 @@ class AbandonConflictTest(BitcoinTestFramework): assert_equal(double_spend["walletconflicts"], [tx["txid"]]) assert_equal(tx["walletconflicts"], [double_spend["txid"]]) + # Test walletconflicts on the receiver's side + txinfo = bob.gettransaction(txAB1) + assert_equal(txinfo['confirmations'], -1) + assert_equal(txinfo['walletconflicts'], [double_spend['txid']]) + + double_spends = [tx for tx in bob.listtransactions() if tx["walletconflicts"] and tx["confirmations"] > 0] + assert_equal(1, len(double_spends)) + double_spend = double_spends[0] + assert_equal(double_spend_txid, double_spend['txid']) + assert_equal(double_spend["walletconflicts"], [txAB1]) + # Verify that B and C's 10 BTC outputs are available for spending again because AB1 is now conflicted - newbalance = self.nodes[0].getbalance() + newbalance = alice.getbalance() assert_equal(newbalance, balance + Decimal("20")) balance = newbalance @@ -207,7 +228,7 @@ class AbandonConflictTest(BitcoinTestFramework): # Invalidate the block with the double spend and B's 10 BTC 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() + newbalance = alice.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") diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py index eb6e497951..f7c80f805c 100755 --- a/test/functional/wallet_address_types.py +++ b/test/functional/wallet_address_types.py @@ -346,13 +346,13 @@ class AddressTypeTest(BitcoinTestFramework): self.test_change_output_type(0, [to_address_bech32_1], 'legacy') if self.options.descriptors: - self.log.info("Nodes with addresstype=p2sh-segwit only use a bech32m change output if any destination address is bech32:") + self.log.info("Nodes with addresstype=p2sh-segwit match the change output") self.test_change_output_type(1, [to_address_p2sh], 'p2sh-segwit') - self.test_change_output_type(1, [to_address_bech32_1], 'bech32m') - self.test_change_output_type(1, [to_address_p2sh, to_address_bech32_1], 'bech32m') - self.test_change_output_type(1, [to_address_bech32_1, to_address_bech32_2], 'bech32m') + self.test_change_output_type(1, [to_address_bech32_1], 'bech32') + self.test_change_output_type(1, [to_address_p2sh, to_address_bech32_1], 'bech32') + self.test_change_output_type(1, [to_address_bech32_1, to_address_bech32_2], 'bech32') else: - self.log.info("Nodes with addresstype=p2sh-segwit only use a P2WPKH change output if any destination address is bech32:") + self.log.info("Nodes with addresstype=p2sh-segwit match the change output") self.test_change_output_type(1, [to_address_p2sh], 'p2sh-segwit') self.test_change_output_type(1, [to_address_bech32_1], 'bech32') self.test_change_output_type(1, [to_address_p2sh, to_address_bech32_1], 'bech32') @@ -363,13 +363,13 @@ class AddressTypeTest(BitcoinTestFramework): self.test_change_output_type(2, [to_address_p2sh], 'bech32') if self.options.descriptors: - self.log.info("Nodes with addresstype=bech32 always use either a bech32 or bech32m change output (unless changetype is set otherwise):") - self.test_change_output_type(3, [to_address_bech32_1], 'bech32m') - self.test_change_output_type(3, [to_address_p2sh], 'bech32') + self.log.info("Nodes with addresstype=bech32 match the change output (unless changetype is set otherwise):") + self.test_change_output_type(3, [to_address_bech32_1], 'bech32') + self.test_change_output_type(3, [to_address_p2sh], 'p2sh-segwit') else: - self.log.info("Nodes with addresstype=bech32 always use a P2WPKH change output (unless changetype is set otherwise):") + self.log.info("Nodes with addresstype=bech32 match the change output (unless changetype is set otherwise):") self.test_change_output_type(3, [to_address_bech32_1], 'bech32') - self.test_change_output_type(3, [to_address_p2sh], 'bech32') + self.test_change_output_type(3, [to_address_p2sh], 'p2sh-segwit') self.log.info('getrawchangeaddress defaults to addresstype if -changetype is not set and argument is absent') self.test_address(3, self.nodes[3].getrawchangeaddress(), multisig=False, typ='bech32') diff --git a/test/functional/wallet_backup.py b/test/functional/wallet_backup.py index 932df4fbff..292fe3a310 100755 --- a/test/functional/wallet_backup.py +++ b/test/functional/wallet_backup.py @@ -110,17 +110,32 @@ class WalletBackupTest(BitcoinTestFramework): os.remove(os.path.join(self.nodes[1].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename)) os.remove(os.path.join(self.nodes[2].datadir, self.chain, 'wallets', self.default_wallet_name, self.wallet_data_filename)) + def restore_invalid_wallet(self): + node = self.nodes[3] + invalid_wallet_file = os.path.join(self.nodes[0].datadir, 'invalid_wallet_file.bak') + open(invalid_wallet_file, 'a', encoding="utf8").write('invald wallet') + wallet_name = "res0" + not_created_wallet_file = os.path.join(node.datadir, self.chain, 'wallets', wallet_name) + error_message = "Wallet file verification failed. Failed to load database path '{}'. Data is not in recognized format.".format(not_created_wallet_file) + assert_raises_rpc_error(-18, error_message, node.restorewallet, wallet_name, invalid_wallet_file) + assert not os.path.exists(not_created_wallet_file) + def restore_nonexistent_wallet(self): node = self.nodes[3] nonexistent_wallet_file = os.path.join(self.nodes[0].datadir, 'nonexistent_wallet.bak') wallet_name = "res0" assert_raises_rpc_error(-8, "Backup file does not exist", node.restorewallet, wallet_name, nonexistent_wallet_file) + not_created_wallet_file = os.path.join(node.datadir, self.chain, 'wallets', wallet_name) + assert not os.path.exists(not_created_wallet_file) def restore_wallet_existent_name(self): node = self.nodes[3] - wallet_file = os.path.join(self.nodes[0].datadir, 'wallet.bak') + backup_file = os.path.join(self.nodes[0].datadir, 'wallet.bak') wallet_name = "res0" - assert_raises_rpc_error(-8, "Wallet name already exists.", node.restorewallet, wallet_name, wallet_file) + wallet_file = os.path.join(node.datadir, self.chain, 'wallets', wallet_name) + error_message = "Failed to create database path '{}'. Database already exists.".format(wallet_file) + assert_raises_rpc_error(-36, error_message, node.restorewallet, wallet_name, backup_file) + assert os.path.exists(wallet_file) def init_three(self): self.init_wallet(node=0) @@ -177,6 +192,7 @@ class WalletBackupTest(BitcoinTestFramework): ## self.log.info("Restoring wallets on node 3 using backup files") + self.restore_invalid_wallet() self.restore_nonexistent_wallet() backup_file_0 = os.path.join(self.nodes[0].datadir, 'wallet.bak') @@ -187,6 +203,10 @@ class WalletBackupTest(BitcoinTestFramework): self.nodes[3].restorewallet("res1", backup_file_1) self.nodes[3].restorewallet("res2", backup_file_2) + assert os.path.exists(os.path.join(self.nodes[3].datadir, self.chain, 'wallets', "res0")) + assert os.path.exists(os.path.join(self.nodes[3].datadir, self.chain, 'wallets', "res1")) + assert os.path.exists(os.path.join(self.nodes[3].datadir, self.chain, 'wallets', "res2")) + res0_rpc = self.nodes[3].get_wallet_rpc("res0") res1_rpc = self.nodes[3].get_wallet_rpc("res1") res2_rpc = self.nodes[3].get_wallet_rpc("res2") diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py index 0d702e44f6..0c9106f800 100755 --- a/test/functional/wallet_encryption.py +++ b/test/functional/wallet_encryption.py @@ -35,14 +35,14 @@ class WalletEncryptionTest(BitcoinTestFramework): assert_raises_rpc_error(-15, "Error: running with an unencrypted wallet, but walletpassphrasechange was called.", self.nodes[0].walletpassphrasechange, 'ff', 'ff') # Encrypt the wallet - assert_raises_rpc_error(-8, "passphrase can not be empty", self.nodes[0].encryptwallet, '') + assert_raises_rpc_error(-8, "passphrase cannot be empty", self.nodes[0].encryptwallet, '') self.nodes[0].encryptwallet(passphrase) # Test that the wallet is encrypted assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].signmessage, address, msg) assert_raises_rpc_error(-15, "Error: running with an encrypted wallet, but encryptwallet was called.", self.nodes[0].encryptwallet, 'ff') - assert_raises_rpc_error(-8, "passphrase can not be empty", self.nodes[0].walletpassphrase, '', 1) - assert_raises_rpc_error(-8, "passphrase can not be empty", self.nodes[0].walletpassphrasechange, '', 'ff') + assert_raises_rpc_error(-8, "passphrase cannot be empty", self.nodes[0].walletpassphrase, '', 1) + assert_raises_rpc_error(-8, "passphrase cannot be empty", self.nodes[0].walletpassphrasechange, '', 'ff') # Check that walletpassphrase works self.nodes[0].walletpassphrase(passphrase, 2) diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py index 9052bc7f7f..eb305c5fa2 100755 --- a/test/functional/wallet_groups.py +++ b/test/functional/wallet_groups.py @@ -108,17 +108,10 @@ class WalletGroupTest(BitcoinTestFramework): assert_equal(input_addrs[0], input_addrs[1]) # Node 2 enforces avoidpartialspends so needs no checking here - if self.options.descriptors: - # Descriptor wallets will use Taproot change by default which has different fees - tx4_ungrouped_fee = 3060 - tx4_grouped_fee = 4400 - tx5_6_ungrouped_fee = 5760 - tx5_6_grouped_fee = 8480 - else: - tx4_ungrouped_fee = 2820 - tx4_grouped_fee = 4160 - tx5_6_ungrouped_fee = 5520 - tx5_6_grouped_fee = 8240 + tx4_ungrouped_fee = 2820 + tx4_grouped_fee = 4160 + tx5_6_ungrouped_fee = 5520 + tx5_6_grouped_fee = 8240 self.log.info("Test wallet option maxapsfee") addr_aps = self.nodes[3].getnewaddress() diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py index d78afb4212..ac878ea0aa 100755 --- a/test/functional/wallet_hd.py +++ b/test/functional/wallet_hd.py @@ -136,7 +136,7 @@ class WalletHDTest(BitcoinTestFramework): keypath = self.nodes[1].getaddressinfo(out['scriptPubKey']['address'])['hdkeypath'] if self.options.descriptors: - assert_equal(keypath[0:14], "m/86'/1'/0'/1/") + assert_equal(keypath[0:14], "m/84'/1'/0'/1/") else: assert_equal(keypath[0:7], "m/0'/1'") diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py index 1b29d163c2..f75877f256 100755 --- a/test/functional/wallet_listtransactions.py +++ b/test/functional/wallet_listtransactions.py @@ -16,6 +16,7 @@ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_array_result, assert_equal, + assert_raises_rpc_error, ) @@ -107,6 +108,7 @@ class ListTransactionsTest(BitcoinTestFramework): self.run_rbf_opt_in_test() self.run_externally_generated_address_test() + self.run_invalid_parameters_test() def run_rbf_opt_in_test(self): """Test the opt-in-rbf flag for sent and received transactions.""" @@ -275,6 +277,13 @@ class ListTransactionsTest(BitcoinTestFramework): assert_equal(['pizza2'], self.nodes[0].getaddressinfo(addr2)['labels']) assert_equal(['pizza3'], self.nodes[0].getaddressinfo(addr3)['labels']) + def run_invalid_parameters_test(self): + self.log.info("Test listtransactions RPC parameter validity") + assert_raises_rpc_error(-8, 'Label argument must be a valid label name or "*".', self.nodes[0].listtransactions, label="") + self.nodes[0].listtransactions(label="*") + assert_raises_rpc_error(-8, "Negative count", self.nodes[0].listtransactions, count=-1) + assert_raises_rpc_error(-8, "Negative from", self.nodes[0].listtransactions, skip=-1) + if __name__ == '__main__': ListTransactionsTest().main() diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index 0b868dde6c..dcb82bbbe9 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -11,6 +11,7 @@ from threading import Thread import os import shutil import stat +import sys import time from test_framework.authproxy import JSONRPCException @@ -141,7 +142,7 @@ class MultiWalletTest(BitcoinTestFramework): # should raise rpc error if wallet path can't be created err_code = -4 if self.options.descriptors else -1 - assert_raises_rpc_error(err_code, "boost::filesystem::create_directory:", self.nodes[0].createwallet, "w8/bad") + assert_raises_rpc_error(err_code, "filesystem error:" if sys.platform != 'win32' else "create_directories:", self.nodes[0].createwallet, "w8/bad") # check that all requested wallets were created self.stop_node(0) diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py index d77d554baa..86e36be8f7 100755 --- a/test/functional/wallet_send.py +++ b/test/functional/wallet_send.py @@ -16,6 +16,7 @@ from test_framework.util import ( assert_fee_amount, assert_greater_than, assert_raises_rpc_error, + count_bytes, ) from test_framework.wallet_util import bytes_to_wif @@ -320,20 +321,20 @@ class WalletSendTest(BitcoinTestFramework): res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=7, add_to_wallet=False) fee = self.nodes[1].decodepsbt(res["psbt"])["fee"] - assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00007")) + assert_fee_amount(fee, count_bytes(res["hex"]), Decimal("0.00007")) # "unset" and None are treated the same for estimate_mode res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, fee_rate=2, estimate_mode="unset", add_to_wallet=False) fee = self.nodes[1].decodepsbt(res["psbt"])["fee"] - assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00002")) + assert_fee_amount(fee, count_bytes(res["hex"]), Decimal("0.00002")) res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=4.531, add_to_wallet=False) fee = self.nodes[1].decodepsbt(res["psbt"])["fee"] - assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00004531")) + assert_fee_amount(fee, count_bytes(res["hex"]), Decimal("0.00004531")) res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=3, add_to_wallet=False) fee = self.nodes[1].decodepsbt(res["psbt"])["fee"] - assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("0.00003")) + assert_fee_amount(fee, count_bytes(res["hex"]), Decimal("0.00003")) # Test that passing fee_rate as both an argument and an option raises. self.test_send(from_wallet=w0, to_wallet=w1, amount=1, arg_fee_rate=1, fee_rate=1, add_to_wallet=False, @@ -518,5 +519,45 @@ class WalletSendTest(BitcoinTestFramework): assert signed["complete"] self.nodes[0].finalizepsbt(signed["psbt"]) + dec = self.nodes[0].decodepsbt(signed["psbt"]) + for i, txin in enumerate(dec["tx"]["vin"]): + if txin["txid"] == ext_utxo["txid"] and txin["vout"] == ext_utxo["vout"]: + input_idx = i + break + psbt_in = dec["inputs"][input_idx] + # Calculate the input weight + # (prevout + sequence + length of scriptSig + 2 bytes buffer) * 4 + len of scriptwitness + len_scriptsig = len(psbt_in["final_scriptSig"]["hex"]) // 2 if "final_scriptSig" in psbt_in else 0 + len_scriptwitness = len(psbt_in["final_scriptwitness"]["hex"]) // 2 if "final_scriptwitness" in psbt_in else 0 + input_weight = ((41 + len_scriptsig + 2) * 4) + len_scriptwitness + + # Input weight error conditions + assert_raises_rpc_error( + -8, + "Input weights should be specified in inputs rather than in options.", + ext_wallet.send, + outputs={self.nodes[0].getnewaddress(): 15}, + options={"inputs": [ext_utxo], "input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 1000}]} + ) + + # Funding should also work when input weights are provided + res = self.test_send( + from_wallet=ext_wallet, + to_wallet=self.nodes[0], + amount=15, + inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": input_weight}], + add_inputs=True, + psbt=True, + include_watching=True, + fee_rate=10 + ) + signed = ext_wallet.walletprocesspsbt(res["psbt"]) + signed = ext_fund.walletprocesspsbt(res["psbt"]) + assert signed["complete"] + tx = self.nodes[0].finalizepsbt(signed["psbt"]) + testres = self.nodes[0].testmempoolaccept([tx["hex"]])[0] + assert_equal(testres["allowed"], True) + assert_fee_amount(testres["fees"]["base"], testres["vsize"], Decimal(0.0001)) + if __name__ == '__main__': WalletSendTest().main() diff --git a/test/functional/wallet_signer.py b/test/functional/wallet_signer.py index 6dadc57b1a..423cfecdc0 100755 --- a/test/functional/wallet_signer.py +++ b/test/functional/wallet_signer.py @@ -25,6 +25,13 @@ class WalletSignerTest(BitcoinTestFramework): else: return path + def mock_invalid_signer_path(self): + path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'mocks', 'invalid_signer.py') + if platform.system() == "Windows": + return "py " + path + else: + return path + def set_test_params(self): self.num_nodes = 2 # The experimental syscall sandbox feature (-sandbox) is not compatible with -signer (which @@ -48,6 +55,11 @@ class WalletSignerTest(BitcoinTestFramework): os.remove(os.path.join(node.cwd, "mock_result")) def run_test(self): + self.test_valid_signer() + self.restart_node(1, [f"-signer={self.mock_invalid_signer_path()}", "-keypool=10"]) + self.test_invalid_signer() + + def test_valid_signer(self): self.log.debug(f"-signer={self.mock_signer_path()}") # Create new wallets for an external signer. @@ -60,10 +72,12 @@ class WalletSignerTest(BitcoinTestFramework): self.nodes[1].createwallet(wallet_name='hww', disable_private_keys=True, descriptors=True, external_signer=True) hww = self.nodes[1].get_wallet_rpc('hww') + assert_equal(hww.getwalletinfo()["external_signer"], True) # Flag can't be set afterwards (could be added later for non-blank descriptor based watch-only wallets) self.nodes[1].createwallet(wallet_name='not_hww', disable_private_keys=True, descriptors=True, external_signer=False) not_hww = self.nodes[1].get_wallet_rpc('not_hww') + assert_equal(not_hww.getwalletinfo()["external_signer"], False) assert_raises_rpc_error(-8, "Wallet flag is immutable: external_signer", not_hww.setwalletflag, "external_signer", True) # assert_raises_rpc_error(-4, "Multiple signers found, please specify which to use", wallet_name='not_hww', disable_private_keys=True, descriptors=True, external_signer=True) @@ -187,5 +201,10 @@ class WalletSignerTest(BitcoinTestFramework): # ) # self.clear_mock_result(self.nodes[4]) + def test_invalid_signer(self): + self.log.debug(f"-signer={self.mock_invalid_signer_path()}") + self.log.info('Test invalid external signer') + assert_raises_rpc_error(-1, "Invalid descriptor", self.nodes[1].createwallet, wallet_name='hww_invalid', disable_private_keys=True, descriptors=True, external_signer=True) + if __name__ == '__main__': WalletSignerTest().main() diff --git a/test/functional/wallet_timelock.py b/test/functional/wallet_timelock.py new file mode 100755 index 0000000000..a71cec6607 --- /dev/null +++ b/test/functional/wallet_timelock.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# Copyright (c) 2022 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 + + +class WalletLocktimeTest(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] + + mtp_tip = node.getblockheader(node.getbestblockhash())["mediantime"] + + self.log.info("Get new address with label") + label = "timelock⌛🔓" + address = node.getnewaddress(label=label) + + self.log.info("Send to new address with locktime") + node.send( + outputs={address: 5}, + options={"locktime": mtp_tip - 1}, + ) + self.generate(node, 1) + + self.log.info("Check that clock cannot change finality of confirmed txs") + amount_before_ad = node.getreceivedbyaddress(address) + amount_before_lb = node.getreceivedbylabel(label) + list_before_ad = node.listreceivedbyaddress(address_filter=address) + list_before_lb = node.listreceivedbylabel(include_empty=False) + balance_before = node.getbalances()["mine"]["trusted"] + coin_before = node.listunspent(maxconf=1) + node.setmocktime(mtp_tip - 1) + assert_equal(node.getreceivedbyaddress(address), amount_before_ad) + assert_equal(node.getreceivedbylabel(label), amount_before_lb) + assert_equal(node.listreceivedbyaddress(address_filter=address), list_before_ad) + assert_equal(node.listreceivedbylabel(include_empty=False), list_before_lb) + assert_equal(node.getbalances()["mine"]["trusted"], balance_before) + assert_equal(node.listunspent(maxconf=1), coin_before) + + +if __name__ == "__main__": + WalletLocktimeTest().main() diff --git a/test/functional/wallet_upgradewallet.py b/test/functional/wallet_upgradewallet.py index 36e72f2dd9..c452e1eafd 100755 --- a/test/functional/wallet_upgradewallet.py +++ b/test/functional/wallet_upgradewallet.py @@ -345,5 +345,16 @@ class UpgradeWalletTest(BitcoinTestFramework): desc_wallet = self.nodes[0].get_wallet_rpc("desc_upgrade") self.test_upgradewallet(desc_wallet, previous_version=169900, expected_version=169900) + self.log.info("Checking that descriptor wallets without privkeys do nothing, successfully") + self.nodes[0].createwallet(wallet_name="desc_upgrade_nopriv", descriptors=True, disable_private_keys=True) + desc_wallet = self.nodes[0].get_wallet_rpc("desc_upgrade_nopriv") + self.test_upgradewallet(desc_wallet, previous_version=169900, expected_version=169900) + + if self.is_bdb_compiled(): + self.log.info("Upgrading a wallet with private keys disabled") + self.nodes[0].createwallet(wallet_name="privkeys_disabled_upgrade", disable_private_keys=True, descriptors=False) + disabled_wallet = self.nodes[0].get_wallet_rpc("privkeys_disabled_upgrade") + self.test_upgradewallet(disabled_wallet, previous_version=169900, expected_version=169900) + if __name__ == '__main__': UpgradeWalletTest().main() |