diff options
Diffstat (limited to 'test')
-rwxr-xr-x | test/functional/feature_rbf.py | 4 | ||||
-rwxr-xr-x | test/functional/p2p_initial_headers_sync.py | 105 | ||||
-rwxr-xr-x | test/functional/rpc_fundrawtransaction.py | 25 | ||||
-rwxr-xr-x | test/functional/test_runner.py | 1 | ||||
-rwxr-xr-x | test/functional/wallet_avoid_mixing_output_types.py | 1 | ||||
-rwxr-xr-x | test/functional/wallet_basic.py | 33 | ||||
-rwxr-xr-x | test/functional/wallet_bumpfee.py | 23 | ||||
-rwxr-xr-x | test/functional/wallet_encryption.py | 18 | ||||
-rwxr-xr-x | test/functional/wallet_listsinceblock.py | 63 | ||||
-rwxr-xr-x | test/lint/lint-includes.py | 3 |
10 files changed, 252 insertions, 24 deletions
diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py index 2237a4171e..7603248ae5 100755 --- a/test/functional/feature_rbf.py +++ b/test/functional/feature_rbf.py @@ -386,7 +386,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): def test_too_many_replacements_with_default_mempool_params(self): """ - Test rule 5 of BIP125 (do not allow replacements that cause more than 100 + Test rule 5 (do not allow replacements that cause more than 100 evictions) without having to rely on non-default mempool parameters. In order to do this, create a number of "root" UTXOs, and then hang @@ -405,7 +405,7 @@ class ReplaceByFeeTest(BitcoinTestFramework): # limit; 10 works. num_tx_graphs = 10 - # (Number of transactions per graph, BIP125 rule 5 failure expected) + # (Number of transactions per graph, rule 5 failure expected) cases = [ # Test the base case of evicting fewer than MAX_REPLACEMENT_LIMIT # transactions. diff --git a/test/functional/p2p_initial_headers_sync.py b/test/functional/p2p_initial_headers_sync.py new file mode 100755 index 0000000000..e67c384da7 --- /dev/null +++ b/test/functional/p2p_initial_headers_sync.py @@ -0,0 +1,105 @@ +#!/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 initial headers download + +Test that we only try to initially sync headers from one peer (until our chain +is close to caught up), and that each block announcement results in only one +additional peer receiving a getheaders message. +""" + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.messages import ( + CInv, + MSG_BLOCK, + msg_headers, + msg_inv, +) +from test_framework.p2p import ( + p2p_lock, + P2PInterface, +) +from test_framework.util import ( + assert_equal, +) +import random + +class HeadersSyncTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + + def announce_random_block(self, peers): + new_block_announcement = msg_inv(inv=[CInv(MSG_BLOCK, random.randrange(1<<256))]) + for p in peers: + p.send_and_ping(new_block_announcement) + + def run_test(self): + self.log.info("Adding a peer to node0") + peer1 = self.nodes[0].add_p2p_connection(P2PInterface()) + + # Wait for peer1 to receive a getheaders + peer1.wait_for_getheaders() + # An empty reply will clear the outstanding getheaders request, + # allowing additional getheaders requests to be sent to this peer in + # the future. + peer1.send_message(msg_headers()) + + self.log.info("Connecting two more peers to node0") + # Connect 2 more peers; they should not receive a getheaders yet + peer2 = self.nodes[0].add_p2p_connection(P2PInterface()) + peer3 = self.nodes[0].add_p2p_connection(P2PInterface()) + + all_peers = [peer1, peer2, peer3] + + self.log.info("Verify that peer2 and peer3 don't receive a getheaders after connecting") + for p in all_peers: + p.sync_with_ping() + with p2p_lock: + assert "getheaders" not in peer2.last_message + assert "getheaders" not in peer3.last_message + + with p2p_lock: + peer1.last_message.pop("getheaders", None) + + self.log.info("Have all peers announce a new block") + self.announce_random_block(all_peers) + + self.log.info("Check that peer1 receives a getheaders in response") + peer1.wait_for_getheaders() + peer1.send_message(msg_headers()) # Send empty response, see above + with p2p_lock: + peer1.last_message.pop("getheaders", None) + + self.log.info("Check that exactly 1 of {peer2, peer3} received a getheaders in response") + count = 0 + peer_receiving_getheaders = None + for p in [peer2, peer3]: + with p2p_lock: + if "getheaders" in p.last_message: + count += 1 + peer_receiving_getheaders = p + p.last_message.pop("getheaders", None) + p.send_message(msg_headers()) # Send empty response, see above + + assert_equal(count, 1) + + self.log.info("Announce another new block, from all peers") + self.announce_random_block(all_peers) + + self.log.info("Check that peer1 receives a getheaders in response") + peer1.wait_for_getheaders() + + self.log.info("Check that the remaining peer received a getheaders as well") + expected_peer = peer2 + if peer2 == peer_receiving_getheaders: + expected_peer = peer3 + + expected_peer.wait_for_getheaders() + + self.log.info("Success!") + +if __name__ == '__main__': + HeadersSyncTest().main() + diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py index cf9ad3f458..5a870de6c7 100755 --- a/test/functional/rpc_fundrawtransaction.py +++ b/test/functional/rpc_fundrawtransaction.py @@ -408,7 +408,7 @@ class RawTransactionsTest(BitcoinTestFramework): inputs = [ {'txid' : "1c7f966dab21119bac53213a2bc7532bff1fa844c124fd750a7d0b1332440bd1", 'vout' : 0} ] #invalid vin! outputs = { self.nodes[0].getnewaddress() : 1.0} rawtx = self.nodes[2].createrawtransaction(inputs, outputs) - assert_raises_rpc_error(-4, "Insufficient funds", self.nodes[2].fundrawtransaction, rawtx) + assert_raises_rpc_error(-4, "Unable to find UTXO for external input", self.nodes[2].fundrawtransaction, rawtx) def test_fee_p2pkh(self): """Compare fee of a standard pubkeyhash transaction.""" @@ -1079,17 +1079,28 @@ class RawTransactionsTest(BitcoinTestFramework): self.nodes[2].createwallet("test_weight_calculation") wallet = self.nodes[2].get_wallet_rpc("test_weight_calculation") - addr = wallet.getnewaddress() - txid = self.nodes[0].sendtoaddress(addr, 5) + addr = wallet.getnewaddress(address_type="bech32") + ext_addr = self.nodes[0].getnewaddress(address_type="bech32") + txid = self.nodes[0].send([{addr: 5}, {ext_addr: 5}])["txid"] vout = find_vout_for_address(self.nodes[0], txid, addr) + ext_vout = find_vout_for_address(self.nodes[0], txid, ext_addr) - self.nodes[0].sendtoaddress(wallet.getnewaddress(), 5) + self.nodes[0].sendtoaddress(wallet.getnewaddress(address_type="bech32"), 5) self.generate(self.nodes[0], 1) - rawtx = wallet.createrawtransaction([{'txid': txid, 'vout': vout}], [{self.nodes[0].getnewaddress(): 9.999}]) - fundedtx = wallet.fundrawtransaction(rawtx, {'fee_rate': 10}) + rawtx = wallet.createrawtransaction([{'txid': txid, 'vout': vout}], [{self.nodes[0].getnewaddress(address_type="bech32"): 8}]) + fundedtx = wallet.fundrawtransaction(rawtx, {'fee_rate': 10, "change_type": "bech32"}) # with 71-byte signatures we should expect following tx size - tx_size = 10 + 41*2 + 31*2 + (2 + 107*2)/4 + # tx overhead (10) + 2 inputs (41 each) + 2 p2wpkh (31 each) + (segwit marker and flag (2) + 2 p2wpkh 71 byte sig witnesses (107 each)) / witness scaling factor (4) + tx_size = ceil(10 + 41*2 + 31*2 + (2 + 107*2)/4) + assert_equal(fundedtx['fee'] * COIN, tx_size * 10) + + # Using the other output should have 72 byte sigs + rawtx = wallet.createrawtransaction([{'txid': txid, 'vout': ext_vout}], [{self.nodes[0].getnewaddress(): 13}]) + ext_desc = self.nodes[0].getaddressinfo(ext_addr)["desc"] + fundedtx = wallet.fundrawtransaction(rawtx, {'fee_rate': 10, "change_type": "bech32", "solving_data": {"descriptors": [ext_desc]}}) + # tx overhead (10) + 3 inputs (41 each) + 2 p2wpkh(31 each) + (segwit marker and flag (2) + 2 p2wpkh 71 bytes sig witnesses (107 each) + p2wpkh 72 byte sig witness (108)) / witness scaling factor (4) + tx_size = ceil(10 + 41*3 + 31*2 + (2 + 107*2 + 108)/4) assert_equal(fundedtx['fee'] * COIN, tx_size * 10) self.nodes[2].unloadwallet("test_weight_calculation") diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 3676b698f0..37d0c4f87e 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -247,6 +247,7 @@ BASE_SCRIPTS = [ 'rpc_generate.py', 'wallet_balance.py --legacy-wallet', 'wallet_balance.py --descriptors', + 'p2p_initial_headers_sync.py', 'feature_nulldummy.py', 'mempool_accept.py', 'mempool_expiry.py', diff --git a/test/functional/wallet_avoid_mixing_output_types.py b/test/functional/wallet_avoid_mixing_output_types.py index 46f41d9c22..cad9d02808 100755 --- a/test/functional/wallet_avoid_mixing_output_types.py +++ b/test/functional/wallet_avoid_mixing_output_types.py @@ -124,6 +124,7 @@ class AddressInputTypeGrouping(BitcoinTestFramework): def skip_test_if_missing_module(self): self.skip_if_no_wallet() + self.skip_if_no_sqlite() def make_payment(self, A, B, v, addr_type): fee_rate = random.randint(1, 20) diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py index 9cf1b3d2c4..adeb551895 100755 --- a/test/functional/wallet_basic.py +++ b/test/functional/wallet_basic.py @@ -7,6 +7,7 @@ from decimal import Decimal from itertools import product from test_framework.blocktools import COINBASE_MATURITY +from test_framework.descriptors import descsum_create from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_array_result, @@ -700,6 +701,38 @@ class WalletTest(BitcoinTestFramework): txid_feeReason_four = self.nodes[2].sendmany(dummy='', amounts={address: 5}, verbose=False) assert_equal(self.nodes[2].gettransaction(txid_feeReason_four)['txid'], txid_feeReason_four) + self.log.info("Testing 'listunspent' outputs the parent descriptor(s) of coins") + # Create two multisig descriptors, and send a UTxO each. + multi_a = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YBNjUo96Jxd1u4XKWgnoc7LsA1jz3Yc2NiDbhtfBhaBtemB73n9V5vtJHwU6FVXwggTbeoJWQ1rzdz8ysDuQkpnaHyvnvzR/*,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*))") + multi_b = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*,tpubD6NzVbkrYhZ4Y2RLiuEzNQkntjmsLpPYDm3LTRBYynUQtDtpzeUKAcb9sYthSFL3YR74cdFgF5mW8yKxv2W2CWuZDFR2dUpE5PF9kbrVXNZ/*))") + addr_a = self.nodes[0].deriveaddresses(multi_a, 0)[0] + addr_b = self.nodes[0].deriveaddresses(multi_b, 0)[0] + txid_a = self.nodes[0].sendtoaddress(addr_a, 0.01) + txid_b = self.nodes[0].sendtoaddress(addr_b, 0.01) + self.generate(self.nodes[0], 1, sync_fun=self.no_op) + # Now import the descriptors, make sure we can identify on which descriptor each coin was received. + self.nodes[0].createwallet(wallet_name="wo", descriptors=True, disable_private_keys=True) + wo_wallet = self.nodes[0].get_wallet_rpc("wo") + wo_wallet.importdescriptors([ + { + "desc": multi_a, + "active": False, + "timestamp": "now", + }, + { + "desc": multi_b, + "active": False, + "timestamp": "now", + }, + ]) + coins = wo_wallet.listunspent(minconf=0) + assert_equal(len(coins), 2) + coin_a = next(c for c in coins if c["txid"] == txid_a) + assert_equal(coin_a["parent_descs"][0], multi_a) + coin_b = next(c for c in coins if c["txid"] == txid_b) + assert_equal(coin_b["parent_descs"][0], multi_b) + self.nodes[0].unloadwallet("wo") + if __name__ == '__main__': WalletTest().main() diff --git a/test/functional/wallet_bumpfee.py b/test/functional/wallet_bumpfee.py index 9a481b290b..79be016b72 100755 --- a/test/functional/wallet_bumpfee.py +++ b/test/functional/wallet_bumpfee.py @@ -86,7 +86,7 @@ class BumpFeeTest(BitcoinTestFramework): self.test_invalid_parameters(rbf_node, peer_node, dest_address) test_segwit_bumpfee_succeeds(self, rbf_node, dest_address) test_nonrbf_bumpfee_fails(self, peer_node, dest_address) - test_notmine_bumpfee_fails(self, rbf_node, peer_node, dest_address) + test_notmine_bumpfee(self, rbf_node, peer_node, dest_address) test_bumpfee_with_descendant_fails(self, rbf_node, rbf_node_address, dest_address) test_dust_to_fee(self, rbf_node, dest_address) test_watchonly_psbt(self, peer_node, rbf_node, dest_address) @@ -232,7 +232,7 @@ def test_nonrbf_bumpfee_fails(self, peer_node, dest_address): self.clear_mempool() -def test_notmine_bumpfee_fails(self, rbf_node, peer_node, dest_address): +def test_notmine_bumpfee(self, rbf_node, peer_node, dest_address): self.log.info('Test that it cannot bump fee if non-owned inputs are included') # here, the rbftx has a peer_node coin and then adds a rbf_node input # Note that this test depends upon the RPC code checking input ownership prior to change outputs @@ -250,8 +250,27 @@ def test_notmine_bumpfee_fails(self, rbf_node, peer_node, dest_address): signedtx = rbf_node.signrawtransactionwithwallet(rawtx) signedtx = peer_node.signrawtransactionwithwallet(signedtx["hex"]) rbfid = rbf_node.sendrawtransaction(signedtx["hex"]) + entry = rbf_node.getmempoolentry(rbfid) + old_fee = entry["fees"]["base"] + old_feerate = int(old_fee / entry["vsize"] * Decimal(1e8)) assert_raises_rpc_error(-4, "Transaction contains inputs that don't belong to this wallet", rbf_node.bumpfee, rbfid) + + def finish_psbtbumpfee(psbt): + psbt = rbf_node.walletprocesspsbt(psbt) + psbt = peer_node.walletprocesspsbt(psbt["psbt"]) + final = rbf_node.finalizepsbt(psbt["psbt"]) + res = rbf_node.testmempoolaccept([final["hex"]]) + assert res[0]["allowed"] + assert_greater_than(res[0]["fees"]["base"], old_fee) + + self.log.info("Test that psbtbumpfee works for non-owned inputs") + psbt = rbf_node.psbtbumpfee(txid=rbfid) + finish_psbtbumpfee(psbt["psbt"]) + + psbt = rbf_node.psbtbumpfee(txid=rbfid, options={"fee_rate": old_feerate + 10}) + finish_psbtbumpfee(psbt["psbt"]) + self.clear_mempool() diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py index 0c9106f800..37c1c4bff3 100755 --- a/test/functional/wallet_encryption.py +++ b/test/functional/wallet_encryption.py @@ -9,8 +9,7 @@ import time from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_raises_rpc_error, - assert_greater_than, - assert_greater_than_or_equal, + assert_equal, ) @@ -76,21 +75,18 @@ class WalletEncryptionTest(BitcoinTestFramework): self.log.info('Check a timeout less than the limit') MAX_VALUE = 100000000 - expected_time = int(time.time()) + MAX_VALUE - 600 + now = int(time.time()) + self.nodes[0].setmocktime(now) + expected_time = now + MAX_VALUE - 600 self.nodes[0].walletpassphrase(passphrase2, MAX_VALUE - 600) - # give buffer for walletpassphrase, since it iterates over all encrypted keys - expected_time_with_buffer = time.time() + MAX_VALUE - 600 actual_time = self.nodes[0].getwalletinfo()['unlocked_until'] - assert_greater_than_or_equal(actual_time, expected_time) - assert_greater_than(expected_time_with_buffer, actual_time) + assert_equal(actual_time, expected_time) self.log.info('Check a timeout greater than the limit') - expected_time = int(time.time()) + MAX_VALUE - 1 + expected_time = now + MAX_VALUE self.nodes[0].walletpassphrase(passphrase2, MAX_VALUE + 1000) - expected_time_with_buffer = time.time() + MAX_VALUE actual_time = self.nodes[0].getwalletinfo()['unlocked_until'] - assert_greater_than_or_equal(actual_time, expected_time) - assert_greater_than(expected_time_with_buffer, actual_time) + assert_equal(actual_time, expected_time) if __name__ == '__main__': diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py index 1f3a276d9c..36dbec467b 100755 --- a/test/functional/wallet_listsinceblock.py +++ b/test/functional/wallet_listsinceblock.py @@ -6,6 +6,7 @@ from test_framework.address import key_to_p2wpkh from test_framework.blocktools import COINBASE_MATURITY +from test_framework.descriptors import descsum_create from test_framework.key import ECKey from test_framework.test_framework import BitcoinTestFramework from test_framework.messages import MAX_BIP125_RBF_SEQUENCE @@ -39,6 +40,8 @@ class ListSinceBlockTest(BitcoinTestFramework): self.test_double_send() self.double_spends_filtered() self.test_targetconfirmations() + self.test_desc() + self.test_send_to_self() def test_no_blockhash(self): self.log.info("Test no blockhash") @@ -383,5 +386,65 @@ class ListSinceBlockTest(BitcoinTestFramework): assert_equal(original_found, False) assert_equal(double_found, False) + def test_desc(self): + """Make sure we can track coins by descriptor.""" + self.log.info("Test descriptor lookup by scriptPubKey.") + + # Create a watchonly wallet tracking two multisig descriptors. + multi_a = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YBNjUo96Jxd1u4XKWgnoc7LsA1jz3Yc2NiDbhtfBhaBtemB73n9V5vtJHwU6FVXwggTbeoJWQ1rzdz8ysDuQkpnaHyvnvzR/*,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*))") + multi_b = descsum_create("wsh(multi(1,tpubD6NzVbkrYhZ4YHdDGMAYGaWxMSC1B6tPRTHuU5t3BcfcS3nrF523iFm5waFd1pP3ZvJt4Jr8XmCmsTBNx5suhcSgtzpGjGMASR3tau1hJz4/*,tpubD6NzVbkrYhZ4Y2RLiuEzNQkntjmsLpPYDm3LTRBYynUQtDtpzeUKAcb9sYthSFL3YR74cdFgF5mW8yKxv2W2CWuZDFR2dUpE5PF9kbrVXNZ/*))") + self.nodes[0].createwallet(wallet_name="wo", descriptors=True, disable_private_keys=True) + wo_wallet = self.nodes[0].get_wallet_rpc("wo") + wo_wallet.importdescriptors([ + { + "desc": multi_a, + "active": False, + "timestamp": "now", + }, + { + "desc": multi_b, + "active": False, + "timestamp": "now", + }, + ]) + + # Send a coin to each descriptor. + assert_equal(len(wo_wallet.listsinceblock()["transactions"]), 0) + addr_a = self.nodes[0].deriveaddresses(multi_a, 0)[0] + addr_b = self.nodes[0].deriveaddresses(multi_b, 0)[0] + self.nodes[2].sendtoaddress(addr_a, 1) + self.nodes[2].sendtoaddress(addr_b, 2) + self.generate(self.nodes[2], 1) + + # We can identify on which descriptor each coin was received. + coins = wo_wallet.listsinceblock()["transactions"] + assert_equal(len(coins), 2) + coin_a = next(c for c in coins if c["amount"] == 1) + assert_equal(coin_a["parent_descs"][0], multi_a) + coin_b = next(c for c in coins if c["amount"] == 2) + assert_equal(coin_b["parent_descs"][0], multi_b) + + def test_send_to_self(self): + """We can make listsinceblock output our change outputs.""" + self.log.info("Test the inclusion of change outputs in the output.") + + # Create a UTxO paying to one of our change addresses. + block_hash = self.nodes[2].getbestblockhash() + addr = self.nodes[2].getrawchangeaddress() + self.nodes[2].sendtoaddress(addr, 1) + + # If we don't list change, we won't have an entry for it. + coins = self.nodes[2].listsinceblock(blockhash=block_hash)["transactions"] + assert not any(c["address"] == addr for c in coins) + + # Now if we list change, we'll get both the send (to a change address) and + # the actual change. + res = self.nodes[2].listsinceblock(blockhash=block_hash, include_change=True) + coins = [entry for entry in res["transactions"] if entry["category"] == "receive"] + assert_equal(len(coins), 2) + assert any(c["address"] == addr for c in coins) + assert all(self.nodes[2].getaddressinfo(c["address"])["ischange"] for c in coins) + + if __name__ == '__main__': ListSinceBlockTest().main() diff --git a/test/lint/lint-includes.py b/test/lint/lint-includes.py index afdca0d418..b3fa4b9303 100755 --- a/test/lint/lint-includes.py +++ b/test/lint/lint-includes.py @@ -21,8 +21,7 @@ EXCLUDED_DIRS = ["src/leveldb/", "src/minisketch/", ] -EXPECTED_BOOST_INCLUDES = ["boost/algorithm/string/replace.hpp", - "boost/date_time/posix_time/posix_time.hpp", +EXPECTED_BOOST_INCLUDES = ["boost/date_time/posix_time/posix_time.hpp", "boost/multi_index/hashed_index.hpp", "boost/multi_index/ordered_index.hpp", "boost/multi_index/sequenced_index.hpp", |