diff --git a/.arclint b/.arclint index ed4e9b77e..e83b697b2 100644 --- a/.arclint +++ b/.arclint @@ -1,341 +1,341 @@ { "linters": { "generated": { "type": "generated" }, "clang-format": { "type": "clang-format", "version": ">=12.0", "bin": [ "clang-format-12", "clang-format" ], "include": "(^(src|chronik)/.*\\.(h|c|cpp|mm)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)" ] }, "autopep8": { "type": "autopep8", "version": ">=1.3.4", "include": "(\\.py$)", "exclude": [ "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)", - "(^test/functional/[a-v].*\\.py$)" + "(^test/functional/.*\\.py$)" ], "flags": [ "--aggressive", "--ignore=W503,W504", "--max-line-length=88" ] }, "black": { "type": "black", "version": ">=23.0.0", "include": [ - "(^test/functional/[a-v].*\\.py$)" + "(^test/functional/.*\\.py$)" ], "flags": [ "--preview" ] }, "flake8": { "type": "flake8", "version": ">=5.0", "include": "(\\.py$)", "exclude": [ "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ], "flags": [ "--ignore=A003,E203,E303,E305,E501,E704,W503,W504", "--require-plugins=flake8-comprehensions,flake8-builtins" ] }, "lint-format-strings": { "type": "lint-format-strings", "include": "(^(src|chronik)/.*\\.(h|c|cpp)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)", "(^src/test/fuzz/strprintf.cpp$)" ] }, "check-doc": { "type": "check-doc", "include": "(^(src|chronik)/.*\\.(h|c|cpp)$)" }, "lint-tests": { "type": "lint-tests", "include": "(^src/(seeder/|rpc/|wallet/)?test/.*\\.(cpp)$)" }, "phpcs": { "type": "phpcs", "include": "(\\.php$)", "exclude": [ "(^arcanist/__phutil_library_.+\\.php$)" ], "phpcs.standard": "arcanist/phpcs.xml" }, "lint-locale-dependence": { "type": "lint-locale-dependence", "include": "(^(src|chronik)/.*\\.(h|cpp)$)", "exclude": [ "(^src/(crypto/ctaes/|leveldb/|secp256k1/|tinyformat.h|univalue/))", "(^src/bench/nanobench.h$)" ] }, "lint-cheader": { "type": "lint-cheader", "include": "(^(src|chronik)/.*\\.(h|cpp)$)", "exclude": [ "(^src/(crypto/ctaes|secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)" ] }, "spelling": { "type": "spelling", "exclude": [ "(^build-aux/m4/)", "(^depends/)", "(^doc/release-notes/)", "(^contrib/gitian-builder/)", "(^src/(qt/locale|secp256k1|univalue|leveldb)/)", "(^test/lint/dictionary/)", "(package-lock.json)" ], "spelling.dictionaries": [ "test/lint/dictionary/english.json" ] }, "lint-assert-with-side-effects": { "type": "lint-assert-with-side-effects", "include": "(^(src|chronik)/.*\\.(h|cpp)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)" ] }, "lint-include-quotes": { "type": "lint-include-quotes", "include": "(^(src|chronik)/.*\\.(h|cpp)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)" ] }, "lint-include-guard": { "type": "lint-include-guard", "include": "(^(src|chronik)/.*\\.h$)", "exclude": [ "(^src/(crypto/ctaes|secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)", "(^src/tinyformat.h$)" ] }, "lint-include-source": { "type": "lint-include-source", "include": "(^(src|chronik)/.*\\.(h|c|cpp)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)" ] }, "lint-std-chrono": { "type": "lint-std-chrono", "include": "(^(src|chronik)/.*\\.(h|cpp)$)" }, "lint-stdint": { "type": "lint-stdint", "include": "(^(src|chronik)/.*\\.(h|c|cpp)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)", "(^src/compat/assumptions.h$)" ] }, "lint-source-filename": { "type": "lint-source-filename", "include": "(^(src|chronik)/.*\\.(h|c|cpp)$)", "exclude": [ "(^src/(secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)" ] }, "lint-boost-dependencies": { "type": "lint-boost-dependencies", "include": "(^(src|chronik)/.*\\.(h|cpp)$)" }, "lint-python-encoding": { "type": "lint-python-encoding", "include": "(\\.py$)", "exclude": [ "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ] }, "lint-python-shebang": { "type": "lint-python-shebang", "include": "(\\.py$)", "exclude": [ "(__init__\\.py$)", "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ] }, "lint-bash-shebang": { "type": "lint-bash-shebang", "include": "(\\.sh$)", "exclude": [ "(^contrib/gitian-builder/)" ] }, "shellcheck": { "type": "shellcheck", "version": ">=0.7.0", "flags": [ "--external-sources", "--source-path=SCRIPTDIR" ], "include": "(\\.sh$)", "exclude": [ "(^contrib/gitian-builder/)", "(^src/(secp256k1|univalue)/)" ] }, "lint-shell-locale": { "type": "lint-shell-locale", "include": "(\\.sh$)", "exclude": [ "(^contrib/gitian-builder/)", "(^src/(secp256k1|univalue)/)", "(^cmake/utils/log-and-print-on-failure.sh)" ] }, "lint-cpp-void-parameters": { "type": "lint-cpp-void-parameters", "include": "(^(src|chronik)/.*\\.(h|cpp)$)", "exclude": [ "(^src/(crypto/ctaes|secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)", "(^src/compat/glibc_compat.cpp$)" ] }, "lint-logs": { "type": "lint-logs", "include": "(^(src|chronik)/.*\\.(h|cpp|rs)$)" }, "lint-qt": { "type": "lint-qt", "include": "(^src/qt/.*\\.(h|cpp)$)", "exclude": [ "(^src/qt/(locale|forms|res)/)" ] }, "lint-doxygen": { "type": "lint-doxygen", "include": "(^(src|chronik)/.*\\.(h|c|cpp)$)", "exclude": [ "(^src/(crypto/ctaes|secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)" ] }, "lint-whitespace": { "type": "lint-whitespace", "include": "(\\.(ac|am|cmake|conf|in|include|json|m4|md|openrc|php|pl|rs|sh|txt|yml)$)", "exclude": [ "(^contrib/gitian-builder/)", "(^src/(secp256k1|univalue|leveldb)/)", "(^src/bench/nanobench.h$)" ] }, "yamllint": { "type": "yamllint", "include": "(\\.(yml|yaml)$)", "exclude": "(^src/(secp256k1|univalue|leveldb)/)" }, "lint-check-nonfatal": { "type": "lint-check-nonfatal", "include": [ "(^src/rpc/.*\\.(h|c|cpp)$)", "(^src/wallet/rpc*.*\\.(h|c|cpp)$)" ], "exclude": "(^src/rpc/server.cpp)" }, "lint-markdown": { "type": "lint-markdown", "include": [ "(\\.md$)" ], "exclude": "(^contrib/gitian-builder/)" }, "lint-python-mypy": { "type": "lint-python-mypy", "version": ">=0.910", "include": "(\\.py$)", "exclude": [ "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)", "(^contrib/macdeploy/)" ], "flags": [ "--ignore-missing-imports", "--install-types", "--non-interactive" ] }, "lint-python-mutable-default": { "type": "lint-python-mutable-default", "include": "(\\.py$)", "exclude": [ "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ] }, "prettier": { "type": "prettier", "version": ">=2.6.0", "include": [ "(^cashtab/.*\\.(css|html|js|json|jsx|md|scss|ts|tsx)$)", "(^web/.*\\.(css|html|js|json|jsx|md|scss|ts|tsx)$)" ], "exclude": "(^web/.*/translations/.*\\.json$)" }, "lint-python-isort": { "type": "lint-python-isort", "version": ">=5.6.4", "include": "(\\.py$)", "exclude": [ "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ] }, "rustfmt": { "type": "rustfmt", "version": ">=1.5.1", "include": "(\\.rs$)" }, "eslint": { "type": "eslint", "version": ">=8.0.0", "include": [ "(cashtab/.*\\.js$)", "(apps/alias-server/.*\\.js$)", "(modules/ecashaddrjs/.*\\.js$)", "(apps/ecash-herald/.*\\.js$)", "(modules/chronik-client/.*\\.(js|jsx|ts|tsx)$)" ] }, "lint-python-flynt": { "type": "lint-python-flynt", "version": ">=0.78", "include": "(\\.py$)", "exclude": [ "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ] } } } diff --git a/test/functional/wallet_abandonconflict.py b/test/functional/wallet_abandonconflict.py index 072518521..29965aa9d 100755 --- a/test/functional/wallet_abandonconflict.py +++ b/test/functional/wallet_abandonconflict.py @@ -1,238 +1,259 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the abandontransaction RPC. The abandontransaction RPC marks a transaction and all its in-wallet descendants as abandoned which allows their inputs to be respent. It can be used to replace "stuck" or evicted transactions. It only works on transactions which are not included in a block and are not currently in the mempool. It has no effect on transactions which are already abandoned. """ from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error, satoshi_round class AbandonConflictTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.extra_args = [["-minrelaytxfee=10"], []] # whitelist peers to speed up tx relay / mempool sync for args in self.extra_args: args.append("-whitelist=noban@127.0.0.1") def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): def total_fees(*txids): total = 0 for txid in txids: # '-=' is because gettransaction(txid)['fee'] returns a negative - total -= self.nodes[0].gettransaction(txid)['fee'] + total -= self.nodes[0].gettransaction(txid)["fee"] return satoshi_round(total) self.generate(self.nodes[1], 100) balance = self.nodes[0].getbalance() txA = self.nodes[0].sendtoaddress( - self.nodes[0].getnewaddress(), Decimal("10000000")) + self.nodes[0].getnewaddress(), Decimal("10000000") + ) txB = self.nodes[0].sendtoaddress( - self.nodes[0].getnewaddress(), Decimal("10000000")) + self.nodes[0].getnewaddress(), Decimal("10000000") + ) txC = self.nodes[0].sendtoaddress( - self.nodes[0].getnewaddress(), Decimal("10000000")) + self.nodes[0].getnewaddress(), Decimal("10000000") + ) 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: self.nodes[0].abandontransaction(txid="ff" * 32), + ) # Can not abandon confirmed transaction - assert_raises_rpc_error(-5, 'Transaction not eligible for abandonment', - lambda: self.nodes[0].abandontransaction(txid=txA)) + assert_raises_rpc_error( + -5, + "Transaction not eligible for abandonment", + lambda: self.nodes[0].abandontransaction(txid=txA), + ) newbalance = self.nodes[0].getbalance() # no more than fees lost assert balance - newbalance <= total_fees(txA, txB, txC) balance = newbalance # Disconnect nodes so node0's transactions don't get into node1's # mempool 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("10000000")) - nB = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction( - txB)["details"] if tx_out["amount"] == Decimal("10000000")) - nC = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction( - txC)["details"] if tx_out["amount"] == Decimal("10000000")) + nA = next( + tx_out["vout"] + for tx_out in self.nodes[0].gettransaction(txA)["details"] + if tx_out["amount"] == Decimal("10000000") + ) + nB = next( + tx_out["vout"] + for tx_out in self.nodes[0].gettransaction(txB)["details"] + if tx_out["amount"] == Decimal("10000000") + ) + nC = next( + tx_out["vout"] + for tx_out in self.nodes[0].gettransaction(txC)["details"] + if tx_out["amount"] == Decimal("10000000") + ) inputs = [] # spend 10btc outputs from txA and txB inputs.append({"txid": txA, "vout": nA}) inputs.append({"txid": txB, "vout": nB}) outputs = {} outputs[self.nodes[0].getnewaddress()] = Decimal("14999980") outputs[self.nodes[1].getnewaddress()] = Decimal("5000000") signed = self.nodes[0].signrawtransactionwithwallet( - self.nodes[0].createrawtransaction(inputs, outputs)) + self.nodes[0].createrawtransaction(inputs, outputs) + ) txAB1 = self.nodes[0].sendrawtransaction(signed["hex"]) # Identify the 14,999,980 XEC output - nAB = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction( - txAB1)["details"] if tx_out["amount"] == Decimal("14999980")) + nAB = next( + tx_out["vout"] + for tx_out in self.nodes[0].gettransaction(txAB1)["details"] + if tx_out["amount"] == Decimal("14999980") + ) # Create a child tx spending AB1 and C inputs = [] # Amount 14,999,980 XEC inputs.append({"txid": txAB1, "vout": nAB}) # Amount 10,000,000 XEC inputs.append({"txid": txC, "vout": nC}) outputs = {} outputs[self.nodes[0].getnewaddress()] = Decimal("24999600") signed2 = self.nodes[0].signrawtransactionwithwallet( - self.nodes[0].createrawtransaction(inputs, outputs)) + self.nodes[0].createrawtransaction(inputs, outputs) + ) txABC2 = self.nodes[0].sendrawtransaction(signed2["hex"]) # Create a child tx spending ABC2 signed3_change = Decimal("24999000") inputs = [{"txid": txABC2, "vout": 0}] outputs = {self.nodes[0].getnewaddress(): signed3_change} signed3 = self.nodes[0].signrawtransactionwithwallet( - self.nodes[0].createrawtransaction(inputs, outputs)) + self.nodes[0].createrawtransaction(inputs, outputs) + ) # note tx is never directly referenced, only abandoned as a child of # the above self.nodes[0].sendrawtransaction(signed3["hex"]) # In mempool txs from self should increase balance from change newbalance = self.nodes[0].getbalance() - assert_equal( - newbalance, - balance - - Decimal("30000000") + - signed3_change) + assert_equal(newbalance, balance - Decimal("30000000") + signed3_change) balance = newbalance # Restart the node with a higher min relay fee so the parent tx is no longer in mempool # TODO: redo with eviction self.restart_node(0, extra_args=["-minrelaytxfee=100"]) - assert self.nodes[0].getmempoolinfo()['loaded'] + assert self.nodes[0].getmempoolinfo()["loaded"] # Verify txs no longer in either node's mempool assert_equal(len(self.nodes[0].getrawmempool()), 0) assert_equal(len(self.nodes[1].getrawmempool()), 0) # Transactions which are not in the mempool should only reduce wallet balance. # Transaction inputs should still be spent, but the change not yet # received. newbalance = self.nodes[0].getbalance() assert_equal(newbalance, balance - signed3_change) # Unconfirmed received funds that are not in mempool also shouldn't show # up in unconfirmed balance. Note that the transactions stored in the wallet # are not necessarily in the node's mempool. - balances = self.nodes[0].getbalances()['mine'] - assert_equal( - balances['untrusted_pending'] + - balances['trusted'], - newbalance) + balances = self.nodes[0].getbalances()["mine"] + assert_equal(balances["untrusted_pending"] + balances["trusted"], newbalance) # Unconfirmed transactions which are not in the mempool should also # not be in listunspent - assert txABC2 not in [utxo["txid"] - for utxo in self.nodes[0].listunspent(0)] + assert txABC2 not in [utxo["txid"] for utxo in self.nodes[0].listunspent(0)] balance = newbalance # Abandon original transaction and verify inputs are available again # including that the child tx was also abandoned self.nodes[0].abandontransaction(txAB1) newbalance = self.nodes[0].getbalance() assert_equal(newbalance, balance + Decimal("30000000")) balance = newbalance self.log.info("Check abandoned transactions in listsinceblock") listsinceblock = self.nodes[0].listsinceblock() - txAB1_listsinceblock = [d for d in listsinceblock['transactions'] - if d['txid'] == txAB1 and d['category'] == 'send'] + 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) - assert_equal(tx['confirmations'], 0) - assert_equal(tx['trusted'], False) + assert_equal(tx["abandoned"], True) + assert_equal(tx["confirmations"], 0) + assert_equal(tx["trusted"], False) # Verify that even with a low min relay fee, the tx is not re-accepted # from wallet on startup once abandoned. self.restart_node(0, extra_args=["-minrelaytxfee=10"]) - assert self.nodes[0].getmempoolinfo()['loaded'] + assert self.nodes[0].getmempoolinfo()["loaded"] assert_equal(len(self.nodes[0].getrawmempool()), 0) assert_equal(self.nodes[0].getbalance(), balance) # If the transaction is re-sent the wallet also unabandons it. The # change should be available, and it's child transaction should remain # abandoned. # NOTE: Abandoned transactions are internal to the wallet, and tracked # separately from other indices. self.nodes[0].sendrawtransaction(signed["hex"]) newbalance = self.nodes[0].getbalance() - assert_equal( - newbalance, - balance - - Decimal("20000000") + - Decimal("14999980")) + assert_equal(newbalance, balance - Decimal("20000000") + Decimal("14999980")) balance = newbalance # Send child tx again so it is no longer abandoned. self.nodes[0].sendrawtransaction(signed2["hex"]) newbalance = self.nodes[0].getbalance() - assert_equal(newbalance, balance - Decimal("10000000") - - Decimal("14999980") + Decimal("24999600")) + assert_equal( + newbalance, + balance - Decimal("10000000") - Decimal("14999980") + Decimal("24999600"), + ) balance = newbalance # Reset to a higher relay fee so that we abandon a transaction self.restart_node(0, extra_args=["-minrelaytxfee=100"]) - assert self.nodes[0].getmempoolinfo()['loaded'] + assert self.nodes[0].getmempoolinfo()["loaded"] assert_equal(len(self.nodes[0].getrawmempool()), 0) newbalance = self.nodes[0].getbalance() assert_equal(newbalance, balance - Decimal("24999600")) balance = newbalance # Create a double spend of AB1. Spend it again from only A's 10 output. # Mine double spend from node 1. inputs = [] inputs.append({"txid": txA, "vout": nA}) outputs = {} outputs[self.nodes[1].getnewaddress()] = Decimal("9999900") 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) self.connect_nodes(0, 1) self.sync_blocks() # Verify that B and C's 10,000,000 XEC outputs are available for # spending again because AB1 is now conflicted newbalance = self.nodes[0].getbalance() assert_equal(newbalance, balance + Decimal("20000000")) balance = newbalance # There is currently a minor bug around this and so this test doesn't # work. See Issue #7315 # Invalidate the block with the double spend and B's 10,000,000 XEC # output should no longer be available. Don't think C's should either self.nodes[0].invalidateblock(self.nodes[0].getbestblockhash()) newbalance = self.nodes[0].getbalance() # assert_equal(newbalance, balance - Decimal("10000000")) self.log.info( - "If balance has not declined after invalidateblock then out of mempool wallet tx which is no longer") + "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") + "conflicted has not resumed causing its inputs to be seen as spent. See" + " Issue #7315" + ) self.log.info(f"{str(balance)} -> {str(newbalance)} ?") -if __name__ == '__main__': +if __name__ == "__main__": AbandonConflictTest().main() diff --git a/test/functional/wallet_address_types.py b/test/functional/wallet_address_types.py index a736366c5..a446ee6ab 100755 --- a/test/functional/wallet_address_types.py +++ b/test/functional/wallet_address_types.py @@ -1,316 +1,337 @@ #!/usr/bin/env python3 # Copyright (c) 2017 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test that the wallet can send and receive using all combinations of address types. There are 4 nodes-under-test: - node0 uses legacy addresses - node1 uses legacy addresses - node2 uses legacy addresses - node3 uses legacy addresses node4 exists to generate new blocks. ## Multisig address test Test that adding a multisig address with: - an uncompressed pubkey always gives a legacy address - only compressed pubkeys gives the an `-addresstype` address ## Sending to address types test A series of tests, iterating over node0-node3. In each iteration of the test, one node sends: - 10/101th of its balance to itself (using getrawchangeaddress for single key addresses) - 20/101th to the next node - 30/101th to the node after that - 40/101th to the remaining node - 1/101th remains as fee+change Iterate over each node for single key addresses, and then over each node for multisig addresses. Repeat test. As every node sends coins after receiving, this also verifies that spending coins sent to all these address types works. """ import itertools from decimal import Decimal from test_framework.descriptors import descsum_check, descsum_create from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_greater_than class AddressTypeTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 5 # whitelist all peers to speed up tx relay / mempool sync self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() def setup_network(self): self.setup_nodes() # Fully mesh-connect nodes for faster mempool sync for i, j in itertools.product(range(self.num_nodes), repeat=2): if i > j: self.connect_nodes(i, j) self.sync_all() - def get_balances(self, key='trusted'): + def get_balances(self, key="trusted"): """Return a list of balances.""" - return [self.nodes[i].getbalances()['mine'][key] for i in range(4)] + return [self.nodes[i].getbalances()["mine"][key] for i in range(4)] def test_address(self, node, address, multisig, typ): """Run sanity checks on an address.""" self.log.info(address) info = self.nodes[node].getaddressinfo(address) - assert self.nodes[node].validateaddress(address)['isvalid'] - assert_equal(info.get('solvable'), True) + assert self.nodes[node].validateaddress(address)["isvalid"] + assert_equal(info.get("solvable"), True) - if not multisig and typ == 'legacy': + if not multisig and typ == "legacy": # P2PKH - assert not info['isscript'] - assert 'pubkey' in info - elif typ == 'legacy': + assert not info["isscript"] + assert "pubkey" in info + elif typ == "legacy": # P2SH-multisig - assert info['isscript'] - assert_equal(info['script'], 'multisig') - assert 'pubkeys' in info + assert info["isscript"] + assert_equal(info["script"], "multisig") + assert "pubkeys" in info else: # Unknown type assert False def test_desc(self, node, address, multisig, typ, utxo): """Run sanity checks on a descriptor reported by getaddressinfo.""" info = self.nodes[node].getaddressinfo(address) - assert 'desc' in info + assert "desc" in info - assert_equal(info['desc'], utxo['desc']) - assert self.nodes[node].validateaddress(address)['isvalid'] + assert_equal(info["desc"], utxo["desc"]) + assert self.nodes[node].validateaddress(address)["isvalid"] # Use a ridiculously roundabout way to find the key origin info through # the PSBT logic. However, this does test consistency between the PSBT reported # fingerprints/paths and the descriptor logic. psbt = self.nodes[node].createpsbt( - [{'txid': utxo['txid'], 'vout':utxo['vout']}], [{address: 100.00}]) - psbt = self.nodes[node].walletprocesspsbt( - psbt, False, "ALL|FORKID", True) - decode = self.nodes[node].decodepsbt(psbt['psbt']) + [{"txid": utxo["txid"], "vout": utxo["vout"]}], [{address: 100.00}] + ) + psbt = self.nodes[node].walletprocesspsbt(psbt, False, "ALL|FORKID", True) + decode = self.nodes[node].decodepsbt(psbt["psbt"]) key_descs = {} - for deriv in decode['inputs'][0]['bip32_derivs']: - assert_equal(len(deriv['master_fingerprint']), 8) - assert_equal(deriv['path'][0], 'm') - key_descs[deriv['pubkey']] = '[' + deriv['master_fingerprint'] + \ - deriv['path'][1:] + ']' + deriv['pubkey'] + for deriv in decode["inputs"][0]["bip32_derivs"]: + assert_equal(len(deriv["master_fingerprint"]), 8) + assert_equal(deriv["path"][0], "m") + key_descs[deriv["pubkey"]] = ( + "[" + + deriv["master_fingerprint"] + + deriv["path"][1:] + + "]" + + deriv["pubkey"] + ) # Verify the descriptor checksum against the Python implementation - assert descsum_check(info['desc']) + assert descsum_check(info["desc"]) # Verify that stripping the checksum and recreating it using Python # roundtrips - assert info['desc'] == descsum_create(info['desc'][:-9]) + assert info["desc"] == descsum_create(info["desc"][:-9]) # Verify that stripping the checksum and feeding it to # getdescriptorinfo roundtrips - assert info['desc'] == self.nodes[0].getdescriptorinfo( - info['desc'][:-9])['descriptor'] + assert ( + info["desc"] + == self.nodes[0].getdescriptorinfo(info["desc"][:-9])["descriptor"] + ) assert_equal( - info['desc'][-8:], self.nodes[0].getdescriptorinfo(info['desc'][:-9])['checksum']) + info["desc"][-8:], + self.nodes[0].getdescriptorinfo(info["desc"][:-9])["checksum"], + ) # Verify that keeping the checksum and feeding it to getdescriptorinfo # roundtrips - assert info['desc'] == self.nodes[0].getdescriptorinfo(info['desc'])[ - 'descriptor'] - assert_equal(info['desc'][-8:], - self.nodes[0].getdescriptorinfo(info['desc'])['checksum']) + assert ( + info["desc"] == self.nodes[0].getdescriptorinfo(info["desc"])["descriptor"] + ) + assert_equal( + info["desc"][-8:], self.nodes[0].getdescriptorinfo(info["desc"])["checksum"] + ) - if not multisig and typ == 'legacy': + if not multisig and typ == "legacy": # P2PKH - assert_equal(info['desc'], - descsum_create(f"pkh({key_descs[info['pubkey']]})")) - elif typ == 'legacy': + assert_equal( + info["desc"], descsum_create(f"pkh({key_descs[info['pubkey']]})") + ) + elif typ == "legacy": # P2SH-multisig - assert_equal(info['desc'], - descsum_create(f"sh(multi(2,{key_descs[info['pubkeys'][0]]}," - f"{key_descs[info['pubkeys'][1]]}))")) + assert_equal( + info["desc"], + descsum_create( + f"sh(multi(2,{key_descs[info['pubkeys'][0]]}," + f"{key_descs[info['pubkeys'][1]]}))" + ), + ) else: # Unknown type assert False - def test_change_output_type( - self, node_sender, destinations, expected_type): + def test_change_output_type(self, node_sender, destinations, expected_type): txid = self.nodes[node_sender].sendmany( - dummy="", amounts=dict.fromkeys( - destinations, 1000.00)) - tx = self.nodes[node_sender].gettransaction( - txid=txid, verbose=True)['decoded'] + dummy="", amounts=dict.fromkeys(destinations, 1000.00) + ) + tx = self.nodes[node_sender].gettransaction(txid=txid, verbose=True)["decoded"] # Make sure the transaction has change: assert_equal(len(tx["vout"]), len(destinations) + 1) # Make sure the destinations are included, and remove them: - output_addresses = [vout['scriptPubKey']['addresses'][0] - for vout in tx["vout"]] - change_addresses = [ - d for d in output_addresses if d not in destinations] + output_addresses = [vout["scriptPubKey"]["addresses"][0] for vout in tx["vout"]] + change_addresses = [d for d in output_addresses if d not in destinations] assert_equal(len(change_addresses), 1) self.log.debug( - f"Check if change address {change_addresses[0]} is {expected_type}") + f"Check if change address {change_addresses[0]} is {expected_type}" + ) self.test_address( - node_sender, - change_addresses[0], - multisig=False, - typ=expected_type) + node_sender, change_addresses[0], multisig=False, typ=expected_type + ) def run_test(self): # Mine 101 blocks on node4 to bring nodes out of IBD and make sure that # no coinbases are maturing for the nodes-under-test during the test self.generate(self.nodes[4], 101) uncompressed_1 = "0496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858ee" uncompressed_2 = "047211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073dee6c89064984f03385237d92167c13e236446b417ab79a0fcae412ae3316b77" - compressed_1 = "0296b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52" - compressed_2 = "037211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073" + compressed_1 = ( + "0296b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52" + ) + compressed_2 = ( + "037211a824f55b505228e4c3d5194c1fcfaa15a456abdf37f9b9d97a4040afc073" + ) if not self.options.descriptors: # Tests for addmultisigaddress's address type behavior is only for # legacy wallets. Descriptor wallets do not have addmultsigaddress # so these tests are not needed for those. # addmultisigaddress with at least 1 uncompressed key should return # a legacy address. for node in range(4): self.test_address( node, self.nodes[node].addmultisigaddress( - 2, [uncompressed_1, uncompressed_2])['address'], + 2, [uncompressed_1, uncompressed_2] + )["address"], True, - 'legacy') + "legacy", + ) self.test_address( node, self.nodes[node].addmultisigaddress( - 2, [compressed_1, uncompressed_2])['address'], + 2, [compressed_1, uncompressed_2] + )["address"], True, - 'legacy') + "legacy", + ) self.test_address( node, self.nodes[node].addmultisigaddress( - 2, [uncompressed_1, compressed_2])['address'], + 2, [uncompressed_1, compressed_2] + )["address"], True, - 'legacy') + "legacy", + ) # addmultisigaddress with all compressed keys should return the # appropriate address type (even when the keys are not ours). self.test_address( 0, - self.nodes[0].addmultisigaddress( - 2, [compressed_1, compressed_2])['address'], + self.nodes[0].addmultisigaddress(2, [compressed_1, compressed_2])[ + "address" + ], True, - 'legacy') + "legacy", + ) do_multisigs = [False] if not self.options.descriptors: do_multisigs.append(True) for multisig, from_node in itertools.product(do_multisigs, range(4)): self.log.info( - f"Sending from node {from_node} with{'' if multisig else 'out'} multisig") + "Sending from node" + f" {from_node} with{'' if multisig else 'out'} multisig" + ) old_balances = self.get_balances() self.log.debug(f"Old balances are {old_balances}") - to_send = ( - old_balances[from_node] / - 101).quantize( - Decimal("0.01")) + to_send = (old_balances[from_node] / 101).quantize(Decimal("0.01")) sends = {} addresses = {} self.log.debug("Prepare sends") for n, to_node in enumerate(range(from_node, from_node + 4)): to_node %= 4 if not multisig: if from_node == to_node: # When sending non-multisig to self, use # getrawchangeaddress address = self.nodes[to_node].getrawchangeaddress() else: address = self.nodes[to_node].getnewaddress() else: addr1 = self.nodes[to_node].getnewaddress() addr2 = self.nodes[to_node].getnewaddress() address = self.nodes[to_node].addmultisigaddress(2, [addr1, addr2])[ - 'address'] + "address" + ] # Do some sanity checking on the created address - typ = 'legacy' + typ = "legacy" self.test_address(to_node, address, multisig, typ) # Output entry sends[address] = to_send * 10 * (1 + n) addresses[to_node] = (address, typ) self.log.debug(f"Sending: {sends}") self.nodes[from_node].sendmany("", sends) self.sync_mempools() - unconf_balances = self.get_balances('untrusted_pending') - self.log.debug( - f"Check unconfirmed balances: {unconf_balances}") + unconf_balances = self.get_balances("untrusted_pending") + self.log.debug(f"Check unconfirmed balances: {unconf_balances}") assert_equal(unconf_balances[from_node], 0) for n, to_node in enumerate(range(from_node + 1, from_node + 4)): to_node %= 4 assert_equal(unconf_balances[to_node], to_send * 10 * (2 + n)) # node4 collects fee and block subsidy to keep accounting simple self.generate(self.nodes[4], 1) # Verify that the receiving wallet contains a UTXO with the # expected address, and expected descriptor for n, to_node in enumerate(range(from_node, from_node + 4)): to_node %= 4 found = False for utxo in self.nodes[to_node].listunspent(): - if utxo['address'] == addresses[to_node][0]: + if utxo["address"] == addresses[to_node][0]: found = True self.test_desc( to_node, addresses[to_node][0], multisig, addresses[to_node][1], - utxo) + utxo, + ) break assert found new_balances = self.get_balances() self.log.debug(f"Check new balances: {new_balances}") # We don't know what fee was set, so we can only check bounds on # the balance of the sending node assert_greater_than(new_balances[from_node], to_send * 10) assert_greater_than(to_send * 11, new_balances[from_node]) for n, to_node in enumerate(range(from_node + 1, from_node + 4)): to_node %= 4 assert_equal( - new_balances[to_node], old_balances[to_node] + to_send * 10 * (2 + n)) + new_balances[to_node], + old_balances[to_node] + to_send * 10 * (2 + n), + ) # Get addresses from node2 and node3: to_address_2 = self.nodes[2].getnewaddress() to_address_3_1 = self.nodes[3].getnewaddress() to_address_3_2 = self.nodes[3].getnewaddress() self.log.info("Various change output tests") - self.test_change_output_type(0, [to_address_3_1], 'legacy') - self.test_change_output_type(1, [to_address_2], 'legacy') - self.test_change_output_type(1, [to_address_3_1], 'legacy') - self.test_change_output_type( - 1, [to_address_2, to_address_3_1], 'legacy') - self.test_change_output_type( - 1, [to_address_3_1, to_address_3_2], 'legacy') - self.test_change_output_type(2, [to_address_3_1], 'legacy') - - self.log.info('Test getrawchangeaddress') + self.test_change_output_type(0, [to_address_3_1], "legacy") + self.test_change_output_type(1, [to_address_2], "legacy") + self.test_change_output_type(1, [to_address_3_1], "legacy") + self.test_change_output_type(1, [to_address_2, to_address_3_1], "legacy") + self.test_change_output_type(1, [to_address_3_1, to_address_3_2], "legacy") + self.test_change_output_type(2, [to_address_3_1], "legacy") + + self.log.info("Test getrawchangeaddress") self.test_address( - 3, - self.nodes[3].getrawchangeaddress(), - multisig=False, - typ='legacy') + 3, self.nodes[3].getrawchangeaddress(), multisig=False, typ="legacy" + ) -if __name__ == '__main__': +if __name__ == "__main__": AddressTypeTest().main() diff --git a/test/functional/wallet_avoidreuse.py b/test/functional/wallet_avoidreuse.py index e9f7e0ccf..0b9898136 100755 --- a/test/functional/wallet_avoidreuse.py +++ b/test/functional/wallet_avoidreuse.py @@ -1,468 +1,454 @@ #!/usr/bin/env python3 # Copyright (c) 2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the avoid_reuse and setwalletflag features.""" from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_approx, assert_equal, assert_raises_rpc_error def reset_balance(node, discardaddr): - '''Throw away all owned coins by the node so it gets a balance of 0.''' + """Throw away all owned coins by the node so it gets a balance of 0.""" balance = node.getbalance(avoid_reuse=False) if balance > 500000: node.sendtoaddress( address=discardaddr, amount=balance, subtractfeefromamount=True, - avoid_reuse=False) + avoid_reuse=False, + ) def count_unspent(node): - '''Count the unspent outputs for the given node and return various statistics''' + """Count the unspent outputs for the given node and return various statistics""" r = { "total": { "count": 0, "sum": 0, }, "reused": { "count": 0, "sum": 0, }, } supports_reused = True for utxo in node.listunspent(minconf=0): r["total"]["count"] += 1 r["total"]["sum"] += utxo["amount"] if supports_reused and "reused" in utxo: if utxo["reused"]: r["reused"]["count"] += 1 r["reused"]["sum"] += utxo["amount"] else: supports_reused = False r["reused"]["supported"] = supports_reused return r -def assert_unspent(node, total_count=None, total_sum=None, - reused_supported=None, reused_count=None, reused_sum=None): - '''Make assertions about a node's unspent output statistics''' +def assert_unspent( + node, + total_count=None, + total_sum=None, + reused_supported=None, + reused_count=None, + reused_sum=None, +): + """Make assertions about a node's unspent output statistics""" stats = count_unspent(node) if total_count is not None: assert_equal(stats["total"]["count"], total_count) if total_sum is not None: assert_approx(stats["total"]["sum"], total_sum, 1000) if reused_supported is not None: assert_equal(stats["reused"]["supported"], reused_supported) if reused_count is not None: assert_equal(stats["reused"]["count"], reused_count) if reused_sum is not None: assert_approx(stats["reused"]["sum"], reused_sum, 0.001) def assert_balances(node, mine): - '''Make assertions about a node's getbalances output''' + """Make assertions about a node's getbalances output""" got = node.getbalances()["mine"] for k, v in mine.items(): assert_approx(got[k], v, 1000) class AvoidReuseTest(BitcoinTestFramework): - def set_test_params(self): self.num_nodes = 2 # This test isn't testing txn relay/timing, so set whitelist on the # peers for instant txn relay. This speeds up the test run time 2-3x. self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): - '''Set up initial chain and run tests defined below''' + """Set up initial chain and run tests defined below""" self.test_persistence() self.test_immutable() self.generate(self.nodes[0], 110) self.test_change_remains_change(self.nodes[1]) reset_balance(self.nodes[1], self.nodes[0].getnewaddress()) self.test_sending_from_reused_address_without_avoid_reuse() reset_balance(self.nodes[1], self.nodes[0].getnewaddress()) self.test_sending_from_reused_address_fails() reset_balance(self.nodes[1], self.nodes[0].getnewaddress()) self.test_getbalances_used() reset_balance(self.nodes[1], self.nodes[0].getnewaddress()) self.test_full_destination_group_is_preferred() reset_balance(self.nodes[1], self.nodes[0].getnewaddress()) self.test_all_destination_groups_are_used() def test_persistence(self): - '''Test that wallet files persist the avoid_reuse flag.''' + """Test that wallet files persist the avoid_reuse flag.""" self.log.info("Test wallet files persist avoid_reuse flag") # Configure node 1 to use avoid_reuse - self.nodes[1].setwalletflag('avoid_reuse') + self.nodes[1].setwalletflag("avoid_reuse") # Flags should be node1.avoid_reuse=false, node2.avoid_reuse=true assert_equal(self.nodes[0].getwalletinfo()["avoid_reuse"], False) assert_equal(self.nodes[1].getwalletinfo()["avoid_reuse"], True) # Stop and restart node 1 self.restart_node(1) self.connect_nodes(0, 1) # Flags should still be node1.avoid_reuse=false, node2.avoid_reuse=true assert_equal(self.nodes[0].getwalletinfo()["avoid_reuse"], False) assert_equal(self.nodes[1].getwalletinfo()["avoid_reuse"], True) # Attempting to set flag to its current state should throw - assert_raises_rpc_error(-8, - "Wallet flag is already set to false", - self.nodes[0].setwalletflag, - 'avoid_reuse', - False) - assert_raises_rpc_error(-8, - "Wallet flag is already set to true", - self.nodes[1].setwalletflag, - 'avoid_reuse', - True) + assert_raises_rpc_error( + -8, + "Wallet flag is already set to false", + self.nodes[0].setwalletflag, + "avoid_reuse", + False, + ) + assert_raises_rpc_error( + -8, + "Wallet flag is already set to true", + self.nodes[1].setwalletflag, + "avoid_reuse", + True, + ) def test_immutable(self): - '''Test immutable wallet flags''' + """Test immutable wallet flags""" self.log.info("Test immutable wallet flags") # Attempt to set the disable_private_keys flag; this should not work - assert_raises_rpc_error(-8, - "Wallet flag is immutable", - self.nodes[1].setwalletflag, - 'disable_private_keys') + assert_raises_rpc_error( + -8, + "Wallet flag is immutable", + self.nodes[1].setwalletflag, + "disable_private_keys", + ) tempwallet = ".wallet_avoidreuse.py_test_immutable_wallet.dat" # Create a wallet with disable_private_keys set; this should work - self.nodes[1].createwallet(wallet_name=tempwallet, - disable_private_keys=True) + self.nodes[1].createwallet(wallet_name=tempwallet, disable_private_keys=True) w = self.nodes[1].get_wallet_rpc(tempwallet) # Attempt to unset the disable_private_keys flag; this should not work - assert_raises_rpc_error(-8, - "Wallet flag is immutable", - w.setwalletflag, - 'disable_private_keys', - False) + assert_raises_rpc_error( + -8, + "Wallet flag is immutable", + w.setwalletflag, + "disable_private_keys", + False, + ) # Unload temp wallet self.nodes[1].unloadwallet(tempwallet) def test_change_remains_change(self, node): - self.log.info( - "Test that change doesn't turn into non-change when spent") + self.log.info("Test that change doesn't turn into non-change when spent") reset_balance(node, node.getnewaddress()) addr = node.getnewaddress() txid = node.sendtoaddress(addr, 1000000) - out = node.listunspent( - minconf=0, query_options={ - 'minimumAmount': 2000000}) + out = node.listunspent(minconf=0, query_options={"minimumAmount": 2000000}) assert_equal(len(out), 1) - assert_equal(out[0]['txid'], txid) - changeaddr = out[0]['address'] + assert_equal(out[0]["txid"], txid) + changeaddr = out[0]["address"] # Make sure it's starting out as change as expected - assert node.getaddressinfo(changeaddr)['ischange'] + assert node.getaddressinfo(changeaddr)["ischange"] for logical_tx in node.listtransactions(): - assert logical_tx.get('address') != changeaddr + assert logical_tx.get("address") != changeaddr # Spend it reset_balance(node, node.getnewaddress()) # It should still be change - assert node.getaddressinfo(changeaddr)['ischange'] + assert node.getaddressinfo(changeaddr)["ischange"] for logical_tx in node.listtransactions(): - assert logical_tx.get('address') != changeaddr + assert logical_tx.get("address") != changeaddr def test_sending_from_reused_address_without_avoid_reuse(self): - ''' + """ Test the same as test_sending_from_reused_address_fails, except send the 10MM XEC with the avoid_reuse flag set to false. This means the 10MM XEC send should succeed, where it fails in test_sending_from_reused_address_fails. - ''' - self.log.info( - "Test sending from reused address with avoid_reuse=false") + """ + self.log.info("Test sending from reused address with avoid_reuse=false") fundaddr = self.nodes[1].getnewaddress() retaddr = self.nodes[0].getnewaddress() self.nodes[0].sendtoaddress(fundaddr, 10000000) self.generate(self.nodes[0], 1) # listunspent should show 1 single, unused 10MM XEC output assert_unspent( self.nodes[1], total_count=1, total_sum=10000000, reused_supported=True, - reused_count=0) + reused_count=0, + ) # getbalances should show no used, 10MM XEC trusted assert_balances(self.nodes[1], mine={"used": 0, "trusted": 10000000}) # node 0 should not show a used entry, as it does not enable # avoid_reuse assert "used" not in self.nodes[0].getbalances()["mine"] self.nodes[1].sendtoaddress(retaddr, 5000000) self.generate(self.nodes[0], 1) # listunspent should show 1 single, unused 5MM XEC output assert_unspent( self.nodes[1], total_count=1, total_sum=5000000, reused_supported=True, - reused_count=0) + reused_count=0, + ) # getbalances should show no used, 5MM XEC trusted assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5000000}) self.nodes[0].sendtoaddress(fundaddr, 10000000) self.generate(self.nodes[0], 1) # listunspent should show 2 total outputs (5MM, 10MM XEC), one unused # (5MM), one reused (10MM) assert_unspent( self.nodes[1], total_count=2, total_sum=15000000, reused_count=1, - reused_sum=10000000) + reused_sum=10000000, + ) # getbalances should show 10MM used, 5MM XEC trusted - assert_balances( - self.nodes[1], - mine={ - "used": 10000000, - "trusted": 5000000}) + assert_balances(self.nodes[1], mine={"used": 10000000, "trusted": 5000000}) - self.nodes[1].sendtoaddress( - address=retaddr, amount=10000000, avoid_reuse=False) + self.nodes[1].sendtoaddress(address=retaddr, amount=10000000, avoid_reuse=False) # listunspent should show 1 total outputs (5MM XEC), unused - assert_unspent( - self.nodes[1], - total_count=1, - total_sum=5000000, - reused_count=0) + assert_unspent(self.nodes[1], total_count=1, total_sum=5000000, reused_count=0) # getbalances should show no used, 5MM XEC trusted assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5000000}) # node 1 should now have about 5MM XEC left (for both cases) assert_approx(self.nodes[1].getbalance(), 5000000, 1000) - assert_approx( - self.nodes[1].getbalance( - avoid_reuse=False), - 5000000, - 1000) + assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 5000000, 1000) def test_sending_from_reused_address_fails(self): - ''' + """ Test the simple case where [1] generates a new address A, then [0] sends 10MM XEC to A. [1] spends 5MM XEC from A. (leaving roughly 5MM XEC useable) [0] sends 10MM XEC to A again. [1] tries to spend 10MM XEC (fails; dirty). [1] tries to spend 4MM XEC (succeeds; change address sufficient) - ''' + """ self.log.info("Test sending from reused address fails") fundaddr = self.nodes[1].getnewaddress(label="", address_type="legacy") retaddr = self.nodes[0].getnewaddress() self.nodes[0].sendtoaddress(fundaddr, 10000000) self.generate(self.nodes[0], 1) # listunspent should show 1 single, unused 10MM XEC output assert_unspent( self.nodes[1], total_count=1, total_sum=10000000, reused_supported=True, - reused_count=0) + reused_count=0, + ) # getbalances should show no used, 10MM XEC trusted assert_balances(self.nodes[1], mine={"used": 0, "trusted": 10000000}) self.nodes[1].sendtoaddress(retaddr, 5000000) self.generate(self.nodes[0], 1) # listunspent should show 1 single, unused 5MM XEC output assert_unspent( self.nodes[1], total_count=1, total_sum=5000000, reused_supported=True, - reused_count=0) + reused_count=0, + ) # getbalances should show no used, 5MM XEC trusted assert_balances(self.nodes[1], mine={"used": 0, "trusted": 5000000}) if not self.options.descriptors: # For the second send, we transmute it to a related single-key address # to make sure it's also detected as re-use # NB: this is not very useful for ABC, but we keep the new variable # name for consistency. new_fundaddr = fundaddr self.nodes[0].sendtoaddress(new_fundaddr, 10000000) self.generate(self.nodes[0], 1) # listunspent should show 2 total outputs (5MM, 10MM XEC), one unused # (5MM), one reused (10MM) assert_unspent( self.nodes[1], total_count=2, total_sum=15000000, reused_count=1, - reused_sum=10000000) + reused_sum=10000000, + ) # getbalances should show 10MM used, 5MM XEC trusted - assert_balances( - self.nodes[1], - mine={ - "used": 10000000, - "trusted": 5000000}) + assert_balances(self.nodes[1], mine={"used": 10000000, "trusted": 5000000}) # node 1 should now have a balance of 5MM (no dirty) or 15MM # (including dirty) assert_approx(self.nodes[1].getbalance(), 5000000, 1000) - assert_approx( - self.nodes[1].getbalance( - avoid_reuse=False), - 15000000, - 1000) + assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 15000000, 1000) - assert_raises_rpc_error(-6, "Insufficient funds", - self.nodes[1].sendtoaddress, retaddr, 10000000) + assert_raises_rpc_error( + -6, "Insufficient funds", self.nodes[1].sendtoaddress, retaddr, 10000000 + ) self.nodes[1].sendtoaddress(retaddr, 4000000) # listunspent should show 2 total outputs (1MM, 10MM XEC), one unused # (1MM), one reused (10MM) assert_unspent( self.nodes[1], total_count=2, total_sum=11000000, reused_count=1, - reused_sum=10000000) + reused_sum=10000000, + ) # getbalances should show 10MM used, 1MM XEC trusted - assert_balances( - self.nodes[1], - mine={ - "used": 10000000, - "trusted": 1000000}) + assert_balances(self.nodes[1], mine={"used": 10000000, "trusted": 1000000}) # node 1 should now have about 1MM XEC left (no dirty) and 11MM # (including dirty) assert_approx(self.nodes[1].getbalance(), 1000000, 1000) - assert_approx( - self.nodes[1].getbalance( - avoid_reuse=False), - 11000000, - 1000) + assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 11000000, 1000) def test_getbalances_used(self): - ''' + """ getbalances and listunspent should pick up on reused addresses immediately, even for address reusing outputs created before the first transaction was spending from that address - ''' + """ self.log.info("Test getbalances used category") # node under test should be completely empty assert_equal(self.nodes[1].getbalance(avoid_reuse=False), 0) new_addr = self.nodes[1].getnewaddress() ret_addr = self.nodes[0].getnewaddress() # send multiple transactions, reusing one address for _ in range(11): self.nodes[0].sendtoaddress(new_addr, 1000000) self.generate(self.nodes[0], 1) # send transaction that should not use all the available outputs # per the current coin selection algorithm self.nodes[1].sendtoaddress(ret_addr, 5000000) # getbalances and listunspent should show the remaining outputs # in the reused address as used/reused assert_unspent( self.nodes[1], total_count=2, total_sum=6000000, reused_count=1, - reused_sum=1000000) - assert_balances( - self.nodes[1], - mine={ - "used": 1000000, - "trusted": 5000000}) + reused_sum=1000000, + ) + assert_balances(self.nodes[1], mine={"used": 1000000, "trusted": 5000000}) def test_full_destination_group_is_preferred(self): - ''' + """ Test the case where [1] only has 11 outputs of 1MM XEC in the same reused address and tries to send a small payment of 500,000 XEC. The wallet should use 10 outputs from the reused address as inputs and not a single 1MM XEC input, in order to join several outputs from the reused address. - ''' + """ self.log.info( - "Test that full destination groups are preferred in coin selection") + "Test that full destination groups are preferred in coin selection" + ) # Node under test should be empty assert_equal(self.nodes[1].getbalance(avoid_reuse=False), 0) new_addr = self.nodes[1].getnewaddress() ret_addr = self.nodes[0].getnewaddress() # Send 11 outputs of 1MM XEC to the same, reused address in the wallet for _ in range(11): self.nodes[0].sendtoaddress(new_addr, 1000000) self.generate(self.nodes[0], 1) # Sending a transaction that is smaller than each one of the # available outputs txid = self.nodes[1].sendtoaddress(address=ret_addr, amount=500000) inputs = self.nodes[1].getrawtransaction(txid, 1)["vin"] # The transaction should use 10 inputs exactly assert_equal(len(inputs), 10) def test_all_destination_groups_are_used(self): - ''' + """ Test the case where [1] only has 22 outputs of 1MM XEC in the same reused address and tries to send a payment of 20,5MM XEC. The wallet should use all 22 outputs from the reused address as inputs. - ''' + """ self.log.info("Test that all destination groups are used") # Node under test should be empty assert_equal(self.nodes[1].getbalance(avoid_reuse=False), 0) new_addr = self.nodes[1].getnewaddress() ret_addr = self.nodes[0].getnewaddress() # Send 22 outputs of 1MM XEC to the same, reused address in the wallet for _ in range(22): self.nodes[0].sendtoaddress(new_addr, 1000000) self.generate(self.nodes[0], 1) # Sending a transaction that needs to use the full groups # of 10 inputs but also the incomplete group of 2 inputs. txid = self.nodes[1].sendtoaddress(address=ret_addr, amount=20500000) inputs = self.nodes[1].getrawtransaction(txid, 1)["vin"] # The transaction should use 22 inputs exactly assert_equal(len(inputs), 22) -if __name__ == '__main__': +if __name__ == "__main__": AvoidReuseTest().main() diff --git a/test/functional/wallet_backup.py b/test/functional/wallet_backup.py index a178c860e..d25220caa 100755 --- a/test/functional/wallet_backup.py +++ b/test/functional/wallet_backup.py @@ -1,257 +1,280 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet backup features. Test case is: 4 nodes. 1 2 and 3 send transactions between each other, fourth node is a miner. 1 2 3 each mine a block to start, then Miner creates 100 blocks so 1 2 3 each have 50 mature coins to spend. Then 5 iterations of 1/2/3 sending coins amongst themselves to get transactions in the wallets, and the miner mining one block. Wallets are backed up using dumpwallet/backupwallet. Then 5 more iterations of transactions and mining a block. Miner then generates 101 more blocks, so any transaction fees paid mature. Sanity check: Sum(1,2,3,4 balances) == 114*50 1/2/3 are shutdown, and their wallets erased. Then restore using wallet.dat backup. And confirm 1/2/3/4 balances are same as before. Shutdown again, restore using importwallet, and confirm again balances are correct. """ import os import shutil from decimal import Decimal from random import randint from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error class WalletBackupTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.setup_clean_chain = True # nodes 1, 2,3 are spenders, let's give them a keypool=100 # whitelist all peers to speed up tx relay / mempool sync self.extra_args = [ ["-whitelist=noban@127.0.0.1", "-keypool=100"], ["-whitelist=noban@127.0.0.1", "-keypool=100"], ["-whitelist=noban@127.0.0.1", "-keypool=100"], ["-whitelist=noban@127.0.0.1"], ] self.rpc_timeout = 120 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def setup_network(self): self.setup_nodes() self.connect_nodes(0, 3) self.connect_nodes(1, 3) self.connect_nodes(2, 3) self.connect_nodes(2, 0) self.sync_all() def one_send(self, from_node, to_address): - if (randint(1, 2) == 1): + if randint(1, 2) == 1: amount = Decimal(randint(1, 10)) * Decimal(100000) self.nodes[from_node].sendtoaddress(to_address, amount) def do_one_round(self): a0 = self.nodes[0].getnewaddress() a1 = self.nodes[1].getnewaddress() a2 = self.nodes[2].getnewaddress() self.one_send(0, a1) self.one_send(0, a2) self.one_send(1, a0) self.one_send(1, a2) self.one_send(2, a0) self.one_send(2, a1) # Have the miner (node3) mine a block. # Must sync mempools before mining. self.sync_mempools() self.generate(self.nodes[3], 1) # As above, this mirrors the original bash test. def start_three(self): self.start_node(0) self.start_node(1) self.start_node(2) self.connect_nodes(0, 3) self.connect_nodes(1, 3) self.connect_nodes(2, 3) self.connect_nodes(2, 0) def stop_three(self): self.stop_node(0) self.stop_node(1) self.stop_node(2) def erase_three(self): os.remove( - os.path.join(self.nodes[0].datadir, self.chain, 'wallets', - self.default_wallet_name, self.wallet_data_filename)) + os.path.join( + self.nodes[0].datadir, + self.chain, + "wallets", + self.default_wallet_name, + self.wallet_data_filename, + ) + ) os.remove( - os.path.join(self.nodes[1].datadir, self.chain, 'wallets', - self.default_wallet_name, self.wallet_data_filename)) + 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)) + os.path.join( + self.nodes[2].datadir, + self.chain, + "wallets", + self.default_wallet_name, + self.wallet_data_filename, + ) + ) def restore_nonexistent_wallet(self): node = self.nodes[3] nonexistent_wallet_file = os.path.join( - self.nodes[0].datadir, 'nonexistent_wallet.bak') + 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) + assert_raises_rpc_error( + -8, + "Backup file does not exist", + node.restorewallet, + wallet_name, + nonexistent_wallet_file, + ) def restore_wallet_existent_name(self): node = self.nodes[3] - wallet_file = os.path.join(self.nodes[0].datadir, 'wallet.bak') + wallet_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) + assert_raises_rpc_error( + -8, + "Wallet name already exists.", + node.restorewallet, + wallet_name, + wallet_file, + ) def run_test(self): self.log.info("Generating initial blockchain") self.generate(self.nodes[0], 1) self.generate(self.nodes[1], 1) self.generate(self.nodes[2], 1) self.generate(self.nodes[3], 100) assert_equal(self.nodes[0].getbalance(), 50000000) assert_equal(self.nodes[1].getbalance(), 50000000) assert_equal(self.nodes[2].getbalance(), 50000000) assert_equal(self.nodes[3].getbalance(), 0) self.log.info("Creating transactions") # Five rounds of sending each other transactions. for _ in range(5): self.do_one_round() self.log.info("Backing up") - self.nodes[0].backupwallet(os.path.join( - self.nodes[0].datadir, 'wallet.bak')) - self.nodes[0].dumpwallet(os.path.join( - self.nodes[0].datadir, 'wallet.dump')) - self.nodes[1].backupwallet(os.path.join( - self.nodes[1].datadir, 'wallet.bak')) - self.nodes[1].dumpwallet(os.path.join( - self.nodes[1].datadir, 'wallet.dump')) - self.nodes[2].backupwallet(os.path.join( - self.nodes[2].datadir, 'wallet.bak')) - self.nodes[2].dumpwallet(os.path.join( - self.nodes[2].datadir, 'wallet.dump')) + self.nodes[0].backupwallet(os.path.join(self.nodes[0].datadir, "wallet.bak")) + self.nodes[0].dumpwallet(os.path.join(self.nodes[0].datadir, "wallet.dump")) + self.nodes[1].backupwallet(os.path.join(self.nodes[1].datadir, "wallet.bak")) + self.nodes[1].dumpwallet(os.path.join(self.nodes[1].datadir, "wallet.dump")) + self.nodes[2].backupwallet(os.path.join(self.nodes[2].datadir, "wallet.bak")) + self.nodes[2].dumpwallet(os.path.join(self.nodes[2].datadir, "wallet.dump")) self.log.info("More transactions") for _ in range(5): self.do_one_round() # Generate 101 more blocks, so any fees paid mature self.generate(self.nodes[3], 101) balance0 = self.nodes[0].getbalance() balance1 = self.nodes[1].getbalance() balance2 = self.nodes[2].getbalance() balance3 = self.nodes[3].getbalance() total = balance0 + balance1 + balance2 + balance3 # At this point, there are 214 blocks (103 for setup, then 10 rounds, then 101.) # 114 are mature, so the sum of all wallets should be 114 * 50 = 5700. assert_equal(total, 5700000000) ## # Test restoring spender wallets from backups ## self.log.info("Restoring wallets on node 3 using backup files") self.restore_nonexistent_wallet() - backup_file_0 = os.path.join(self.nodes[0].datadir, 'wallet.bak') - backup_file_1 = os.path.join(self.nodes[1].datadir, 'wallet.bak') - backup_file_2 = os.path.join(self.nodes[2].datadir, 'wallet.bak') + backup_file_0 = os.path.join(self.nodes[0].datadir, "wallet.bak") + backup_file_1 = os.path.join(self.nodes[1].datadir, "wallet.bak") + backup_file_2 = os.path.join(self.nodes[2].datadir, "wallet.bak") self.nodes[3].restorewallet("res0", backup_file_0) self.nodes[3].restorewallet("res1", backup_file_1) self.nodes[3].restorewallet("res2", backup_file_2) 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") assert_equal(res0_rpc.getbalance(), balance0) assert_equal(res1_rpc.getbalance(), balance1) assert_equal(res2_rpc.getbalance(), balance2) self.restore_wallet_existent_name() self.log.info("Restoring using dumped wallet") self.stop_three() self.erase_three() # start node2 with no chain - shutil.rmtree( - os.path.join( - self.nodes[2].datadir, - self.chain, - 'blocks')) - shutil.rmtree(os.path.join( - self.nodes[2].datadir, self.chain, 'chainstate')) + shutil.rmtree(os.path.join(self.nodes[2].datadir, self.chain, "blocks")) + shutil.rmtree(os.path.join(self.nodes[2].datadir, self.chain, "chainstate")) self.start_three() assert_equal(self.nodes[0].getbalance(), 0) assert_equal(self.nodes[1].getbalance(), 0) assert_equal(self.nodes[2].getbalance(), 0) - self.nodes[0].importwallet(os.path.join( - self.nodes[0].datadir, 'wallet.dump')) - self.nodes[1].importwallet(os.path.join( - self.nodes[1].datadir, 'wallet.dump')) - self.nodes[2].importwallet(os.path.join( - self.nodes[2].datadir, 'wallet.dump')) + self.nodes[0].importwallet(os.path.join(self.nodes[0].datadir, "wallet.dump")) + self.nodes[1].importwallet(os.path.join(self.nodes[1].datadir, "wallet.dump")) + self.nodes[2].importwallet(os.path.join(self.nodes[2].datadir, "wallet.dump")) self.sync_blocks() assert_equal(self.nodes[0].getbalance(), balance0) assert_equal(self.nodes[1].getbalance(), balance1) assert_equal(self.nodes[2].getbalance(), balance2) # Backup to source wallet file must fail sourcePaths = [ - os.path.join(self.nodes[0].datadir, self.chain, 'wallets', - self.default_wallet_name, self.wallet_data_filename), - os.path.join(self.nodes[0].datadir, self.chain, '.', 'wallets', - self.default_wallet_name, self.wallet_data_filename), - os.path.join(self.nodes[0].datadir, self.chain, 'wallets', - self.default_wallet_name), - os.path.join(self.nodes[0].datadir, self.chain, 'wallets')] + os.path.join( + self.nodes[0].datadir, + self.chain, + "wallets", + self.default_wallet_name, + self.wallet_data_filename, + ), + os.path.join( + self.nodes[0].datadir, + self.chain, + ".", + "wallets", + self.default_wallet_name, + self.wallet_data_filename, + ), + os.path.join( + self.nodes[0].datadir, self.chain, "wallets", self.default_wallet_name + ), + os.path.join(self.nodes[0].datadir, self.chain, "wallets"), + ] for sourcePath in sourcePaths: - assert_raises_rpc_error(-4, "backup failed", - self.nodes[0].backupwallet, sourcePath) + assert_raises_rpc_error( + -4, "backup failed", self.nodes[0].backupwallet, sourcePath + ) -if __name__ == '__main__': +if __name__ == "__main__": WalletBackupTest().main() diff --git a/test/functional/wallet_balance.py b/test/functional/wallet_balance.py index 4ec4fe306..e1ed00b9a 100755 --- a/test/functional/wallet_balance.py +++ b/test/functional/wallet_balance.py @@ -1,342 +1,351 @@ #!/usr/bin/env python3 # Copyright (c) 2018-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet balance RPC methods.""" import struct from decimal import Decimal from test_framework.address import ADDRESS_ECREG_UNSPENDABLE as ADDRESS_WATCHONLY from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error FAR_IN_THE_FUTURE = 2000000000 def create_transactions(node, address, amt, fees): # Create and sign raw transactions from node to address for amt. # Creates a transaction for each fee and returns an array # of the raw transactions. - utxos = [u for u in node.listunspent(0) if u['spendable']] + utxos = [u for u in node.listunspent(0) if u["spendable"]] # Create transactions inputs = [] ins_total = 0 for utxo in utxos: inputs.append({"txid": utxo["txid"], "vout": utxo["vout"]}) - ins_total += utxo['amount'] + ins_total += utxo["amount"] if ins_total >= amt + max(fees): break # make sure there was enough utxos assert ins_total >= amt + max(fees) txs = [] for fee in fees: outputs = {address: amt} # prevent 0 change output if ins_total > amt + fee: outputs[node.getrawchangeaddress()] = ins_total - amt - fee raw_tx = node.createrawtransaction(inputs, outputs, 0) raw_tx = node.signrawtransactionwithwallet(raw_tx) - assert_equal(raw_tx['complete'], True) + assert_equal(raw_tx["complete"], True) txs.append(raw_tx) return txs class WalletTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True self.extra_args = [ # Limit mempool descendants as a hack to have wallet txs rejected # from the mempool. This will no longer work after wellington, so # move the activation in the future for this test. - ['-limitdescendantcount=3', - f'-wellingtonactivationtime={FAR_IN_THE_FUTURE}', - ], + [ + "-limitdescendantcount=3", + f"-wellingtonactivationtime={FAR_IN_THE_FUTURE}", + ], [], ] # whitelist peers to speed up tx relay / mempool sync for args in self.extra_args: args.append("-whitelist=noban@127.0.0.1") def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): if not self.options.descriptors: # Tests legacy watchonly behavior which is not present (and does # not need to be tested) in descriptor wallets self.nodes[0].importaddress(ADDRESS_WATCHONLY) # Check that nodes don't own any UTXOs assert_equal(len(self.nodes[0].listunspent()), 0) assert_equal(len(self.nodes[1].listunspent()), 0) self.log.info("Check that only node 0 is watching an address") - assert 'watchonly' in self.nodes[0].getbalances() - assert 'watchonly' not in self.nodes[1].getbalances() + assert "watchonly" in self.nodes[0].getbalances() + assert "watchonly" not in self.nodes[1].getbalances() self.log.info("Mining blocks ...") self.generate(self.nodes[0], 1) self.generate(self.nodes[1], 1) self.generatetoaddress(self.nodes[1], 101, ADDRESS_WATCHONLY) if not self.options.descriptors: # Tests legacy watchonly behavior which is not present (and does not # need to be tested) in descriptor wallets - assert_equal(self.nodes[0].getbalances()['mine']['trusted'], - 50000000) - assert_equal(self.nodes[0].getwalletinfo()['balance'], 50000000) - assert_equal(self.nodes[1].getbalances()['mine']['trusted'], - 50000000) + assert_equal(self.nodes[0].getbalances()["mine"]["trusted"], 50000000) + assert_equal(self.nodes[0].getwalletinfo()["balance"], 50000000) + assert_equal(self.nodes[1].getbalances()["mine"]["trusted"], 50000000) - assert_equal(self.nodes[0].getbalances()['watchonly']['immature'], - 5000000000) - assert 'watchonly' not in self.nodes[1].getbalances() + assert_equal( + self.nodes[0].getbalances()["watchonly"]["immature"], 5000000000 + ) + assert "watchonly" not in self.nodes[1].getbalances() assert_equal(self.nodes[0].getbalance(), 50000000) assert_equal(self.nodes[1].getbalance(), 50000000) self.log.info("Test getbalance with different arguments") assert_equal(self.nodes[0].getbalance("*"), 50000000) assert_equal(self.nodes[0].getbalance("*", 1), 50000000) assert_equal(self.nodes[0].getbalance(minconf=1), 50000000) if not self.options.descriptors: - assert_equal(self.nodes[0].getbalance(minconf=0, - include_watchonly=True), - 100_000_000) + assert_equal( + self.nodes[0].getbalance(minconf=0, include_watchonly=True), 100_000_000 + ) assert_equal(self.nodes[0].getbalance("*", 1, True), 100_000_000) else: - assert_equal(self.nodes[0].getbalance(minconf=0, - include_watchonly=True), - 50_000_000) + assert_equal( + self.nodes[0].getbalance(minconf=0, include_watchonly=True), 50_000_000 + ) assert_equal(self.nodes[0].getbalance("*", 1, True), 50_000_000) assert_equal( - self.nodes[1].getbalance( - minconf=0, - include_watchonly=True), - 50000000) + self.nodes[1].getbalance(minconf=0, include_watchonly=True), 50000000 + ) # Send 40 BTC from 0 to 1 and 60 BTC from 1 to 0. txs = create_transactions( - self.nodes[0], self.nodes[1].getnewaddress(), 40000000, [Decimal('10000')]) - self.nodes[0].sendrawtransaction(txs[0]['hex']) + self.nodes[0], self.nodes[1].getnewaddress(), 40000000, [Decimal("10000")] + ) + self.nodes[0].sendrawtransaction(txs[0]["hex"]) # sending on both nodes is faster than waiting for propagation - self.nodes[1].sendrawtransaction(txs[0]['hex']) + self.nodes[1].sendrawtransaction(txs[0]["hex"]) self.sync_all() - txs = create_transactions(self.nodes[1], self.nodes[0].getnewaddress(), 60000000, [ - Decimal('10000'), Decimal('20000')]) - self.nodes[1].sendrawtransaction(txs[0]['hex']) + txs = create_transactions( + self.nodes[1], + self.nodes[0].getnewaddress(), + 60000000, + [Decimal("10000"), Decimal("20000")], + ) + self.nodes[1].sendrawtransaction(txs[0]["hex"]) # sending on both nodes is faster than waiting for propagation - self.nodes[0].sendrawtransaction(txs[0]['hex']) + self.nodes[0].sendrawtransaction(txs[0]["hex"]) self.sync_all() # First argument of getbalance must be set to "*" - assert_raises_rpc_error(-32, "dummy first argument must be excluded or set to \"*\"", - self.nodes[1].getbalance, "") + assert_raises_rpc_error( + -32, + 'dummy first argument must be excluded or set to "*"', + self.nodes[1].getbalance, + "", + ) self.log.info("Test balances with unconfirmed inputs") # Before `test_balance()`, we have had two nodes with a balance of 50 # each and then we: # # 1) Sent 40 from node A to node B with fee 0.01 # 2) Sent 60 from node B to node A with fee 0.01 # # Then we check the balances: # # 1) As is # 2) With transaction 2 from above with 2x the fee # # Prior to #16766, in this situation, the node would immediately report # a balance of 30 on node B as unconfirmed and trusted. # # After #16766, we show that balance as unconfirmed. # # The balance is indeed "trusted" and "confirmed" insofar as removing # the mempool transactions would return at least that much money. But # the algorithm after #16766 marks it as unconfirmed because the 'taint' # tracking of transaction trust for summing balances doesn't consider # which inputs belong to a user. In this case, the change output in # question could be "destroyed" by replace the 1st transaction above. # # The post #16766 behavior is correct; we shouldn't be treating those # funds as confirmed. If you want to rely on that specific UTXO existing # which has given you that balance, you cannot, as a third party # spending the other input would destroy that unconfirmed. # # For example, if the test transactions were: # # 1) Sent 40 from node A to node B with fee 0.01 # 2) Sent 10 from node B to node A with fee 0.01 # # Then our node would report a confirmed balance of 40 + 50 - 10 = 80 # BTC, which is more than would be available if transaction 1 were # replaced. def test_balances(*, fee_node_1=0): # getbalances - expected_balances_0 = {'mine': {'immature': Decimal('0E-2'), - # change from node 0's send - 'trusted': Decimal('9990000'), - 'untrusted_pending': Decimal('60000000.0')}, - 'watchonly': {'immature': Decimal('5000000000'), - 'trusted': Decimal('50000000.0'), - 'untrusted_pending': Decimal('0E-2')}} - expected_balances_1 = {'mine': {'immature': Decimal('0E-2'), - # node 1's send had an unsafe input - 'trusted': Decimal('0E-2'), - # Doesn't include output of node - # 0's send since it was spent - 'untrusted_pending': Decimal('30000000.0') - fee_node_1}} + expected_balances_0 = { + "mine": { + "immature": Decimal("0E-2"), + # change from node 0's send + "trusted": Decimal("9990000"), + "untrusted_pending": Decimal("60000000.0"), + }, + "watchonly": { + "immature": Decimal("5000000000"), + "trusted": Decimal("50000000.0"), + "untrusted_pending": Decimal("0E-2"), + }, + } + expected_balances_1 = { + "mine": { + "immature": Decimal("0E-2"), + # node 1's send had an unsafe input + "trusted": Decimal("0E-2"), + # Doesn't include output of node + # 0's send since it was spent + "untrusted_pending": Decimal("30000000.0") - fee_node_1, + } + } if self.options.descriptors: del expected_balances_0["watchonly"] assert_equal(self.nodes[0].getbalances(), expected_balances_0) assert_equal(self.nodes[1].getbalances(), expected_balances_1) # getbalance without any arguments includes unconfirmed transactions, but not untrusted transactions # change from node 0's send - assert_equal(self.nodes[0].getbalance(), Decimal('9990000')) + assert_equal(self.nodes[0].getbalance(), Decimal("9990000")) # node 1's send had an unsafe input - assert_equal(self.nodes[1].getbalance(), Decimal('0')) + assert_equal(self.nodes[1].getbalance(), Decimal("0")) # Same with minconf=0 - assert_equal( - self.nodes[0].getbalance( - minconf=0), - Decimal('9990000')) - assert_equal(self.nodes[1].getbalance(minconf=0), Decimal('0')) + assert_equal(self.nodes[0].getbalance(minconf=0), Decimal("9990000")) + assert_equal(self.nodes[1].getbalance(minconf=0), Decimal("0")) # getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago # TODO: fix getbalance tracking of coin spentness depth - assert_equal(self.nodes[0].getbalance(minconf=1), Decimal('0')) - assert_equal(self.nodes[1].getbalance(minconf=1), Decimal('0')) + assert_equal(self.nodes[0].getbalance(minconf=1), Decimal("0")) + assert_equal(self.nodes[1].getbalance(minconf=1), Decimal("0")) # getunconfirmedbalance # output of node 1's spend - assert_equal( - self.nodes[0].getunconfirmedbalance(), - Decimal('60000000')) + assert_equal(self.nodes[0].getunconfirmedbalance(), Decimal("60000000")) # Doesn't include output of node 0's send since it was spent assert_equal( - self.nodes[1].getunconfirmedbalance(), - Decimal('30000000') - fee_node_1) + self.nodes[1].getunconfirmedbalance(), Decimal("30000000") - fee_node_1 + ) # getwalletinfo.unconfirmed_balance - assert_equal(self.nodes[0].getwalletinfo()[ - "unconfirmed_balance"], Decimal('60000000')) + assert_equal( + self.nodes[0].getwalletinfo()["unconfirmed_balance"], + Decimal("60000000"), + ) assert_equal( self.nodes[1].getwalletinfo()["unconfirmed_balance"], - Decimal('30000000') - fee_node_1) + Decimal("30000000") - fee_node_1, + ) - test_balances(fee_node_1=Decimal('10000')) + test_balances(fee_node_1=Decimal("10000")) # In the original Core version of this test, Node 1 would've bumped # the fee by 0.01 here to resend, but this is XEC, so it has 10000 XEC # left to spend on goods and services self.sync_all() self.log.info( - "Test getbalance and getbalances.mine.untrusted_pending with conflicted unconfirmed inputs") - test_balances(fee_node_1=Decimal('10000')) + "Test getbalance and getbalances.mine.untrusted_pending with conflicted" + " unconfirmed inputs" + ) + test_balances(fee_node_1=Decimal("10000")) self.generatetoaddress(self.nodes[1], 1, ADDRESS_WATCHONLY) # balances are correct after the transactions are confirmed # node 1's send plus change from node 0's send - balance_node0 = Decimal('69990000') + balance_node0 = Decimal("69990000") # change from node 0's send - balance_node1 = Decimal('29990000') - assert_equal(self.nodes[0].getbalances()[ - 'mine']['trusted'], balance_node0) - assert_equal(self.nodes[1].getbalances()[ - 'mine']['trusted'], balance_node1) + balance_node1 = Decimal("29990000") + assert_equal(self.nodes[0].getbalances()["mine"]["trusted"], balance_node0) + assert_equal(self.nodes[1].getbalances()["mine"]["trusted"], balance_node1) assert_equal(self.nodes[0].getbalance(), balance_node0) assert_equal(self.nodes[1].getbalance(), balance_node1) # Send total balance away from node 1 - txs = create_transactions(self.nodes[1], self.nodes[0].getnewaddress( - ), Decimal('29970000'), [Decimal('10000')]) - self.nodes[1].sendrawtransaction(txs[0]['hex']) + txs = create_transactions( + self.nodes[1], + self.nodes[0].getnewaddress(), + Decimal("29970000"), + [Decimal("10000")], + ) + self.nodes[1].sendrawtransaction(txs[0]["hex"]) self.generatetoaddress(self.nodes[1], 2, ADDRESS_WATCHONLY) # getbalance with a minconf incorrectly excludes coins that have been spent more recently than the minconf blocks ago # TODO: fix getbalance tracking of coin spentness depth # getbalance with minconf=3 should still show the old balance - assert_equal(self.nodes[1].getbalance(minconf=3), Decimal('0')) + assert_equal(self.nodes[1].getbalance(minconf=3), Decimal("0")) # getbalance with minconf=2 will show the new balance. - assert_equal(self.nodes[1].getbalance(minconf=2), Decimal('10000')) + assert_equal(self.nodes[1].getbalance(minconf=2), Decimal("10000")) # check mempool transactions count for wallet unconfirmed balance after # dynamically loading the wallet. - before = self.nodes[1].getbalances()['mine']['untrusted_pending'] + before = self.nodes[1].getbalances()["mine"]["untrusted_pending"] dst = self.nodes[1].getnewaddress() self.nodes[1].unloadwallet(self.default_wallet_name) self.nodes[0].sendtoaddress(dst, 100000) self.sync_all() self.nodes[1].loadwallet(self.default_wallet_name) - after = self.nodes[1].getbalances()['mine']['untrusted_pending'] - assert_equal(before + Decimal('100000'), after) + after = self.nodes[1].getbalances()["mine"]["untrusted_pending"] + assert_equal(before + Decimal("100000"), after) # Create 3 more wallet txs, where the last is not accepted to the # mempool because it is the third descendant of the tx above for _ in range(3): # Set amount high enough such that all coins are spent by each tx - txid = self.nodes[0].sendtoaddress( - self.nodes[0].getnewaddress(), 99000000) + txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 99000000) - self.log.info('Check that wallet txs not in the mempool are untrusted') + self.log.info("Check that wallet txs not in the mempool are untrusted") assert txid not in self.nodes[0].getrawmempool() - assert_equal(self.nodes[0].gettransaction(txid)['trusted'], False) + assert_equal(self.nodes[0].gettransaction(txid)["trusted"], False) assert_equal(self.nodes[0].getbalance(minconf=0), 0) self.log.info("Test replacement and reorg of non-mempool tx") - tx_orig = self.nodes[0].gettransaction(txid)['hex'] + tx_orig = self.nodes[0].gettransaction(txid)["hex"] # Increase fee by 1 coin tx_replace = tx_orig.replace( struct.pack(" 1 for address in output_addresses: - ischange = self.nodes[0].getaddressinfo(address)['ischange'] + ischange = self.nodes[0].getaddressinfo(address)["ischange"] assert_equal(ischange, address != destination) if ischange: change = address - self.nodes[0].setlabel(change, 'foobar') - assert_equal(self.nodes[0].getaddressinfo(change)['ischange'], False) + self.nodes[0].setlabel(change, "foobar") + assert_equal(self.nodes[0].getaddressinfo(change)["ischange"], False) # Test gettransaction response with different arguments. - self.log.info( - "Testing gettransaction response with different arguments...") - self.nodes[0].setlabel(change, 'baz') + self.log.info("Testing gettransaction response with different arguments...") + self.nodes[0].setlabel(change, "baz") baz = self.nodes[0].listtransactions(label="baz", count=1)[0] - expected_receive_vout = {"label": "baz", - "address": baz["address"], - "amount": baz["amount"], - "category": baz["category"], - "vout": baz["vout"]} - expected_fields = frozenset({'amount', - 'confirmations', - 'details', - 'fee', - 'hex', - 'time', - 'timereceived', - 'trusted', - 'txid', - 'walletconflicts'}) + expected_receive_vout = { + "label": "baz", + "address": baz["address"], + "amount": baz["amount"], + "category": baz["category"], + "vout": baz["vout"], + } + expected_fields = frozenset( + { + "amount", + "confirmations", + "details", + "fee", + "hex", + "time", + "timereceived", + "trusted", + "txid", + "walletconflicts", + } + ) verbose_field = "decoded" expected_verbose_fields = expected_fields | {verbose_field} self.log.debug("Testing gettransaction response without verbose") tx = self.nodes[0].gettransaction(txid=txid) assert_equal(set(tx), expected_fields) assert_array_result( - tx["details"], { - "category": "receive"}, expected_receive_vout) + tx["details"], {"category": "receive"}, expected_receive_vout + ) - self.log.debug( - "Testing gettransaction response with verbose set to False") + self.log.debug("Testing gettransaction response with verbose set to False") tx = self.nodes[0].gettransaction(txid=txid, verbose=False) assert_equal(set(tx), expected_fields) assert_array_result( - tx["details"], { - "category": "receive"}, expected_receive_vout) + tx["details"], {"category": "receive"}, expected_receive_vout + ) - self.log.debug( - "Testing gettransaction response with verbose set to True") + self.log.debug("Testing gettransaction response with verbose set to True") tx = self.nodes[0].gettransaction(txid=txid, verbose=True) assert_equal(set(tx), expected_verbose_fields) assert_array_result( - tx["details"], { - "category": "receive"}, expected_receive_vout) - assert_equal( - tx[verbose_field], - self.nodes[0].decoderawtransaction( - tx["hex"])) + tx["details"], {"category": "receive"}, expected_receive_vout + ) + assert_equal(tx[verbose_field], self.nodes[0].decoderawtransaction(tx["hex"])) -if __name__ == '__main__': +if __name__ == "__main__": WalletTest().main() diff --git a/test/functional/wallet_coinbase_category.py b/test/functional/wallet_coinbase_category.py index 91628456c..7562a5599 100755 --- a/test/functional/wallet_coinbase_category.py +++ b/test/functional/wallet_coinbase_category.py @@ -1,59 +1,65 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test coinbase transactions return the correct categories. Tests listtransactions, listsinceblock, and gettransaction. """ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_array_result class CoinbaseCategoryTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def assert_category(self, category, address, txid, skip): - assert_array_result(self.nodes[0].listtransactions(skip=skip), - {"address": address}, - {"category": category}) - assert_array_result(self.nodes[0].listsinceblock()["transactions"], - {"address": address}, - {"category": category}) - assert_array_result(self.nodes[0].gettransaction(txid)["details"], - {"address": address}, - {"category": category}) + assert_array_result( + self.nodes[0].listtransactions(skip=skip), + {"address": address}, + {"category": category}, + ) + assert_array_result( + self.nodes[0].listsinceblock()["transactions"], + {"address": address}, + {"category": category}, + ) + assert_array_result( + self.nodes[0].gettransaction(txid)["details"], + {"address": address}, + {"category": category}, + ) def run_test(self): # Generate one block to an address address = self.nodes[0].getnewaddress() self.generatetoaddress(self.nodes[0], 1, address) blockhash = self.nodes[0].getbestblockhash() txid = self.nodes[0].getblock(blockhash)["tx"][0] # Coinbase transaction is immature after 1 confirmation self.assert_category("immature", address, txid, 0) # Mine another 99 blocks on top self.generate(self.nodes[0], 99) # Coinbase transaction is still immature after 100 confirmations self.assert_category("immature", address, txid, 99) # Mine one more block self.generate(self.nodes[0], 1) # Coinbase transaction is now matured, so category is "generate" self.assert_category("generate", address, txid, 100) # Orphan block that paid to address self.nodes[0].invalidateblock(blockhash) # Coinbase transaction is now orphaned self.assert_category("orphan", address, txid, 100) -if __name__ == '__main__': +if __name__ == "__main__": CoinbaseCategoryTest().main() diff --git a/test/functional/wallet_create_tx.py b/test/functional/wallet_create_tx.py index 433948189..d2fd860a9 100755 --- a/test/functional/wallet_create_tx.py +++ b/test/functional/wallet_create_tx.py @@ -1,87 +1,89 @@ #!/usr/bin/env python3 # Copyright (c) 2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. from test_framework.blocktools import TIME_GENESIS_BLOCK from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error class CreateTxWalletTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): - self.log.info('Create some old blocks') + self.log.info("Create some old blocks") self.nodes[0].setmocktime(TIME_GENESIS_BLOCK) self.generate(self.nodes[0], 200) self.nodes[0].setmocktime(0) self.test_anti_fee_sniping_disabled() self.test_tx_size_too_large() def test_anti_fee_sniping_disabled(self): # Wallet transactions used to set locktime to the current block height # to prevent fee-sniping. This feature is now disabled because it hurts # privacy and fee-sniping is now mitigated by avalanche post-consensus. self.log.info( - 'Check that we have some (old) blocks and that anti-fee-sniping is disabled') - assert_equal(self.nodes[0].getblockchaininfo()['blocks'], 200) - txid = self.nodes[0].sendtoaddress( - self.nodes[0].getnewaddress(), 1000000) - tx = self.nodes[0].gettransaction(txid=txid, verbose=True)['decoded'] - assert_equal(tx['locktime'], 0) + "Check that we have some (old) blocks and that anti-fee-sniping is disabled" + ) + assert_equal(self.nodes[0].getblockchaininfo()["blocks"], 200) + txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1000000) + tx = self.nodes[0].gettransaction(txid=txid, verbose=True)["decoded"] + assert_equal(tx["locktime"], 0) self.log.info( - 'Check that anti-fee-sniping is still disabled when we mine a recent block') + "Check that anti-fee-sniping is still disabled when we mine a recent block" + ) self.generate(self.nodes[0], 1) - txid = self.nodes[0].sendtoaddress( - self.nodes[0].getnewaddress(), 1000000) - tx = self.nodes[0].gettransaction(txid=txid, verbose=True)['decoded'] - assert_equal(tx['locktime'], 0) + txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 1000000) + tx = self.nodes[0].gettransaction(txid=txid, verbose=True)["decoded"] + assert_equal(tx["locktime"], 0) def test_tx_size_too_large(self): # More than 10kB of outputs, so that we hit -maxtxfee with a high # feerate outputs = {self.nodes[0].getnewaddress(): 25 for _ in range(400)} raw_tx = self.nodes[0].createrawtransaction(inputs=[], outputs=outputs) - for fee_setting in ['-minrelaytxfee=10000', - '-mintxfee=10000', '-paytxfee=10000']: - self.log.info( - f'Check maxtxfee in combination with {fee_setting}') + for fee_setting in [ + "-minrelaytxfee=10000", + "-mintxfee=10000", + "-paytxfee=10000", + ]: + self.log.info(f"Check maxtxfee in combination with {fee_setting}") self.restart_node(0, extra_args=[fee_setting]) assert_raises_rpc_error( -6, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)", lambda: self.nodes[0].sendmany(dummy="", amounts=outputs), ) assert_raises_rpc_error( -4, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)", lambda: self.nodes[0].fundrawtransaction(hexstring=raw_tx), ) - self.log.info('Check maxtxfee in combination with settxfee') + self.log.info("Check maxtxfee in combination with settxfee") self.restart_node(0) self.nodes[0].settxfee(10000) assert_raises_rpc_error( -6, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)", lambda: self.nodes[0].sendmany(dummy="", amounts=outputs), ) assert_raises_rpc_error( -4, "Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)", lambda: self.nodes[0].fundrawtransaction(hexstring=raw_tx), ) self.nodes[0].settxfee(0) -if __name__ == '__main__': +if __name__ == "__main__": CreateTxWalletTest().main() diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py index caa3ac836..13601fd56 100755 --- a/test/functional/wallet_createwallet.py +++ b/test/functional/wallet_createwallet.py @@ -1,250 +1,309 @@ #!/usr/bin/env python3 # Copyright (c) 2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test createwallet arguments. """ from test_framework.address import key_to_p2pkh from test_framework.descriptors import descsum_create from test_framework.key import ECKey from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error from test_framework.wallet_util import bytes_to_wif, generate_wif_key class CreateWalletTest(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] # Leave IBD for sethdseed self.generate(node, 1) - self.nodes[0].createwallet(wallet_name='w0') - w0 = node.get_wallet_rpc('w0') + self.nodes[0].createwallet(wallet_name="w0") + w0 = node.get_wallet_rpc("w0") address1 = w0.getnewaddress() self.log.info("Test disableprivatekeys creation.") - self.nodes[0].createwallet(wallet_name='w1', disable_private_keys=True) - w1 = node.get_wallet_rpc('w1') - assert_raises_rpc_error(-4, - "Error: This wallet has no available keys", w1.getnewaddress) - assert_raises_rpc_error(-4, "Error: This wallet has no available keys", - w1.getrawchangeaddress) - w1.importpubkey(w0.getaddressinfo(address1)['pubkey']) - - self.log.info('Test that private keys cannot be imported') + self.nodes[0].createwallet(wallet_name="w1", disable_private_keys=True) + w1 = node.get_wallet_rpc("w1") + assert_raises_rpc_error( + -4, "Error: This wallet has no available keys", w1.getnewaddress + ) + assert_raises_rpc_error( + -4, "Error: This wallet has no available keys", w1.getrawchangeaddress + ) + w1.importpubkey(w0.getaddressinfo(address1)["pubkey"]) + + self.log.info("Test that private keys cannot be imported") eckey = ECKey() eckey.generate() privkey = bytes_to_wif(eckey.get_bytes()) assert_raises_rpc_error( - -4, 'Cannot import private keys to a wallet with private keys disabled', w1.importprivkey, privkey) + -4, + "Cannot import private keys to a wallet with private keys disabled", + w1.importprivkey, + privkey, + ) if self.options.descriptors: result = w1.importdescriptors( - [{'desc': descsum_create(f"pkh({privkey})"), - 'timestamp': 'now'}]) + [{"desc": descsum_create(f"pkh({privkey})"), "timestamp": "now"}] + ) else: result = w1.importmulti( - [{'scriptPubKey': { - 'address': key_to_p2pkh(eckey.get_pubkey().get_bytes())}, - 'timestamp': 'now', 'keys': [privkey]}]) - assert not result[0]['success'] - assert 'warning' not in result[0] - assert_equal(result[0]['error']['code'], -4) - assert_equal(result[0]['error']['message'], - 'Cannot import private keys to a wallet with private keys disabled') + [ + { + "scriptPubKey": { + "address": key_to_p2pkh(eckey.get_pubkey().get_bytes()) + }, + "timestamp": "now", + "keys": [privkey], + } + ] + ) + assert not result[0]["success"] + assert "warning" not in result[0] + assert_equal(result[0]["error"]["code"], -4) + assert_equal( + result[0]["error"]["message"], + "Cannot import private keys to a wallet with private keys disabled", + ) self.log.info("Test blank creation with private keys disabled.") self.nodes[0].createwallet( - wallet_name='w2', disable_private_keys=True, blank=True) - w2 = node.get_wallet_rpc('w2') - assert_raises_rpc_error(-4, - "Error: This wallet has no available keys", w2.getnewaddress) - assert_raises_rpc_error(-4, "Error: This wallet has no available keys", - w2.getrawchangeaddress) - w2.importpubkey(w0.getaddressinfo(address1)['pubkey']) + wallet_name="w2", disable_private_keys=True, blank=True + ) + w2 = node.get_wallet_rpc("w2") + assert_raises_rpc_error( + -4, "Error: This wallet has no available keys", w2.getnewaddress + ) + assert_raises_rpc_error( + -4, "Error: This wallet has no available keys", w2.getrawchangeaddress + ) + w2.importpubkey(w0.getaddressinfo(address1)["pubkey"]) self.log.info("Test blank creation with private keys enabled.") self.nodes[0].createwallet( - wallet_name='w3', disable_private_keys=False, blank=True) - w3 = node.get_wallet_rpc('w3') - assert_equal(w3.getwalletinfo()['keypoolsize'], 0) - assert_raises_rpc_error(-4, - "Error: This wallet has no available keys", w3.getnewaddress) - assert_raises_rpc_error(-4, "Error: This wallet has no available keys", - w3.getrawchangeaddress) + wallet_name="w3", disable_private_keys=False, blank=True + ) + w3 = node.get_wallet_rpc("w3") + assert_equal(w3.getwalletinfo()["keypoolsize"], 0) + assert_raises_rpc_error( + -4, "Error: This wallet has no available keys", w3.getnewaddress + ) + assert_raises_rpc_error( + -4, "Error: This wallet has no available keys", w3.getrawchangeaddress + ) # Import private key w3.importprivkey(generate_wif_key()) # Imported private keys are currently ignored by the keypool - assert_equal(w3.getwalletinfo()['keypoolsize'], 0) - assert_raises_rpc_error(-4, - "Error: This wallet has no available keys", w3.getnewaddress) + assert_equal(w3.getwalletinfo()["keypoolsize"], 0) + assert_raises_rpc_error( + -4, "Error: This wallet has no available keys", w3.getnewaddress + ) # Set the seed if self.options.descriptors: - w3.importdescriptors([{ - 'desc': descsum_create( - 'pkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/0h/*)'), - 'timestamp': 'now', - 'active': True - }, - { - 'desc': descsum_create( - 'pkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/1h/*)'), - 'timestamp': 'now', - 'active': True, - 'internal': True - }]) + w3.importdescriptors( + [ + { + "desc": descsum_create( + "pkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/0h/*)" + ), + "timestamp": "now", + "active": True, + }, + { + "desc": descsum_create( + "pkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/1h/*)" + ), + "timestamp": "now", + "active": True, + "internal": True, + }, + ] + ) else: w3.sethdseed() - assert_equal(w3.getwalletinfo()['keypoolsize'], 1) + assert_equal(w3.getwalletinfo()["keypoolsize"], 1) w3.getnewaddress() w3.getrawchangeaddress() - self.log.info( - "Test blank creation with privkeys enabled and then encryption") + self.log.info("Test blank creation with privkeys enabled and then encryption") self.nodes[0].createwallet( - wallet_name='w4', disable_private_keys=False, blank=True) - w4 = node.get_wallet_rpc('w4') - assert_equal(w4.getwalletinfo()['keypoolsize'], 0) - assert_raises_rpc_error(-4, - "Error: This wallet has no available keys", w4.getnewaddress) - assert_raises_rpc_error(-4, "Error: This wallet has no available keys", - w4.getrawchangeaddress) + wallet_name="w4", disable_private_keys=False, blank=True + ) + w4 = node.get_wallet_rpc("w4") + assert_equal(w4.getwalletinfo()["keypoolsize"], 0) + assert_raises_rpc_error( + -4, "Error: This wallet has no available keys", w4.getnewaddress + ) + assert_raises_rpc_error( + -4, "Error: This wallet has no available keys", w4.getrawchangeaddress + ) # Encrypt the wallet. Nothing should change about the keypool - w4.encryptwallet('pass') - assert_raises_rpc_error(-4, - "Error: This wallet has no available keys", w4.getnewaddress) - assert_raises_rpc_error(-4, "Error: This wallet has no available keys", - w4.getrawchangeaddress) + w4.encryptwallet("pass") + assert_raises_rpc_error( + -4, "Error: This wallet has no available keys", w4.getnewaddress + ) + assert_raises_rpc_error( + -4, "Error: This wallet has no available keys", w4.getrawchangeaddress + ) # Now set a seed and it should work. Wallet should also be encrypted - w4.walletpassphrase('pass', 2) + w4.walletpassphrase("pass", 2) if self.options.descriptors: - w4.importdescriptors([{ - 'desc': descsum_create( - 'pkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/0h/*)'), - 'timestamp': 'now', - 'active': True - }, - { - 'desc': descsum_create( - 'pkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/1h/*)'), - 'timestamp': 'now', - 'active': True, - 'internal': True - }]) + w4.importdescriptors( + [ + { + "desc": descsum_create( + "pkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/0h/*)" + ), + "timestamp": "now", + "active": True, + }, + { + "desc": descsum_create( + "pkh(tprv8ZgxMBicQKsPcwuZGKp8TeWppSuLMiLe2d9PupB14QpPeQsqoj3LneJLhGHH13xESfvASyd4EFLJvLrG8b7DrLxEuV7hpF9uUc6XruKA1Wq/1h/*)" + ), + "timestamp": "now", + "active": True, + "internal": True, + }, + ] + ) else: w4.sethdseed() w4.getnewaddress() w4.getrawchangeaddress() - self.log.info( - "Test blank creation with privkeys disabled and then encryption") + self.log.info("Test blank creation with privkeys disabled and then encryption") self.nodes[0].createwallet( - wallet_name='w5', disable_private_keys=True, blank=True) - - w5 = node.get_wallet_rpc('w5') - assert_equal(w5.getwalletinfo()['keypoolsize'], 0) - assert_raises_rpc_error(-4, - "Error: This wallet has no available keys", w5.getnewaddress) - assert_raises_rpc_error(-4, "Error: This wallet has no available keys", - w5.getrawchangeaddress) + wallet_name="w5", disable_private_keys=True, blank=True + ) + + w5 = node.get_wallet_rpc("w5") + assert_equal(w5.getwalletinfo()["keypoolsize"], 0) + assert_raises_rpc_error( + -4, "Error: This wallet has no available keys", w5.getnewaddress + ) + assert_raises_rpc_error( + -4, "Error: This wallet has no available keys", w5.getrawchangeaddress + ) # Encrypt the wallet - assert_raises_rpc_error(-16, - "Error: wallet does not contain private keys, nothing to encrypt.", - w5.encryptwallet, - 'pass') - assert_raises_rpc_error(-4, - "Error: This wallet has no available keys", w5.getnewaddress) - assert_raises_rpc_error(-4, "Error: This wallet has no available keys", - w5.getrawchangeaddress) - - self.log.info('New blank and encrypted wallets can be created') + assert_raises_rpc_error( + -16, + "Error: wallet does not contain private keys, nothing to encrypt.", + w5.encryptwallet, + "pass", + ) + assert_raises_rpc_error( + -4, "Error: This wallet has no available keys", w5.getnewaddress + ) + assert_raises_rpc_error( + -4, "Error: This wallet has no available keys", w5.getrawchangeaddress + ) + + self.log.info("New blank and encrypted wallets can be created") self.nodes[0].createwallet( - wallet_name='wblank', + wallet_name="wblank", disable_private_keys=False, blank=True, - passphrase='thisisapassphrase') - wblank = node.get_wallet_rpc('wblank') + passphrase="thisisapassphrase", + ) + wblank = node.get_wallet_rpc("wblank") assert_raises_rpc_error( -13, "Error: Please enter the wallet passphrase with walletpassphrase first.", wblank.signmessage, "needanargument", - "test") - wblank.walletpassphrase('thisisapassphrase', 10) - assert_raises_rpc_error(-4, - "Error: This wallet has no available keys", - wblank.getnewaddress) - assert_raises_rpc_error(-4, - "Error: This wallet has no available keys", - wblank.getrawchangeaddress) - - self.log.info('Test creating a new encrypted wallet.') + "test", + ) + wblank.walletpassphrase("thisisapassphrase", 10) + assert_raises_rpc_error( + -4, "Error: This wallet has no available keys", wblank.getnewaddress + ) + assert_raises_rpc_error( + -4, "Error: This wallet has no available keys", wblank.getrawchangeaddress + ) + + self.log.info("Test creating a new encrypted wallet.") # Born encrypted wallet is created (has keys) self.nodes[0].createwallet( - wallet_name='w6', + wallet_name="w6", disable_private_keys=False, blank=False, - passphrase='thisisapassphrase') - w6 = node.get_wallet_rpc('w6') + passphrase="thisisapassphrase", + ) + w6 = node.get_wallet_rpc("w6") assert_raises_rpc_error( -13, "Error: Please enter the wallet passphrase with walletpassphrase first.", w6.signmessage, "needanargument", - "test") - w6.walletpassphrase('thisisapassphrase', 10) - w6.signmessage(w6.getnewaddress('', 'legacy'), "test") + "test", + ) + w6.walletpassphrase("thisisapassphrase", 10) + w6.signmessage(w6.getnewaddress("", "legacy"), "test") w6.keypoolrefill(1) # There should only be 1 key for legacy and for descriptors walletinfo = w6.getwalletinfo() - assert_equal(walletinfo['keypoolsize'], 1) - assert_equal(walletinfo['keypoolsize_hd_internal'], 1) + assert_equal(walletinfo["keypoolsize"], 1) + assert_equal(walletinfo["keypoolsize_hd_internal"], 1) # Allow empty passphrase, but there should be a warning resp = self.nodes[0].createwallet( - wallet_name='w7', - disable_private_keys=False, - blank=False, - passphrase='') + wallet_name="w7", disable_private_keys=False, blank=False, passphrase="" + ) assert ( - 'Empty string given as passphrase, wallet will not be encrypted.' - in resp['warning']) - w7 = node.get_wallet_rpc('w7') + "Empty string given as passphrase, wallet will not be encrypted." + in resp["warning"] + ) + w7 = node.get_wallet_rpc("w7") assert_raises_rpc_error( -15, - 'Error: running with an unencrypted wallet, but walletpassphrase was called.', + ( + "Error: running with an unencrypted wallet, but walletpassphrase was" + " called." + ), w7.walletpassphrase, - '', - 10) + "", + 10, + ) - self.log.info('Test making a wallet with avoid reuse flag') + self.log.info("Test making a wallet with avoid reuse flag") # Use positional arguments to check for bug where avoid_reuse could not # be set for wallets without needing them to be encrypted - self.nodes[0].createwallet('w8', False, False, '', True) - w8 = node.get_wallet_rpc('w8') + self.nodes[0].createwallet("w8", False, False, "", True) + w8 = node.get_wallet_rpc("w8") assert_raises_rpc_error( -15, - 'Error: running with an unencrypted wallet, but walletpassphrase was called.', + ( + "Error: running with an unencrypted wallet, but walletpassphrase was" + " called." + ), w7.walletpassphrase, - '', - 10) + "", + 10, + ) assert_equal(w8.getwalletinfo()["avoid_reuse"], True) - self.log.info( - 'Using a passphrase with private keys disabled returns error') + self.log.info("Using a passphrase with private keys disabled returns error") assert_raises_rpc_error( -4, - 'Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled.', + ( + "Passphrase provided but private keys are disabled. A passphrase is" + " only used to encrypt private keys, so cannot be used for wallets with" + " private keys disabled." + ), self.nodes[0].createwallet, - wallet_name='w9', + wallet_name="w9", disable_private_keys=True, - passphrase='thisisapassphrase') + passphrase="thisisapassphrase", + ) -if __name__ == '__main__': +if __name__ == "__main__": CreateWalletTest().main() diff --git a/test/functional/wallet_descriptor.py b/test/functional/wallet_descriptor.py index 05192f0ea..5a6189a74 100755 --- a/test/functional/wallet_descriptor.py +++ b/test/functional/wallet_descriptor.py @@ -1,167 +1,193 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test descriptor wallet function.""" from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error class WalletDescriptorTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 - self.extra_args = [['-keypool=100']] + self.extra_args = [["-keypool=100"]] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): # Make a descriptor wallet self.log.info("Making a descriptor wallet") self.nodes[0].createwallet(wallet_name="desc1", descriptors=True) self.nodes[0].unloadwallet(self.default_wallet_name) # A descriptor wallet should have 100 addresses = 100 keys self.log.info("Checking wallet info") wallet_info = self.nodes[0].getwalletinfo() - assert_equal(wallet_info['keypoolsize'], 100) - assert_equal(wallet_info['keypoolsize_hd_internal'], 100) - assert 'keypoololdest' not in wallet_info + assert_equal(wallet_info["keypoolsize"], 100) + assert_equal(wallet_info["keypoolsize_hd_internal"], 100) + assert "keypoololdest" not in wallet_info # Check that getnewaddress works self.log.info("Test that getnewaddress and getrawchangeaddress work") addr = self.nodes[0].getnewaddress("", "legacy") addr_info = self.nodes[0].getaddressinfo(addr) - assert addr_info['desc'].startswith('pkh(') - assert_equal(addr_info['hdkeypath'], 'm/44\'/1\'/0\'/0/0') + assert addr_info["desc"].startswith("pkh(") + assert_equal(addr_info["hdkeypath"], "m/44'/1'/0'/0/0") # Check that getrawchangeaddress works addr = self.nodes[0].getrawchangeaddress() addr_info = self.nodes[0].getaddressinfo(addr) - assert addr_info['desc'].startswith('pkh(') - assert_equal(addr_info['hdkeypath'], 'm/44\'/1\'/0\'/1/0') + assert addr_info["desc"].startswith("pkh(") + assert_equal(addr_info["hdkeypath"], "m/44'/1'/0'/1/0") # Make a wallet to receive coins at self.nodes[0].createwallet(wallet_name="desc2", descriptors=True) recv_wrpc = self.nodes[0].get_wallet_rpc("desc2") send_wrpc = self.nodes[0].get_wallet_rpc("desc1") # Generate some coins self.generatetoaddress(self.nodes[0], 101, send_wrpc.getnewaddress()) # Make transactions self.log.info("Test sending and receiving") addr = recv_wrpc.getnewaddress() send_wrpc.sendtoaddress(addr, 10) # Make sure things are disabled self.log.info("Test disabled RPCs") - assert_raises_rpc_error(-4, - "This type of wallet does not support this command", - recv_wrpc.rpc.importprivkey, - "cVpF924EspNh8KjYsfhgY96mmxvT6DgdWiTYMtMjuM74hJaU5psW") - assert_raises_rpc_error(-4, - "This type of wallet does not support this command", - recv_wrpc.rpc.importpubkey, - send_wrpc.getaddressinfo(send_wrpc.getnewaddress())) - assert_raises_rpc_error(-4, - "This type of wallet does not support this command", - recv_wrpc.rpc.importaddress, - recv_wrpc.getnewaddress()) - assert_raises_rpc_error(-4, - "This type of wallet does not support this command", - recv_wrpc.rpc.importmulti, - []) - assert_raises_rpc_error(-4, - "This type of wallet does not support this command", - recv_wrpc.rpc.addmultisigaddress, - 1, - [recv_wrpc.getnewaddress()]) - assert_raises_rpc_error(-4, - "This type of wallet does not support this command", - recv_wrpc.rpc.dumpprivkey, - recv_wrpc.getnewaddress()) - assert_raises_rpc_error(-4, - "This type of wallet does not support this command", - recv_wrpc.rpc.dumpwallet, - 'wallet.dump') - assert_raises_rpc_error(-4, - "This type of wallet does not support this command", - recv_wrpc.rpc.importwallet, - 'wallet.dump') - assert_raises_rpc_error(-4, - "This type of wallet does not support this command", - recv_wrpc.rpc.sethdseed) + assert_raises_rpc_error( + -4, + "This type of wallet does not support this command", + recv_wrpc.rpc.importprivkey, + "cVpF924EspNh8KjYsfhgY96mmxvT6DgdWiTYMtMjuM74hJaU5psW", + ) + assert_raises_rpc_error( + -4, + "This type of wallet does not support this command", + recv_wrpc.rpc.importpubkey, + send_wrpc.getaddressinfo(send_wrpc.getnewaddress()), + ) + assert_raises_rpc_error( + -4, + "This type of wallet does not support this command", + recv_wrpc.rpc.importaddress, + recv_wrpc.getnewaddress(), + ) + assert_raises_rpc_error( + -4, + "This type of wallet does not support this command", + recv_wrpc.rpc.importmulti, + [], + ) + assert_raises_rpc_error( + -4, + "This type of wallet does not support this command", + recv_wrpc.rpc.addmultisigaddress, + 1, + [recv_wrpc.getnewaddress()], + ) + assert_raises_rpc_error( + -4, + "This type of wallet does not support this command", + recv_wrpc.rpc.dumpprivkey, + recv_wrpc.getnewaddress(), + ) + assert_raises_rpc_error( + -4, + "This type of wallet does not support this command", + recv_wrpc.rpc.dumpwallet, + "wallet.dump", + ) + assert_raises_rpc_error( + -4, + "This type of wallet does not support this command", + recv_wrpc.rpc.importwallet, + "wallet.dump", + ) + assert_raises_rpc_error( + -4, + "This type of wallet does not support this command", + recv_wrpc.rpc.sethdseed, + ) self.log.info("Test encryption") # Get the master fingerprint before encrypt info1 = send_wrpc.getaddressinfo(send_wrpc.getnewaddress()) # Encrypt wallet 0 - send_wrpc.encryptwallet('pass') - send_wrpc.walletpassphrase('pass', 10) + send_wrpc.encryptwallet("pass") + send_wrpc.walletpassphrase("pass", 10) addr = send_wrpc.getnewaddress() info2 = send_wrpc.getaddressinfo(addr) - assert info1['hdmasterfingerprint'] != info2['hdmasterfingerprint'] + assert info1["hdmasterfingerprint"] != info2["hdmasterfingerprint"] send_wrpc.walletlock() - assert 'hdmasterfingerprint' in send_wrpc.getaddressinfo( - send_wrpc.getnewaddress()) + assert "hdmasterfingerprint" in send_wrpc.getaddressinfo( + send_wrpc.getnewaddress() + ) info3 = send_wrpc.getaddressinfo(addr) - assert_equal(info2['desc'], info3['desc']) + assert_equal(info2["desc"], info3["desc"]) self.log.info( - "Test that getnewaddress still works after keypool is exhausted in an encrypted wallet") + "Test that getnewaddress still works after keypool is exhausted in an" + " encrypted wallet" + ) for _ in range(500): send_wrpc.getnewaddress() self.log.info( - "Test that unlock is needed when deriving only hardened keys in an encrypted wallet") - send_wrpc.walletpassphrase('pass', 10) - send_wrpc.importdescriptors([{ - "desc": "sh(pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/*h))#45ls09gz", - "timestamp": "now", - "range": [0, 10], - "active": True - }]) + "Test that unlock is needed when deriving only hardened keys in an" + " encrypted wallet" + ) + send_wrpc.walletpassphrase("pass", 10) + send_wrpc.importdescriptors( + [ + { + "desc": "sh(pkh(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/*h))#45ls09gz", + "timestamp": "now", + "range": [0, 10], + "active": True, + } + ] + ) send_wrpc.walletlock() # Exhaust keypool of 100 for _ in range(100): send_wrpc.getnewaddress() # This should now error - assert_raises_rpc_error(-12, - "Keypool ran out, please call keypoolrefill first", - send_wrpc.getnewaddress, - '') + assert_raises_rpc_error( + -12, + "Keypool ran out, please call keypoolrefill first", + send_wrpc.getnewaddress, + "", + ) self.log.info("Test born encrypted wallets") - self.nodes[0].createwallet( - 'desc_enc', False, False, 'pass', False, True) - enc_rpc = self.nodes[0].get_wallet_rpc('desc_enc') + self.nodes[0].createwallet("desc_enc", False, False, "pass", False, True) + enc_rpc = self.nodes[0].get_wallet_rpc("desc_enc") # Makes sure that we can get a new address from a born encrypted wallet enc_rpc.getnewaddress() self.log.info("Test blank descriptor wallets") self.nodes[0].createwallet( - wallet_name='desc_blank', - blank=True, - descriptors=True) - blank_rpc = self.nodes[0].get_wallet_rpc('desc_blank') - assert_raises_rpc_error(-4, - 'This wallet has no available keys', - blank_rpc.getnewaddress) + wallet_name="desc_blank", blank=True, descriptors=True + ) + blank_rpc = self.nodes[0].get_wallet_rpc("desc_blank") + assert_raises_rpc_error( + -4, "This wallet has no available keys", blank_rpc.getnewaddress + ) self.log.info("Test descriptor wallet with disabled private keys") self.nodes[0].createwallet( - wallet_name='desc_no_priv', - disable_private_keys=True, - descriptors=True) - nopriv_rpc = self.nodes[0].get_wallet_rpc('desc_no_priv') - assert_raises_rpc_error(-4, - 'This wallet has no available keys', - nopriv_rpc.getnewaddress) + wallet_name="desc_no_priv", disable_private_keys=True, descriptors=True + ) + nopriv_rpc = self.nodes[0].get_wallet_rpc("desc_no_priv") + assert_raises_rpc_error( + -4, "This wallet has no available keys", nopriv_rpc.getnewaddress + ) -if __name__ == '__main__': +if __name__ == "__main__": WalletDescriptorTest().main() diff --git a/test/functional/wallet_disable.py b/test/functional/wallet_disable.py index dcd0d45a9..88e6c77e0 100755 --- a/test/functional/wallet_disable.py +++ b/test/functional/wallet_disable.py @@ -1,40 +1,44 @@ #!/usr/bin/env python3 # Copyright (c) 2015-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test a node with the -disablewallet option. - Test that validateaddress RPC works when running with -disablewallet - Test that it is not possible to mine to an invalid address. """ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_raises_rpc_error -class DisableWalletTest (BitcoinTestFramework): +class DisableWalletTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.extra_args = [["-disablewallet"]] self.wallet_names = [] def run_test(self): # Make sure wallet is really disabled - assert_raises_rpc_error(-32601, 'Method not found', - self.nodes[0].getwalletinfo) - x = self.nodes[0].validateaddress('3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy') - assert x['isvalid'] is False - x = self.nodes[0].validateaddress('mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ') - assert x['isvalid'] is True + assert_raises_rpc_error(-32601, "Method not found", self.nodes[0].getwalletinfo) + x = self.nodes[0].validateaddress("3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy") + assert x["isvalid"] is False + x = self.nodes[0].validateaddress("mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ") + assert x["isvalid"] is True # Checking mining to an address without a wallet. Generating to a valid address should succeed # but generating to an invalid address will fail. - self.generatetoaddress(self.nodes[0], - 1, 'mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ') - assert_raises_rpc_error(-5, "Invalid address", - self.generatetoaddress, self.nodes[0], 1, '3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy') - - -if __name__ == '__main__': + self.generatetoaddress(self.nodes[0], 1, "mneYUmWYsuk7kySiURxCi3AGxrAqZxLgPZ") + assert_raises_rpc_error( + -5, + "Invalid address", + self.generatetoaddress, + self.nodes[0], + 1, + "3J98t1WpEZ73CNmQviecrnyiWrnqRhWNLy", + ) + + +if __name__ == "__main__": DisableWalletTest().main() diff --git a/test/functional/wallet_dump.py b/test/functional/wallet_dump.py index 787b5f516..fe3e2c259 100755 --- a/test/functional/wallet_dump.py +++ b/test/functional/wallet_dump.py @@ -1,232 +1,263 @@ #!/usr/bin/env python3 # Copyright (c) 2016-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the dumpwallet RPC.""" import datetime import os import time from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error def read_dump(file_name, addrs, script_addrs, hd_master_addr_old): """ Read the given dump, count the addrs that match, count change and reserve. Also check that the old hd_master is inactive """ - with open(file_name, encoding='utf8') as inputfile: + with open(file_name, encoding="utf8") as inputfile: found_addr = 0 found_comments = [] found_script_addr = 0 found_addr_chg = 0 found_addr_rsv = 0 hd_master_addr_ret = None for line in inputfile: line = line.strip() if not line: continue - if line[0] == '#': + if line[0] == "#": found_comments.append(line) else: # split out some data key_date_label, comment = line.split("#") key_date_label = key_date_label.split(" ") # key = key_date_label[0] date = key_date_label[1] keytype = key_date_label[2] - imported_key = date == '1970-01-01T00:00:01Z' + imported_key = date == "1970-01-01T00:00:01Z" if imported_key: # Imported keys have multiple addresses, no label (keypath) and timestamp # Skip them continue addr_keypath = comment.split(" addr=")[1] addr = addr_keypath.split(" ")[0] keypath = None if keytype == "inactivehdseed=1": # ensure the old master is still available assert hd_master_addr_old == addr elif keytype == "hdseed=1": # ensure we have generated a new hd master key assert hd_master_addr_old != addr hd_master_addr_ret = addr elif keytype == "script=1": # scripts don't have keypaths keypath = None else: keypath = addr_keypath.rstrip().split("hdkeypath=")[1] # count key types for addrObj in addrs: - if addrObj['address'] == addr and addrObj['hdkeypath'] == keypath and keytype == "label=": + if ( + addrObj["address"] == addr + and addrObj["hdkeypath"] == keypath + and keytype == "label=" + ): found_addr += 1 break elif keytype == "change=1": found_addr_chg += 1 break elif keytype == "reserve=1": found_addr_rsv += 1 break # count scripts for script_addr in script_addrs: if script_addr == addr.rstrip() and keytype == "script=1": found_script_addr += 1 break - return found_comments, found_addr, found_script_addr, found_addr_chg, found_addr_rsv, hd_master_addr_ret + return ( + found_comments, + found_addr, + found_script_addr, + found_addr_chg, + found_addr_rsv, + hd_master_addr_ret, + ) class WalletDumpTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.extra_args = [["-keypool=90", "-wallet=dump"]] self.rpc_timeout = 120 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def setup_network(self): self.add_nodes(self.num_nodes, extra_args=self.extra_args) self.start_nodes() def run_test(self): wallet_unenc_dump = os.path.join( - self.nodes[0].datadir, "wallet.unencrypted.dump") - wallet_enc_dump = os.path.join( - self.nodes[0].datadir, "wallet.encrypted.dump") + self.nodes[0].datadir, "wallet.unencrypted.dump" + ) + wallet_enc_dump = os.path.join(self.nodes[0].datadir, "wallet.encrypted.dump") # generate 20 addresses to compare against the dump test_addr_count = 20 addrs = [] for _ in range(test_addr_count): addr = self.nodes[0].getnewaddress() - vaddr = self.nodes[0].getaddressinfo( - addr) # required to get hd keypath + vaddr = self.nodes[0].getaddressinfo(addr) # required to get hd keypath addrs.append(vaddr) # Should be a no-op: self.nodes[0].keypoolrefill() # Test scripts dump by adding a 1-of-1 multisig address - multisig_addr = self.nodes[0].addmultisigaddress( - 1, [addrs[0]["address"]])["address"] + multisig_addr = self.nodes[0].addmultisigaddress(1, [addrs[0]["address"]])[ + "address" + ] - self.log.info('Mine a block one second before the wallet is dumped') + self.log.info("Mine a block one second before the wallet is dumped") dump_time = int(time.time()) self.nodes[0].setmocktime(dump_time - 1) self.generate(self.nodes[0], 1) self.nodes[0].setmocktime(dump_time) - dump_time_str = '# * Created on {}Z'.format( + dump_time_str = "# * Created on {}Z".format( datetime.datetime.fromtimestamp( dump_time, tz=datetime.timezone.utc, - ).replace(tzinfo=None).isoformat()) + ) + .replace(tzinfo=None) + .isoformat() + ) dump_best_block_1 = ( - f'# * Best block at time of backup was {self.nodes[0].getblockcount()} ' - f'({self.nodes[0].getbestblockhash()}),' + f"# * Best block at time of backup was {self.nodes[0].getblockcount()} " + f"({self.nodes[0].getbestblockhash()})," ) - dump_best_block_2 = '# mined on {}Z'.format( + dump_best_block_2 = "# mined on {}Z".format( datetime.datetime.fromtimestamp( dump_time - 1, tz=datetime.timezone.utc, - ).replace(tzinfo=None).isoformat()) + ) + .replace(tzinfo=None) + .isoformat() + ) - self.log.info('Dump unencrypted wallet') + self.log.info("Dump unencrypted wallet") result = self.nodes[0].dumpwallet(wallet_unenc_dump) - assert_equal(result['filename'], wallet_unenc_dump) + assert_equal(result["filename"], wallet_unenc_dump) - found_comments, found_addr, found_script_addr, found_addr_chg, found_addr_rsv, hd_master_addr_unenc = \ - read_dump(wallet_unenc_dump, addrs, [multisig_addr], None) + ( + found_comments, + found_addr, + found_script_addr, + found_addr_chg, + found_addr_rsv, + hd_master_addr_unenc, + ) = read_dump(wallet_unenc_dump, addrs, [multisig_addr], None) # Check that file is not corrupt - assert '# End of dump' in found_comments - assert found_comments[0].startswith( - "# Wallet dump created by Bitcoin ABC") + assert "# End of dump" in found_comments + assert found_comments[0].startswith("# Wallet dump created by Bitcoin ABC") assert_equal( - dump_time_str, next( - c for c in found_comments if c.startswith('# * Created on'))) + dump_time_str, + next(c for c in found_comments if c.startswith("# * Created on")), + ) assert_equal( - dump_best_block_1, next( - c for c in found_comments if c.startswith('# * Best block'))) + dump_best_block_1, + next(c for c in found_comments if c.startswith("# * Best block")), + ) assert_equal( - dump_best_block_2, next( - c for c in found_comments if c.startswith('# mined on'))) + dump_best_block_2, + next(c for c in found_comments if c.startswith("# mined on")), + ) # all keys must be in the dump assert_equal(found_addr, test_addr_count) # all scripts must be in the dump assert_equal(found_script_addr, 1) # 0 blocks where mined assert_equal(found_addr_chg, 0) # 90 keys plus 100% internal keys assert_equal(found_addr_rsv, 90 * 2) # encrypt wallet, restart, unlock and dump - self.nodes[0].encryptwallet('test') - self.nodes[0].walletpassphrase('test', 10) + self.nodes[0].encryptwallet("test") + self.nodes[0].walletpassphrase("test", 10) # Should be a no-op: self.nodes[0].keypoolrefill() self.nodes[0].dumpwallet(wallet_enc_dump) - found_comments, found_addr, found_script_addr, found_addr_chg, found_addr_rsv, _ = \ - read_dump( - wallet_enc_dump, - addrs, - [multisig_addr], - hd_master_addr_unenc) + ( + found_comments, + found_addr, + found_script_addr, + found_addr_chg, + found_addr_rsv, + _, + ) = read_dump(wallet_enc_dump, addrs, [multisig_addr], hd_master_addr_unenc) # Check that file is not corrupt - assert '# End of dump' in found_comments + assert "# End of dump" in found_comments assert_equal(found_addr, test_addr_count) assert_equal( - dump_time_str, next( - c for c in found_comments if c.startswith('# * Created on'))) + dump_time_str, + next(c for c in found_comments if c.startswith("# * Created on")), + ) assert_equal( - dump_best_block_1, next( - c for c in found_comments if c.startswith('# * Best block'))) + dump_best_block_1, + next(c for c in found_comments if c.startswith("# * Best block")), + ) assert_equal( - dump_best_block_2, next( - c for c in found_comments if c.startswith('# mined on'))) + dump_best_block_2, + next(c for c in found_comments if c.startswith("# mined on")), + ) assert_equal(found_script_addr, 1) # old reserve keys are marked as change now assert_equal(found_addr_chg, 90 * 2) assert_equal(found_addr_rsv, 90 * 2) # Overwriting should fail - assert_raises_rpc_error(-8, - "already exists", - lambda: self.nodes[0].dumpwallet(wallet_enc_dump)) + assert_raises_rpc_error( + -8, "already exists", lambda: self.nodes[0].dumpwallet(wallet_enc_dump) + ) # Restart node with new wallet, and test importwallet - self.restart_node(0, ['-wallet=w2']) + self.restart_node(0, ["-wallet=w2"]) # Make sure the address is not IsMine before import result = self.nodes[0].getaddressinfo(multisig_addr) - assert result['ismine'] is False + assert result["ismine"] is False self.nodes[0].importwallet(wallet_unenc_dump) # Now check IsMine is true result = self.nodes[0].getaddressinfo(multisig_addr) - assert result['ismine'] is True + assert result["ismine"] is True - self.log.info('Check that wallet is flushed') - with self.nodes[0].assert_debug_log(['Flushing wallet.dat'], timeout=20): + self.log.info("Check that wallet is flushed") + with self.nodes[0].assert_debug_log(["Flushing wallet.dat"], timeout=20): self.nodes[0].getnewaddress() # Make sure that dumpwallet doesn't have a lock order issue when there is an unconfirmed tx and it is reloaded # See https://github.com/bitcoin/bitcoin/issues/22489 self.nodes[0].createwallet("w3") w3 = self.nodes[0].get_wallet_rpc("w3") w3.importprivkey( privkey=self.nodes[0].get_deterministic_priv_key().key, - label="coinbase_import") + label="coinbase_import", + ) w3.sendtoaddress(w3.getnewaddress(), 10) w3.unloadwallet() self.nodes[0].loadwallet("w3") w3.dumpwallet(os.path.join(self.nodes[0].datadir, "w3.dump")) -if __name__ == '__main__': +if __name__ == "__main__": WalletDumpTest().main() diff --git a/test/functional/wallet_encryption.py b/test/functional/wallet_encryption.py index 100192f16..6c6135a01 100755 --- a/test/functional/wallet_encryption.py +++ b/test/functional/wallet_encryption.py @@ -1,127 +1,172 @@ #!/usr/bin/env python3 # Copyright (c) 2016 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test Wallet encryption""" import time from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_greater_than, assert_greater_than_or_equal, assert_raises_rpc_error, ) class WalletEncryptionTest(BitcoinTestFramework): - def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): passphrase = "WalletPassphrase" passphrase2 = "SecondWalletPassphrase" # Make sure the wallet isn't encrypted first msg = "test message" address = self.nodes[0].getnewaddress() sig = self.nodes[0].signmessage(address, msg) assert self.nodes[0].verifymessage(address, sig, msg) assert_raises_rpc_error( - -15, "Error: running with an unencrypted wallet, but walletpassphrase was called", - self.nodes[0].walletpassphrase, 'ff', 1) + -15, + ( + "Error: running with an unencrypted wallet, but walletpassphrase was" + " called" + ), + self.nodes[0].walletpassphrase, + "ff", + 1, + ) assert_raises_rpc_error( -15, - "Error: running with an unencrypted wallet, but walletpassphrasechange was called.", - self.nodes[0].walletpassphrasechange, 'ff', 'ff') + ( + "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, '') + -8, "passphrase can not be empty", self.nodes[0].encryptwallet, "" + ) self.nodes[0].encryptwallet(passphrase) # Check the encrypted wallet is marked as locked on initialization - assert_equal(self.nodes[0].getwalletinfo()['unlocked_until'], 0) + assert_equal(self.nodes[0].getwalletinfo()["unlocked_until"], 0) # 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) + -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') + -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) + -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') + -8, + "passphrase can not be empty", + self.nodes[0].walletpassphrasechange, + "", + "ff", + ) # Check that walletpassphrase works self.nodes[0].walletpassphrase(passphrase, 2) sig = self.nodes[0].signmessage(address, msg) assert self.nodes[0].verifymessage(address, sig, msg) # Check that the timeout is right time.sleep(3) assert_raises_rpc_error( - -13, "Please enter the wallet passphrase with walletpassphrase first", - self.nodes[0].signmessage, address, msg) + -13, + "Please enter the wallet passphrase with walletpassphrase first", + self.nodes[0].signmessage, + address, + msg, + ) # Test wrong passphrase - assert_raises_rpc_error(-14, "wallet passphrase entered was incorrect", - self.nodes[0].walletpassphrase, f"{passphrase}wrong", 10) + assert_raises_rpc_error( + -14, + "wallet passphrase entered was incorrect", + self.nodes[0].walletpassphrase, + f"{passphrase}wrong", + 10, + ) # Test walletlock and unlocked_until values self.mocktime = 1 self.nodes[0].setmocktime(self.mocktime) self.nodes[0].walletpassphrase(passphrase, 84600) sig = self.nodes[0].signmessage(address, msg) assert self.nodes[0].verifymessage(address, sig, msg) - assert_equal( - self.nodes[0].getwalletinfo()['unlocked_until'], 1 + 84600) + assert_equal(self.nodes[0].getwalletinfo()["unlocked_until"], 1 + 84600) self.nodes[0].walletlock() assert_raises_rpc_error( - -13, "Please enter the wallet passphrase with walletpassphrase first", - self.nodes[0].signmessage, address, msg) - assert_equal(self.nodes[0].getwalletinfo()['unlocked_until'], 0) + -13, + "Please enter the wallet passphrase with walletpassphrase first", + self.nodes[0].signmessage, + address, + msg, + ) + assert_equal(self.nodes[0].getwalletinfo()["unlocked_until"], 0) # Test passphrase changes self.nodes[0].walletpassphrasechange(passphrase, passphrase2) - assert_raises_rpc_error(-14, "wallet passphrase entered was incorrect", - self.nodes[0].walletpassphrase, passphrase, 10) + assert_raises_rpc_error( + -14, + "wallet passphrase entered was incorrect", + self.nodes[0].walletpassphrase, + passphrase, + 10, + ) self.nodes[0].walletpassphrase(passphrase2, 10) sig = self.nodes[0].signmessage(address, msg) assert self.nodes[0].verifymessage(address, sig, msg) self.nodes[0].walletlock() # Test timeout bounds - assert_raises_rpc_error(-8, "Timeout cannot be negative.", - self.nodes[0].walletpassphrase, passphrase2, -10) + assert_raises_rpc_error( + -8, + "Timeout cannot be negative.", + self.nodes[0].walletpassphrase, + passphrase2, + -10, + ) # Check the timeout # Check a time less than the limit MAX_VALUE = 100000000 expected_time = self.mocktime + MAX_VALUE - 600 self.nodes[0].walletpassphrase(passphrase2, MAX_VALUE - 600) - actual_time = self.nodes[0].getwalletinfo()['unlocked_until'] + actual_time = self.nodes[0].getwalletinfo()["unlocked_until"] assert_greater_than_or_equal(actual_time, expected_time) # 5 second buffer assert_greater_than(expected_time + 5, actual_time) # Check a time greater than the limit expected_time = self.mocktime + MAX_VALUE - 1 self.nodes[0].walletpassphrase(passphrase2, MAX_VALUE + 1000) - actual_time = self.nodes[0].getwalletinfo()['unlocked_until'] + actual_time = self.nodes[0].getwalletinfo()["unlocked_until"] assert_greater_than_or_equal(actual_time, expected_time) # 5 second buffer assert_greater_than(expected_time + 5, actual_time) -if __name__ == '__main__': +if __name__ == "__main__": WalletEncryptionTest().main() diff --git a/test/functional/wallet_groups.py b/test/functional/wallet_groups.py index bd27df92f..ace6ec8be 100755 --- a/test/functional/wallet_groups.py +++ b/test/functional/wallet_groups.py @@ -1,180 +1,187 @@ #!/usr/bin/env python3 # Copyright (c) 2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test wallet group functionality.""" from test_framework.messages import CTransaction, FromHex, ToHex from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_approx, assert_equal class WalletGroupTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 5 self.extra_args = [ [], [], - ['-avoidpartialspends'], + ["-avoidpartialspends"], # 2.93 XEC is the threshold that causes a node to prefer a # non-grouped tx (3 inputs, 2 outputs) to a grouped tx (5 inputs, # 2 outputs). The fee for the grouped tx is 294 sats higher than # the fee for the non-grouped tx. See tx5 below. ["-maxapsfee=2.93"], - ["-maxapsfee=2.94"]] + ["-maxapsfee=2.94"], + ] # whitelist peers to speed up tx relay / mempool sync for args in self.extra_args: args.append("-whitelist=noban@127.0.0.1") self.rpc_timeout = 480 self.supports_cli = False def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): # Mine some coins self.generate(self.nodes[0], 110) # Get some addresses from the two nodes addr1 = [self.nodes[1].getnewaddress() for _ in range(3)] addr2 = [self.nodes[2].getnewaddress() for _ in range(3)] addrs = addr1 + addr2 # Send 1 + 0.5 coin to each address [self.nodes[0].sendtoaddress(addr, 1000000) for addr in addrs] [self.nodes[0].sendtoaddress(addr, 500000) for addr in addrs] self.generate(self.nodes[0], 1) # For each node, send 0.2 coins back to 0; # - node[1] should pick one 0.5 UTXO and leave the rest # - node[2] should pick one (1.0 + 0.5) UTXO group corresponding to a # given address, and leave the rest - txid1 = self.nodes[1].sendtoaddress( - self.nodes[0].getnewaddress(), 200000) + txid1 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 200000) tx1 = self.nodes[1].getrawtransaction(txid1, True) # txid1 should have 1 input and 2 outputs assert_equal(1, len(tx1["vin"])) assert_equal(2, len(tx1["vout"])) # one output should be 0.2, the other should be ~0.3 v = sorted([vout["value"] for vout in tx1["vout"]]) assert_approx(v[0], vexp=200_000, vspan=100) assert_approx(v[1], vexp=300_000, vspan=100) - txid2 = self.nodes[2].sendtoaddress( - self.nodes[0].getnewaddress(), 200000) + txid2 = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 200000) tx2 = self.nodes[2].getrawtransaction(txid2, True) # txid2 should have 2 inputs and 2 outputs assert_equal(2, len(tx2["vin"])) assert_equal(2, len(tx2["vout"])) # one output should be 0.2, the other should be ~1.3 v = sorted([vout["value"] for vout in tx2["vout"]]) assert_approx(v[0], vexp=200_000, vspan=100) assert_approx(v[1], vexp=1_300_000, vspan=100) # Test 'avoid partial if warranted, even if disabled' self.sync_all() self.generate(self.nodes[0], 1) # Nodes 1-2 now have confirmed UTXOs (letters denote destinations): # Node #1: Node #2: # - A 1.0 - D0 1.0 # - B0 1.0 - D1 0.5 # - B1 0.5 - E0 1.0 # - C0 1.0 - E1 0.5 # - C1 0.5 - F ~1.3 # - D ~0.3 assert_approx(self.nodes[1].getbalance(), vexp=4_300_000, vspan=100) assert_approx(self.nodes[2].getbalance(), vexp=4_300_000, vspan=100) # Sending 1.4 btc should pick one 1.0 + one more. For node #1, # this could be (A / B0 / C0) + (B1 / C1 / D). We ensure that it is # B0 + B1 or C0 + C1, because this avoids partial spends while not being # detrimental to transaction cost - txid3 = self.nodes[1].sendtoaddress( - self.nodes[0].getnewaddress(), 1400000) + txid3 = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1400000) tx3 = self.nodes[1].getrawtransaction(txid3, True) # tx3 should have 2 inputs and 2 outputs assert_equal(2, len(tx3["vin"])) assert_equal(2, len(tx3["vout"])) # the accumulated value should be 1.5, so the outputs should be # ~0.1 and 1.4 and should come from the same destination values = sorted([vout["value"] for vout in tx3["vout"]]) assert_approx(values[0], vexp=100_000, vspan=100) assert_approx(values[1], vexp=1_400_000, vspan=100) input_txids = [vin["txid"] for vin in tx3["vin"]] - input_addrs = [self.nodes[1].gettransaction( - txid)['details'][0]['address'] for txid in input_txids] + input_addrs = [ + self.nodes[1].gettransaction(txid)["details"][0]["address"] + for txid in input_txids + ] assert_equal(input_addrs[0], input_addrs[1]) # Node 2 enforces avoidpartialspends so needs no checking here # Test wallet option maxapsfee with Node 3 addr_aps = self.nodes[3].getnewaddress() self.nodes[0].sendtoaddress(addr_aps, 1000000) self.nodes[0].sendtoaddress(addr_aps, 1000000) self.generate(self.nodes[0], 1) - with self.nodes[3].assert_debug_log([ - 'Fee non-grouped = 225, grouped = 372, using grouped']): - txid4 = self.nodes[3].sendtoaddress( - self.nodes[0].getnewaddress(), 100_000) + with self.nodes[3].assert_debug_log( + ["Fee non-grouped = 225, grouped = 372, using grouped"] + ): + txid4 = self.nodes[3].sendtoaddress(self.nodes[0].getnewaddress(), 100_000) tx4 = self.nodes[3].getrawtransaction(txid4, True) # tx4 should have 2 inputs and 2 outputs although one output would # have been enough and the transaction caused higher fees assert_equal(2, len(tx4["vin"])) assert_equal(2, len(tx4["vout"])) addr_aps2 = self.nodes[3].getnewaddress() [self.nodes[0].sendtoaddress(addr_aps2, 1_000_000) for _ in range(5)] self.generate(self.nodes[0], 1) - with self.nodes[3].assert_debug_log([ - 'Fee non-grouped = 519, grouped = 813, using non-grouped']): - txid5 = self.nodes[3].sendtoaddress(self.nodes[0].getnewaddress(), - 2_950_000) + with self.nodes[3].assert_debug_log( + ["Fee non-grouped = 519, grouped = 813, using non-grouped"] + ): + txid5 = self.nodes[3].sendtoaddress( + self.nodes[0].getnewaddress(), 2_950_000 + ) tx5 = self.nodes[3].getrawtransaction(txid5, True) # tx5 should have 3 inputs (1.0, 1.0, 1.0) and 2 outputs assert_equal(3, len(tx5["vin"])) assert_equal(2, len(tx5["vout"])) # Test wallet option maxapsfee with node 4, which sets maxapsfee # 1 sat higher, crossing the threshold from non-grouped to grouped. addr_aps3 = self.nodes[4].getnewaddress() [self.nodes[0].sendtoaddress(addr_aps3, 1_000_000) for _ in range(5)] self.generate(self.nodes[0], 1) - with self.nodes[4].assert_debug_log([ - 'Fee non-grouped = 519, grouped = 813, using grouped']): - txid6 = self.nodes[4].sendtoaddress(self.nodes[0].getnewaddress(), - 2_950_000) + with self.nodes[4].assert_debug_log( + ["Fee non-grouped = 519, grouped = 813, using grouped"] + ): + txid6 = self.nodes[4].sendtoaddress( + self.nodes[0].getnewaddress(), 2_950_000 + ) tx6 = self.nodes[4].getrawtransaction(txid6, True) # tx6 should have 5 inputs and 2 outputs assert_equal(5, len(tx6["vin"])) assert_equal(2, len(tx6["vout"])) # Empty out node2's wallet - self.nodes[2].sendtoaddress(address=self.nodes[0].getnewaddress( - ), amount=self.nodes[2].getbalance(), subtractfeefromamount=True) + self.nodes[2].sendtoaddress( + address=self.nodes[0].getnewaddress(), + amount=self.nodes[2].getbalance(), + subtractfeefromamount=True, + ) self.sync_all() self.generate(self.nodes[0], 1) # Fill node2's wallet with 10000 outputs corresponding to the same # scriptPubKey for _ in range(5): raw_tx = self.nodes[0].createrawtransaction( - [{"txid": "0" * 64, "vout": 0}], [{addr2[0]: 50_000}]) + [{"txid": "0" * 64, "vout": 0}], [{addr2[0]: 50_000}] + ) tx = FromHex(CTransaction(), raw_tx) tx.vin = [] tx.vout = [tx.vout[0]] * 2000 funded_tx = self.nodes[0].fundrawtransaction(ToHex(tx)) - signed_tx = self.nodes[0].signrawtransactionwithwallet( - funded_tx['hex']) - self.nodes[0].sendrawtransaction(signed_tx['hex']) + signed_tx = self.nodes[0].signrawtransactionwithwallet(funded_tx["hex"]) + self.nodes[0].sendrawtransaction(signed_tx["hex"]) self.generate(self.nodes[0], 1) # Check that we can create a transaction that only requires ~100 of our # utxos, without pulling in all outputs and creating a transaction that # is way too big. assert self.nodes[2].sendtoaddress(address=addr2[0], amount=5000000) -if __name__ == '__main__': +if __name__ == "__main__": WalletGroupTest().main() diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py index 295264e44..c8675124d 100755 --- a/test/functional/wallet_hd.py +++ b/test/functional/wallet_hd.py @@ -1,353 +1,365 @@ #!/usr/bin/env python3 # Copyright (c) 2016-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test Hierarchical Deterministic wallet function.""" import os import shutil from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error class WalletHDTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 - self.extra_args = [[], ['-keypool=0']] + self.extra_args = [[], ["-keypool=0"]] # 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 run_test(self): # Make sure we use hd, keep masterkeyid - hd_fingerprint = self.nodes[1].getaddressinfo( - self.nodes[1].getnewaddress())['hdmasterfingerprint'] + hd_fingerprint = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())[ + "hdmasterfingerprint" + ] assert_equal(len(hd_fingerprint), 8) # create an internal key change_addr = self.nodes[1].getrawchangeaddress() change_addrV = self.nodes[1].getaddressinfo(change_addr) if self.options.descriptors: assert_equal(change_addrV["hdkeypath"], "m/44'/1'/0'/1/0") else: # first internal child key assert_equal(change_addrV["hdkeypath"], "m/0'/1'/0'") # Import a non-HD private key in the HD wallet - non_hd_add = 'ecregtest:qr09jgufyeae4s97nqp6mv0tv6eymfunygeyv0llfe' - non_hd_key = 'cS9umN9w6cDMuRVYdbkfE4c7YUFLJRoXMfhQ569uY4odiQbVN8Rt' + non_hd_add = "ecregtest:qr09jgufyeae4s97nqp6mv0tv6eymfunygeyv0llfe" + non_hd_key = "cS9umN9w6cDMuRVYdbkfE4c7YUFLJRoXMfhQ569uY4odiQbVN8Rt" self.nodes[1].importprivkey(non_hd_key) # This should be enough to keep the master key and the non-HD key - self.nodes[1].backupwallet( - os.path.join(self.nodes[1].datadir, "hd.bak")) + self.nodes[1].backupwallet(os.path.join(self.nodes[1].datadir, "hd.bak")) # self.nodes[1].dumpwallet(os.path.join(self.nodes[1].datadir, "hd.dump")) # Derive some HD addresses and remember the last # Also send funds to each add self.generate(self.nodes[0], 101) hd_add = None NUM_HD_ADDS = 10 for i in range(1, NUM_HD_ADDS + 1): hd_add = self.nodes[1].getnewaddress() hd_info = self.nodes[1].getaddressinfo(hd_add) if self.options.descriptors: assert_equal(hd_info["hdkeypath"], f"m/44'/1'/0'/0/{str(i)}") else: assert_equal(hd_info["hdkeypath"], f"m/0'/0'/{str(i)}'") assert_equal(hd_info["hdmasterfingerprint"], hd_fingerprint) self.nodes[0].sendtoaddress(hd_add, 1000000) self.generate(self.nodes[0], 1) self.nodes[0].sendtoaddress(non_hd_add, 1000000) self.generate(self.nodes[0], 1) # create an internal key (again) change_addr = self.nodes[1].getrawchangeaddress() change_addrV = self.nodes[1].getaddressinfo(change_addr) if self.options.descriptors: assert_equal(change_addrV["hdkeypath"], "m/44'/1'/0'/1/1") else: # second internal child key assert_equal(change_addrV["hdkeypath"], "m/0'/1'/1'") self.sync_all() - assert_equal( - self.nodes[1].getbalance(), - (NUM_HD_ADDS * 1000000) + 1000000) + assert_equal(self.nodes[1].getbalance(), (NUM_HD_ADDS * 1000000) + 1000000) self.log.info("Restore backup ...") self.stop_node(1) # we need to delete the complete regtest directory # otherwise node1 would auto-recover all funds in flag the keypool keys # as used - shutil.rmtree( + shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "blocks")) + shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "chainstate")) + shutil.copyfile( + os.path.join(self.nodes[1].datadir, "hd.bak"), os.path.join( self.nodes[1].datadir, self.chain, - "blocks")) - shutil.rmtree(os.path.join( - self.nodes[1].datadir, self.chain, "chainstate")) - shutil.copyfile( - os.path.join(self.nodes[1].datadir, "hd.bak"), - os.path.join(self.nodes[1].datadir, self.chain, 'wallets', - self.default_wallet_name, self.wallet_data_filename)) + "wallets", + self.default_wallet_name, + self.wallet_data_filename, + ), + ) self.start_node(1) # Assert that derivation is deterministic hd_add_2 = None for i in range(1, NUM_HD_ADDS + 1): hd_add_2 = self.nodes[1].getnewaddress() hd_info_2 = self.nodes[1].getaddressinfo(hd_add_2) if self.options.descriptors: assert_equal(hd_info_2["hdkeypath"], f"m/44'/1'/0'/0/{str(i)}") else: assert_equal(hd_info_2["hdkeypath"], f"m/0'/0'/{str(i)}'") assert_equal(hd_info_2["hdmasterfingerprint"], hd_fingerprint) assert_equal(hd_add, hd_add_2) self.connect_nodes(0, 1) self.sync_all() # Needs rescan - self.restart_node(1, extra_args=self.extra_args[1] + ['-rescan']) - assert_equal( - self.nodes[1].getbalance(), - (NUM_HD_ADDS * 1000000) + 1000000) + self.restart_node(1, extra_args=self.extra_args[1] + ["-rescan"]) + assert_equal(self.nodes[1].getbalance(), (NUM_HD_ADDS * 1000000) + 1000000) # Try a RPC based rescan self.stop_node(1) - shutil.rmtree( + shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "blocks")) + shutil.rmtree(os.path.join(self.nodes[1].datadir, self.chain, "chainstate")) + shutil.copyfile( + os.path.join(self.nodes[1].datadir, "hd.bak"), os.path.join( self.nodes[1].datadir, self.chain, - "blocks")) - shutil.rmtree(os.path.join( - self.nodes[1].datadir, self.chain, "chainstate")) - shutil.copyfile( - os.path.join(self.nodes[1].datadir, "hd.bak"), - os.path.join(self.nodes[1].datadir, self.chain, "wallets", - self.default_wallet_name, self.wallet_data_filename)) + "wallets", + self.default_wallet_name, + self.wallet_data_filename, + ), + ) self.start_node(1, extra_args=self.extra_args[1]) self.connect_nodes(0, 1) self.sync_all() # Wallet automatically scans blocks older than key on startup - assert_equal( - self.nodes[1].getbalance(), - (NUM_HD_ADDS * 1000000) + 1000000) + assert_equal(self.nodes[1].getbalance(), (NUM_HD_ADDS * 1000000) + 1000000) out = self.nodes[1].rescanblockchain(0, 1) - assert_equal(out['start_height'], 0) - assert_equal(out['stop_height'], 1) + assert_equal(out["start_height"], 0) + assert_equal(out["stop_height"], 1) out = self.nodes[1].rescanblockchain(2, 4) - assert_equal(out['start_height'], 2) - assert_equal(out['stop_height'], 4) + assert_equal(out["start_height"], 2) + assert_equal(out["stop_height"], 4) out = self.nodes[1].rescanblockchain(3) - assert_equal(out['start_height'], 3) - assert_equal(out['stop_height'], self.nodes[1].getblockcount()) + assert_equal(out["start_height"], 3) + assert_equal(out["stop_height"], self.nodes[1].getblockcount()) out = self.nodes[1].rescanblockchain() - assert_equal(out['start_height'], 0) - assert_equal(out['stop_height'], self.nodes[1].getblockcount()) - assert_equal( - self.nodes[1].getbalance(), - (NUM_HD_ADDS * 1000000) + 1000000) + assert_equal(out["start_height"], 0) + assert_equal(out["stop_height"], self.nodes[1].getblockcount()) + assert_equal(self.nodes[1].getbalance(), (NUM_HD_ADDS * 1000000) + 1000000) # send a tx and make sure its using the internal chain for the # changeoutput - txid = self.nodes[1].sendtoaddress( - self.nodes[0].getnewaddress(), 1000000) - outs = self.nodes[1].gettransaction( - txid=txid, verbose=True)['decoded']['vout'] + txid = self.nodes[1].sendtoaddress(self.nodes[0].getnewaddress(), 1000000) + outs = self.nodes[1].gettransaction(txid=txid, verbose=True)["decoded"]["vout"] keypath = "" for out in outs: - if out['value'] != 1000000: + if out["value"] != 1000000: keypath = self.nodes[1].getaddressinfo( - out['scriptPubKey']['addresses'][0])['hdkeypath'] + out["scriptPubKey"]["addresses"][0] + )["hdkeypath"] if self.options.descriptors: assert_equal(keypath[0:14], "m/44'/1'/0'/1/") else: assert_equal(keypath[0:7], "m/0'/1'") if not self.options.descriptors: # Generate a new HD seed on node 1 and make sure it is set - orig_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] + orig_masterkeyid = self.nodes[1].getwalletinfo()["hdseedid"] self.nodes[1].sethdseed() - new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] + new_masterkeyid = self.nodes[1].getwalletinfo()["hdseedid"] assert orig_masterkeyid != new_masterkeyid addr = self.nodes[1].getnewaddress() # Make sure the new address is the first from the keypool - assert_equal(self.nodes[1].getaddressinfo( - addr)['hdkeypath'], 'm/0\'/0\'/0\'') + assert_equal(self.nodes[1].getaddressinfo(addr)["hdkeypath"], "m/0'/0'/0'") # Fill keypool with 1 key self.nodes[1].keypoolrefill(1) # Set a new HD seed on node 1 without flushing the keypool new_seed = self.nodes[0].dumpprivkey(self.nodes[0].getnewaddress()) orig_masterkeyid = new_masterkeyid self.nodes[1].sethdseed(False, new_seed) - new_masterkeyid = self.nodes[1].getwalletinfo()['hdseedid'] + new_masterkeyid = self.nodes[1].getwalletinfo()["hdseedid"] assert orig_masterkeyid != new_masterkeyid addr = self.nodes[1].getnewaddress() - assert_equal(orig_masterkeyid, self.nodes[1].getaddressinfo( - addr)['hdseedid']) + assert_equal( + orig_masterkeyid, self.nodes[1].getaddressinfo(addr)["hdseedid"] + ) # Make sure the new address continues previous keypool - assert_equal(self.nodes[1].getaddressinfo( - addr)['hdkeypath'], 'm/0\'/0\'/1\'') + assert_equal(self.nodes[1].getaddressinfo(addr)["hdkeypath"], "m/0'/0'/1'") # Check that the next address is from the new seed self.nodes[1].keypoolrefill(1) next_addr = self.nodes[1].getnewaddress() - assert_equal(new_masterkeyid, self.nodes[1].getaddressinfo( - next_addr)['hdseedid']) + assert_equal( + new_masterkeyid, self.nodes[1].getaddressinfo(next_addr)["hdseedid"] + ) # Make sure the new address is not from previous keypool - assert_equal(self.nodes[1].getaddressinfo( - next_addr)['hdkeypath'], 'm/0\'/0\'/0\'') + assert_equal( + self.nodes[1].getaddressinfo(next_addr)["hdkeypath"], "m/0'/0'/0'" + ) assert next_addr != addr # Sethdseed parameter validity - assert_raises_rpc_error(-1, 'sethdseed', self.nodes[0].sethdseed, - False, new_seed, 0) - assert_raises_rpc_error(-5, "Invalid private key", - self.nodes[1].sethdseed, False, "not_wif") assert_raises_rpc_error( - -1, "JSON value is not a boolean as expected", - self.nodes[1].sethdseed, "Not_bool") + -1, "sethdseed", self.nodes[0].sethdseed, False, new_seed, 0 + ) + assert_raises_rpc_error( + -5, "Invalid private key", self.nodes[1].sethdseed, False, "not_wif" + ) + assert_raises_rpc_error( + -1, + "JSON value is not a boolean as expected", + self.nodes[1].sethdseed, + "Not_bool", + ) assert_raises_rpc_error( - -1, "JSON value is not a string as expected", - self.nodes[1].sethdseed, False, True) + -1, + "JSON value is not a string as expected", + self.nodes[1].sethdseed, + False, + True, + ) assert_raises_rpc_error( - -5, "Already have this key", self.nodes[1].sethdseed, False, - new_seed) + -5, "Already have this key", self.nodes[1].sethdseed, False, new_seed + ) assert_raises_rpc_error( - -5, "Already have this key", self.nodes[1].sethdseed, False, - self.nodes[1].dumpprivkey(self.nodes[1].getnewaddress())) + -5, + "Already have this key", + self.nodes[1].sethdseed, + False, + self.nodes[1].dumpprivkey(self.nodes[1].getnewaddress()), + ) self.log.info( - 'Test sethdseed restoring with keys outside of the initial keypool') + "Test sethdseed restoring with keys outside of the initial keypool" + ) self.generate(self.nodes[0], 10) # Restart node 1 with keypool of 3 and a different wallet - self.nodes[1].createwallet(wallet_name='origin', blank=True) - self.restart_node(1, extra_args=['-keypool=3', '-wallet=origin']) + self.nodes[1].createwallet(wallet_name="origin", blank=True) + self.restart_node(1, extra_args=["-keypool=3", "-wallet=origin"]) self.connect_nodes(0, 1) # sethdseed restoring and seeing txs to addresses out of the # keypool - origin_rpc = self.nodes[1].get_wallet_rpc('origin') + origin_rpc = self.nodes[1].get_wallet_rpc("origin") seed = self.nodes[0].dumpprivkey(self.nodes[0].getnewaddress()) origin_rpc.sethdseed(True, seed) - self.nodes[1].createwallet(wallet_name='restore', blank=True) - restore_rpc = self.nodes[1].get_wallet_rpc('restore') + self.nodes[1].createwallet(wallet_name="restore", blank=True) + restore_rpc = self.nodes[1].get_wallet_rpc("restore") # Set to be the same seed as origin_rpc restore_rpc.sethdseed(True, seed) # Rotate to a new seed, making original `seed` inactive restore_rpc.sethdseed(True) - self.nodes[1].createwallet(wallet_name='restore2', blank=True) - restore2_rpc = self.nodes[1].get_wallet_rpc('restore2') + self.nodes[1].createwallet(wallet_name="restore2", blank=True) + restore2_rpc = self.nodes[1].get_wallet_rpc("restore2") # Set to be the same seed as origin_rpc restore2_rpc.sethdseed(True, seed) # Rotate to a new seed, making original `seed` inactive restore2_rpc.sethdseed(True) # Check persistence of inactive seed by reloading restore. restore2 # is still loaded to test the case where the wallet is not reloaded restore_rpc.unloadwallet() - self.nodes[1].loadwallet('restore') - restore_rpc = self.nodes[1].get_wallet_rpc('restore') + self.nodes[1].loadwallet("restore") + restore_rpc = self.nodes[1].get_wallet_rpc("restore") # Empty origin keypool and get an address that is beyond the # initial keypool origin_rpc.getnewaddress() origin_rpc.getnewaddress() # Last address of initial keypool last_addr = origin_rpc.getnewaddress() # First address beyond initial keypool addr = origin_rpc.getnewaddress() # Check that the restored seed has last_addr but does not have addr info = restore_rpc.getaddressinfo(last_addr) - assert_equal(info['ismine'], True) + assert_equal(info["ismine"], True) info = restore_rpc.getaddressinfo(addr) - assert_equal(info['ismine'], False) + assert_equal(info["ismine"], False) info = restore2_rpc.getaddressinfo(last_addr) - assert_equal(info['ismine'], True) + assert_equal(info["ismine"], True) info = restore2_rpc.getaddressinfo(addr) - assert_equal(info['ismine'], False) + assert_equal(info["ismine"], False) # Check that the origin seed has addr info = origin_rpc.getaddressinfo(addr) - assert_equal(info['ismine'], True) + assert_equal(info["ismine"], True) # Send a transaction to addr, which is out of the initial keypool. # The wallet that has set a new seed (restore_rpc) should not # detect this transaction. txid = self.nodes[0].sendtoaddress(addr, 1000000) - origin_rpc.sendrawtransaction( - self.nodes[0].gettransaction(txid)['hex']) + origin_rpc.sendrawtransaction(self.nodes[0].gettransaction(txid)["hex"]) self.generate(self.nodes[0], 1) origin_rpc.gettransaction(txid) - assert_raises_rpc_error(-5, - 'Invalid or non-wallet transaction id', - restore_rpc.gettransaction, - txid) + assert_raises_rpc_error( + -5, + "Invalid or non-wallet transaction id", + restore_rpc.gettransaction, + txid, + ) out_of_kp_txid = txid # Send a transaction to last_addr, which is in the initial keypool. # The wallet that has set a new seed (restore_rpc) should detect # this transaction and generate 3 new keys from the initial seed. # The previous transaction (out_of_kp_txid) should still not be # detected as a rescan is required. txid = self.nodes[0].sendtoaddress(last_addr, 1000000) - origin_rpc.sendrawtransaction( - self.nodes[0].gettransaction(txid)['hex']) + origin_rpc.sendrawtransaction(self.nodes[0].gettransaction(txid)["hex"]) self.generate(self.nodes[0], 1) origin_rpc.gettransaction(txid) restore_rpc.gettransaction(txid) - assert_raises_rpc_error(-5, - 'Invalid or non-wallet transaction id', - restore_rpc.gettransaction, - out_of_kp_txid) + assert_raises_rpc_error( + -5, + "Invalid or non-wallet transaction id", + restore_rpc.gettransaction, + out_of_kp_txid, + ) restore2_rpc.gettransaction(txid) - assert_raises_rpc_error(-5, - 'Invalid or non-wallet transaction id', - restore2_rpc.gettransaction, - out_of_kp_txid) + assert_raises_rpc_error( + -5, + "Invalid or non-wallet transaction id", + restore2_rpc.gettransaction, + out_of_kp_txid, + ) # After rescanning, restore_rpc should now see out_of_kp_txid and # generate an additional key. # addr should now be part of restore_rpc and be ismine restore_rpc.rescanblockchain() restore_rpc.gettransaction(out_of_kp_txid) info = restore_rpc.getaddressinfo(addr) - assert_equal(info['ismine'], True) + assert_equal(info["ismine"], True) restore2_rpc.rescanblockchain() restore2_rpc.gettransaction(out_of_kp_txid) info = restore2_rpc.getaddressinfo(addr) - assert_equal(info['ismine'], True) + assert_equal(info["ismine"], True) # Check again that 3 keys were derived. # Empty keypool and get an address that is beyond the initial # keypool origin_rpc.getnewaddress() origin_rpc.getnewaddress() last_addr = origin_rpc.getnewaddress() addr = origin_rpc.getnewaddress() # Check that the restored seed has last_addr but does not have addr info = restore_rpc.getaddressinfo(last_addr) - assert_equal(info['ismine'], True) + assert_equal(info["ismine"], True) info = restore_rpc.getaddressinfo(addr) - assert_equal(info['ismine'], False) + assert_equal(info["ismine"], False) info = restore2_rpc.getaddressinfo(last_addr) - assert_equal(info['ismine'], True) + assert_equal(info["ismine"], True) info = restore2_rpc.getaddressinfo(addr) - assert_equal(info['ismine'], False) + assert_equal(info["ismine"], False) -if __name__ == '__main__': +if __name__ == "__main__": WalletHDTest().main() diff --git a/test/functional/wallet_import_rescan.py b/test/functional/wallet_import_rescan.py index ea503efa0..e2ea56e4f 100755 --- a/test/functional/wallet_import_rescan.py +++ b/test/functional/wallet_import_rescan.py @@ -1,234 +1,257 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test wallet import RPCs. Test rescan behavior of importaddress, importpubkey, importprivkey, and importmulti RPCs with different types of keys and rescan options. In the first part of the test, node 0 creates an address for each type of import RPC call and node 0 sends XEC to it. Then other nodes import the addresses, and the test makes listtransactions and getbalance calls to confirm that the importing node either did or did not execute rescans picking up the send transactions. In the second part of the test, node 0 sends more XEC to each address, and the test makes more listtransactions and getbalance calls to confirm that the importing nodes pick up the new transactions regardless of whether rescans happened previously. """ import collections import enum import itertools import random from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, set_node_times Call = enum.Enum("Call", "single multiaddress multiscript") Data = enum.Enum("Data", "address pub priv") Rescan = enum.Enum("Rescan", "no yes late_timestamp") class Variant(collections.namedtuple("Variant", "call data rescan prune")): """Helper for importing one key and verifying scanned transactions.""" def do_import(self, timestamp): """Call one key import RPC.""" rescan = self.rescan == Rescan.yes assert_equal(self.address["solvable"], True) if self.call == Call.single: if self.data == Data.address: response = self.node.importaddress( - address=self.address["address"], label=self.label, rescan=rescan) + address=self.address["address"], label=self.label, rescan=rescan + ) elif self.data == Data.pub: response = self.node.importpubkey( - pubkey=self.address["pubkey"], label=self.label, rescan=rescan) + pubkey=self.address["pubkey"], label=self.label, rescan=rescan + ) elif self.data == Data.priv: response = self.node.importprivkey( - privkey=self.key, label=self.label, rescan=rescan) + privkey=self.key, label=self.label, rescan=rescan + ) assert_equal(response, None) elif self.call in (Call.multiaddress, Call.multiscript): request = { - "scriptPubKey": { - "address": self.address["address"] - } if self.call == Call.multiaddress else self.address["scriptPubKey"], - "timestamp": timestamp + TIMESTAMP_WINDOW + ( - 1 if self.rescan == Rescan.late_timestamp else 0), + "scriptPubKey": ( + {"address": self.address["address"]} + if self.call == Call.multiaddress + else self.address["scriptPubKey"] + ), + "timestamp": ( + timestamp + + TIMESTAMP_WINDOW + + (1 if self.rescan == Rescan.late_timestamp else 0) + ), "pubkeys": [self.address["pubkey"]] if self.data == Data.pub else [], "keys": [self.key] if self.data == Data.priv else [], "label": self.label, - "watchonly": self.data != Data.priv + "watchonly": self.data != Data.priv, } response = self.node.importmulti( requests=[request], - options={ - "rescan": self.rescan in ( - Rescan.yes, - Rescan.late_timestamp)}, + options={"rescan": self.rescan in (Rescan.yes, Rescan.late_timestamp)}, ) assert_equal(response, [{"success": True}]) def check(self, txid=None, amount=None, confirmation_height=None): """Verify that listtransactions/listreceivedbyaddress return expected values.""" txs = self.node.listtransactions( - label=self.label, count=10000, include_watchonly=True) + label=self.label, count=10000, include_watchonly=True + ) current_height = self.node.getblockcount() assert_equal(len(txs), self.expected_txs) addresses = self.node.listreceivedbyaddress( - minconf=0, include_watchonly=True, address_filter=self.address['address']) + minconf=0, include_watchonly=True, address_filter=self.address["address"] + ) if self.expected_txs: assert_equal(len(addresses[0]["txids"]), self.expected_txs) if txid is not None: - tx, = [tx for tx in txs if tx["txid"] == txid] + (tx,) = [tx for tx in txs if tx["txid"] == txid] assert_equal(tx["label"], self.label) assert_equal(tx["address"], self.address["address"]) assert_equal(tx["amount"], amount) assert_equal(tx["category"], "receive") assert_equal(tx["label"], self.label) assert_equal(tx["txid"], txid) - assert_equal(tx["confirmations"], - 1 + current_height - confirmation_height) + assert_equal(tx["confirmations"], 1 + current_height - confirmation_height) assert_equal("trusted" not in tx, True) - address, = [ad for ad in addresses if txid in ad["txids"]] + (address,) = [ad for ad in addresses if txid in ad["txids"]] assert_equal(address["address"], self.address["address"]) assert_equal(address["amount"], self.expected_balance) - assert_equal(address["confirmations"], - 1 + current_height - confirmation_height) + assert_equal( + address["confirmations"], 1 + current_height - confirmation_height + ) # Verify the transaction is correctly marked watchonly depending on # whether the transaction pays to an imported public key or # imported private key. The test setup ensures that transaction # inputs will not be from watchonly keys (important because # involvesWatchonly will be true if either the transaction output # or inputs are watchonly). if self.data != Data.priv: assert_equal(address["involvesWatchonly"], True) else: assert_equal("involvesWatchonly" not in address, True) # List of Variants for each way a key or address could be imported. -IMPORT_VARIANTS = [Variant(*variants) - for variants in itertools.product(Call, Data, Rescan, (False, True))] +IMPORT_VARIANTS = [ + Variant(*variants) + for variants in itertools.product(Call, Data, Rescan, (False, True)) +] # List of nodes to import keys to. Half the nodes will have pruning disabled, # half will have it enabled. Different nodes will be used for imports that are # expected to cause rescans, and imports that are not expected to cause # rescans, in order to prevent rescans during later imports picking up # transactions associated with earlier imports. This makes it easier to keep # track of expected balances and transactions. ImportNode = collections.namedtuple("ImportNode", "prune rescan") -IMPORT_NODES = [ImportNode(*fields) - for fields in itertools.product((False, True), repeat=2)] +IMPORT_NODES = [ + ImportNode(*fields) for fields in itertools.product((False, True), repeat=2) +] # Rescans start at the earliest block up to 2 hours before the key timestamp. TIMESTAMP_WINDOW = 2 * 60 * 60 AMOUNT_DUST = 5.46 def get_rand_amount(): r = random.uniform(AMOUNT_DUST, 1000000) return Decimal(str(round(r, 2))) class ImportRescanTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 + len(IMPORT_NODES) self.supports_cli = False self.rpc_timeout = 120 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def setup_network(self): self.extra_args = [[] for _ in range(self.num_nodes)] for i, import_node in enumerate(IMPORT_NODES, 2): if import_node.prune: self.extra_args[i] += ["-prune=1"] self.add_nodes(self.num_nodes, extra_args=self.extra_args) # Import keys with pruning disabled self.start_nodes(extra_args=[[]] * self.num_nodes) self.import_deterministic_coinbase_privkeys() self.stop_nodes() self.start_nodes() for i in range(1, self.num_nodes): self.connect_nodes(i, 0) def run_test(self): # Create one transaction on node 0 with a unique amount for # each possible type of wallet import RPC. for i, variant in enumerate(IMPORT_VARIANTS): variant.label = f"label {i} {variant}" variant.address = self.nodes[1].getaddressinfo( - self.nodes[1].getnewaddress(label=variant.label)) + self.nodes[1].getnewaddress(label=variant.label) + ) variant.key = self.nodes[1].dumpprivkey(variant.address["address"]) variant.initial_amount = get_rand_amount() variant.initial_txid = self.nodes[0].sendtoaddress( - variant.address["address"], variant.initial_amount) + variant.address["address"], variant.initial_amount + ) # Generate one block for each send self.generate(self.nodes[0], 1) variant.confirmation_height = self.nodes[0].getblockcount() variant.timestamp = self.nodes[0].getblockheader( - self.nodes[0].getbestblockhash())["time"] + self.nodes[0].getbestblockhash() + )["time"] # Generate a block further in the future (past the rescan window). assert_equal(self.nodes[0].getrawmempool(), []) - set_node_times(self.nodes, self.nodes[0].getblockheader( - self.nodes[0].getbestblockhash())["time"] + TIMESTAMP_WINDOW + 1) + set_node_times( + self.nodes, + self.nodes[0].getblockheader(self.nodes[0].getbestblockhash())["time"] + + TIMESTAMP_WINDOW + + 1, + ) self.generate(self.nodes[0], 1) # For each variation of wallet key import, invoke the import RPC and # check the results from getbalance and listtransactions. for variant in IMPORT_VARIANTS: - self.log.info(f'Run import for variant {variant}') + self.log.info(f"Run import for variant {variant}") expect_rescan = variant.rescan == Rescan.yes - variant.node = self.nodes[2 + IMPORT_NODES.index( - ImportNode(variant.prune, expect_rescan))] + variant.node = self.nodes[ + 2 + IMPORT_NODES.index(ImportNode(variant.prune, expect_rescan)) + ] variant.do_import(variant.timestamp) if expect_rescan: variant.expected_balance = variant.initial_amount variant.expected_txs = 1 - variant.check(variant.initial_txid, variant.initial_amount, - variant.confirmation_height) + variant.check( + variant.initial_txid, + variant.initial_amount, + variant.confirmation_height, + ) else: variant.expected_balance = 0 variant.expected_txs = 0 variant.check() # Create new transactions sending to each address. for i, variant in enumerate(IMPORT_VARIANTS): variant.sent_amount = get_rand_amount() variant.sent_txid = self.nodes[0].sendtoaddress( - variant.address["address"], variant.sent_amount) + variant.address["address"], variant.sent_amount + ) # Generate one block for each send self.generate(self.nodes[0], 1) variant.confirmation_height = self.nodes[0].getblockcount() assert_equal(self.nodes[0].getrawmempool(), []) self.sync_all() # Check the latest results from getbalance and listtransactions. for variant in IMPORT_VARIANTS: - self.log.info(f'Run check for variant {variant}') + self.log.info(f"Run check for variant {variant}") variant.expected_balance += variant.sent_amount variant.expected_txs += 1 - variant.check(variant.sent_txid, variant.sent_amount, - variant.confirmation_height) + variant.check( + variant.sent_txid, variant.sent_amount, variant.confirmation_height + ) if __name__ == "__main__": ImportRescanTest().main() diff --git a/test/functional/wallet_import_with_label.py b/test/functional/wallet_import_with_label.py index f6d393f34..9ae155abb 100755 --- a/test/functional/wallet_import_with_label.py +++ b/test/functional/wallet_import_with_label.py @@ -1,156 +1,146 @@ #!/usr/bin/env python3 # Copyright (c) 2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the behavior of RPC importprivkey on set and unset labels of addresses. It tests different cases in which an address is imported with importaddress with or without a label and then its private key is imported with importprivkey with and without a label. """ from test_framework.address import script_to_p2sh from test_framework.script import ( OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160, CScript, hash160, ) from test_framework.test_framework import BitcoinTestFramework from test_framework.wallet_util import test_address class ImportWithLabel(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): """Main test logic""" - self.log.info( - "Test importaddress with label and importprivkey without label." - ) + self.log.info("Test importaddress with label and importprivkey without label.") self.log.info("Import a watch-only address with a label.") address = self.nodes[0].getnewaddress() label = "Test Label" self.nodes[1].importaddress(address, label) - test_address(self.nodes[1], - address, - iswatchonly=True, - ismine=False, - labels=[label]) + test_address( + self.nodes[1], address, iswatchonly=True, ismine=False, labels=[label] + ) self.log.info( "Import the watch-only address's private key without a " "label and the address should keep its label." ) priv_key = self.nodes[0].dumpprivkey(address) self.nodes[1].importprivkey(priv_key) test_address(self.nodes[1], address, labels=[label]) - self.log.info( - "Test importaddress without label and importprivkey with label." - ) + self.log.info("Test importaddress without label and importprivkey with label.") self.log.info("Import a watch-only address without a label.") address2 = self.nodes[0].getnewaddress() self.nodes[1].importaddress(address2) - test_address(self.nodes[1], - address2, - iswatchonly=True, - ismine=False, - labels=[""]) + test_address( + self.nodes[1], address2, iswatchonly=True, ismine=False, labels=[""] + ) self.log.info( "Import the watch-only address's private key with a " "label and the address should have its label updated." ) priv_key2 = self.nodes[0].dumpprivkey(address2) label2 = "Test Label 2" self.nodes[1].importprivkey(priv_key2, label2) test_address(self.nodes[1], address2, labels=[label2]) - self.log.info( - "Test importaddress with label and importprivkey with label.") + self.log.info("Test importaddress with label and importprivkey with label.") self.log.info("Import a watch-only address with a label.") address3 = self.nodes[0].getnewaddress() label3_addr = "Test Label 3 for importaddress" self.nodes[1].importaddress(address3, label3_addr) - test_address(self.nodes[1], - address3, - iswatchonly=True, - ismine=False, - labels=[label3_addr]) + test_address( + self.nodes[1], + address3, + iswatchonly=True, + ismine=False, + labels=[label3_addr], + ) self.log.info( "Import the watch-only address's private key with a " "label and the address should have its label updated." ) priv_key3 = self.nodes[0].dumpprivkey(address3) label3_priv = "Test Label 3 for importprivkey" self.nodes[1].importprivkey(priv_key3, label3_priv) - test_address( - self.nodes[1], - address3, - labels=[label3_priv]) + test_address(self.nodes[1], address3, labels=[label3_priv]) self.log.info( "Test importprivkey won't label new dests with the same " "label as others labeled dests for the same key." ) self.log.info("Import a watch-only legacy address with a label.") address4 = self.nodes[0].getnewaddress() label4_addr = "Test Label 4 for importaddress" self.nodes[1].importaddress(address4, label4_addr) - test_address(self.nodes[1], - address4, - iswatchonly=True, - ismine=False, - labels=[label4_addr], - embedded=None) + test_address( + self.nodes[1], + address4, + iswatchonly=True, + ismine=False, + labels=[label4_addr], + embedded=None, + ) self.log.info( "Import the watch-only address's private key without a " "label and new destinations for the key should have an " "empty label while the 'old' destination should keep " "its label." ) # Build a P2SH manually for this test. priv_key4 = self.nodes[0].dumpprivkey(address4) - pubkey4 = self.nodes[0].getaddressinfo(address4)['pubkey'] + pubkey4 = self.nodes[0].getaddressinfo(address4)["pubkey"] pkh4 = hash160(bytes.fromhex(pubkey4)) - script4 = CScript( - [OP_DUP, OP_HASH160, pkh4, OP_EQUALVERIFY, OP_CHECKSIG]) + script4 = CScript([OP_DUP, OP_HASH160, pkh4, OP_EQUALVERIFY, OP_CHECKSIG]) p2shaddr4 = script_to_p2sh(script4) - self.nodes[1].importmulti([{ - "scriptPubKey": {"address": p2shaddr4}, - "timestamp": "now", - "redeemscript": script4.hex(), - "keys": [priv_key4], - }]) - - test_address(self.nodes[1], - p2shaddr4, - labels=[""]) - - embedded_addr = self.nodes[1].getaddressinfo( - p2shaddr4)['embedded']['address'] - test_address(self.nodes[1], - embedded_addr, - labels=[label4_addr]) + self.nodes[1].importmulti( + [ + { + "scriptPubKey": {"address": p2shaddr4}, + "timestamp": "now", + "redeemscript": script4.hex(), + "keys": [priv_key4], + } + ] + ) + + test_address(self.nodes[1], p2shaddr4, labels=[""]) + + embedded_addr = self.nodes[1].getaddressinfo(p2shaddr4)["embedded"]["address"] + test_address(self.nodes[1], embedded_addr, labels=[label4_addr]) self.stop_nodes() if __name__ == "__main__": ImportWithLabel().main() diff --git a/test/functional/wallet_importdescriptors.py b/test/functional/wallet_importdescriptors.py index 1ef44b01b..a8d4bc0dd 100755 --- a/test/functional/wallet_importdescriptors.py +++ b/test/functional/wallet_importdescriptors.py @@ -1,493 +1,614 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the importdescriptors RPC. Test importdescriptors by generating keys on node0, importing the corresponding descriptors on node1 and then testing the address info for the different address variants. - `get_generate_key()` is called to generate keys and return the privkeys, pubkeys and all variants of scriptPubKey and address. - `test_importdesc()` is called to send an importdescriptors call to node1, test success, and (if unsuccessful) test the error code and error message returned. - `test_address()` is called to call getaddressinfo for an address on node1 and test the values returned.""" from test_framework.address import key_to_p2pkh from test_framework.descriptors import descsum_create from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, find_vout_for_address, ) from test_framework.wallet_util import get_generate_key, test_address class ImportDescriptorsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.extra_args = [[], ["-keypool=5"]] # whitelist peers to speed up tx relay / mempool sync for args in self.extra_args: args.append("-whitelist=noban@127.0.0.1") self.setup_clean_chain = True def skip_test_if_missing_module(self): self.skip_if_no_wallet() - def test_importdesc(self, req, success, error_code=None, - error_message=None, warnings=None, wallet=None): + def test_importdesc( + self, + req, + success, + error_code=None, + error_message=None, + warnings=None, + wallet=None, + ): """Run importdescriptors and assert success""" if warnings is None: warnings = [] - wrpc = self.nodes[1].get_wallet_rpc('w1') + wrpc = self.nodes[1].get_wallet_rpc("w1") if wallet is not None: wrpc = wallet result = wrpc.importdescriptors([req]) observed_warnings = [] - if 'warnings' in result[0]: - observed_warnings = result[0]['warnings'] - assert_equal( - "\n".join( - sorted(warnings)), "\n".join( - sorted(observed_warnings))) - assert_equal(result[0]['success'], success) + if "warnings" in result[0]: + observed_warnings = result[0]["warnings"] + assert_equal("\n".join(sorted(warnings)), "\n".join(sorted(observed_warnings))) + assert_equal(result[0]["success"], success) if error_code is not None: - assert_equal(result[0]['error']['code'], error_code) - assert_equal(result[0]['error']['message'], error_message) + assert_equal(result[0]["error"]["code"], error_code) + assert_equal(result[0]["error"]["message"], error_message) def run_test(self): - self.log.info('Setting up wallets') - self.nodes[0].createwallet( - wallet_name='w0', - disable_private_keys=False) - w0 = self.nodes[0].get_wallet_rpc('w0') + self.log.info("Setting up wallets") + self.nodes[0].createwallet(wallet_name="w0", disable_private_keys=False) + w0 = self.nodes[0].get_wallet_rpc("w0") self.nodes[1].createwallet( - wallet_name='w1', - disable_private_keys=True, - blank=True, - descriptors=True) - w1 = self.nodes[1].get_wallet_rpc('w1') - assert_equal(w1.getwalletinfo()['keypoolsize'], 0) + wallet_name="w1", disable_private_keys=True, blank=True, descriptors=True + ) + w1 = self.nodes[1].get_wallet_rpc("w1") + assert_equal(w1.getwalletinfo()["keypoolsize"], 0) self.nodes[1].createwallet( wallet_name="wpriv", disable_private_keys=False, blank=True, - descriptors=True) + descriptors=True, + ) wpriv = self.nodes[1].get_wallet_rpc("wpriv") - assert_equal(wpriv.getwalletinfo()['keypoolsize'], 0) + assert_equal(wpriv.getwalletinfo()["keypoolsize"], 0) - self.log.info('Mining coins') + self.log.info("Mining coins") self.generatetoaddress(self.nodes[0], 101, w0.getnewaddress()) # RPC importdescriptors ----------------------------------------------- # # Test import fails if no descriptor present key = get_generate_key() self.log.info("Import should fail if a descriptor is not provided") - self.test_importdesc({"timestamp": "now"}, - success=False, - error_code=-8, - error_message='Descriptor not found.') + self.test_importdesc( + {"timestamp": "now"}, + success=False, + error_code=-8, + error_message="Descriptor not found.", + ) # # Test importing of a P2PKH descriptor key = get_generate_key() self.log.info("Should import a p2pkh descriptor") - self.test_importdesc({"desc": descsum_create(f"pkh({key.pubkey})"), - "timestamp": "now", - "label": "Descriptor import test"}, - success=True) - test_address(w1, - key.p2pkh_addr, - solvable=True, - ismine=True, - labels=["Descriptor import test"]) - assert_equal(w1.getwalletinfo()['keypoolsize'], 0) + self.test_importdesc( + { + "desc": descsum_create(f"pkh({key.pubkey})"), + "timestamp": "now", + "label": "Descriptor import test", + }, + success=True, + ) + test_address( + w1, + key.p2pkh_addr, + solvable=True, + ismine=True, + labels=["Descriptor import test"], + ) + assert_equal(w1.getwalletinfo()["keypoolsize"], 0) self.log.info("Internal addresses cannot have labels") - self.test_importdesc({"desc": descsum_create(f"pkh({key.pubkey})"), - "timestamp": "now", - "internal": True, - "label": "Descriptor import test"}, - success=False, - error_code=-8, - error_message="Internal addresses should not have a label") + self.test_importdesc( + { + "desc": descsum_create(f"pkh({key.pubkey})"), + "timestamp": "now", + "internal": True, + "label": "Descriptor import test", + }, + success=False, + error_code=-8, + error_message="Internal addresses should not have a label", + ) self.log.info("Internal addresses should be detected as such") key = get_generate_key() addr = key_to_p2pkh(key.pubkey) - self.test_importdesc({"desc": descsum_create(f"pkh({key.pubkey})"), - "timestamp": "now", - "internal": True}, - success=True) + self.test_importdesc( + { + "desc": descsum_create(f"pkh({key.pubkey})"), + "timestamp": "now", + "internal": True, + }, + success=True, + ) info = w1.getaddressinfo(addr) assert_equal(info["ismine"], True) assert_equal(info["ischange"], True) - assert_equal(w1.getwalletinfo()['keypoolsize'], 0) + assert_equal(w1.getwalletinfo()["keypoolsize"], 0) - test_address(w1, - key.p2pkh_addr, - ismine=True, - solvable=True) + test_address(w1, key.p2pkh_addr, ismine=True, solvable=True) # Check persistence of data and that loading works correctly w1.unloadwallet() - self.nodes[1].loadwallet('w1') - test_address(w1, - key.p2pkh_addr, - ismine=True, - solvable=True) + self.nodes[1].loadwallet("w1") + test_address(w1, key.p2pkh_addr, ismine=True, solvable=True) # # Test importing of a multisig descriptor key1 = get_generate_key() key2 = get_generate_key() self.log.info("Should import a 1-of-2 bare multisig from descriptor") - self.test_importdesc({"desc": descsum_create(f"multi(1,{key1.pubkey},{key2.pubkey})"), - "timestamp": "now"}, - success=True) + self.test_importdesc( + { + "desc": descsum_create(f"multi(1,{key1.pubkey},{key2.pubkey})"), + "timestamp": "now", + }, + success=True, + ) self.log.info( - "Should not treat individual keys from the imported bare multisig as watchonly") - test_address(w1, - key1.p2pkh_addr, - ismine=False) + "Should not treat individual keys from the imported bare multisig as" + " watchonly" + ) + test_address(w1, key1.p2pkh_addr, ismine=False) # # Test ranged descriptors xpriv = "tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg" xpub = "tpubD6NzVbkrYhZ4YNXVQbNhMK1WqguFsUXceaVJKbmno2aZ3B6QfbMeraaYvnBSGpV3vxLyTTK9DYT1yoEck4XUScMzXoQ2U2oSmE2JyMedq3H" addresses = [ "2N7yv4p8G8yEaPddJxY41kPihnWvs39qCMf", - "2MsHxyb2JS3pAySeNUsJ7mNnurtpeenDzLA"] # hdkeypath=m/0'/0'/0' and 1' + "2MsHxyb2JS3pAySeNUsJ7mNnurtpeenDzLA", + ] # hdkeypath=m/0'/0'/0' and 1' # wpkh subscripts corresponding to the above addresses - addresses += ["ecregtest:prvn9ycvgr5atuyh49sua3mapskh2mnnzg7t9yp6dt", - "ecregtest:pp3n087yx0njv2e5wcvltahfxqst7l66rutz8ceeat"] + addresses += [ + "ecregtest:prvn9ycvgr5atuyh49sua3mapskh2mnnzg7t9yp6dt", + "ecregtest:pp3n087yx0njv2e5wcvltahfxqst7l66rutz8ceeat", + ] desc = f"sh(pkh({xpub}/0/0/*))" self.log.info("Ranged descriptors cannot have labels") - self.test_importdesc({"desc": descsum_create(desc), - "timestamp": "now", - "range": [0, 100], - "label": "test"}, - success=False, - error_code=-8, - error_message='Ranged descriptors should not have a label') + self.test_importdesc( + { + "desc": descsum_create(desc), + "timestamp": "now", + "range": [0, 100], + "label": "test", + }, + success=False, + error_code=-8, + error_message="Ranged descriptors should not have a label", + ) self.log.info("Private keys required for private keys enabled wallet") - self.test_importdesc({"desc": descsum_create(desc), - "timestamp": "now", - "range": [0, 100]}, - success=False, - error_code=-4, - error_message='Cannot import descriptor without private keys to a wallet with private keys enabled', - wallet=wpriv) - - self.log.info( - "Ranged descriptor import should warn without a specified range") - self.test_importdesc({"desc": descsum_create(desc), - "timestamp": "now"}, - success=True, - warnings=['Range not given, using default keypool range']) - assert_equal(w1.getwalletinfo()['keypoolsize'], 0) + self.test_importdesc( + {"desc": descsum_create(desc), "timestamp": "now", "range": [0, 100]}, + success=False, + error_code=-4, + error_message=( + "Cannot import descriptor without private keys to a wallet with private" + " keys enabled" + ), + wallet=wpriv, + ) + + self.log.info("Ranged descriptor import should warn without a specified range") + self.test_importdesc( + {"desc": descsum_create(desc), "timestamp": "now"}, + success=True, + warnings=["Range not given, using default keypool range"], + ) + assert_equal(w1.getwalletinfo()["keypoolsize"], 0) # # Test importing of a ranged descriptor with xpriv self.log.info( - "Should not import a ranged descriptor that includes xpriv into a watch-only wallet") + "Should not import a ranged descriptor that includes xpriv into a" + " watch-only wallet" + ) desc = f"sh(pkh({xpriv}/0'/0'/*'))" - self.test_importdesc({"desc": descsum_create(desc), - "timestamp": "now", - "range": 1}, - success=False, - error_code=-4, - error_message='Cannot import private keys to a wallet with private keys disabled') - - self.log.info("Should not import a descriptor with hardened " - "derivations when private keys are disabled") - self.test_importdesc({"desc": descsum_create(f"pkh({xpub}/1h/*)"), - "timestamp": "now", - "range": 1}, - success=False, - error_code=-4, - error_message='Cannot expand descriptor. Probably because of hardened derivations without private keys provided') + self.test_importdesc( + {"desc": descsum_create(desc), "timestamp": "now", "range": 1}, + success=False, + error_code=-4, + error_message=( + "Cannot import private keys to a wallet with private keys disabled" + ), + ) - for address in addresses: - test_address(w1, - address, - ismine=False, - solvable=False) - - self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": -1}, - success=False, error_code=-8, error_message='End of range is too high') - - self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [-1, 10]}, - success=False, error_code=-8, error_message='Range should be greater or equal than 0') - - self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [(2 << 31 + 1) - 1000000, (2 << 31 + 1)]}, - success=False, error_code=-8, error_message='End of range is too high') - - self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [2, 1]}, - success=False, error_code=-8, error_message='Range specified as [begin,end] must not have begin after end') + self.log.info( + "Should not import a descriptor with hardened " + "derivations when private keys are disabled" + ) + self.test_importdesc( + { + "desc": descsum_create(f"pkh({xpub}/1h/*)"), + "timestamp": "now", + "range": 1, + }, + success=False, + error_code=-4, + error_message=( + "Cannot expand descriptor. Probably because of hardened derivations" + " without private keys provided" + ), + ) - self.test_importdesc({"desc": descsum_create(desc), "timestamp": "now", "range": [0, 1000001]}, - success=False, error_code=-8, error_message='Range is too large') + for address in addresses: + test_address(w1, address, ismine=False, solvable=False) + + self.test_importdesc( + {"desc": descsum_create(desc), "timestamp": "now", "range": -1}, + success=False, + error_code=-8, + error_message="End of range is too high", + ) + + self.test_importdesc( + {"desc": descsum_create(desc), "timestamp": "now", "range": [-1, 10]}, + success=False, + error_code=-8, + error_message="Range should be greater or equal than 0", + ) + + self.test_importdesc( + { + "desc": descsum_create(desc), + "timestamp": "now", + "range": [(2 << 31 + 1) - 1000000, (2 << 31 + 1)], + }, + success=False, + error_code=-8, + error_message="End of range is too high", + ) + + self.test_importdesc( + {"desc": descsum_create(desc), "timestamp": "now", "range": [2, 1]}, + success=False, + error_code=-8, + error_message=( + "Range specified as [begin,end] must not have begin after end" + ), + ) + + self.test_importdesc( + {"desc": descsum_create(desc), "timestamp": "now", "range": [0, 1000001]}, + success=False, + error_code=-8, + error_message="Range is too large", + ) # Make sure ranged imports import keys in order - w1 = self.nodes[1].get_wallet_rpc('w1') - self.log.info('Key ranges should be imported in order') + w1 = self.nodes[1].get_wallet_rpc("w1") + self.log.info("Key ranges should be imported in order") xpub = "tpubDAXcJ7s7ZwicqjprRaEWdPoHKrCS215qxGYxpusRLLmJuT69ZSicuGdSfyvyKpvUNYBW1s2U3NSrT6vrCYB9e6nZUEvrqnwXPF8ArTCRXMY" addresses = [ - 'ecregtest:qp0v86h53rc92hjrlpwzpjtdlgzsxu25svv6g40fpl', # m/0'/0'/0 - 'ecregtest:qqasy0zlkdleqt4pkn8fs4ehm5gnnz6qpgdcpt90fq', # m/0'/0'/1 - 'ecregtest:qp0sp4wlhctvprqvdt2dgvqcfdjssu04xgey0l3syw', # m/0'/0'/2 - 'ecregtest:qrhn24tegn04cptfv4ldhtkduxq55zcwrycjfdj9vr', # m/0'/0'/3 - 'ecregtest:qzpqhett2uwltq803vrxv7zkqhft5vsnmcjeh50v0p', # m/0'/0'/4 + "ecregtest:qp0v86h53rc92hjrlpwzpjtdlgzsxu25svv6g40fpl", # m/0'/0'/0 + "ecregtest:qqasy0zlkdleqt4pkn8fs4ehm5gnnz6qpgdcpt90fq", # m/0'/0'/1 + "ecregtest:qp0sp4wlhctvprqvdt2dgvqcfdjssu04xgey0l3syw", # m/0'/0'/2 + "ecregtest:qrhn24tegn04cptfv4ldhtkduxq55zcwrycjfdj9vr", # m/0'/0'/3 + "ecregtest:qzpqhett2uwltq803vrxv7zkqhft5vsnmcjeh50v0p", # m/0'/0'/4 ] - self.test_importdesc({'desc': descsum_create(f"sh(pkh([abcdef12/0h/0h]{xpub}/*))"), - 'active': True, - 'range': [0, 2], - 'timestamp': 'now' - }, - success=True) - self.test_importdesc({'desc': descsum_create(f"pkh([12345678/0h/0h]{xpub}/*)"), - 'active': True, - 'range': [0, 2], - 'timestamp': 'now' - }, - success=True) - - assert_equal(w1.getwalletinfo()['keypoolsize'], 5) + self.test_importdesc( + { + "desc": descsum_create(f"sh(pkh([abcdef12/0h/0h]{xpub}/*))"), + "active": True, + "range": [0, 2], + "timestamp": "now", + }, + success=True, + ) + self.test_importdesc( + { + "desc": descsum_create(f"pkh([12345678/0h/0h]{xpub}/*)"), + "active": True, + "range": [0, 2], + "timestamp": "now", + }, + success=True, + ) + + assert_equal(w1.getwalletinfo()["keypoolsize"], 5) for i, expected_addr in enumerate(addresses): - pkh_addr = w1.getnewaddress('') - assert_raises_rpc_error(-4, - 'This wallet has no available keys', - w1.getrawchangeaddress) + pkh_addr = w1.getnewaddress("") + assert_raises_rpc_error( + -4, "This wallet has no available keys", w1.getrawchangeaddress + ) assert_equal(pkh_addr, expected_addr) pkh_addr_info = w1.getaddressinfo(pkh_addr) - assert_equal(pkh_addr_info['desc'][:22], - f'pkh([12345678/0\'/0\'/{i}]') + assert_equal(pkh_addr_info["desc"][:22], f"pkh([12345678/0'/0'/{i}]") # After retrieving a key, we don't refill the keypool again, so # it's one less for each address type - assert_equal(w1.getwalletinfo()['keypoolsize'], 4) + assert_equal(w1.getwalletinfo()["keypoolsize"], 4) w1.keypoolrefill() - assert_equal(w1.getwalletinfo()['keypoolsize'], 5) + assert_equal(w1.getwalletinfo()["keypoolsize"], 5) # Check active=False default - self.log.info('Check imported descriptors are not active by default') - self.test_importdesc({'desc': descsum_create(f"pkh([12345678/0h/0h]{xpub}/*)"), - 'range': [0, 2], - 'timestamp': 'now', - 'internal': True - }, - success=True) - assert_raises_rpc_error(-4, - 'This wallet has no available keys', - w1.getrawchangeaddress) + self.log.info("Check imported descriptors are not active by default") + self.test_importdesc( + { + "desc": descsum_create(f"pkh([12345678/0h/0h]{xpub}/*)"), + "range": [0, 2], + "timestamp": "now", + "internal": True, + }, + success=True, + ) + assert_raises_rpc_error( + -4, "This wallet has no available keys", w1.getrawchangeaddress + ) # # Test importing a descriptor containing a WIF private key wif_priv = "cTe1f5rdT8A8DFgVWTjyPwACsDPJM9ff4QngFxUixCSvvbg1x6sh" address = "ecregtest:ppn85zpvym8cdccmgw8km6e48jfhnpa435h3hkyfd6" desc = f"sh(pkh({wif_priv}))" - self.log.info( - "Should import a descriptor with a WIF private key as spendable") - self.test_importdesc({"desc": descsum_create(desc), - "timestamp": "now"}, - success=True, - wallet=wpriv) - test_address(wpriv, - address, - solvable=True, - ismine=True) + self.log.info("Should import a descriptor with a WIF private key as spendable") + self.test_importdesc( + {"desc": descsum_create(desc), "timestamp": "now"}, + success=True, + wallet=wpriv, + ) + test_address(wpriv, address, solvable=True, ismine=True) txid = w0.sendtoaddress(address, 49999996.00) self.generatetoaddress(self.nodes[0], 6, w0.getnewaddress()) - tx = wpriv.createrawtransaction([{"txid": txid, "vout": 0}], { - w0.getnewaddress(): 49999000}) + tx = wpriv.createrawtransaction( + [{"txid": txid, "vout": 0}], {w0.getnewaddress(): 49999000} + ) signed_tx = wpriv.signrawtransactionwithwallet(tx) - w1.sendrawtransaction(signed_tx['hex']) + w1.sendrawtransaction(signed_tx["hex"]) # Make sure that we can use import and use multisig as addresses self.log.info( - 'Test that multisigs can be imported, signed for, and getnewaddress\'d') + "Test that multisigs can be imported, signed for, and getnewaddress'd" + ) self.nodes[1].createwallet( wallet_name="wmulti_priv", disable_private_keys=False, blank=True, - descriptors=True) + descriptors=True, + ) wmulti_priv = self.nodes[1].get_wallet_rpc("wmulti_priv") - assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 0) - - self.test_importdesc({"desc": "sh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/0h/0h/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/0h/0h/*,tprv8ZgxMBicQKsPeonDt8Ka2mrQmHa61hQ5FQCsvWBTpSNzBFgM58cV2EuXNAHF14VawVpznnme3SuTbA62sGriwWyKifJmXntfNeK7zeqMCj1/84h/0h/0h/*))#f5nqn4ax", - "active": True, - "range": 1000, - "next_index": 0, - "timestamp": "now"}, - success=True, - wallet=wmulti_priv) - self.test_importdesc({"desc": "sh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/1h/0h/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/1h/0h/*,tprv8ZgxMBicQKsPeonDt8Ka2mrQmHa61hQ5FQCsvWBTpSNzBFgM58cV2EuXNAHF14VawVpznnme3SuTbA62sGriwWyKifJmXntfNeK7zeqMCj1/84h/1h/0h/*))#m4e4s5de", - "active": True, - "internal": True, - "range": 1000, - "next_index": 0, - "timestamp": "now"}, - success=True, - wallet=wmulti_priv) + assert_equal(wmulti_priv.getwalletinfo()["keypoolsize"], 0) + + self.test_importdesc( + { + "desc": "sh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/0h/0h/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/0h/0h/*,tprv8ZgxMBicQKsPeonDt8Ka2mrQmHa61hQ5FQCsvWBTpSNzBFgM58cV2EuXNAHF14VawVpznnme3SuTbA62sGriwWyKifJmXntfNeK7zeqMCj1/84h/0h/0h/*))#f5nqn4ax", + "active": True, + "range": 1000, + "next_index": 0, + "timestamp": "now", + }, + success=True, + wallet=wmulti_priv, + ) + self.test_importdesc( + { + "desc": "sh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/1h/0h/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/1h/0h/*,tprv8ZgxMBicQKsPeonDt8Ka2mrQmHa61hQ5FQCsvWBTpSNzBFgM58cV2EuXNAHF14VawVpznnme3SuTbA62sGriwWyKifJmXntfNeK7zeqMCj1/84h/1h/0h/*))#m4e4s5de", + "active": True, + "internal": True, + "range": 1000, + "next_index": 0, + "timestamp": "now", + }, + success=True, + wallet=wmulti_priv, + ) # Range end (1000) is inclusive, so 1001 addresses generated - assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 1001) - addr = wmulti_priv.getnewaddress('') + assert_equal(wmulti_priv.getwalletinfo()["keypoolsize"], 1001) + addr = wmulti_priv.getnewaddress("") # Derived at m/84'/0'/0'/0 - assert_equal( - addr, - 'ecregtest:pzkcf26dw7np58jcspnpxaupgz9csnc3wsf5wdh2a3') + assert_equal(addr, "ecregtest:pzkcf26dw7np58jcspnpxaupgz9csnc3wsf5wdh2a3") change_addr = wmulti_priv.getrawchangeaddress() assert_equal( - change_addr, - 'ecregtest:prnkfg7pxe3kpyv3l4v00ft6q3sfseag7vnva0k49n') + change_addr, "ecregtest:prnkfg7pxe3kpyv3l4v00ft6q3sfseag7vnva0k49n" + ) - assert_equal(wmulti_priv.getwalletinfo()['keypoolsize'], 1000) + assert_equal(wmulti_priv.getwalletinfo()["keypoolsize"], 1000) txid = w0.sendtoaddress(addr, 10000000) self.generate(self.nodes[0], 6) self.nodes[1].createwallet( wallet_name="wmulti_pub", disable_private_keys=True, blank=True, - descriptors=True) + descriptors=True, + ) wmulti_pub = self.nodes[1].get_wallet_rpc("wmulti_pub") - assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 0) - - self.test_importdesc({"desc": "sh(multi(2,[7b2d0242/84h/0h/0h]tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*,[59b09cd6/84h/0h/0h]tpubDDBF2BTR6s8drwrfDei8WxtckGuSm1cyoKxYY1QaKSBFbHBYQArWhHPA6eJrzZej6nfHGLSURYSLHr7GuYch8aY5n61tGqgn8b4cXrMuoPH/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))#x75vpsak", - "active": True, - "range": 1000, - "next_index": 0, - "timestamp": "now"}, - success=True, - wallet=wmulti_pub) - self.test_importdesc({"desc": "sh(multi(2,[7b2d0242/84h/1h/0h]tpubDCXqdwWZcszwqYJSnZp8eARkxGJfHAk23KDxbztV4BbschfaTfYLTcSkSJ3TN64dRqwa1rnFUScsYormKkGqNbbPwkorQimVevXjxzUV9Gf/*,[59b09cd6/84h/1h/0h]tpubDCYfZY2ceyHzYzMMVPt9MNeiqtQ2T7Uyp9QSFwYXh8Vi9iJFYXcuphJaGXfF3jUQJi5Y3GMNXvM11gaL4txzZgNGK22BFAwMXynnzv4z2Jh/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))#v0t48ucu", - "active": True, - "internal": True, - "range": 1000, - "next_index": 0, - "timestamp": "now"}, - success=True, - wallet=wmulti_pub) + assert_equal(wmulti_pub.getwalletinfo()["keypoolsize"], 0) + + self.test_importdesc( + { + "desc": "sh(multi(2,[7b2d0242/84h/0h/0h]tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*,[59b09cd6/84h/0h/0h]tpubDDBF2BTR6s8drwrfDei8WxtckGuSm1cyoKxYY1QaKSBFbHBYQArWhHPA6eJrzZej6nfHGLSURYSLHr7GuYch8aY5n61tGqgn8b4cXrMuoPH/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))#x75vpsak", + "active": True, + "range": 1000, + "next_index": 0, + "timestamp": "now", + }, + success=True, + wallet=wmulti_pub, + ) + self.test_importdesc( + { + "desc": "sh(multi(2,[7b2d0242/84h/1h/0h]tpubDCXqdwWZcszwqYJSnZp8eARkxGJfHAk23KDxbztV4BbschfaTfYLTcSkSJ3TN64dRqwa1rnFUScsYormKkGqNbbPwkorQimVevXjxzUV9Gf/*,[59b09cd6/84h/1h/0h]tpubDCYfZY2ceyHzYzMMVPt9MNeiqtQ2T7Uyp9QSFwYXh8Vi9iJFYXcuphJaGXfF3jUQJi5Y3GMNXvM11gaL4txzZgNGK22BFAwMXynnzv4z2Jh/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))#v0t48ucu", + "active": True, + "internal": True, + "range": 1000, + "next_index": 0, + "timestamp": "now", + }, + success=True, + wallet=wmulti_pub, + ) # The first one was already consumed by previous import and is detected # as used - assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 1000) - addr = wmulti_pub.getnewaddress('') + assert_equal(wmulti_pub.getwalletinfo()["keypoolsize"], 1000) + addr = wmulti_pub.getnewaddress("") # Derived at m/84'/0'/0'/1 - assert_equal( - addr, - 'ecregtest:pr5xql8r03jp5dvrep22dns59vf7hhykr5nmaucy2h') + assert_equal(addr, "ecregtest:pr5xql8r03jp5dvrep22dns59vf7hhykr5nmaucy2h") change_addr = wmulti_pub.getrawchangeaddress() assert_equal( - change_addr, - 'ecregtest:prnkfg7pxe3kpyv3l4v00ft6q3sfseag7vnva0k49n') + change_addr, "ecregtest:prnkfg7pxe3kpyv3l4v00ft6q3sfseag7vnva0k49n" + ) - assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 999) + assert_equal(wmulti_pub.getwalletinfo()["keypoolsize"], 999) txid = w0.sendtoaddress(addr, 10000000) vout = find_vout_for_address(self.nodes[0], txid, addr) self.generate(self.nodes[0], 6) assert_equal(wmulti_pub.getbalance(), wmulti_priv.getbalance()) # Make sure that descriptor wallets containing multiple xpubs in a # single descriptor load correctly wmulti_pub.unloadwallet() - self.nodes[1].loadwallet('wmulti_pub') + self.nodes[1].loadwallet("wmulti_pub") self.log.info("Multisig with distributed keys") - self.nodes[1].createwallet( - wallet_name="wmulti_priv1", - descriptors=True) + self.nodes[1].createwallet(wallet_name="wmulti_priv1", descriptors=True) wmulti_priv1 = self.nodes[1].get_wallet_rpc("wmulti_priv1") - res = wmulti_priv1.importdescriptors([ - { - "desc": descsum_create("sh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/0h/0h/*,[59b09cd6/84h/0h/0h]tpubDDBF2BTR6s8drwrfDei8WxtckGuSm1cyoKxYY1QaKSBFbHBYQArWhHPA6eJrzZej6nfHGLSURYSLHr7GuYch8aY5n61tGqgn8b4cXrMuoPH/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))"), - "active": True, - "range": 1000, - "next_index": 0, - "timestamp": "now" - }, - { - "desc": descsum_create("sh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/1h/0h/*,[59b09cd6/84h/1h/0h]tpubDCYfZY2ceyHzYzMMVPt9MNeiqtQ2T7Uyp9QSFwYXh8Vi9iJFYXcuphJaGXfF3jUQJi5Y3GMNXvM11gaL4txzZgNGK22BFAwMXynnzv4z2Jh/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))"), - "active": True, - "internal": True, - "range": 1000, - "next_index": 0, - "timestamp": "now" - }]) - assert_equal(res[0]['success'], True) + res = wmulti_priv1.importdescriptors( + [ + { + "desc": descsum_create( + "sh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/0h/0h/*,[59b09cd6/84h/0h/0h]tpubDDBF2BTR6s8drwrfDei8WxtckGuSm1cyoKxYY1QaKSBFbHBYQArWhHPA6eJrzZej6nfHGLSURYSLHr7GuYch8aY5n61tGqgn8b4cXrMuoPH/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))" + ), + "active": True, + "range": 1000, + "next_index": 0, + "timestamp": "now", + }, + { + "desc": descsum_create( + "sh(multi(2,tprv8ZgxMBicQKsPevADjDCWsa6DfhkVXicu8NQUzfibwX2MexVwW4tCec5mXdCW8kJwkzBRRmAay1KZya4WsehVvjTGVW6JLqiqd8DdZ4xSg52/84h/1h/0h/*,[59b09cd6/84h/1h/0h]tpubDCYfZY2ceyHzYzMMVPt9MNeiqtQ2T7Uyp9QSFwYXh8Vi9iJFYXcuphJaGXfF3jUQJi5Y3GMNXvM11gaL4txzZgNGK22BFAwMXynnzv4z2Jh/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))" + ), + "active": True, + "internal": True, + "range": 1000, + "next_index": 0, + "timestamp": "now", + }, + ] + ) + assert_equal(res[0]["success"], True) assert_equal( - res[0]['warnings'][0], - 'Not all private keys provided. Some wallet functionality may return unexpected errors') - assert_equal(res[1]['success'], True) + res[0]["warnings"][0], + ( + "Not all private keys provided. Some wallet functionality may return" + " unexpected errors" + ), + ) + assert_equal(res[1]["success"], True) assert_equal( - res[1]['warnings'][0], - 'Not all private keys provided. Some wallet functionality may return unexpected errors') + res[1]["warnings"][0], + ( + "Not all private keys provided. Some wallet functionality may return" + " unexpected errors" + ), + ) self.nodes[1].createwallet( - wallet_name='wmulti_priv2', - blank=True, - descriptors=True) - wmulti_priv2 = self.nodes[1].get_wallet_rpc('wmulti_priv2') - res = wmulti_priv2.importdescriptors([ - { - "desc": descsum_create("sh(multi(2,[7b2d0242/84h/0h/0h]tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/0h/0h/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))"), - "active": True, - "range": 1000, - "next_index": 0, - "timestamp": "now" - }, - { - "desc": descsum_create("sh(multi(2,[7b2d0242/84h/1h/0h]tpubDCXqdwWZcszwqYJSnZp8eARkxGJfHAk23KDxbztV4BbschfaTfYLTcSkSJ3TN64dRqwa1rnFUScsYormKkGqNbbPwkorQimVevXjxzUV9Gf/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/1h/0h/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))"), - "active": True, - "internal": True, - "range": 1000, - "next_index": 0, - "timestamp": "now" - }]) - assert_equal(res[0]['success'], True) + wallet_name="wmulti_priv2", blank=True, descriptors=True + ) + wmulti_priv2 = self.nodes[1].get_wallet_rpc("wmulti_priv2") + res = wmulti_priv2.importdescriptors( + [ + { + "desc": descsum_create( + "sh(multi(2,[7b2d0242/84h/0h/0h]tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/0h/0h/*,[e81a0532/84h/0h/0h]tpubDCsWoW1kuQB9kG5MXewHqkbjPtqPueRnXju7uM2NK7y3JYb2ajAZ9EiuZXNNuE4661RAfriBWhL8UsnAPpk8zrKKnZw1Ug7X4oHgMdZiU4E/*))" + ), + "active": True, + "range": 1000, + "next_index": 0, + "timestamp": "now", + }, + { + "desc": descsum_create( + "sh(multi(2,[7b2d0242/84h/1h/0h]tpubDCXqdwWZcszwqYJSnZp8eARkxGJfHAk23KDxbztV4BbschfaTfYLTcSkSJ3TN64dRqwa1rnFUScsYormKkGqNbbPwkorQimVevXjxzUV9Gf/*,tprv8ZgxMBicQKsPdSNWUhDiwTScDr6JfkZuLshTRwzvZGnMSnGikV6jxpmdDkC3YRc4T3GD6Nvg9uv6hQg73RVv1EiTXDZwxVbsLugVHU8B1aq/84h/1h/0h/*,[e81a0532/84h/1h/0h]tpubDC6UGqnsQStngYuGD4MKsMy7eD1Yg9NTJfPdvjdG2JE5oZ7EsSL3WHg4Gsw2pR5K39ZwJ46M1wZayhedVdQtMGaUhq5S23PH6fnENK3V1sb/*))" + ), + "active": True, + "internal": True, + "range": 1000, + "next_index": 0, + "timestamp": "now", + }, + ] + ) + assert_equal(res[0]["success"], True) assert_equal( - res[0]['warnings'][0], - 'Not all private keys provided. Some wallet functionality may return unexpected errors') - assert_equal(res[1]['success'], True) + res[0]["warnings"][0], + ( + "Not all private keys provided. Some wallet functionality may return" + " unexpected errors" + ), + ) + assert_equal(res[1]["success"], True) assert_equal( - res[1]['warnings'][0], - 'Not all private keys provided. Some wallet functionality may return unexpected errors') + res[1]["warnings"][0], + ( + "Not all private keys provided. Some wallet functionality may return" + " unexpected errors" + ), + ) rawtx = self.nodes[1].createrawtransaction( - [{'txid': txid, 'vout': vout}], {w0.getnewaddress(): 9999000}) + [{"txid": txid, "vout": vout}], {w0.getnewaddress(): 9999000} + ) tx_signed_1 = wmulti_priv1.signrawtransactionwithwallet(rawtx) - assert_equal(tx_signed_1['complete'], False) - tx_signed_2 = wmulti_priv2.signrawtransactionwithwallet( - tx_signed_1['hex']) - assert_equal(tx_signed_2['complete'], True) - self.nodes[1].sendrawtransaction(tx_signed_2['hex']) + assert_equal(tx_signed_1["complete"], False) + tx_signed_2 = wmulti_priv2.signrawtransactionwithwallet(tx_signed_1["hex"]) + assert_equal(tx_signed_2["complete"], True) + self.nodes[1].sendrawtransaction(tx_signed_2["hex"]) self.log.info("Combo descriptors cannot be active") - self.test_importdesc({"desc": descsum_create("combo(tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*)"), - "active": True, - "range": 1, - "timestamp": "now"}, - success=False, - error_code=-4, - error_message="Combo descriptors cannot be set to active") + self.test_importdesc( + { + "desc": descsum_create( + "combo(tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*)" + ), + "active": True, + "range": 1, + "timestamp": "now", + }, + success=False, + error_code=-4, + error_message="Combo descriptors cannot be set to active", + ) self.log.info("Descriptors with no type cannot be active") - self.test_importdesc({"desc": descsum_create("pk(tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*)"), - "active": True, - "range": 1, - "timestamp": "now"}, - success=True, - warnings=["Unknown output type, cannot set descriptor to active."]) + self.test_importdesc( + { + "desc": descsum_create( + "pk(tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*)" + ), + "active": True, + "range": 1, + "timestamp": "now", + }, + success=True, + warnings=["Unknown output type, cannot set descriptor to active."], + ) -if __name__ == '__main__': +if __name__ == "__main__": ImportDescriptorsTest().main() diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py index 83c242ea0..1da68af85 100755 --- a/test/functional/wallet_importmulti.py +++ b/test/functional/wallet_importmulti.py @@ -1,788 +1,1042 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the importmulti RPC. Test importmulti by generating keys on node0, importing the scriptPubKeys and addresses on node1 and then testing the address info for the different address variants. - `get_key()` and `get_multisig()` are called to generate keys on node0 and return the privkeys, pubkeys and all variants of scriptPubKey and address. - `test_importmulti()` is called to send an importmulti call to node1, test success, and (if unsuccessful) test the error code and error message returned. - `test_address()` is called to call getaddressinfo for an address on node1 and test the values returned.""" from test_framework.descriptors import descsum_create from test_framework.script import OP_NOP, CScript from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_greater_than, assert_raises_rpc_error, ) from test_framework.wallet_util import get_key, get_multisig, test_address class ImportMultiTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True def skip_test_if_missing_module(self): self.skip_if_no_wallet() def setup_network(self, split=False): self.setup_nodes() - def test_importmulti(self, req, success, error_code=None, - error_message=None, warnings=None): + def test_importmulti( + self, req, success, error_code=None, error_message=None, warnings=None + ): """Run importmulti and assert success""" if warnings is None: warnings = [] result = self.nodes[1].importmulti([req]) observed_warnings = [] - if 'warnings' in result[0]: - observed_warnings = result[0]['warnings'] - assert_equal( - "\n".join( - sorted(warnings)), "\n".join( - sorted(observed_warnings))) - assert_equal(result[0]['success'], success) + if "warnings" in result[0]: + observed_warnings = result[0]["warnings"] + assert_equal("\n".join(sorted(warnings)), "\n".join(sorted(observed_warnings))) + assert_equal(result[0]["success"], success) if error_code is not None: - assert_equal(result[0]['error']['code'], error_code) - assert_equal(result[0]['error']['message'], error_message) + assert_equal(result[0]["error"]["code"], error_code) + assert_equal(result[0]["error"]["message"], error_message) def run_test(self): self.log.info("Mining blocks...") self.generate(self.nodes[0], 1, sync_fun=self.no_op) self.generate(self.nodes[1], 1, sync_fun=self.no_op) - timestamp = self.nodes[1].getblock( - self.nodes[1].getbestblockhash())['mediantime'] + timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())[ + "mediantime" + ] # Sync the timestamp to the wallet, so that importmulti works self.nodes[1].syncwithvalidationinterfacequeue() - node0_address1 = self.nodes[0].getaddressinfo( - self.nodes[0].getnewaddress()) + node0_address1 = self.nodes[0].getaddressinfo(self.nodes[0].getnewaddress()) # Check only one address - assert_equal(node0_address1['ismine'], True) + assert_equal(node0_address1["ismine"], True) # Node 1 sync test assert_equal(self.nodes[1].getblockcount(), 1) # Address Test - before import - address_info = self.nodes[1].getaddressinfo(node0_address1['address']) - assert_equal(address_info['iswatchonly'], False) - assert_equal(address_info['ismine'], False) + address_info = self.nodes[1].getaddressinfo(node0_address1["address"]) + assert_equal(address_info["iswatchonly"], False) + assert_equal(address_info["ismine"], False) # RPC importmulti ----------------------------------------------- # Bitcoin Address (implicit non-internal) self.log.info("Should import an address") key = get_key(self.nodes[0]) - self.test_importmulti({"scriptPubKey": {"address": key.p2pkh_addr}, - "timestamp": "now"}, - success=True) - test_address(self.nodes[1], - key.p2pkh_addr, - iswatchonly=True, - ismine=False, - timestamp=timestamp, - ischange=False) + self.test_importmulti( + {"scriptPubKey": {"address": key.p2pkh_addr}, "timestamp": "now"}, + success=True, + ) + test_address( + self.nodes[1], + key.p2pkh_addr, + iswatchonly=True, + ismine=False, + timestamp=timestamp, + ischange=False, + ) watchonly_address = key.p2pkh_addr watchonly_timestamp = timestamp self.log.info("Should not import an invalid address") - self.test_importmulti({"scriptPubKey": {"address": "not valid address"}, - "timestamp": "now"}, - success=False, - error_code=-5, - error_message='Invalid address \"not valid address\"') + self.test_importmulti( + {"scriptPubKey": {"address": "not valid address"}, "timestamp": "now"}, + success=False, + error_code=-5, + error_message='Invalid address "not valid address"', + ) # ScriptPubKey + internal self.log.info("Should import a scriptPubKey with internal flag") key = get_key(self.nodes[0]) - self.test_importmulti({"scriptPubKey": key.p2pkh_script, - "timestamp": "now", - "internal": True}, - success=True) - test_address(self.nodes[1], - key.p2pkh_addr, - iswatchonly=True, - ismine=False, - timestamp=timestamp, - ischange=True) + self.test_importmulti( + {"scriptPubKey": key.p2pkh_script, "timestamp": "now", "internal": True}, + success=True, + ) + test_address( + self.nodes[1], + key.p2pkh_addr, + iswatchonly=True, + ismine=False, + timestamp=timestamp, + ischange=True, + ) # ScriptPubKey + internal + label - self.log.info( - "Should not allow a label to be specified when internal is true") + self.log.info("Should not allow a label to be specified when internal is true") key = get_key(self.nodes[0]) - self.test_importmulti({"scriptPubKey": key.p2pkh_script, - "timestamp": "now", - "internal": True, - "label": "Unsuccessful labelling for internal addresses"}, - success=False, - error_code=-8, - error_message='Internal addresses should not have a label') + self.test_importmulti( + { + "scriptPubKey": key.p2pkh_script, + "timestamp": "now", + "internal": True, + "label": "Unsuccessful labelling for internal addresses", + }, + success=False, + error_code=-8, + error_message="Internal addresses should not have a label", + ) # Nonstandard scriptPubKey + !internal self.log.info( - "Should not import a nonstandard scriptPubKey without internal flag") + "Should not import a nonstandard scriptPubKey without internal flag" + ) nonstandardScriptPubKey = key.p2pkh_script + CScript([OP_NOP]).hex() key = get_key(self.nodes[0]) - self.test_importmulti({"scriptPubKey": nonstandardScriptPubKey, - "timestamp": "now"}, - success=False, - error_code=-8, - error_message='Internal must be set to true for nonstandard scriptPubKey imports.') - test_address(self.nodes[1], - key.p2pkh_addr, - iswatchonly=False, - ismine=False, - timestamp=None) + self.test_importmulti( + {"scriptPubKey": nonstandardScriptPubKey, "timestamp": "now"}, + success=False, + error_code=-8, + error_message=( + "Internal must be set to true for nonstandard scriptPubKey imports." + ), + ) + test_address( + self.nodes[1], + key.p2pkh_addr, + iswatchonly=False, + ismine=False, + timestamp=None, + ) # Address + Public key + !Internal(explicit) self.log.info("Should import an address with public key") key = get_key(self.nodes[0]) - self.test_importmulti({"scriptPubKey": {"address": key.p2pkh_addr}, - "timestamp": "now", - "pubkeys": [key.pubkey], - "internal": False}, - success=True, - warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) - test_address(self.nodes[1], - key.p2pkh_addr, - iswatchonly=True, - ismine=False, - timestamp=timestamp) + self.test_importmulti( + { + "scriptPubKey": {"address": key.p2pkh_addr}, + "timestamp": "now", + "pubkeys": [key.pubkey], + "internal": False, + }, + success=True, + warnings=[ + "Some private keys are missing, outputs will be considered watchonly." + " If this is intentional, specify the watchonly flag." + ], + ) + test_address( + self.nodes[1], + key.p2pkh_addr, + iswatchonly=True, + ismine=False, + timestamp=timestamp, + ) # ScriptPubKey + Public key + internal - self.log.info( - "Should import a scriptPubKey with internal and with public key") + self.log.info("Should import a scriptPubKey with internal and with public key") key = get_key(self.nodes[0]) - self.test_importmulti({"scriptPubKey": key.p2pkh_script, - "timestamp": "now", - "pubkeys": [key.pubkey], - "internal": True}, - success=True, - warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) - test_address(self.nodes[1], - key.p2pkh_addr, - iswatchonly=True, - ismine=False, - timestamp=timestamp) + self.test_importmulti( + { + "scriptPubKey": key.p2pkh_script, + "timestamp": "now", + "pubkeys": [key.pubkey], + "internal": True, + }, + success=True, + warnings=[ + "Some private keys are missing, outputs will be considered watchonly." + " If this is intentional, specify the watchonly flag." + ], + ) + test_address( + self.nodes[1], + key.p2pkh_addr, + iswatchonly=True, + ismine=False, + timestamp=timestamp, + ) # Nonstandard scriptPubKey + Public key + !internal self.log.info( - "Should not import a nonstandard scriptPubKey without internal and with public key") + "Should not import a nonstandard scriptPubKey without internal and with" + " public key" + ) key = get_key(self.nodes[0]) - self.test_importmulti({"scriptPubKey": nonstandardScriptPubKey, - "timestamp": "now", - "pubkeys": [key.pubkey]}, - success=False, - error_code=-8, - error_message='Internal must be set to true for nonstandard scriptPubKey imports.') - test_address(self.nodes[1], - key.p2pkh_addr, - iswatchonly=False, - ismine=False, - timestamp=None) + self.test_importmulti( + { + "scriptPubKey": nonstandardScriptPubKey, + "timestamp": "now", + "pubkeys": [key.pubkey], + }, + success=False, + error_code=-8, + error_message=( + "Internal must be set to true for nonstandard scriptPubKey imports." + ), + ) + test_address( + self.nodes[1], + key.p2pkh_addr, + iswatchonly=False, + ismine=False, + timestamp=None, + ) # Address + Private key + !watchonly self.log.info("Should import an address with private key") key = get_key(self.nodes[0]) - self.test_importmulti({"scriptPubKey": {"address": key.p2pkh_addr}, - "timestamp": "now", - "keys": [key.privkey]}, - success=True) - test_address(self.nodes[1], - key.p2pkh_addr, - iswatchonly=False, - ismine=True, - timestamp=timestamp) + self.test_importmulti( + { + "scriptPubKey": {"address": key.p2pkh_addr}, + "timestamp": "now", + "keys": [key.privkey], + }, + success=True, + ) + test_address( + self.nodes[1], + key.p2pkh_addr, + iswatchonly=False, + ismine=True, + timestamp=timestamp, + ) self.log.info( - "Should not import an address with private key if is already imported") - self.test_importmulti({"scriptPubKey": {"address": key.p2pkh_addr}, - "timestamp": "now", - "keys": [key.privkey]}, - success=False, - error_code=-4, - error_message=f"The wallet already contains the private key for this address or script (\"{key.p2pkh_script}\")") + "Should not import an address with private key if is already imported" + ) + self.test_importmulti( + { + "scriptPubKey": {"address": key.p2pkh_addr}, + "timestamp": "now", + "keys": [key.privkey], + }, + success=False, + error_code=-4, + error_message=( + "The wallet already contains the private key for this address or" + f' script ("{key.p2pkh_script}")' + ), + ) # Address + Private key + watchonly - self.log.info( - "Should import an address with private key and with watchonly") + self.log.info("Should import an address with private key and with watchonly") key = get_key(self.nodes[0]) - self.test_importmulti({"scriptPubKey": {"address": key.p2pkh_addr}, - "timestamp": "now", - "keys": [key.privkey], - "watchonly": True}, - success=True, - warnings=["All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag."]) - test_address(self.nodes[1], - key.p2pkh_addr, - iswatchonly=False, - ismine=True, - timestamp=timestamp) + self.test_importmulti( + { + "scriptPubKey": {"address": key.p2pkh_addr}, + "timestamp": "now", + "keys": [key.privkey], + "watchonly": True, + }, + success=True, + warnings=[ + "All private keys are provided, outputs will be considered spendable." + " If this is intentional, do not specify the watchonly flag." + ], + ) + test_address( + self.nodes[1], + key.p2pkh_addr, + iswatchonly=False, + ismine=True, + timestamp=timestamp, + ) # ScriptPubKey + Private key + internal - self.log.info( - "Should import a scriptPubKey with internal and with private key") + self.log.info("Should import a scriptPubKey with internal and with private key") key = get_key(self.nodes[0]) - self.test_importmulti({"scriptPubKey": key.p2pkh_script, - "timestamp": "now", - "keys": [key.privkey], - "internal": True}, - success=True) - test_address(self.nodes[1], - key.p2pkh_addr, - iswatchonly=False, - ismine=True, - timestamp=timestamp) + self.test_importmulti( + { + "scriptPubKey": key.p2pkh_script, + "timestamp": "now", + "keys": [key.privkey], + "internal": True, + }, + success=True, + ) + test_address( + self.nodes[1], + key.p2pkh_addr, + iswatchonly=False, + ismine=True, + timestamp=timestamp, + ) # Nonstandard scriptPubKey + Private key + !internal self.log.info( - "Should not import a nonstandard scriptPubKey without internal and with private key") + "Should not import a nonstandard scriptPubKey without internal and with" + " private key" + ) key = get_key(self.nodes[0]) - self.test_importmulti({"scriptPubKey": nonstandardScriptPubKey, - "timestamp": "now", - "keys": [key.privkey]}, - success=False, - error_code=-8, - error_message='Internal must be set to true for nonstandard scriptPubKey imports.') - test_address(self.nodes[1], - key.p2pkh_addr, - iswatchonly=False, - ismine=False, - timestamp=None) + self.test_importmulti( + { + "scriptPubKey": nonstandardScriptPubKey, + "timestamp": "now", + "keys": [key.privkey], + }, + success=False, + error_code=-8, + error_message=( + "Internal must be set to true for nonstandard scriptPubKey imports." + ), + ) + test_address( + self.nodes[1], + key.p2pkh_addr, + iswatchonly=False, + ismine=False, + timestamp=None, + ) # P2SH address multisig = get_multisig(self.nodes[0]) self.generate(self.nodes[1], 100, sync_fun=self.no_op) self.nodes[1].sendtoaddress(multisig.p2sh_addr, 10.00) self.generate(self.nodes[1], 1, sync_fun=self.no_op) - timestamp = self.nodes[1].getblock( - self.nodes[1].getbestblockhash())['mediantime'] + timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())[ + "mediantime" + ] self.nodes[1].syncwithvalidationinterfacequeue() self.log.info("Should import a p2sh") - self.test_importmulti({"scriptPubKey": {"address": multisig.p2sh_addr}, - "timestamp": "now"}, - success=True) - test_address(self.nodes[1], - multisig.p2sh_addr, - isscript=True, - iswatchonly=True, - timestamp=timestamp) - p2shunspent = self.nodes[1].listunspent( - 0, 999999, [multisig.p2sh_addr])[0] - assert_equal(p2shunspent['spendable'], False) - assert_equal(p2shunspent['solvable'], False) + self.test_importmulti( + {"scriptPubKey": {"address": multisig.p2sh_addr}, "timestamp": "now"}, + success=True, + ) + test_address( + self.nodes[1], + multisig.p2sh_addr, + isscript=True, + iswatchonly=True, + timestamp=timestamp, + ) + p2shunspent = self.nodes[1].listunspent(0, 999999, [multisig.p2sh_addr])[0] + assert_equal(p2shunspent["spendable"], False) + assert_equal(p2shunspent["solvable"], False) # P2SH + Redeem script multisig = get_multisig(self.nodes[0]) self.generate(self.nodes[1], 100, sync_fun=self.no_op) self.nodes[1].sendtoaddress(multisig.p2sh_addr, 10.00) self.generate(self.nodes[1], 1, sync_fun=self.no_op) - timestamp = self.nodes[1].getblock( - self.nodes[1].getbestblockhash())['mediantime'] + timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())[ + "mediantime" + ] self.nodes[1].syncwithvalidationinterfacequeue() self.log.info("Should import a p2sh with respective redeem script") - self.test_importmulti({"scriptPubKey": {"address": multisig.p2sh_addr}, - "timestamp": "now", - "redeemscript": multisig.redeem_script}, - success=True, - warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) + self.test_importmulti( + { + "scriptPubKey": {"address": multisig.p2sh_addr}, + "timestamp": "now", + "redeemscript": multisig.redeem_script, + }, + success=True, + warnings=[ + "Some private keys are missing, outputs will be considered watchonly." + " If this is intentional, specify the watchonly flag." + ], + ) test_address( self.nodes[1], multisig.p2sh_addr, timestamp=timestamp, iswatchonly=True, ismine=False, - solvable=True) + solvable=True, + ) - p2shunspent = self.nodes[1].listunspent( - 0, 999999, [multisig.p2sh_addr])[0] - assert_equal(p2shunspent['spendable'], False) - assert_equal(p2shunspent['solvable'], True) + p2shunspent = self.nodes[1].listunspent(0, 999999, [multisig.p2sh_addr])[0] + assert_equal(p2shunspent["spendable"], False) + assert_equal(p2shunspent["solvable"], True) # P2SH + Redeem script + Private Keys + !Watchonly multisig = get_multisig(self.nodes[0]) self.generate(self.nodes[1], 100, sync_fun=self.no_op) self.nodes[1].sendtoaddress(multisig.p2sh_addr, 10.00) self.generate(self.nodes[1], 1, sync_fun=self.no_op) - timestamp = self.nodes[1].getblock( - self.nodes[1].getbestblockhash())['mediantime'] + timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())[ + "mediantime" + ] self.nodes[1].syncwithvalidationinterfacequeue() self.log.info( - "Should import a p2sh with respective redeem script and private keys") - self.test_importmulti({"scriptPubKey": {"address": multisig.p2sh_addr}, - "timestamp": "now", - "redeemscript": multisig.redeem_script, - "keys": multisig.privkeys[0:2]}, - success=True, - warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) - test_address(self.nodes[1], - multisig.p2sh_addr, - timestamp=timestamp, - ismine=False, - iswatchonly=True, - solvable=True) - - p2shunspent = self.nodes[1].listunspent( - 0, 999999, [multisig.p2sh_addr])[0] - assert_equal(p2shunspent['spendable'], False) - assert_equal(p2shunspent['solvable'], True) + "Should import a p2sh with respective redeem script and private keys" + ) + self.test_importmulti( + { + "scriptPubKey": {"address": multisig.p2sh_addr}, + "timestamp": "now", + "redeemscript": multisig.redeem_script, + "keys": multisig.privkeys[0:2], + }, + success=True, + warnings=[ + "Some private keys are missing, outputs will be considered watchonly." + " If this is intentional, specify the watchonly flag." + ], + ) + test_address( + self.nodes[1], + multisig.p2sh_addr, + timestamp=timestamp, + ismine=False, + iswatchonly=True, + solvable=True, + ) + + p2shunspent = self.nodes[1].listunspent(0, 999999, [multisig.p2sh_addr])[0] + assert_equal(p2shunspent["spendable"], False) + assert_equal(p2shunspent["solvable"], True) # P2SH + Redeem script + Private Keys + Watchonly multisig = get_multisig(self.nodes[0]) self.generate(self.nodes[1], 100, sync_fun=self.no_op) self.nodes[1].sendtoaddress(multisig.p2sh_addr, 10.00) self.generate(self.nodes[1], 1, sync_fun=self.no_op) - timestamp = self.nodes[1].getblock( - self.nodes[1].getbestblockhash())['mediantime'] + timestamp = self.nodes[1].getblock(self.nodes[1].getbestblockhash())[ + "mediantime" + ] self.nodes[1].syncwithvalidationinterfacequeue() self.log.info( - "Should import a p2sh with respective redeem script and private keys") - self.test_importmulti({"scriptPubKey": {"address": multisig.p2sh_addr}, - "timestamp": "now", - "redeemscript": multisig.redeem_script, - "keys": multisig.privkeys[0:2], - "watchonly": True}, - success=True) - test_address(self.nodes[1], - multisig.p2sh_addr, - iswatchonly=True, - ismine=False, - solvable=True, - timestamp=timestamp) + "Should import a p2sh with respective redeem script and private keys" + ) + self.test_importmulti( + { + "scriptPubKey": {"address": multisig.p2sh_addr}, + "timestamp": "now", + "redeemscript": multisig.redeem_script, + "keys": multisig.privkeys[0:2], + "watchonly": True, + }, + success=True, + ) + test_address( + self.nodes[1], + multisig.p2sh_addr, + iswatchonly=True, + ismine=False, + solvable=True, + timestamp=timestamp, + ) # Address + Public key + !Internal + Wrong pubkey self.log.info( - "Should not import an address with the wrong public key as non-solvable") + "Should not import an address with the wrong public key as non-solvable" + ) key = get_key(self.nodes[0]) wrong_key = get_key(self.nodes[0]).pubkey - self.test_importmulti({"scriptPubKey": {"address": key.p2pkh_addr}, - "timestamp": "now", - "pubkeys": [wrong_key]}, - success=True, - warnings=["Importing as non-solvable: some required keys are missing. If this is intentional, don't provide any keys, pubkeys or redeemscript.", "Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) - test_address(self.nodes[1], - key.p2pkh_addr, - iswatchonly=True, - ismine=False, - solvable=False, - timestamp=timestamp) + self.test_importmulti( + { + "scriptPubKey": {"address": key.p2pkh_addr}, + "timestamp": "now", + "pubkeys": [wrong_key], + }, + success=True, + warnings=[ + ( + "Importing as non-solvable: some required keys are missing. If this" + " is intentional, don't provide any keys, pubkeys or redeemscript." + ), + ( + "Some private keys are missing, outputs will be considered" + " watchonly. If this is intentional, specify the watchonly flag." + ), + ], + ) + test_address( + self.nodes[1], + key.p2pkh_addr, + iswatchonly=True, + ismine=False, + solvable=False, + timestamp=timestamp, + ) # ScriptPubKey + Public key + internal + Wrong pubkey self.log.info( - "Should import a scriptPubKey with internal and with a wrong public key as non-solvable") + "Should import a scriptPubKey with internal and with a wrong public key as" + " non-solvable" + ) key = get_key(self.nodes[0]) wrong_key = get_key(self.nodes[0]).pubkey - self.test_importmulti({"scriptPubKey": key.p2pkh_script, - "timestamp": "now", - "pubkeys": [wrong_key], - "internal": True}, - success=True, - warnings=["Importing as non-solvable: some required keys are missing. If this is intentional, don't provide any keys, pubkeys or redeemscript.", "Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) - test_address(self.nodes[1], - key.p2pkh_addr, - iswatchonly=True, - ismine=False, - solvable=False, - timestamp=timestamp) + self.test_importmulti( + { + "scriptPubKey": key.p2pkh_script, + "timestamp": "now", + "pubkeys": [wrong_key], + "internal": True, + }, + success=True, + warnings=[ + ( + "Importing as non-solvable: some required keys are missing. If this" + " is intentional, don't provide any keys, pubkeys or redeemscript." + ), + ( + "Some private keys are missing, outputs will be considered" + " watchonly. If this is intentional, specify the watchonly flag." + ), + ], + ) + test_address( + self.nodes[1], + key.p2pkh_addr, + iswatchonly=True, + ismine=False, + solvable=False, + timestamp=timestamp, + ) # Address + Private key + !watchonly + Wrong private key self.log.info( - "Should import an address with a wrong private key as non-solvable") + "Should import an address with a wrong private key as non-solvable" + ) key = get_key(self.nodes[0]) wrong_privkey = get_key(self.nodes[0]).privkey - self.test_importmulti({"scriptPubKey": {"address": key.p2pkh_addr}, - "timestamp": "now", - "keys": [wrong_privkey]}, - success=True, - warnings=["Importing as non-solvable: some required keys are missing. If this is intentional, don't provide any keys, pubkeys or redeemscript.", "Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) - test_address(self.nodes[1], - key.p2pkh_addr, - iswatchonly=True, - ismine=False, - solvable=False, - timestamp=timestamp) + self.test_importmulti( + { + "scriptPubKey": {"address": key.p2pkh_addr}, + "timestamp": "now", + "keys": [wrong_privkey], + }, + success=True, + warnings=[ + ( + "Importing as non-solvable: some required keys are missing. If this" + " is intentional, don't provide any keys, pubkeys or redeemscript." + ), + ( + "Some private keys are missing, outputs will be considered" + " watchonly. If this is intentional, specify the watchonly flag." + ), + ], + ) + test_address( + self.nodes[1], + key.p2pkh_addr, + iswatchonly=True, + ismine=False, + solvable=False, + timestamp=timestamp, + ) # ScriptPubKey + Private key + internal + Wrong private key self.log.info( - "Should import a scriptPubKey with internal and with a wrong private key as non-solvable") + "Should import a scriptPubKey with internal and with a wrong private key as" + " non-solvable" + ) key = get_key(self.nodes[0]) wrong_privkey = get_key(self.nodes[0]).privkey - self.test_importmulti({"scriptPubKey": key.p2pkh_script, - "timestamp": "now", - "keys": [wrong_privkey], - "internal": True}, - success=True, - warnings=["Importing as non-solvable: some required keys are missing. If this is intentional, don't provide any keys, pubkeys or redeemscript.", "Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) - test_address(self.nodes[1], - key.p2pkh_addr, - iswatchonly=True, - ismine=False, - solvable=False, - timestamp=timestamp) + self.test_importmulti( + { + "scriptPubKey": key.p2pkh_script, + "timestamp": "now", + "keys": [wrong_privkey], + "internal": True, + }, + success=True, + warnings=[ + ( + "Importing as non-solvable: some required keys are missing. If this" + " is intentional, don't provide any keys, pubkeys or redeemscript." + ), + ( + "Some private keys are missing, outputs will be considered" + " watchonly. If this is intentional, specify the watchonly flag." + ), + ], + ) + test_address( + self.nodes[1], + key.p2pkh_addr, + iswatchonly=True, + ismine=False, + solvable=False, + timestamp=timestamp, + ) # Importing existing watch only address with new timestamp should # replace saved timestamp. assert_greater_than(timestamp, watchonly_timestamp) self.log.info("Should replace previously saved watch only timestamp.") - self.test_importmulti({"scriptPubKey": {"address": watchonly_address}, - "timestamp": "now"}, - success=True) - test_address(self.nodes[1], - watchonly_address, - iswatchonly=True, - ismine=False, - timestamp=timestamp) + self.test_importmulti( + {"scriptPubKey": {"address": watchonly_address}, "timestamp": "now"}, + success=True, + ) + test_address( + self.nodes[1], + watchonly_address, + iswatchonly=True, + ismine=False, + timestamp=timestamp, + ) watchonly_timestamp = timestamp # restart nodes to check for proper serialization/deserialization of # watch only address self.stop_nodes() self.start_nodes() - test_address(self.nodes[1], - watchonly_address, - iswatchonly=True, - ismine=False, - timestamp=watchonly_timestamp) + test_address( + self.nodes[1], + watchonly_address, + iswatchonly=True, + ismine=False, + timestamp=watchonly_timestamp, + ) # Bad or missing timestamps self.log.info("Should throw on invalid or missing timestamp values") - assert_raises_rpc_error(-3, 'Missing required timestamp field for key', - self.nodes[1].importmulti, [{"scriptPubKey": key.p2pkh_script}]) - assert_raises_rpc_error(-3, 'Expected number or "now" timestamp value for key. got type string', - self.nodes[1].importmulti, [{ - "scriptPubKey": key.p2pkh_script, - "timestamp": "" - }]) + assert_raises_rpc_error( + -3, + "Missing required timestamp field for key", + self.nodes[1].importmulti, + [{"scriptPubKey": key.p2pkh_script}], + ) + assert_raises_rpc_error( + -3, + 'Expected number or "now" timestamp value for key. got type string', + self.nodes[1].importmulti, + [{"scriptPubKey": key.p2pkh_script, "timestamp": ""}], + ) # Test that importing of a P2PKH address via descriptor without # checksum fails key = get_key(self.nodes[0]) self.log.info( - "Should fail to import a p2pkh address from descriptor with no checksum") - self.test_importmulti({"desc": f"pkh({key.pubkey})", - "timestamp": "now", - "label": "Descriptor import test"}, - success=False, - error_code=-5, - error_message='Missing checksum') + "Should fail to import a p2pkh address from descriptor with no checksum" + ) + self.test_importmulti( + { + "desc": f"pkh({key.pubkey})", + "timestamp": "now", + "label": "Descriptor import test", + }, + success=False, + error_code=-5, + error_message="Missing checksum", + ) # Test ranged descriptor fails if range is not specified xpriv = "tprv8ZgxMBicQKsPeuVhWwi6wuMQGfPKi9Li5GtX35jVNknACgqe3CY4g5xgkfDDJcmtF7o1QnxWDRYw4H5P26PXq7sbcUkEqeR4fg3Kxp2tigg" # hdkeypath=m/0'/0'/0' and 1' addresses = [ "ecregtest:prvn9ycvgr5atuyh49sua3mapskh2mnnzg7t9yp6dt", - "ecregtest:pp3n087yx0njv2e5wcvltahfxqst7l66rutz8ceeat"] + "ecregtest:pp3n087yx0njv2e5wcvltahfxqst7l66rutz8ceeat", + ] # pkh subscripts corresponding to the above addresses addresses += [ "ecregtest:qqdkxd2xnzftq2p8wr3sqqyw8lntap7tncs546s6pr", "ecregtest:qpyryy83jfaec5u0gpzldk6teadsuq8zlyqh5l30uq", ] desc = f"sh(pkh({xpriv}/0'/0'/*'))" - self.log.info( - "Ranged descriptor import should fail without a specified range") - self.test_importmulti({"desc": descsum_create(desc), - "timestamp": "now"}, - success=False, - error_code=-8, - error_message='Descriptor is ranged, please specify the range') + self.log.info("Ranged descriptor import should fail without a specified range") + self.test_importmulti( + {"desc": descsum_create(desc), "timestamp": "now"}, + success=False, + error_code=-8, + error_message="Descriptor is ranged, please specify the range", + ) # Test importing of a ranged descriptor with xpriv self.log.info( - "Should import the ranged descriptor with specified range as solvable") - self.test_importmulti({"desc": descsum_create(desc), - "timestamp": "now", - "range": 1}, - success=True) + "Should import the ranged descriptor with specified range as solvable" + ) + self.test_importmulti( + {"desc": descsum_create(desc), "timestamp": "now", "range": 1}, success=True + ) for address in addresses: test_address(self.nodes[1], address, solvable=True, ismine=True) - self.test_importmulti({"desc": descsum_create(desc), "timestamp": "now", "range": -1}, - success=False, error_code=-8, error_message='End of range is too high') + self.test_importmulti( + {"desc": descsum_create(desc), "timestamp": "now", "range": -1}, + success=False, + error_code=-8, + error_message="End of range is too high", + ) - self.test_importmulti({"desc": descsum_create(desc), "timestamp": "now", "range": [-1, 10]}, - success=False, error_code=-8, error_message='Range should be greater or equal than 0') + self.test_importmulti( + {"desc": descsum_create(desc), "timestamp": "now", "range": [-1, 10]}, + success=False, + error_code=-8, + error_message="Range should be greater or equal than 0", + ) - self.test_importmulti({"desc": descsum_create(desc), "timestamp": "now", "range": [(2 << 31 + 1) - 1000000, (2 << 31 + 1)]}, - success=False, error_code=-8, error_message='End of range is too high') + self.test_importmulti( + { + "desc": descsum_create(desc), + "timestamp": "now", + "range": [(2 << 31 + 1) - 1000000, (2 << 31 + 1)], + }, + success=False, + error_code=-8, + error_message="End of range is too high", + ) - self.test_importmulti({"desc": descsum_create(desc), "timestamp": "now", "range": [2, 1]}, - success=False, error_code=-8, error_message='Range specified as [begin,end] must not have begin after end') + self.test_importmulti( + {"desc": descsum_create(desc), "timestamp": "now", "range": [2, 1]}, + success=False, + error_code=-8, + error_message=( + "Range specified as [begin,end] must not have begin after end" + ), + ) - self.test_importmulti({"desc": descsum_create(desc), "timestamp": "now", "range": [0, 1000001]}, - success=False, error_code=-8, error_message='Range is too large') + self.test_importmulti( + {"desc": descsum_create(desc), "timestamp": "now", "range": [0, 1000001]}, + success=False, + error_code=-8, + error_message="Range is too large", + ) # Test importing a descriptor containing a WIF private key wif_priv = "cTe1f5rdT8A8DFgVWTjyPwACsDPJM9ff4QngFxUixCSvvbg1x6sh" # Note: in Core's test, this address refers to the sh(wpkh()) address. # For a sh(pkh()) this does not refer to a key, so we use the subscript # address instead, which returns the same privkey. address = "ecregtest:qzh6rch6st3wjvp0h2ud87gn7xnxvf6h8yrk8gcg8t" desc = f"sh(pkh({wif_priv}))" - self.log.info( - "Should import a descriptor with a WIF private key as spendable") - self.test_importmulti({"desc": descsum_create(desc), - "timestamp": "now"}, - success=True) - test_address(self.nodes[1], - address, - solvable=True, - ismine=True) + self.log.info("Should import a descriptor with a WIF private key as spendable") + self.test_importmulti( + {"desc": descsum_create(desc), "timestamp": "now"}, success=True + ) + test_address(self.nodes[1], address, solvable=True, ismine=True) # dump the private key to ensure it matches what was imported privkey = self.nodes[1].dumpprivkey(address) assert_equal(privkey, wif_priv) # Test importing of a P2PKH address via descriptor key = get_key(self.nodes[0]) p2pkh_label = "P2PKH descriptor import" self.log.info("Should import a p2pkh address from descriptor") - self.test_importmulti({"desc": descsum_create(f"pkh({key.pubkey})"), - "timestamp": "now", - "label": p2pkh_label}, - success=True, - warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) - test_address(self.nodes[1], - key.p2pkh_addr, - solvable=True, - ismine=False, - labels=[p2pkh_label]) + self.test_importmulti( + { + "desc": descsum_create(f"pkh({key.pubkey})"), + "timestamp": "now", + "label": p2pkh_label, + }, + success=True, + warnings=[ + "Some private keys are missing, outputs will be considered watchonly." + " If this is intentional, specify the watchonly flag." + ], + ) + test_address( + self.nodes[1], + key.p2pkh_addr, + solvable=True, + ismine=False, + labels=[p2pkh_label], + ) # Test import fails if both desc and scriptPubKey are provided key = get_key(self.nodes[0]) - self.log.info( - "Import should fail if both scriptPubKey and desc are provided") - self.test_importmulti({"desc": descsum_create(f"pkh({key.pubkey})"), - "scriptPubKey": {"address": key.p2pkh_addr}, - "timestamp": "now"}, - success=False, - error_code=-8, - error_message='Both a descriptor and a scriptPubKey should not be provided.') + self.log.info("Import should fail if both scriptPubKey and desc are provided") + self.test_importmulti( + { + "desc": descsum_create(f"pkh({key.pubkey})"), + "scriptPubKey": {"address": key.p2pkh_addr}, + "timestamp": "now", + }, + success=False, + error_code=-8, + error_message=( + "Both a descriptor and a scriptPubKey should not be provided." + ), + ) # Test import fails if neither desc nor scriptPubKey are present key = get_key(self.nodes[0]) self.log.info( - "Import should fail if neither a descriptor nor a scriptPubKey are provided") - self.test_importmulti({"timestamp": "now"}, - success=False, - error_code=-8, - error_message='Either a descriptor or scriptPubKey must be provided.') + "Import should fail if neither a descriptor nor a scriptPubKey are provided" + ) + self.test_importmulti( + {"timestamp": "now"}, + success=False, + error_code=-8, + error_message="Either a descriptor or scriptPubKey must be provided.", + ) # Test importing of a multisig via descriptor key1 = get_key(self.nodes[0]) key2 = get_key(self.nodes[0]) self.log.info("Should import a 1-of-2 bare multisig from descriptor") - self.test_importmulti({"desc": descsum_create(f"multi(1,{key1.pubkey},{key2.pubkey})"), - "timestamp": "now"}, - success=True, - warnings=["Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."]) + self.test_importmulti( + { + "desc": descsum_create(f"multi(1,{key1.pubkey},{key2.pubkey})"), + "timestamp": "now", + }, + success=True, + warnings=[ + "Some private keys are missing, outputs will be considered watchonly." + " If this is intentional, specify the watchonly flag." + ], + ) self.log.info( - "Should not treat individual keys from the imported bare multisig as watchonly") - test_address(self.nodes[1], - key1.p2pkh_addr, - ismine=False, - iswatchonly=False) + "Should not treat individual keys from the imported bare multisig as" + " watchonly" + ) + test_address(self.nodes[1], key1.p2pkh_addr, ismine=False, iswatchonly=False) # Import pubkeys with key origin info self.log.info( - "Addresses should have hd keypath and master key id after import with key origin") + "Addresses should have hd keypath and master key id after import with key" + " origin" + ) pub_addr = self.nodes[1].getnewaddress() pub_addr = self.nodes[1].getnewaddress() info = self.nodes[1].getaddressinfo(pub_addr) - pub = info['pubkey'] - pub_keypath = info['hdkeypath'] - pub_fpr = info['hdmasterfingerprint'] + pub = info["pubkey"] + pub_keypath = info["hdkeypath"] + pub_fpr = info["hdmasterfingerprint"] result = self.nodes[0].importmulti( - [{ - 'desc': descsum_create(f"pkh([{pub_fpr}{pub_keypath[1:]}]{pub})"), - "timestamp": "now", - }] + [ + { + "desc": descsum_create(f"pkh([{pub_fpr}{pub_keypath[1:]}]{pub})"), + "timestamp": "now", + } + ] ) - assert result[0]['success'] + assert result[0]["success"] pub_import_info = self.nodes[0].getaddressinfo(pub_addr) - assert_equal(pub_import_info['hdmasterfingerprint'], pub_fpr) - assert_equal(pub_import_info['pubkey'], pub) - assert_equal(pub_import_info['hdkeypath'], pub_keypath) + assert_equal(pub_import_info["hdmasterfingerprint"], pub_fpr) + assert_equal(pub_import_info["pubkey"], pub) + assert_equal(pub_import_info["hdkeypath"], pub_keypath) # Import privkeys with key origin info priv_addr = self.nodes[1].getnewaddress() info = self.nodes[1].getaddressinfo(priv_addr) priv = self.nodes[1].dumpprivkey(priv_addr) - priv_keypath = info['hdkeypath'] - priv_fpr = info['hdmasterfingerprint'] + priv_keypath = info["hdkeypath"] + priv_fpr = info["hdmasterfingerprint"] result = self.nodes[0].importmulti( - [{ - 'desc': descsum_create(f"pkh([{priv_fpr}{priv_keypath[1:]}]{priv})"), - "timestamp": "now", - }] + [ + { + "desc": descsum_create( + f"pkh([{priv_fpr}{priv_keypath[1:]}]{priv})" + ), + "timestamp": "now", + } + ] ) - assert result[0]['success'] + assert result[0]["success"] priv_import_info = self.nodes[0].getaddressinfo(priv_addr) - assert_equal(priv_import_info['hdmasterfingerprint'], priv_fpr) - assert_equal(priv_import_info['hdkeypath'], priv_keypath) + assert_equal(priv_import_info["hdmasterfingerprint"], priv_fpr) + assert_equal(priv_import_info["hdkeypath"], priv_keypath) # Make sure the key origin info are still there after a restart self.stop_nodes() self.start_nodes() import_info = self.nodes[0].getaddressinfo(pub_addr) - assert_equal(import_info['hdmasterfingerprint'], pub_fpr) - assert_equal(import_info['hdkeypath'], pub_keypath) + assert_equal(import_info["hdmasterfingerprint"], pub_fpr) + assert_equal(import_info["hdkeypath"], pub_keypath) import_info = self.nodes[0].getaddressinfo(priv_addr) - assert_equal(import_info['hdmasterfingerprint'], priv_fpr) - assert_equal(import_info['hdkeypath'], priv_keypath) + assert_equal(import_info["hdmasterfingerprint"], priv_fpr) + assert_equal(import_info["hdkeypath"], priv_keypath) # Check legacy import does not import key origin info self.log.info("Legacy imports don't have key origin info") pub_addr = self.nodes[1].getnewaddress() info = self.nodes[1].getaddressinfo(pub_addr) - pub = info['pubkey'] + pub = info["pubkey"] result = self.nodes[0].importmulti( - [{ - 'scriptPubKey': {'address': pub_addr}, - 'pubkeys': [pub], - "timestamp": "now", - }] + [ + { + "scriptPubKey": {"address": pub_addr}, + "pubkeys": [pub], + "timestamp": "now", + } + ] ) - assert result[0]['success'] + assert result[0]["success"] pub_import_info = self.nodes[0].getaddressinfo(pub_addr) - assert_equal(pub_import_info['pubkey'], pub) - assert 'hdmasterfingerprint' not in pub_import_info - assert 'hdkeypath' not in pub_import_info + assert_equal(pub_import_info["pubkey"], pub) + assert "hdmasterfingerprint" not in pub_import_info + assert "hdkeypath" not in pub_import_info # Import some public keys to the keypool of a no privkey wallet self.log.info("Adding pubkey to keypool of disableprivkey wallet") - self.nodes[1].createwallet( - wallet_name="noprivkeys", - disable_private_keys=True) + self.nodes[1].createwallet(wallet_name="noprivkeys", disable_private_keys=True) wrpc = self.nodes[1].get_wallet_rpc("noprivkeys") addr1 = self.nodes[0].getnewaddress() addr2 = self.nodes[0].getnewaddress() - pub1 = self.nodes[0].getaddressinfo(addr1)['pubkey'] - pub2 = self.nodes[0].getaddressinfo(addr2)['pubkey'] + pub1 = self.nodes[0].getaddressinfo(addr1)["pubkey"] + pub2 = self.nodes[0].getaddressinfo(addr2)["pubkey"] result = wrpc.importmulti( - [{ - 'desc': descsum_create(f"pkh({pub1})"), - 'keypool': True, - "timestamp": "now", - }, + [ { - 'desc': descsum_create(f"pkh({pub2})"), - 'keypool': True, - "timestamp": "now", - }] + "desc": descsum_create(f"pkh({pub1})"), + "keypool": True, + "timestamp": "now", + }, + { + "desc": descsum_create(f"pkh({pub2})"), + "keypool": True, + "timestamp": "now", + }, + ] ) - assert result[0]['success'] - assert result[1]['success'] + assert result[0]["success"] + assert result[1]["success"] assert_equal(wrpc.getwalletinfo()["keypoolsize"], 2) newaddr1 = wrpc.getnewaddress() assert_equal(addr1, newaddr1) newaddr2 = wrpc.getnewaddress() assert_equal(addr2, newaddr2) # Import some public keys to the internal keypool of a no privkey # wallet - self.log.info( - "Adding pubkey to internal keypool of disableprivkey wallet") + self.log.info("Adding pubkey to internal keypool of disableprivkey wallet") addr1 = self.nodes[0].getnewaddress() addr2 = self.nodes[0].getnewaddress() - pub1 = self.nodes[0].getaddressinfo(addr1)['pubkey'] - pub2 = self.nodes[0].getaddressinfo(addr2)['pubkey'] + pub1 = self.nodes[0].getaddressinfo(addr1)["pubkey"] + pub2 = self.nodes[0].getaddressinfo(addr2)["pubkey"] result = wrpc.importmulti( - [{ - 'desc': descsum_create(f"pkh({pub1})"), - 'keypool': True, - 'internal': True, - "timestamp": "now", - }, + [ { - 'desc': descsum_create(f"pkh({pub2})"), - 'keypool': True, - 'internal': True, - "timestamp": "now", - }] + "desc": descsum_create(f"pkh({pub1})"), + "keypool": True, + "internal": True, + "timestamp": "now", + }, + { + "desc": descsum_create(f"pkh({pub2})"), + "keypool": True, + "internal": True, + "timestamp": "now", + }, + ] ) - assert result[0]['success'] - assert result[1]['success'] + assert result[0]["success"] + assert result[1]["success"] assert_equal(wrpc.getwalletinfo()["keypoolsize_hd_internal"], 2) newaddr1 = wrpc.getrawchangeaddress() assert_equal(addr1, newaddr1) newaddr2 = wrpc.getrawchangeaddress() assert_equal(addr2, newaddr2) # Import a multisig and make sure the keys don't go into the keypool self.log.info( - 'Imported scripts with pubkeys shoud not have their pubkeys go into the keypool') + "Imported scripts with pubkeys shoud not have their pubkeys go into the" + " keypool" + ) addr1 = self.nodes[0].getnewaddress() addr2 = self.nodes[0].getnewaddress() - pub1 = self.nodes[0].getaddressinfo(addr1)['pubkey'] - pub2 = self.nodes[0].getaddressinfo(addr2)['pubkey'] + pub1 = self.nodes[0].getaddressinfo(addr1)["pubkey"] + pub2 = self.nodes[0].getaddressinfo(addr2)["pubkey"] result = wrpc.importmulti( - [{ - 'desc': descsum_create(f"sh(multi(2,{pub1},{pub2}))"), - 'keypool': True, - "timestamp": "now", - }] + [ + { + "desc": descsum_create(f"sh(multi(2,{pub1},{pub2}))"), + "keypool": True, + "timestamp": "now", + } + ] ) - assert result[0]['success'] + assert result[0]["success"] assert_equal(wrpc.getwalletinfo()["keypoolsize"], 0) # Cannot import those pubkeys to keypool of wallet with privkeys self.log.info( - "Pubkeys cannot be added to the keypool of a wallet with private keys") + "Pubkeys cannot be added to the keypool of a wallet with private keys" + ) wrpc = self.nodes[1].get_wallet_rpc(self.default_wallet_name) - assert wrpc.getwalletinfo()['private_keys_enabled'] + assert wrpc.getwalletinfo()["private_keys_enabled"] result = wrpc.importmulti( - [{ - 'desc': descsum_create(f"pkh({pub1})"), - 'keypool': True, - "timestamp": "now", - }] + [ + { + "desc": descsum_create(f"pkh({pub1})"), + "keypool": True, + "timestamp": "now", + } + ] ) - assert_equal(result[0]['error']['code'], -8) + assert_equal(result[0]["error"]["code"], -8) assert_equal( - result[0]['error']['message'], - "Keys can only be imported to the keypool when private keys are disabled") + result[0]["error"]["message"], + "Keys can only be imported to the keypool when private keys are disabled", + ) # Make sure ranged imports import keys in order - self.log.info('Key ranges should be imported in order') + self.log.info("Key ranges should be imported in order") wrpc = self.nodes[1].get_wallet_rpc("noprivkeys") assert_equal(wrpc.getwalletinfo()["keypoolsize"], 0) assert_equal(wrpc.getwalletinfo()["private_keys_enabled"], False) xpub = "tpubDAXcJ7s7ZwicqjprRaEWdPoHKrCS215qxGYxpusRLLmJuT69ZSicuGdSfyvyKpvUNYBW1s2U3NSrT6vrCYB9e6nZUEvrqnwXPF8ArTCRXMY" addresses = [ - 'ecregtest:qp0v86h53rc92hjrlpwzpjtdlgzsxu25svv6g40fpl', # m/0'/0'/0 - 'ecregtest:qqasy0zlkdleqt4pkn8fs4ehm5gnnz6qpgdcpt90fq', # m/0'/0'/1 - 'ecregtest:qp0sp4wlhctvprqvdt2dgvqcfdjssu04xgey0l3syw', # m/0'/0'/2 - 'ecregtest:qrhn24tegn04cptfv4ldhtkduxq55zcwrycjfdj9vr', # m/0'/0'/3 - 'ecregtest:qzpqhett2uwltq803vrxv7zkqhft5vsnmcjeh50v0p', # m/0'/0'/4 + "ecregtest:qp0v86h53rc92hjrlpwzpjtdlgzsxu25svv6g40fpl", # m/0'/0'/0 + "ecregtest:qqasy0zlkdleqt4pkn8fs4ehm5gnnz6qpgdcpt90fq", # m/0'/0'/1 + "ecregtest:qp0sp4wlhctvprqvdt2dgvqcfdjssu04xgey0l3syw", # m/0'/0'/2 + "ecregtest:qrhn24tegn04cptfv4ldhtkduxq55zcwrycjfdj9vr", # m/0'/0'/3 + "ecregtest:qzpqhett2uwltq803vrxv7zkqhft5vsnmcjeh50v0p", # m/0'/0'/4 ] result = wrpc.importmulti( - [{ - 'desc': descsum_create(f"pkh([80002067/0h/0h]{xpub}/*)"), - 'keypool': True, - 'timestamp': 'now', - 'range': [0, 4], - }] + [ + { + "desc": descsum_create(f"pkh([80002067/0h/0h]{xpub}/*)"), + "keypool": True, + "timestamp": "now", + "range": [0, 4], + } + ] ) self.log.info(result) for i in range(0, 5): - addr = wrpc.getnewaddress('') + addr = wrpc.getnewaddress("") assert_equal(addr, addresses[i]) -if __name__ == '__main__': +if __name__ == "__main__": ImportMultiTest().main() diff --git a/test/functional/wallet_importprunedfunds.py b/test/functional/wallet_importprunedfunds.py index 607a6d384..8c8c42082 100755 --- a/test/functional/wallet_importprunedfunds.py +++ b/test/functional/wallet_importprunedfunds.py @@ -1,134 +1,148 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2016 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the importprunedfunds and removeprunedfunds RPCs.""" from decimal import Decimal from test_framework.address import key_to_p2pkh from test_framework.key import ECKey from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error from test_framework.wallet_util import bytes_to_wif class ImportPrunedFundsTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): self.log.info("Mining blocks...") self.generate(self.nodes[0], 101) # address address1 = self.nodes[0].getnewaddress() # pubkey address2 = self.nodes[0].getnewaddress() # privkey eckey = ECKey() eckey.generate() address3_privkey = bytes_to_wif(eckey.get_bytes()) address3 = key_to_p2pkh(eckey.get_pubkey().get_bytes()) self.nodes[0].importprivkey(address3_privkey) # Check only one address address_info = self.nodes[0].getaddressinfo(address1) - assert_equal(address_info['ismine'], True) + assert_equal(address_info["ismine"], True) self.sync_all() # Node 1 sync test assert_equal(self.nodes[1].getblockcount(), 101) # Address Test - before import address_info = self.nodes[1].getaddressinfo(address1) - assert_equal(address_info['iswatchonly'], False) - assert_equal(address_info['ismine'], False) + assert_equal(address_info["iswatchonly"], False) + assert_equal(address_info["ismine"], False) address_info = self.nodes[1].getaddressinfo(address2) - assert_equal(address_info['iswatchonly'], False) - assert_equal(address_info['ismine'], False) + assert_equal(address_info["iswatchonly"], False) + assert_equal(address_info["ismine"], False) address_info = self.nodes[1].getaddressinfo(address3) - assert_equal(address_info['iswatchonly'], False) - assert_equal(address_info['ismine'], False) + assert_equal(address_info["iswatchonly"], False) + assert_equal(address_info["ismine"], False) # Send funds to self txnid1 = self.nodes[0].sendtoaddress(address1, 100000) self.generate(self.nodes[0], 1) - rawtxn1 = self.nodes[0].gettransaction(txnid1)['hex'] + rawtxn1 = self.nodes[0].gettransaction(txnid1)["hex"] proof1 = self.nodes[0].gettxoutproof([txnid1]) txnid2 = self.nodes[0].sendtoaddress(address2, 50000) self.generate(self.nodes[0], 1) - rawtxn2 = self.nodes[0].gettransaction(txnid2)['hex'] + rawtxn2 = self.nodes[0].gettransaction(txnid2)["hex"] proof2 = self.nodes[0].gettxoutproof([txnid2]) txnid3 = self.nodes[0].sendtoaddress(address3, 25000) self.generate(self.nodes[0], 1) - rawtxn3 = self.nodes[0].gettransaction(txnid3)['hex'] + rawtxn3 = self.nodes[0].gettransaction(txnid3)["hex"] proof3 = self.nodes[0].gettxoutproof([txnid3]) self.sync_all() # Import with no affiliated address assert_raises_rpc_error( - -5, "No addresses", self.nodes[1].importprunedfunds, rawtxn1, proof1) + -5, "No addresses", self.nodes[1].importprunedfunds, rawtxn1, proof1 + ) balance1 = self.nodes[1].getbalance() assert_equal(balance1, Decimal(0)) # Import with affiliated address with no rescan - self.nodes[1].createwallet('wwatch', disable_private_keys=True) - wwatch = self.nodes[1].get_wallet_rpc('wwatch') + self.nodes[1].createwallet("wwatch", disable_private_keys=True) + wwatch = self.nodes[1].get_wallet_rpc("wwatch") wwatch.importaddress(address=address2, rescan=False) wwatch.importprunedfunds(rawtransaction=rawtxn2, txoutproof=proof2) - assert [tx for tx in wwatch.listtransactions(include_watchonly=True) - if tx['txid'] == txnid2] + assert [ + tx + for tx in wwatch.listtransactions(include_watchonly=True) + if tx["txid"] == txnid2 + ] # Import with private key with no rescan w1 = self.nodes[1].get_wallet_rpc(self.default_wallet_name) w1.importprivkey(privkey=address3_privkey, rescan=False) w1.importprunedfunds(rawtxn3, proof3) - assert [tx for tx in w1.listtransactions() if tx['txid'] == txnid3] + assert [tx for tx in w1.listtransactions() if tx["txid"] == txnid3] balance3 = w1.getbalance() - assert_equal(balance3, Decimal('25000')) + assert_equal(balance3, Decimal("25000")) # Addresses Test - after import address_info = w1.getaddressinfo(address1) - assert_equal(address_info['iswatchonly'], False) - assert_equal(address_info['ismine'], False) + assert_equal(address_info["iswatchonly"], False) + assert_equal(address_info["ismine"], False) address_info = wwatch.getaddressinfo(address2) if self.options.descriptors: - assert_equal(address_info['iswatchonly'], False) - assert_equal(address_info['ismine'], True) + assert_equal(address_info["iswatchonly"], False) + assert_equal(address_info["ismine"], True) else: - assert_equal(address_info['iswatchonly'], True) - assert_equal(address_info['ismine'], False) + assert_equal(address_info["iswatchonly"], True) + assert_equal(address_info["ismine"], False) address_info = w1.getaddressinfo(address3) - assert_equal(address_info['iswatchonly'], False) - assert_equal(address_info['ismine'], True) + assert_equal(address_info["iswatchonly"], False) + assert_equal(address_info["ismine"], True) # Remove transactions assert_raises_rpc_error( - -8, "Transaction does not exist in wallet.", w1.removeprunedfunds, txnid1) + -8, "Transaction does not exist in wallet.", w1.removeprunedfunds, txnid1 + ) - assert not [tx for tx in w1.listtransactions( - include_watchonly=True) if tx['txid'] == txnid1] + assert not [ + tx + for tx in w1.listtransactions(include_watchonly=True) + if tx["txid"] == txnid1 + ] wwatch.removeprunedfunds(txnid2) - assert not [tx for tx in wwatch.listtransactions( - include_watchonly=True) if tx['txid'] == txnid2] + assert not [ + tx + for tx in wwatch.listtransactions(include_watchonly=True) + if tx["txid"] == txnid2 + ] w1.removeprunedfunds(txnid3) - assert not [tx for tx in w1.listtransactions( - include_watchonly=True) if tx['txid'] == txnid3] + assert not [ + tx + for tx in w1.listtransactions(include_watchonly=True) + if tx["txid"] == txnid3 + ] -if __name__ == '__main__': +if __name__ == "__main__": ImportPrunedFundsTest().main() diff --git a/test/functional/wallet_keypool.py b/test/functional/wallet_keypool.py index a2e118e36..b5f4be59d 100755 --- a/test/functional/wallet_keypool.py +++ b/test/functional/wallet_keypool.py @@ -1,170 +1,190 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet keypool and interaction with wallet encryption/locking.""" import time from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error class KeyPoolTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): nodes = self.nodes addr_before_encrypting = nodes[0].getnewaddress() - addr_before_encrypting_data = nodes[ - 0].getaddressinfo(addr_before_encrypting) + addr_before_encrypting_data = nodes[0].getaddressinfo(addr_before_encrypting) wallet_info_old = nodes[0].getwalletinfo() - assert addr_before_encrypting_data[ - 'hdseedid'] == wallet_info_old['hdseedid'] + assert addr_before_encrypting_data["hdseedid"] == wallet_info_old["hdseedid"] # Encrypt wallet and wait to terminate - nodes[0].encryptwallet('test') + nodes[0].encryptwallet("test") # Keep creating keys addr = nodes[0].getnewaddress() addr_data = nodes[0].getaddressinfo(addr) wallet_info = nodes[0].getwalletinfo() - assert addr_before_encrypting_data[ - 'hdseedid'] != wallet_info['hdseedid'] - assert addr_data['hdseedid'] == wallet_info['hdseedid'] + assert addr_before_encrypting_data["hdseedid"] != wallet_info["hdseedid"] + assert addr_data["hdseedid"] == wallet_info["hdseedid"] assert_raises_rpc_error( - -12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress) + -12, + "Error: Keypool ran out, please call keypoolrefill first", + nodes[0].getnewaddress, + ) # put six (plus 2) new keys in the keypool (100% external-, +100% # internal-keys, 1 in min) - nodes[0].walletpassphrase('test', 12000) + nodes[0].walletpassphrase("test", 12000) nodes[0].keypoolrefill(6) nodes[0].walletlock() wi = nodes[0].getwalletinfo() - assert_equal(wi['keypoolsize_hd_internal'], 6) - assert_equal(wi['keypoolsize'], 6) + assert_equal(wi["keypoolsize_hd_internal"], 6) + assert_equal(wi["keypoolsize"], 6) # drain the internal keys nodes[0].getrawchangeaddress() nodes[0].getrawchangeaddress() nodes[0].getrawchangeaddress() nodes[0].getrawchangeaddress() nodes[0].getrawchangeaddress() nodes[0].getrawchangeaddress() addr = set() # the next one should fail - assert_raises_rpc_error(-12, "Keypool ran out", - nodes[0].getrawchangeaddress) + assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].getrawchangeaddress) # drain the external keys addr.add(nodes[0].getnewaddress()) addr.add(nodes[0].getnewaddress()) addr.add(nodes[0].getnewaddress()) addr.add(nodes[0].getnewaddress()) addr.add(nodes[0].getnewaddress()) addr.add(nodes[0].getnewaddress()) assert len(addr) == 6 # the next one should fail assert_raises_rpc_error( - -12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress) + -12, + "Error: Keypool ran out, please call keypoolrefill first", + nodes[0].getnewaddress, + ) # refill keypool with three new addresses - nodes[0].walletpassphrase('test', 1) + nodes[0].walletpassphrase("test", 1) nodes[0].keypoolrefill(3) # test walletpassphrase timeout time.sleep(1.1) assert_equal(nodes[0].getwalletinfo()["unlocked_until"], 0) # drain the keypool for _ in range(3): nodes[0].getnewaddress() assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].getnewaddress) - nodes[0].walletpassphrase('test', 100) + nodes[0].walletpassphrase("test", 100) nodes[0].keypoolrefill(100) wi = nodes[0].getwalletinfo() - assert_equal(wi['keypoolsize_hd_internal'], 100) - assert_equal(wi['keypoolsize'], 100) + assert_equal(wi["keypoolsize_hd_internal"], 100) + assert_equal(wi["keypoolsize"], 100) # create a blank wallet - nodes[0].createwallet(wallet_name='w2', blank=True) - w2 = nodes[0].get_wallet_rpc('w2') + nodes[0].createwallet(wallet_name="w2", blank=True) + w2 = nodes[0].get_wallet_rpc("w2") # refer to initial wallet as w1 w1 = nodes[0].get_wallet_rpc(self.default_wallet_name) # import private key and fund it address = addr.pop() privkey = w1.dumpprivkey(address) res = w2.importmulti( - [{'scriptPubKey': {'address': address}, 'keys': [privkey], 'timestamp': 'now'}]) - assert_equal(res[0]['success'], True) - w1.walletpassphrase('test', 100) + [ + { + "scriptPubKey": {"address": address}, + "keys": [privkey], + "timestamp": "now", + } + ] + ) + assert_equal(res[0]["success"], True) + w1.walletpassphrase("test", 100) res = w1.sendtoaddress(address=address, amount=100.00) self.generate(nodes[0], 1) destination = addr.pop() # Using a fee rate (10 sat / byte) well above the minimum relay rate # creating a 5,000 sat transaction with change should not be possible - assert_raises_rpc_error(-4, - "Transaction needs a change address, but we can't generate it. Please call keypoolrefill first.", - w2.walletcreatefundedpsbt, - inputs=[], - outputs=[{addr.pop(): 50.00}], - options={"subtractFeeFromOutputs": [0], - "feeRate": 100}) + assert_raises_rpc_error( + -4, + ( + "Transaction needs a change address, but we can't generate it. Please" + " call keypoolrefill first." + ), + w2.walletcreatefundedpsbt, + inputs=[], + outputs=[{addr.pop(): 50.00}], + options={"subtractFeeFromOutputs": [0], "feeRate": 100}, + ) # creating a 10,000 sat transaction without change, with a manual # input, should still be possible res = w2.walletcreatefundedpsbt( inputs=w2.listunspent(), outputs=[{destination: 100.00}], - options={"subtractFeeFromOutputs": [0], "feeRate": 100}) + options={"subtractFeeFromOutputs": [0], "feeRate": 100}, + ) assert_equal("psbt" in res, True) # creating a 10,000 sat transaction without change should still be # possible res = w2.walletcreatefundedpsbt( inputs=[], outputs=[{destination: 100.00}], - options={"subtractFeeFromOutputs": [0], "feeRate": 100}) + options={"subtractFeeFromOutputs": [0], "feeRate": 100}, + ) assert_equal("psbt" in res, True) # should work without subtractFeeFromOutputs if the exact fee is # subtracted from the amount - res = w2.walletcreatefundedpsbt(inputs=[], - outputs=[{destination: 80.00}], - options={"feeRate": 100}) + res = w2.walletcreatefundedpsbt( + inputs=[], outputs=[{destination: 80.00}], options={"feeRate": 100} + ) assert_equal("psbt" in res, True) # dust change should be removed - res = w2.walletcreatefundedpsbt(inputs=[], - outputs=[{destination: 79.00}], - options={"feeRate": 100}) + res = w2.walletcreatefundedpsbt( + inputs=[], outputs=[{destination: 79.00}], options={"feeRate": 100} + ) assert_equal("psbt" in res, True) # create a transaction without change at the maximum fee rate, such # that the output is still spendable: res = w2.walletcreatefundedpsbt( inputs=[], outputs=[{destination: 100.00}], - options={"subtractFeeFromOutputs": [0], "feeRate": 494.90}) + options={"subtractFeeFromOutputs": [0], "feeRate": 494.90}, + ) assert_equal("psbt" in res, True) assert_equal(res["fee"], Decimal("94.53")) # creating a 10,000 sat transaction with a manual change address should # be possible - res = w2.walletcreatefundedpsbt(inputs=[], - outputs=[{destination: 100.00}], - options={"subtractFeeFromOutputs": [0], - "feeRate": 100, - "changeAddress": addr.pop()}) + res = w2.walletcreatefundedpsbt( + inputs=[], + outputs=[{destination: 100.00}], + options={ + "subtractFeeFromOutputs": [0], + "feeRate": 100, + "changeAddress": addr.pop(), + }, + ) assert_equal("psbt" in res, True) -if __name__ == '__main__': +if __name__ == "__main__": KeyPoolTest().main() diff --git a/test/functional/wallet_keypool_topup.py b/test/functional/wallet_keypool_topup.py index efadbd03d..9dd9e4633 100755 --- a/test/functional/wallet_keypool_topup.py +++ b/test/functional/wallet_keypool_topup.py @@ -1,79 +1,86 @@ #!/usr/bin/env python3 # Copyright (c) 2017-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test HD Wallet keypool restore function. Two nodes. Node1 is under test. Node0 is providing transactions and generating blocks. - Start node1, shutdown and backup wallet. - Generate 110 keys (enough to drain the keypool). Store key 90 (in the initial keypool) and key 110 (beyond the initial keypool). Send funds to key 90 and key 110. - Stop node1, clear the datadir, move wallet file back into the datadir and restart node1. - connect node1 to node0. Verify that they sync and node1 receives its funds.""" import os import shutil from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal class KeypoolRestoreTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 - self.extra_args = [[], ['-keypool=100']] + self.extra_args = [[], ["-keypool=100"]] def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): wallet_path = os.path.join( - self.nodes[1].datadir, self.chain, "wallets", - self.default_wallet_name, self.wallet_data_filename) + self.nodes[1].datadir, + self.chain, + "wallets", + self.default_wallet_name, + self.wallet_data_filename, + ) wallet_backup_path = os.path.join(self.nodes[1].datadir, "wallet.bak") self.generate(self.nodes[0], 101) self.log.info("Make backup of wallet") self.stop_node(1) shutil.copyfile(wallet_path, wallet_backup_path) self.start_node(1, self.extra_args[1]) self.connect_nodes(0, 1) self.log.info("Generate keys for wallet") for _ in range(90): addr_oldpool = self.nodes[1].getnewaddress() for _ in range(20): addr_extpool = self.nodes[1].getnewaddress() self.log.info("Send funds to wallet") self.nodes[0].sendtoaddress(addr_oldpool, 10000000) self.generate(self.nodes[0], 1) self.nodes[0].sendtoaddress(addr_extpool, 5000000) self.generate(self.nodes[0], 1) self.log.info("Restart node with wallet backup") self.stop_node(1) shutil.copyfile(wallet_backup_path, wallet_path) self.start_node(1, self.extra_args[1]) self.connect_nodes(0, 1) self.sync_all() self.log.info("Verify keypool is restored and balance is correct") assert_equal(self.nodes[1].getbalance(), 15000000) - assert_equal(self.nodes[1].listtransactions() - [0]['category'], "receive") + assert_equal(self.nodes[1].listtransactions()[0]["category"], "receive") # Check that we have marked all keys up to the used keypool key as used if self.options.descriptors: assert_equal( - self.nodes[1].getaddressinfo( - self.nodes[1].getnewaddress())['hdkeypath'], - "m/44'/1'/0'/0/110") + self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())[ + "hdkeypath" + ], + "m/44'/1'/0'/0/110", + ) else: assert_equal( - self.nodes[1].getaddressinfo( - self.nodes[1].getnewaddress())['hdkeypath'], - "m/0'/0'/110'") + self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())[ + "hdkeypath" + ], + "m/0'/0'/110'", + ) -if __name__ == '__main__': +if __name__ == "__main__": KeypoolRestoreTest().main() diff --git a/test/functional/wallet_labels.py b/test/functional/wallet_labels.py index a50ef8511..4bbeb79c1 100755 --- a/test/functional/wallet_labels.py +++ b/test/functional/wallet_labels.py @@ -1,190 +1,189 @@ #!/usr/bin/env python3 # Copyright (c) 2016-2017 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test label RPCs. RPCs tested are: - getaddressesbylabel - listaddressgroupings - setlabel """ from collections import defaultdict from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error from test_framework.wallet_util import test_address class WalletLabelsTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): # Check that there's no UTXO on the node node = self.nodes[0] assert_equal(len(node.listunspent()), 0) # Note each time we call generate, all generated coins go into # the same address, so we call twice to get two addresses w/50 each - self.generatetoaddress(node, - nblocks=1, address=node.getnewaddress( - label='coinbase')) - self.generatetoaddress(node, - nblocks=101, - address=node.getnewaddress( - label='coinbase')) + self.generatetoaddress( + node, nblocks=1, address=node.getnewaddress(label="coinbase") + ) + self.generatetoaddress( + node, nblocks=101, address=node.getnewaddress(label="coinbase") + ) assert_equal(node.getbalance(), 100000000) # there should be 2 address groups # each with 1 address with a balance of 50 Bitcoins address_groups = node.listaddressgroupings() assert_equal(len(address_groups), 2) # the addresses aren't linked now, but will be after we send to the # common address linked_addresses = set() for address_group in address_groups: assert_equal(len(address_group), 1) assert_equal(len(address_group[0]), 3) assert_equal(address_group[0][1], 50000000) - assert_equal(address_group[0][2], 'coinbase') + assert_equal(address_group[0][2], "coinbase") linked_addresses.add(address_group[0][0]) # send 50 from each address to a third address not in this wallet common_address = "msf4WtN1YQKXvNtvdFYt9JBnUD2FB41kjr" node.sendmany( amounts={common_address: 100000000}, subtractfeefrom=[common_address], minconf=1, ) # there should be 1 address group, with the previously # unlinked addresses now linked (they both have 0 balance) address_groups = node.listaddressgroupings() assert_equal(len(address_groups), 1) assert_equal(len(address_groups[0]), 2) assert_equal({a[0] for a in address_groups[0]}, linked_addresses) assert_equal([a[1] for a in address_groups[0]], [0, 0]) self.generate(node, 1) # we want to reset so that the "" label has what's expected. # otherwise we're off by exactly the fee amount as that's mined # and matures in the next 100 blocks amount_to_send = 1000000 # Create labels and make sure subsequent label API calls # recognize the label/address associations. - labels = [Label(name) - for name in ("a", "b", "c", "d", "e")] + labels = [Label(name) for name in ("a", "b", "c", "d", "e")] for label in labels: address = node.getnewaddress(label.name) label.add_receive_address(address) label.verify(node) # Check all labels are returned by listlabels. - assert_equal(node.listlabels(), sorted( - ['coinbase'] + [label.name for label in labels])) + assert_equal( + node.listlabels(), sorted(["coinbase"] + [label.name for label in labels]) + ) # Send a transaction to each label. for label in labels: node.sendtoaddress(label.addresses[0], amount_to_send) label.verify(node) # Check the amounts received. self.generate(node, 1) for label in labels: - assert_equal( - node.getreceivedbyaddress(label.addresses[0]), amount_to_send) + assert_equal(node.getreceivedbyaddress(label.addresses[0]), amount_to_send) assert_equal(node.getreceivedbylabel(label.name), amount_to_send) for i, label in enumerate(labels): to_label = labels[(i + 1) % len(labels)] node.sendtoaddress(to_label.addresses[0], amount_to_send) self.generate(node, 1) for label in labels: address = node.getnewaddress(label.name) label.add_receive_address(address) label.verify(node) assert_equal(node.getreceivedbylabel(label.name), 2000000) label.verify(node) self.generate(node, 101) # Check that setlabel can assign a label to a new unused address. for label in labels: address = node.getnewaddress() node.setlabel(address, label.name) label.add_address(address) label.verify(node) - assert_raises_rpc_error(-11, - "No addresses with label", - node.getaddressesbylabel, - "") + assert_raises_rpc_error( + -11, "No addresses with label", node.getaddressesbylabel, "" + ) # Check that addmultisigaddress can assign labels. if not self.options.descriptors: for label in labels: addresses = [] for _ in range(10): addresses.append(node.getnewaddress()) - multisig_address = node.addmultisigaddress( - 5, addresses, label.name)['address'] + multisig_address = node.addmultisigaddress(5, addresses, label.name)[ + "address" + ] label.add_address(multisig_address) label.purpose[multisig_address] = "send" label.verify(node) self.generate(node, 101) # Check that setlabel can change the label of an address from a # different label. change_label(node, labels[0].addresses[0], labels[0], labels[1]) # Check that setlabel can set the label of an address already # in the label. This is a no-op. change_label(node, labels[2].addresses[0], labels[2], labels[2]) class Label: def __init__(self, name): # Label name self.name = name # Current receiving address associated with this label. self.receive_address = None # List of all addresses assigned with this label self.addresses = [] # Map of address to address purpose self.purpose = defaultdict(lambda: "receive") def add_address(self, address): assert_equal(address not in self.addresses, True) self.addresses.append(address) def add_receive_address(self, address): self.add_address(address) def verify(self, node): if self.receive_address is not None: assert self.receive_address in self.addresses for address in self.addresses: test_address(node, address, labels=[self.name]) assert self.name in node.listlabels() assert_equal( node.getaddressesbylabel(self.name), - {address: {"purpose": self.purpose[address]} for address in self.addresses}) + {address: {"purpose": self.purpose[address]} for address in self.addresses}, + ) def change_label(node, address, old_label, new_label): assert_equal(address in old_label.addresses, True) node.setlabel(address, new_label.name) old_label.addresses.remove(address) new_label.add_address(address) old_label.verify(node) new_label.verify(node) -if __name__ == '__main__': +if __name__ == "__main__": WalletLabelsTest().main() diff --git a/test/functional/wallet_listreceivedby.py b/test/functional/wallet_listreceivedby.py index cb70c7cec..deadd4a41 100755 --- a/test/functional/wallet_listreceivedby.py +++ b/test/functional/wallet_listreceivedby.py @@ -1,191 +1,260 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the listreceivedbyaddress RPC.""" from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_array_result, assert_equal, assert_raises_rpc_error, ) from test_framework.wallet_util import test_address class ReceivedByTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 # whitelist peers to speed up tx relay / mempool sync self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() self.skip_if_no_cli() def run_test(self): # Generate block to get out of IBD self.generate(self.nodes[0], 1) # save the number of coinbase reward addresses so far num_cb_reward_addresses = len( self.nodes[1].listreceivedbyaddress( - minconf=0, - include_empty=True, - include_watchonly=True)) + minconf=0, include_empty=True, include_watchonly=True + ) + ) self.log.info("listreceivedbyaddress Test") # Send from node 0 to 1 addr = self.nodes[1].getnewaddress() txid = self.nodes[0].sendtoaddress(addr, 100000) self.sync_all() # Check not listed in listreceivedbyaddress because has 0 confirmations - assert_array_result(self.nodes[1].listreceivedbyaddress(), - {"address": addr}, - {}, - True) + assert_array_result( + self.nodes[1].listreceivedbyaddress(), {"address": addr}, {}, True + ) # Bury Tx under 10 block so it will be returned by # listreceivedbyaddress self.generate(self.nodes[1], 10) - assert_array_result(self.nodes[1].listreceivedbyaddress(), - {"address": addr}, - {"address": addr, "label": "", "amount": Decimal("100000"), "confirmations": 10, "txids": [txid, ]}) + assert_array_result( + self.nodes[1].listreceivedbyaddress(), + {"address": addr}, + { + "address": addr, + "label": "", + "amount": Decimal("100000"), + "confirmations": 10, + "txids": [ + txid, + ], + }, + ) # With min confidence < 10 - assert_array_result(self.nodes[1].listreceivedbyaddress(5), - {"address": addr}, - {"address": addr, "label": "", "amount": Decimal("100000"), "confirmations": 10, "txids": [txid, ]}) + assert_array_result( + self.nodes[1].listreceivedbyaddress(5), + {"address": addr}, + { + "address": addr, + "label": "", + "amount": Decimal("100000"), + "confirmations": 10, + "txids": [ + txid, + ], + }, + ) # With min confidence > 10, should not find Tx - assert_array_result(self.nodes[1].listreceivedbyaddress(11), { - "address": addr}, {}, True) + assert_array_result( + self.nodes[1].listreceivedbyaddress(11), {"address": addr}, {}, True + ) # Empty Tx empty_addr = self.nodes[1].getnewaddress() - assert_array_result(self.nodes[1].listreceivedbyaddress(0, True), - {"address": empty_addr}, - {"address": empty_addr, "label": "", "amount": 0, "confirmations": 0, "txids": []}) + assert_array_result( + self.nodes[1].listreceivedbyaddress(0, True), + {"address": empty_addr}, + { + "address": empty_addr, + "label": "", + "amount": 0, + "confirmations": 0, + "txids": [], + }, + ) # Test Address filtering # Only on addr - expected = {"address": addr, "label": "", "amount": Decimal( - "100000"), "confirmations": 10, "txids": [txid, ]} + expected = { + "address": addr, + "label": "", + "amount": Decimal("100000"), + "confirmations": 10, + "txids": [ + txid, + ], + } res = self.nodes[1].listreceivedbyaddress( - minconf=0, include_empty=True, include_watchonly=True, address_filter=addr) + minconf=0, include_empty=True, include_watchonly=True, address_filter=addr + ) assert_array_result(res, {"address": addr}, expected) assert_equal(len(res), 1) # Test for regression on CLI calls with address string (#14173) cli_res = self.nodes[1].cli.listreceivedbyaddress(0, True, True, addr) assert_array_result(cli_res, {"address": addr}, expected) assert_equal(len(cli_res), 1) # Error on invalid address - assert_raises_rpc_error(-4, "address_filter parameter was invalid", - self.nodes[1].listreceivedbyaddress, minconf=0, include_empty=True, include_watchonly=True, address_filter="bamboozling") + assert_raises_rpc_error( + -4, + "address_filter parameter was invalid", + self.nodes[1].listreceivedbyaddress, + minconf=0, + include_empty=True, + include_watchonly=True, + address_filter="bamboozling", + ) # Another address receive money res = self.nodes[1].listreceivedbyaddress(0, True, True) # Right now 2 entries assert_equal(len(res), 2 + num_cb_reward_addresses) other_addr = self.nodes[1].getnewaddress() txid2 = self.nodes[0].sendtoaddress(other_addr, 100000) self.generate(self.nodes[0], 1) # Same test as above should still pass - expected = {"address": addr, "label": "", "amount": Decimal( - "100000"), "confirmations": 11, "txids": [txid, ]} + expected = { + "address": addr, + "label": "", + "amount": Decimal("100000"), + "confirmations": 11, + "txids": [ + txid, + ], + } res = self.nodes[1].listreceivedbyaddress(0, True, True, addr) assert_array_result(res, {"address": addr}, expected) assert_equal(len(res), 1) # Same test as above but with other_addr should still pass - expected = {"address": other_addr, "label": "", "amount": Decimal( - "100000"), "confirmations": 1, "txids": [txid2, ]} + expected = { + "address": other_addr, + "label": "", + "amount": Decimal("100000"), + "confirmations": 1, + "txids": [ + txid2, + ], + } res = self.nodes[1].listreceivedbyaddress(0, True, True, other_addr) assert_array_result(res, {"address": other_addr}, expected) assert_equal(len(res), 1) # Should be two entries though without filter res = self.nodes[1].listreceivedbyaddress(0, True, True) # Became 3 entries assert_equal(len(res), 3 + num_cb_reward_addresses) # Not on random addr # note on node[0]! just a random addr other_addr = self.nodes[0].getnewaddress() res = self.nodes[1].listreceivedbyaddress(0, True, True, other_addr) assert_equal(len(res), 0) self.log.info("getreceivedbyaddress Test") # Send from node 0 to 1 addr = self.nodes[1].getnewaddress() txid = self.nodes[0].sendtoaddress(addr, 100000) self.sync_all() # Check balance is 0 because of 0 confirmations balance = self.nodes[1].getreceivedbyaddress(addr) assert_equal(balance, Decimal("0.0")) # Check balance is 0.1 balance = self.nodes[1].getreceivedbyaddress(addr, 0) assert_equal(balance, Decimal("100000")) # Bury Tx under 10 block so it will be returned by the default # getreceivedbyaddress self.generate(self.nodes[1], 10) balance = self.nodes[1].getreceivedbyaddress(addr) assert_equal(balance, Decimal("100000")) # Trying to getreceivedby for an address the wallet doesn't own should # return an error - assert_raises_rpc_error(-4, "Address not found in wallet", - self.nodes[0].getreceivedbyaddress, addr) + assert_raises_rpc_error( + -4, "Address not found in wallet", self.nodes[0].getreceivedbyaddress, addr + ) self.log.info("listreceivedbylabel + getreceivedbylabel Test") # set pre-state - label = '' + label = "" address = self.nodes[1].getnewaddress() test_address(self.nodes[1], address, labels=[label]) received_by_label_json = [ - r for r in self.nodes[1].listreceivedbylabel() if r["label"] == label][0] + r for r in self.nodes[1].listreceivedbylabel() if r["label"] == label + ][0] balance_by_label = self.nodes[1].getreceivedbylabel(label) txid = self.nodes[0].sendtoaddress(addr, 100000) self.sync_all() # listreceivedbylabel should return received_by_label_json because of 0 # confirmations - assert_array_result(self.nodes[1].listreceivedbylabel(), - {"label": label}, - received_by_label_json) + assert_array_result( + self.nodes[1].listreceivedbylabel(), + {"label": label}, + received_by_label_json, + ) # getreceivedbyaddress should return same balance because of 0 # confirmations balance = self.nodes[1].getreceivedbylabel(label) assert_equal(balance, balance_by_label) self.generate(self.nodes[1], 10) # listreceivedbylabel should return updated received list - assert_array_result(self.nodes[1].listreceivedbylabel(), - {"label": label}, - {"label": received_by_label_json["label"], "amount": (received_by_label_json["amount"] + Decimal("100000"))}) + assert_array_result( + self.nodes[1].listreceivedbylabel(), + {"label": label}, + { + "label": received_by_label_json["label"], + "amount": received_by_label_json["amount"] + Decimal("100000"), + }, + ) # getreceivedbylabel should return updated receive total balance = self.nodes[1].getreceivedbylabel(label) assert_equal(balance, balance_by_label + Decimal("100000")) # Create a new label named "mynewlabel" that has a 0 balance address = self.nodes[1].getnewaddress() self.nodes[1].setlabel(address, "mynewlabel") - received_by_label_json = [r for r in self.nodes[1].listreceivedbylabel( - 0, True) if r["label"] == "mynewlabel"][0] + received_by_label_json = [ + r + for r in self.nodes[1].listreceivedbylabel(0, True) + if r["label"] == "mynewlabel" + ][0] # Test includeempty of listreceivedbylabel assert_equal(received_by_label_json["amount"], Decimal("0.0")) # Test getreceivedbylabel for 0 amount labels balance = self.nodes[1].getreceivedbylabel("mynewlabel") assert_equal(balance, Decimal("0.0")) -if __name__ == '__main__': +if __name__ == "__main__": ReceivedByTest().main() diff --git a/test/functional/wallet_listsinceblock.py b/test/functional/wallet_listsinceblock.py index bca5321bb..53014999f 100755 --- a/test/functional/wallet_listsinceblock.py +++ b/test/functional/wallet_listsinceblock.py @@ -1,348 +1,383 @@ #!/usr/bin/env python3 # Copyright (c) 2017-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the listsinceblock RPC.""" from test_framework import cashaddr from test_framework.key import ECKey from test_framework.script import hash160 from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_array_result, assert_equal, assert_raises_rpc_error, ) from test_framework.wallet_util import bytes_to_wif class ListSinceBlockTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 4 self.setup_clean_chain = True self.extra_args = [["-noparkdeepreorg"], ["-noparkdeepreorg"], [], []] # whitelist peers to speed up tx relay / mempool sync for args in self.extra_args: args.append("-whitelist=noban@127.0.0.1") def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): # All nodes are in IBD from genesis, so they'll need the miner (node2) to be an outbound connection, or have # only one connection. (See fPreferredDownload in net_processing) self.connect_nodes(1, 2) self.generate(self.nodes[2], 101) self.test_no_blockhash() self.test_invalid_blockhash() self.test_reorg() self.test_double_spend() self.test_double_send() self.test_targetconfirmations() def test_no_blockhash(self): self.log.info("Test no blockhash") - txid = self.nodes[2].sendtoaddress( - self.nodes[0].getnewaddress(), 1000000) - blockhash, = self.generate(self.nodes[2], 1) - blockheight = self.nodes[2].getblockheader(blockhash)['height'] + txid = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 1000000) + (blockhash,) = self.generate(self.nodes[2], 1) + blockheight = self.nodes[2].getblockheader(blockhash)["height"] txs = self.nodes[0].listtransactions() - assert_array_result(txs, {"txid": txid}, { - "category": "receive", - "amount": 1000000, - "blockhash": blockhash, - "blockheight": blockheight, - "confirmations": 1, - }) + assert_array_result( + txs, + {"txid": txid}, + { + "category": "receive", + "amount": 1000000, + "blockhash": blockhash, + "blockheight": blockheight, + "confirmations": 1, + }, + ) assert_equal( self.nodes[0].listsinceblock(), - {"lastblock": blockhash, - "removed": [], - "transactions": txs}) + {"lastblock": blockhash, "removed": [], "transactions": txs}, + ) assert_equal( self.nodes[0].listsinceblock(""), - {"lastblock": blockhash, - "removed": [], - "transactions": txs}) + {"lastblock": blockhash, "removed": [], "transactions": txs}, + ) def test_invalid_blockhash(self): self.log.info("Test invalid blockhash") - assert_raises_rpc_error(-5, "Block not found", self.nodes[0].listsinceblock, - "42759cde25462784395a337460bde75f58e73d3f08bd31fdc3507cbac856a2c4") - assert_raises_rpc_error(-5, "Block not found", self.nodes[0].listsinceblock, - "0000000000000000000000000000000000000000000000000000000000000000") - assert_raises_rpc_error(-8, "blockhash must be of length 64 (not 11, for 'invalid-hex')", self.nodes[0].listsinceblock, - "invalid-hex") - assert_raises_rpc_error(-8, "blockhash must be hexadecimal string (not 'Z000000000000000000000000000000000000000000000000000000000000000')", self.nodes[0].listsinceblock, - "Z000000000000000000000000000000000000000000000000000000000000000") + assert_raises_rpc_error( + -5, + "Block not found", + self.nodes[0].listsinceblock, + "42759cde25462784395a337460bde75f58e73d3f08bd31fdc3507cbac856a2c4", + ) + assert_raises_rpc_error( + -5, + "Block not found", + self.nodes[0].listsinceblock, + "0000000000000000000000000000000000000000000000000000000000000000", + ) + assert_raises_rpc_error( + -8, + "blockhash must be of length 64 (not 11, for 'invalid-hex')", + self.nodes[0].listsinceblock, + "invalid-hex", + ) + assert_raises_rpc_error( + -8, + ( + "blockhash must be hexadecimal string (not" + " 'Z000000000000000000000000000000000000000000000000000000000000000')" + ), + self.nodes[0].listsinceblock, + "Z000000000000000000000000000000000000000000000000000000000000000", + ) def test_targetconfirmations(self): - ''' + """ This tests when the value of target_confirmations exceeds the number of blocks in the main chain. In this case, the genesis block hash should be given for the `lastblock` property. If target_confirmations is < 1, then a -8 invalid parameter error is thrown. - ''' + """ self.log.info("Test target_confirmations") - blockhash, = self.generate(self.nodes[2], 1) - blockheight = self.nodes[2].getblockheader(blockhash)['height'] + (blockhash,) = self.generate(self.nodes[2], 1) + blockheight = self.nodes[2].getblockheader(blockhash)["height"] assert_equal( self.nodes[0].getblockhash(0), - self.nodes[0].listsinceblock(blockhash, blockheight + 1)['lastblock']) + self.nodes[0].listsinceblock(blockhash, blockheight + 1)["lastblock"], + ) assert_equal( self.nodes[0].getblockhash(0), - self.nodes[0].listsinceblock(blockhash, blockheight + 1000)['lastblock']) - assert_raises_rpc_error(-8, "Invalid parameter", - self.nodes[0].listsinceblock, blockhash, 0) + self.nodes[0].listsinceblock(blockhash, blockheight + 1000)["lastblock"], + ) + assert_raises_rpc_error( + -8, "Invalid parameter", self.nodes[0].listsinceblock, blockhash, 0 + ) def test_reorg(self): - ''' + """ `listsinceblock` did not behave correctly when handed a block that was no longer in the main chain: ab0 / \ aa1 [tx0] bb1 | | aa2 bb2 | | aa3 bb3 | bb4 Consider a client that has only seen block `aa3` above. It asks the node to `listsinceblock aa3`. But at some point prior the main chain switched to the bb chain. Previously: listsinceblock would find height=4 for block aa3 and compare this to height=5 for the tip of the chain (bb4). It would then return results restricted to bb3-bb4. Now: listsinceblock finds the fork at ab0 and returns results in the range bb1-bb4. This test only checks that [tx0] is present. - ''' + """ self.log.info("Test reorg") # Split network into two self.split_network() # send to nodes[0] from nodes[2] - senttx = self.nodes[2].sendtoaddress( - self.nodes[0].getnewaddress(), 1000000) + senttx = self.nodes[2].sendtoaddress(self.nodes[0].getnewaddress(), 1000000) # generate on both sides nodes1_last_blockhash = self.generate( - self.nodes[1], 6, sync_fun=lambda: self.sync_all(self.nodes[:2]))[-1] + self.nodes[1], 6, sync_fun=lambda: self.sync_all(self.nodes[:2]) + )[-1] nodes2_first_blockhash = self.generate( - self.nodes[2], 7, sync_fun=lambda: self.sync_all(self.nodes[2:]))[0] - self.log.debug( - f"nodes[1] last blockhash = {nodes1_last_blockhash}") - self.log.debug( - f"nodes[2] first blockhash = {nodes2_first_blockhash}") + self.nodes[2], 7, sync_fun=lambda: self.sync_all(self.nodes[2:]) + )[0] + self.log.debug(f"nodes[1] last blockhash = {nodes1_last_blockhash}") + self.log.debug(f"nodes[2] first blockhash = {nodes2_first_blockhash}") self.join_network() # listsinceblock(nodes1_last_blockhash) should now include tx as seen from nodes[0] # and return the block height which listsinceblock now exposes since # rABC6098a1cb2b25. - transactions = self.nodes[0].listsinceblock( - nodes1_last_blockhash)['transactions'] - found = next(tx for tx in transactions if tx['txid'] == senttx) + transactions = self.nodes[0].listsinceblock(nodes1_last_blockhash)[ + "transactions" + ] + found = next(tx for tx in transactions if tx["txid"] == senttx) assert_equal( - found['blockheight'], - self.nodes[0].getblockheader(nodes2_first_blockhash)['height']) + found["blockheight"], + self.nodes[0].getblockheader(nodes2_first_blockhash)["height"], + ) def test_double_spend(self): - ''' + """ This tests the case where the same UTXO is spent twice on two separate blocks as part of a reorg. ab0 / \ aa1 [tx1] bb1 [tx2] | | aa2 bb2 | | aa3 bb3 | bb4 Problematic case: 1. User 1 receives XEC in tx1 from utxo1 in block aa1. 2. User 2 receives XEC in tx2 from utxo1 (same) in block bb1 3. User 1 sees 2 confirmations at block aa3. 4. Reorg into bb chain. 5. User 1 asks `listsinceblock aa3` and does not see that tx1 is now invalidated. Currently the solution to this is to detect that a reorg'd block is asked for in listsinceblock, and to iterate back over existing blocks up until the fork point, and to include all transactions that relate to the node wallet. - ''' + """ self.log.info("Test double spend") self.sync_all() # share utxo between nodes[1] and nodes[2] eckey = ECKey() eckey.generate() privkey = bytes_to_wif(eckey.get_bytes()) - address = cashaddr.encode_full("ecregtest", cashaddr.PUBKEY_TYPE, - hash160(eckey.get_pubkey().get_bytes())) + address = cashaddr.encode_full( + "ecregtest", cashaddr.PUBKEY_TYPE, hash160(eckey.get_pubkey().get_bytes()) + ) self.nodes[2].sendtoaddress(address, 10_000_000) self.generate(self.nodes[2], 6) self.nodes[2].importprivkey(privkey) utxos = self.nodes[2].listunspent() utxo = [u for u in utxos if u["address"] == address][0] self.nodes[1].importprivkey(privkey) # Split network into two self.split_network() # send from nodes[1] using utxo to nodes[0] change = f"{float(utxo['amount']) - 1000300.0:.2f}" recipient_dict = { self.nodes[0].getnewaddress(): 1000000, self.nodes[1].getnewaddress(): change, } - utxo_dicts = [{ - 'txid': utxo['txid'], - 'vout': utxo['vout'], - }] + utxo_dicts = [ + { + "txid": utxo["txid"], + "vout": utxo["vout"], + } + ] txid1 = self.nodes[1].sendrawtransaction( self.nodes[1].signrawtransactionwithwallet( - self.nodes[1].createrawtransaction(utxo_dicts, recipient_dict))['hex']) + self.nodes[1].createrawtransaction(utxo_dicts, recipient_dict) + )["hex"] + ) # send from nodes[2] using utxo to nodes[3] recipient_dict2 = { self.nodes[3].getnewaddress(): 1000000, self.nodes[2].getnewaddress(): change, } self.nodes[2].sendrawtransaction( self.nodes[2].signrawtransactionwithwallet( - self.nodes[2].createrawtransaction(utxo_dicts, recipient_dict2))['hex']) + self.nodes[2].createrawtransaction(utxo_dicts, recipient_dict2) + )["hex"] + ) # generate on both sides lastblockhash = self.generate(self.nodes[1], 3, sync_fun=self.no_op)[2] self.generate(self.nodes[2], 4, sync_fun=self.no_op) self.join_network() self.sync_all() # gettransaction should work for txid1 - assert self.nodes[0].gettransaction( - txid1)['txid'] == txid1, "gettransaction failed to find txid1" + assert ( + self.nodes[0].gettransaction(txid1)["txid"] == txid1 + ), "gettransaction failed to find txid1" # listsinceblock(lastblockhash) should now include txid1, as seen from # nodes[0] lsbres = self.nodes[0].listsinceblock(lastblockhash) - assert any(tx['txid'] == txid1 for tx in lsbres['removed']) + assert any(tx["txid"] == txid1 for tx in lsbres["removed"]) # but it should not include 'removed' if include_removed=false lsbres2 = self.nodes[0].listsinceblock( - blockhash=lastblockhash, include_removed=False) - assert 'removed' not in lsbres2 + blockhash=lastblockhash, include_removed=False + ) + assert "removed" not in lsbres2 def test_double_send(self): - ''' + """ This tests the case where the same transaction is submitted twice on two separate blocks as part of a reorg. The former will vanish and the latter will appear as the true transaction (with confirmations dropping as a result). ab0 / \ aa1 [tx1] bb1 | | aa2 bb2 | | aa3 bb3 [tx1] | bb4 Asserted: 1. tx1 is listed in listsinceblock. 2. It is included in 'removed' as it was removed, even though it is now present in a different block. 3. It is listed with a confirmation count of 2 (bb3, bb4), not 3 (aa1, aa2, aa3). - ''' + """ self.log.info("Test double send") self.sync_all() # Split network into two self.split_network() # create and sign a transaction utxos = self.nodes[2].listunspent() utxo = utxos[0] change = f"{float(utxo['amount']) - 1000300.0:.2f}" recipient_dict = { self.nodes[0].getnewaddress(): 1000000, self.nodes[2].getnewaddress(): change, } - utxo_dicts = [{ - 'txid': utxo['txid'], - 'vout': utxo['vout'], - }] + utxo_dicts = [ + { + "txid": utxo["txid"], + "vout": utxo["vout"], + } + ] signedtxres = self.nodes[2].signrawtransactionwithwallet( - self.nodes[2].createrawtransaction(utxo_dicts, recipient_dict)) - assert signedtxres['complete'] + self.nodes[2].createrawtransaction(utxo_dicts, recipient_dict) + ) + assert signedtxres["complete"] - signedtx = signedtxres['hex'] + signedtx = signedtxres["hex"] # send from nodes[1]; this will end up in aa1 txid1 = self.nodes[1].sendrawtransaction(signedtx) # generate bb1-bb2 on right side self.generate(self.nodes[2], 2, sync_fun=self.no_op) # send from nodes[2]; this will end up in bb3 txid2 = self.nodes[2].sendrawtransaction(signedtx) assert_equal(txid1, txid2) # generate on both sides lastblockhash = self.generate(self.nodes[1], 3, sync_fun=self.no_op)[2] self.generate(self.nodes[2], 2, sync_fun=self.no_op) self.join_network() self.sync_all() # gettransaction should work for txid1 tx1 = self.nodes[0].gettransaction(txid1) assert_equal( - tx1['blockheight'], - self.nodes[0].getblockheader( - tx1['blockhash'])['height']) + tx1["blockheight"], self.nodes[0].getblockheader(tx1["blockhash"])["height"] + ) # listsinceblock(lastblockhash) should now include txid1 in transactions # as well as in removed lsbres = self.nodes[0].listsinceblock(lastblockhash) - assert any(tx['txid'] == txid1 for tx in lsbres['transactions']) - assert any(tx['txid'] == txid1 for tx in lsbres['removed']) + assert any(tx["txid"] == txid1 for tx in lsbres["transactions"]) + assert any(tx["txid"] == txid1 for tx in lsbres["removed"]) # find transaction and ensure confirmations is valid - for tx in lsbres['transactions']: - if tx['txid'] == txid1: - assert_equal(tx['confirmations'], 2) + for tx in lsbres["transactions"]: + if tx["txid"] == txid1: + assert_equal(tx["confirmations"], 2) # the same check for the removed array; confirmations should STILL be 2 - for tx in lsbres['removed']: - if tx['txid'] == txid1: - assert_equal(tx['confirmations'], 2) + for tx in lsbres["removed"]: + if tx["txid"] == txid1: + assert_equal(tx["confirmations"], 2) -if __name__ == '__main__': +if __name__ == "__main__": ListSinceBlockTest().main() diff --git a/test/functional/wallet_listtransactions.py b/test/functional/wallet_listtransactions.py index 774412259..d3a5ad33d 100755 --- a/test/functional/wallet_listtransactions.py +++ b/test/functional/wallet_listtransactions.py @@ -1,119 +1,174 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the listtransactions API.""" from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_array_result, assert_equal class ListTransactionsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 # This test isn't testing txn relay/timing, so set whitelist on the # peers for instant txn relay. This speeds up the test run time 2-3x. self.extra_args = [["-whitelist=noban@127.0.0.1"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): # Leave IBD self.generate(self.nodes[0], 1) # Simple send, 0 to 1: - txid = self.nodes[0].sendtoaddress( - self.nodes[1].getnewaddress(), 100000) + txid = self.nodes[0].sendtoaddress(self.nodes[1].getnewaddress(), 100000) self.sync_all() - assert_array_result(self.nodes[0].listtransactions(), - {"txid": txid}, - {"category": "send", "amount": Decimal("-100000"), "confirmations": 0}) - assert_array_result(self.nodes[1].listtransactions(), - {"txid": txid}, - {"category": "receive", "amount": Decimal("100000"), "confirmations": 0}) + assert_array_result( + self.nodes[0].listtransactions(), + {"txid": txid}, + {"category": "send", "amount": Decimal("-100000"), "confirmations": 0}, + ) + assert_array_result( + self.nodes[1].listtransactions(), + {"txid": txid}, + {"category": "receive", "amount": Decimal("100000"), "confirmations": 0}, + ) # mine a block, confirmations should change: blockhash = self.generate(self.nodes[0], 1)[0] - blockheight = self.nodes[0].getblockheader(blockhash)['height'] - assert_array_result(self.nodes[0].listtransactions(), - {"txid": txid}, - {"category": "send", "amount": Decimal("-100000"), "confirmations": 1, "blockhash": blockhash, "blockheight": blockheight}) - assert_array_result(self.nodes[1].listtransactions(), - {"txid": txid}, - {"category": "receive", "amount": Decimal("100000"), "confirmations": 1, "blockhash": blockhash, "blockheight": blockheight}) + blockheight = self.nodes[0].getblockheader(blockhash)["height"] + assert_array_result( + self.nodes[0].listtransactions(), + {"txid": txid}, + { + "category": "send", + "amount": Decimal("-100000"), + "confirmations": 1, + "blockhash": blockhash, + "blockheight": blockheight, + }, + ) + assert_array_result( + self.nodes[1].listtransactions(), + {"txid": txid}, + { + "category": "receive", + "amount": Decimal("100000"), + "confirmations": 1, + "blockhash": blockhash, + "blockheight": blockheight, + }, + ) # send-to-self: - txid = self.nodes[0].sendtoaddress( - self.nodes[0].getnewaddress(), 200000) - assert_array_result(self.nodes[0].listtransactions(), - {"txid": txid, "category": "send"}, - {"amount": Decimal("-200000")}) - assert_array_result(self.nodes[0].listtransactions(), - {"txid": txid, "category": "receive"}, - {"amount": Decimal("200000")}) + txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), 200000) + assert_array_result( + self.nodes[0].listtransactions(), + {"txid": txid, "category": "send"}, + {"amount": Decimal("-200000")}, + ) + assert_array_result( + self.nodes[0].listtransactions(), + {"txid": txid, "category": "receive"}, + {"amount": Decimal("200000")}, + ) # sendmany from node1: twice to self, twice to node2: - send_to = {self.nodes[0].getnewaddress(): 110000, - self.nodes[1].getnewaddress(): 220000, - self.nodes[0].getnewaddress(): 330000, - self.nodes[1].getnewaddress(): 440000} + send_to = { + self.nodes[0].getnewaddress(): 110000, + self.nodes[1].getnewaddress(): 220000, + self.nodes[0].getnewaddress(): 330000, + self.nodes[1].getnewaddress(): 440000, + } txid = self.nodes[1].sendmany("", send_to) self.sync_all() - assert_array_result(self.nodes[1].listtransactions(), - {"category": "send", "amount": Decimal("-110000")}, - {"txid": txid}) - assert_array_result(self.nodes[0].listtransactions(), - {"category": "receive", - "amount": Decimal("110000")}, - {"txid": txid}) - assert_array_result(self.nodes[1].listtransactions(), - {"category": "send", "amount": Decimal("-220000")}, - {"txid": txid}) - assert_array_result(self.nodes[1].listtransactions(), - {"category": "receive", - "amount": Decimal("220000")}, - {"txid": txid}) - assert_array_result(self.nodes[1].listtransactions(), - {"category": "send", "amount": Decimal("-330000")}, - {"txid": txid}) - assert_array_result(self.nodes[0].listtransactions(), - {"category": "receive", - "amount": Decimal("330000")}, - {"txid": txid}) - assert_array_result(self.nodes[1].listtransactions(), - {"category": "send", "amount": Decimal("-440000")}, - {"txid": txid}) - assert_array_result(self.nodes[1].listtransactions(), - {"category": "receive", - "amount": Decimal("440000")}, - {"txid": txid}) + assert_array_result( + self.nodes[1].listtransactions(), + {"category": "send", "amount": Decimal("-110000")}, + {"txid": txid}, + ) + assert_array_result( + self.nodes[0].listtransactions(), + {"category": "receive", "amount": Decimal("110000")}, + {"txid": txid}, + ) + assert_array_result( + self.nodes[1].listtransactions(), + {"category": "send", "amount": Decimal("-220000")}, + {"txid": txid}, + ) + assert_array_result( + self.nodes[1].listtransactions(), + {"category": "receive", "amount": Decimal("220000")}, + {"txid": txid}, + ) + assert_array_result( + self.nodes[1].listtransactions(), + {"category": "send", "amount": Decimal("-330000")}, + {"txid": txid}, + ) + assert_array_result( + self.nodes[0].listtransactions(), + {"category": "receive", "amount": Decimal("330000")}, + {"txid": txid}, + ) + assert_array_result( + self.nodes[1].listtransactions(), + {"category": "send", "amount": Decimal("-440000")}, + {"txid": txid}, + ) + assert_array_result( + self.nodes[1].listtransactions(), + {"category": "receive", "amount": Decimal("440000")}, + {"txid": txid}, + ) if not self.options.descriptors: # include_watchonly is a legacy wallet feature, so don't test it # for descriptor wallets - pubkey = self.nodes[1].getaddressinfo( - self.nodes[1].getnewaddress())['pubkey'] + pubkey = self.nodes[1].getaddressinfo(self.nodes[1].getnewaddress())[ + "pubkey" + ] multisig = self.nodes[1].createmultisig(1, [pubkey]) self.nodes[0].importaddress( - multisig["redeemScript"], "watchonly", False, True) + multisig["redeemScript"], "watchonly", False, True + ) txid = self.nodes[1].sendtoaddress(multisig["address"], 100000) self.generate(self.nodes[1], 1) - assert_equal(len(self.nodes[0].listtransactions( - label="watchonly", include_watchonly=True)), 1) - assert_equal(len(self.nodes[0].listtransactions( - dummy="watchonly", include_watchonly=True)), 1) - assert len( - self.nodes[0].listtransactions( - label="watchonly", - count=100, - include_watchonly=False)) == 0 + assert_equal( + len( + self.nodes[0].listtransactions( + label="watchonly", include_watchonly=True + ) + ), + 1, + ) + assert_equal( + len( + self.nodes[0].listtransactions( + dummy="watchonly", include_watchonly=True + ) + ), + 1, + ) + assert ( + len( + self.nodes[0].listtransactions( + label="watchonly", count=100, include_watchonly=False + ) + ) + == 0 + ) assert_array_result( - self.nodes[0].listtransactions(label="watchonly", count=100, - include_watchonly=True), + self.nodes[0].listtransactions( + label="watchonly", count=100, include_watchonly=True + ), {"category": "receive", "amount": Decimal("100000")}, - {"txid": txid, "label": "watchonly"}) + {"txid": txid, "label": "watchonly"}, + ) -if __name__ == '__main__': +if __name__ == "__main__": ListTransactionsTest().main() diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py index 515affa5e..08220e496 100755 --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -1,487 +1,618 @@ #!/usr/bin/env python3 # Copyright (c) 2017-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test multiwallet. Verify that a bitcoind node can load multiple wallet files """ import os import shutil import sys import time from decimal import Decimal from threading import Thread from test_framework.authproxy import JSONRPCException from test_framework.test_framework import BitcoinTestFramework from test_framework.test_node import ErrorMatch from test_framework.util import assert_equal, assert_raises_rpc_error, get_rpc_proxy got_loading_error = False -def test_load_unload(node, name, timeout=60.): +def test_load_unload(node, name, timeout=60.0): global got_loading_error t0 = time.time() while time.time() - t0 < timeout and not got_loading_error: try: node.loadwallet(name) node.unloadwallet(name) except JSONRPCException as e: - if e.error['code'] == - \ - 4 and 'Wallet already being loading' in e.error['message']: + if ( + e.error["code"] == -4 + and "Wallet already being loading" in e.error["message"] + ): got_loading_error = True return # Add a small sleep to avoid CPU exhaustion in the unlikely case # the race never happens. time.sleep(0.001) class MultiWalletTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 self.rpc_timeout = 120 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def add_options(self, parser): parser.add_argument( - '--data_wallets_dir', + "--data_wallets_dir", default=os.path.join( - os.path.dirname( - os.path.realpath(__file__)), - 'data/wallets/'), - help='Test data with wallet directories (default: %(default)s)', + os.path.dirname(os.path.realpath(__file__)), "data/wallets/" + ), + help="Test data with wallet directories (default: %(default)s)", ) def run_test(self): node = self.nodes[0] - def data_dir(*p): return os.path.join(node.datadir, self.chain, *p) + def data_dir(*p): + return os.path.join(node.datadir, self.chain, *p) - def wallet_dir(*p): return data_dir('wallets', *p) + def wallet_dir(*p): + return data_dir("wallets", *p) - def wallet(name): return node.get_wallet_rpc(name) + def wallet(name): + return node.get_wallet_rpc(name) def wallet_file(name): if os.path.isdir(wallet_dir(name)): return wallet_dir(name, self.wallet_data_filename) return wallet_dir(name) - assert_equal(self.nodes[0].listwalletdir(), - {'wallets': [{'name': self.default_wallet_name}]}) + assert_equal( + self.nodes[0].listwalletdir(), + {"wallets": [{"name": self.default_wallet_name}]}, + ) # check wallet.dat is created self.stop_nodes() - assert_equal(os.path.isfile(wallet_dir(self.default_wallet_name, - self.wallet_data_filename)), - True) + assert_equal( + os.path.isfile( + wallet_dir(self.default_wallet_name, self.wallet_data_filename) + ), + True, + ) # create symlink to verify wallet directory path can be referenced # through symlink - if os.name != 'nt': - os.mkdir(wallet_dir('w7')) - os.symlink('w7', wallet_dir('w7_symlink')) + if os.name != "nt": + os.mkdir(wallet_dir("w7")) + os.symlink("w7", wallet_dir("w7_symlink")) # rename wallet.dat to make sure plain wallet file paths (as opposed to # directory paths) can be loaded - os.rename(wallet_dir(self.default_wallet_name, self.wallet_data_filename), - wallet_dir("w8")) + os.rename( + wallet_dir(self.default_wallet_name, self.wallet_data_filename), + wallet_dir("w8"), + ) # create another dummy wallet for use in testing backups later - self.start_node( - 0, ["-nowallet", f"-wallet={self.default_wallet_name}"]) + self.start_node(0, ["-nowallet", f"-wallet={self.default_wallet_name}"]) self.stop_nodes() - empty_wallet = os.path.join(self.options.tmpdir, 'empty.dat') - os.rename(wallet_dir(self.default_wallet_name, self.wallet_data_filename), - empty_wallet) + empty_wallet = os.path.join(self.options.tmpdir, "empty.dat") + os.rename( + wallet_dir(self.default_wallet_name, self.wallet_data_filename), + empty_wallet, + ) # restart node with a mix of wallet names: # w1, w2, w3 - to verify new wallets created when non-existing paths specified # w - to verify wallet name matching works when one wallet path is prefix of another # sub/w5 - to verify relative wallet path is created correctly # extern/w6 - to verify absolute wallet path is created correctly # w7_symlink - to verify symlinked wallet path is initialized correctly # w8 - to verify existing wallet file is loaded correctly # '' - to verify default wallet file is created correctly - wallet_names = ['w1', 'w2', 'w3', 'w', 'sub/w5', - os.path.join(self.options.tmpdir, 'extern/w6'), - 'w7_symlink', 'w8', self.default_wallet_name] - if os.name == 'nt': - wallet_names.remove('w7_symlink') - extra_args = ['-nowallet'] + \ - [f'-wallet={n}' for n in wallet_names] + wallet_names = [ + "w1", + "w2", + "w3", + "w", + "sub/w5", + os.path.join(self.options.tmpdir, "extern/w6"), + "w7_symlink", + "w8", + self.default_wallet_name, + ] + if os.name == "nt": + wallet_names.remove("w7_symlink") + extra_args = ["-nowallet"] + [f"-wallet={n}" for n in wallet_names] self.start_node(0, extra_args) assert_equal( - sorted(w['name'] for w in self.nodes[0].listwalletdir()['wallets']), - [self.default_wallet_name, os.path.join('sub', 'w5'), 'w', 'w1', - 'w2', 'w3', 'w7', 'w7_symlink', 'w8']) + sorted(w["name"] for w in self.nodes[0].listwalletdir()["wallets"]), + [ + self.default_wallet_name, + os.path.join("sub", "w5"), + "w", + "w1", + "w2", + "w3", + "w7", + "w7_symlink", + "w8", + ], + ) assert_equal(set(node.listwallets()), set(wallet_names)) # check that all requested wallets were created self.stop_node(0) for wallet_name in wallet_names: assert_equal(os.path.isfile(wallet_file(wallet_name)), True) # should not initialize if wallet path can't be created exp_stderr = ( - "filesystem error:" if sys.platform != 'win32' - else "create_directories:" + "filesystem error:" if sys.platform != "win32" else "create_directories:" ) self.nodes[0].assert_start_raises_init_error( - ['-wallet=w8/bad'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX) + ["-wallet=w8/bad"], exp_stderr, match=ErrorMatch.PARTIAL_REGEX + ) self.nodes[0].assert_start_raises_init_error( - ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" does not exist') + ["-walletdir=wallets"], + 'Error: Specified -walletdir "wallets" does not exist', + ) self.nodes[0].assert_start_raises_init_error( - ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" is a relative path', cwd=data_dir()) + ["-walletdir=wallets"], + 'Error: Specified -walletdir "wallets" is a relative path', + cwd=data_dir(), + ) self.nodes[0].assert_start_raises_init_error( - ['-walletdir=debug.log'], 'Error: Specified -walletdir "debug.log" is not a directory', cwd=data_dir()) + ["-walletdir=debug.log"], + 'Error: Specified -walletdir "debug.log" is not a directory', + cwd=data_dir(), + ) - self.start_node(0, ['-wallet=w1', '-wallet=w1']) - self.stop_node(0, 'Warning: Ignoring duplicate -wallet w1.') + self.start_node(0, ["-wallet=w1", "-wallet=w1"]) + self.stop_node(0, "Warning: Ignoring duplicate -wallet w1.") # should not initialize if one wallet is a copy of another - shutil.copyfile(wallet_dir('w8'), wallet_dir('w8_copy')) - exp_stderr = r"BerkeleyDatabase: Can't open database w8_copy \(duplicates fileid \w+ from w8\)" + shutil.copyfile(wallet_dir("w8"), wallet_dir("w8_copy")) + exp_stderr = ( + r"BerkeleyDatabase: Can't open database w8_copy \(duplicates fileid \w+" + r" from w8\)" + ) self.nodes[0].assert_start_raises_init_error( - ['-wallet=w8', '-wallet=w8_copy'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX) + ["-wallet=w8", "-wallet=w8_copy"], + exp_stderr, + match=ErrorMatch.PARTIAL_REGEX, + ) # should not initialize if wallet file is a symlink - if os.name != 'nt': - os.symlink('w8', wallet_dir('w8_symlink')) + if os.name != "nt": + os.symlink("w8", wallet_dir("w8_symlink")) self.nodes[0].assert_start_raises_init_error( - ['-wallet=w8_symlink'], r'Error: Invalid -wallet path \'w8_symlink\'\. .*', match=ErrorMatch.FULL_REGEX) + ["-wallet=w8_symlink"], + r"Error: Invalid -wallet path \'w8_symlink\'\. .*", + match=ErrorMatch.FULL_REGEX, + ) # should not initialize if the specified walletdir does not exist self.nodes[0].assert_start_raises_init_error( - ['-walletdir=bad'], 'Error: Specified -walletdir "bad" does not exist') + ["-walletdir=bad"], 'Error: Specified -walletdir "bad" does not exist' + ) # should not initialize if the specified walletdir is not a directory - not_a_dir = wallet_dir('notadir') - open(not_a_dir, 'a', encoding="utf8").close() + not_a_dir = wallet_dir("notadir") + open(not_a_dir, "a", encoding="utf8").close() self.nodes[0].assert_start_raises_init_error( - [f"-walletdir={not_a_dir}"], f"Error: Specified -walletdir \"{not_a_dir}\" is not a directory") + [f"-walletdir={not_a_dir}"], + f'Error: Specified -walletdir "{not_a_dir}" is not a directory', + ) # if wallets/ doesn't exist, datadir should be the default wallet dir - wallet_dir2 = data_dir('walletdir') + wallet_dir2 = data_dir("walletdir") os.rename(wallet_dir(), wallet_dir2) - self.start_node(0, ['-nowallet', '-wallet=w4', '-wallet=w5']) + self.start_node(0, ["-nowallet", "-wallet=w4", "-wallet=w5"]) assert_equal(set(node.listwallets()), {"w4", "w5"}) w5 = wallet("w5") self.generatetoaddress( - node, - nblocks=1, - address=w5.getnewaddress(), - sync_fun=self.no_op) + node, nblocks=1, address=w5.getnewaddress(), sync_fun=self.no_op + ) # now if wallets/ exists again, but the rootdir is specified as the # walletdir, w4 and w5 should still be loaded os.rename(wallet_dir2, wallet_dir()) - self.restart_node(0, ['-nowallet', '-wallet=w4', '-wallet=w5', - f"-walletdir={data_dir()}"]) + self.restart_node( + 0, ["-nowallet", "-wallet=w4", "-wallet=w5", f"-walletdir={data_dir()}"] + ) assert_equal(set(node.listwallets()), {"w4", "w5"}) w5 = wallet("w5") w5_info = w5.getwalletinfo() - assert_equal(w5_info['immature_balance'], 50000000) + assert_equal(w5_info["immature_balance"], 50000000) - competing_wallet_dir = os.path.join( - self.options.tmpdir, 'competing_walletdir') + competing_wallet_dir = os.path.join(self.options.tmpdir, "competing_walletdir") os.mkdir(competing_wallet_dir) self.restart_node(0, [f"-walletdir={competing_wallet_dir}"]) - exp_stderr = r"Error: Error initializing wallet database environment \"\S+competing_walletdir\"!" + exp_stderr = ( + r"Error: Error initializing wallet database environment" + r" \"\S+competing_walletdir\"!" + ) self.nodes[1].assert_start_raises_init_error( - [f"-walletdir={competing_wallet_dir}"], exp_stderr, match=ErrorMatch.PARTIAL_REGEX) + [f"-walletdir={competing_wallet_dir}"], + exp_stderr, + match=ErrorMatch.PARTIAL_REGEX, + ) self.restart_node(0, extra_args) assert_equal( - sorted(w['name'] for w in self.nodes[0].listwalletdir()['wallets']), - [self.default_wallet_name, os.path.join('sub', 'w5'), 'w', 'w1', 'w2', - 'w3', 'w7', 'w7_symlink', 'w8', 'w8_copy']) + sorted(w["name"] for w in self.nodes[0].listwalletdir()["wallets"]), + [ + self.default_wallet_name, + os.path.join("sub", "w5"), + "w", + "w1", + "w2", + "w3", + "w7", + "w7_symlink", + "w8", + "w8_copy", + ], + ) wallets = [wallet(w) for w in wallet_names] wallet_bad = wallet("bad") # check wallet names and balances self.generatetoaddress( - node, nblocks=1, address=wallets[0].getnewaddress(), sync_fun=self.no_op) + node, nblocks=1, address=wallets[0].getnewaddress(), sync_fun=self.no_op + ) for wallet_name, wallet in zip(wallet_names, wallets): info = wallet.getwalletinfo() - assert_equal(info['immature_balance'], - 50000000 if wallet is wallets[0] else 0) - assert_equal(info['walletname'], wallet_name) + assert_equal( + info["immature_balance"], 50000000 if wallet is wallets[0] else 0 + ) + assert_equal(info["walletname"], wallet_name) # accessing invalid wallet fails - assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", - wallet_bad.getwalletinfo) + assert_raises_rpc_error( + -18, + "Requested wallet does not exist or is not loaded", + wallet_bad.getwalletinfo, + ) # accessing wallet RPC without using wallet endpoint fails - assert_raises_rpc_error(-19, "Wallet file not specified (must request wallet RPC through /wallet/ uri-path).", - node.getwalletinfo) + assert_raises_rpc_error( + -19, + ( + "Wallet file not specified (must request wallet RPC through" + " /wallet/ uri-path)." + ), + node.getwalletinfo, + ) w1, w2, w3, w4, *_ = wallets self.generatetoaddress( - node, - nblocks=101, - address=w1.getnewaddress(), - sync_fun=self.no_op) + node, nblocks=101, address=w1.getnewaddress(), sync_fun=self.no_op + ) assert_equal(w1.getbalance(), 100000000) assert_equal(w2.getbalance(), 0) assert_equal(w3.getbalance(), 0) assert_equal(w4.getbalance(), 0) w1.sendtoaddress(w2.getnewaddress(), 1000000) w1.sendtoaddress(w3.getnewaddress(), 2000000) w1.sendtoaddress(w4.getnewaddress(), 3000000) self.generatetoaddress( - node, - nblocks=1, - address=w1.getnewaddress(), - sync_fun=self.no_op) + node, nblocks=1, address=w1.getnewaddress(), sync_fun=self.no_op + ) assert_equal(w2.getbalance(), 1000000) assert_equal(w3.getbalance(), 2000000) assert_equal(w4.getbalance(), 3000000) - batch = w1.batch([w1.getblockchaininfo.get_request(), - w1.getwalletinfo.get_request()]) + batch = w1.batch( + [w1.getblockchaininfo.get_request(), w1.getwalletinfo.get_request()] + ) assert_equal(batch[0]["result"]["chain"], self.chain) assert_equal(batch[1]["result"]["walletname"], "w1") - self.log.info('Check for per-wallet settxfee call') - assert_equal(w1.getwalletinfo()['paytxfee'], 0) - assert_equal(w2.getwalletinfo()['paytxfee'], 0) + self.log.info("Check for per-wallet settxfee call") + assert_equal(w1.getwalletinfo()["paytxfee"], 0) + assert_equal(w2.getwalletinfo()["paytxfee"], 0) w2.settxfee(1000) - assert_equal(w1.getwalletinfo()['paytxfee'], 0) - assert_equal(w2.getwalletinfo()['paytxfee'], Decimal('1000.00')) + assert_equal(w1.getwalletinfo()["paytxfee"], 0) + assert_equal(w2.getwalletinfo()["paytxfee"], Decimal("1000.00")) self.log.info("Test dynamic wallet loading") - self.restart_node(0, ['-nowallet']) + self.restart_node(0, ["-nowallet"]) assert_equal(node.listwallets(), []) assert_raises_rpc_error( -18, - "No wallet is loaded. Load a wallet using loadwallet or create a new" - " one with createwallet. (Note: A default wallet is no longer " - "automatically created)", - node.getwalletinfo + ( + "No wallet is loaded. Load a wallet using loadwallet or create a new" + " one with createwallet. (Note: A default wallet is no longer " + "automatically created)" + ), + node.getwalletinfo, ) self.log.info("Load first wallet") loadwallet_name = node.loadwallet(wallet_names[0]) - assert_equal(loadwallet_name['name'], wallet_names[0]) + assert_equal(loadwallet_name["name"], wallet_names[0]) assert_equal(node.listwallets(), wallet_names[0:1]) node.getwalletinfo() w1 = node.get_wallet_rpc(wallet_names[0]) w1.getwalletinfo() self.log.info("Load second wallet") loadwallet_name = node.loadwallet(wallet_names[1]) - assert_equal(loadwallet_name['name'], wallet_names[1]) + assert_equal(loadwallet_name["name"], wallet_names[1]) assert_equal(node.listwallets(), wallet_names[0:2]) - assert_raises_rpc_error(-19, - "Wallet file not specified", node.getwalletinfo) + assert_raises_rpc_error(-19, "Wallet file not specified", node.getwalletinfo) w2 = node.get_wallet_rpc(wallet_names[1]) w2.getwalletinfo() self.log.info("Concurrent wallet loading") threads = [] for _ in range(3): - n = node.cli if self.options.usecli else get_rpc_proxy( - node.url, 1, timeout=600, coveragedir=node.coverage_dir) + n = ( + node.cli + if self.options.usecli + else get_rpc_proxy( + node.url, 1, timeout=600, coveragedir=node.coverage_dir + ) + ) t = Thread( target=test_load_unload, - args=( - n, - wallet_names[2], - 20 * self.rpc_timeout)) + args=(n, wallet_names[2], 20 * self.rpc_timeout), + ) t.start() threads.append(t) for t in threads: t.join() global got_loading_error assert_equal(got_loading_error, True) self.log.info("Load remaining wallets") for wallet_name in wallet_names[2:]: loadwallet_name = self.nodes[0].loadwallet(wallet_name) - assert_equal(loadwallet_name['name'], wallet_name) + assert_equal(loadwallet_name["name"], wallet_name) assert_equal(set(self.nodes[0].listwallets()), set(wallet_names)) # Fail to load if wallet doesn't exist - path = os.path.join(self.options.tmpdir, "node0", "regtest", - "wallets", "wallets") + path = os.path.join( + self.options.tmpdir, "node0", "regtest", "wallets", "wallets" + ) assert_raises_rpc_error( -18, - f"Wallet file verification failed. Failed to load database path '{path}'. " - "Path does not exist.", - self.nodes[0].loadwallet, 'wallets') + ( + "Wallet file verification failed. Failed to load database path" + f" '{path}'. Path does not exist." + ), + self.nodes[0].loadwallet, + "wallets", + ) # Fail to load duplicate wallets path = os.path.join( self.options.tmpdir, "node0", "regtest", "wallets", "w1", - self.wallet_data_filename) + self.wallet_data_filename, + ) assert_raises_rpc_error( -4, - "Wallet file verification failed. Refusing to load database. " - f"Data file '{path}' is already loaded.", + ( + "Wallet file verification failed. Refusing to load database. " + f"Data file '{path}' is already loaded." + ), self.nodes[0].loadwallet, - wallet_names[0]) + wallet_names[0], + ) # Fail to load duplicate wallets by different ways (directory and # filepath) path = os.path.join( self.options.tmpdir, "node0", "regtest", "wallets", - self.wallet_data_filename) + self.wallet_data_filename, + ) assert_raises_rpc_error( -4, - "Wallet file verification failed. Refusing to load database. " - f"Data file '{path}' is already loaded.", + ( + "Wallet file verification failed. Refusing to load database. " + f"Data file '{path}' is already loaded." + ), self.nodes[0].loadwallet, - self.wallet_data_filename) + self.wallet_data_filename, + ) # Fail to load if one wallet is a copy of another - assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", - self.nodes[0].loadwallet, 'w8_copy') + assert_raises_rpc_error( + -4, + "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", + self.nodes[0].loadwallet, + "w8_copy", + ) # Fail to load if one wallet is a copy of another. # Test this twice to make sure that we don't re-introduce # https://github.com/bitcoin/bitcoin/issues/14304 - assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", - self.nodes[0].loadwallet, 'w8_copy') + assert_raises_rpc_error( + -4, + "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid", + self.nodes[0].loadwallet, + "w8_copy", + ) # Fail to load if wallet file is a symlink - if os.name != 'nt': + if os.name != "nt": assert_raises_rpc_error( -4, "Wallet file verification failed. Invalid -wallet path 'w8_symlink'", self.nodes[0].loadwallet, - 'w8_symlink') + "w8_symlink", + ) # Fail to load if a directory is specified that doesn't contain a # wallet - os.mkdir(wallet_dir('empty_wallet_dir')) - path = os.path.join(self.options.tmpdir, "node0", "regtest", - "wallets", "empty_wallet_dir") + os.mkdir(wallet_dir("empty_wallet_dir")) + path = os.path.join( + self.options.tmpdir, "node0", "regtest", "wallets", "empty_wallet_dir" + ) assert_raises_rpc_error( -18, - f"Wallet file verification failed. Failed to load database path '{path}'. " - "Data is not in recognized format.", - self.nodes[0].loadwallet, 'empty_wallet_dir') + ( + "Wallet file verification failed. Failed to load database path" + f" '{path}'. Data is not in recognized format." + ), + self.nodes[0].loadwallet, + "empty_wallet_dir", + ) self.log.info("Test dynamic wallet creation.") # Fail to create a wallet if it already exists. - path = os.path.join(self.options.tmpdir, "node0", "regtest", - "wallets", "w2") + path = os.path.join(self.options.tmpdir, "node0", "regtest", "wallets", "w2") assert_raises_rpc_error( -4, f"Failed to create database path '{path}'. Database already exists.", - self.nodes[0].createwallet, 'w2') + self.nodes[0].createwallet, + "w2", + ) # Successfully create a wallet with a new name - loadwallet_name = self.nodes[0].createwallet('w9') - assert_equal(loadwallet_name['name'], 'w9') - w9 = node.get_wallet_rpc('w9') - assert_equal(w9.getwalletinfo()['walletname'], 'w9') + loadwallet_name = self.nodes[0].createwallet("w9") + assert_equal(loadwallet_name["name"], "w9") + w9 = node.get_wallet_rpc("w9") + assert_equal(w9.getwalletinfo()["walletname"], "w9") - assert 'w9' in self.nodes[0].listwallets() + assert "w9" in self.nodes[0].listwallets() # Successfully create a wallet using a full path - new_wallet_dir = os.path.join(self.options.tmpdir, 'new_walletdir') - new_wallet_name = os.path.join(new_wallet_dir, 'w10') + new_wallet_dir = os.path.join(self.options.tmpdir, "new_walletdir") + new_wallet_name = os.path.join(new_wallet_dir, "w10") loadwallet_name = self.nodes[0].createwallet(new_wallet_name) - assert_equal(loadwallet_name['name'], new_wallet_name) + assert_equal(loadwallet_name["name"], new_wallet_name) w10 = node.get_wallet_rpc(new_wallet_name) - assert_equal(w10.getwalletinfo()['walletname'], new_wallet_name) + assert_equal(w10.getwalletinfo()["walletname"], new_wallet_name) assert new_wallet_name in self.nodes[0].listwallets() self.log.info("Test dynamic wallet unloading") # Test `unloadwallet` errors - assert_raises_rpc_error(-1, "JSON value is not a string as expected", - self.nodes[0].unloadwallet) - assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", - self.nodes[0].unloadwallet, "dummy") - assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", - node.get_wallet_rpc("dummy").unloadwallet) - assert_raises_rpc_error(-8, "Cannot unload the requested wallet", - w1.unloadwallet, "w2"), + assert_raises_rpc_error( + -1, "JSON value is not a string as expected", self.nodes[0].unloadwallet + ) + assert_raises_rpc_error( + -18, + "Requested wallet does not exist or is not loaded", + self.nodes[0].unloadwallet, + "dummy", + ) + assert_raises_rpc_error( + -18, + "Requested wallet does not exist or is not loaded", + node.get_wallet_rpc("dummy").unloadwallet, + ) + assert_raises_rpc_error( + -8, "Cannot unload the requested wallet", w1.unloadwallet, "w2" + ), # Successfully unload the specified wallet name self.nodes[0].unloadwallet("w1") - assert 'w1' not in self.nodes[0].listwallets() + assert "w1" not in self.nodes[0].listwallets() # Successfully unload the wallet referenced by the request endpoint # Also ensure unload works during walletpassphrase timeout - w2.encryptwallet('test') - w2.walletpassphrase('test', 1) + w2.encryptwallet("test") + w2.walletpassphrase("test", 1) w2.unloadwallet() time.sleep(1.1) - assert 'w2' not in self.nodes[0].listwallets() + assert "w2" not in self.nodes[0].listwallets() # Successfully unload all wallets for wallet_name in self.nodes[0].listwallets(): self.nodes[0].unloadwallet(wallet_name) assert_equal(self.nodes[0].listwallets(), []) assert_raises_rpc_error( -18, - "No wallet is loaded. Load a wallet using loadwallet or create a new" - " one with createwallet. (Note: A default wallet is no longer " - "automatically created)", - self.nodes[0].getwalletinfo + ( + "No wallet is loaded. Load a wallet using loadwallet or create a new" + " one with createwallet. (Note: A default wallet is no longer " + "automatically created)" + ), + self.nodes[0].getwalletinfo, ) # Successfully load a previously unloaded wallet - self.nodes[0].loadwallet('w1') - assert_equal(self.nodes[0].listwallets(), ['w1']) - assert_equal(w1.getwalletinfo()['walletname'], 'w1') + self.nodes[0].loadwallet("w1") + assert_equal(self.nodes[0].listwallets(), ["w1"]) + assert_equal(w1.getwalletinfo()["walletname"], "w1") assert_equal( - sorted(w['name'] for w in self.nodes[0].listwalletdir()['wallets']), - [self.default_wallet_name, os.path.join('sub', 'w5'), 'w', 'w1', 'w2', - 'w3', 'w7', 'w7_symlink', 'w8', 'w8_copy', 'w9']) + sorted(w["name"] for w in self.nodes[0].listwalletdir()["wallets"]), + [ + self.default_wallet_name, + os.path.join("sub", "w5"), + "w", + "w1", + "w2", + "w3", + "w7", + "w7_symlink", + "w8", + "w8_copy", + "w9", + ], + ) # Test backing up and restoring wallets self.log.info("Test wallet backup") - self.restart_node(0, ['-nowallet']) + self.restart_node(0, ["-nowallet"]) for wallet_name in wallet_names: self.nodes[0].loadwallet(wallet_name) for wallet_name in wallet_names: rpc = self.nodes[0].get_wallet_rpc(wallet_name) addr = rpc.getnewaddress() - backup = os.path.join(self.options.tmpdir, 'backup.dat') + backup = os.path.join(self.options.tmpdir, "backup.dat") rpc.backupwallet(backup) self.nodes[0].unloadwallet(wallet_name) shutil.copyfile(empty_wallet, wallet_file(wallet_name)) self.nodes[0].loadwallet(wallet_name) - assert_equal(rpc.getaddressinfo(addr)['ismine'], False) + assert_equal(rpc.getaddressinfo(addr)["ismine"], False) self.nodes[0].unloadwallet(wallet_name) shutil.copyfile(backup, wallet_file(wallet_name)) self.nodes[0].loadwallet(wallet_name) - assert_equal(rpc.getaddressinfo(addr)['ismine'], True) + assert_equal(rpc.getaddressinfo(addr)["ismine"], True) # Test .walletlock file is closed self.start_node(1) - wallet = os.path.join(self.options.tmpdir, 'my_wallet') + wallet = os.path.join(self.options.tmpdir, "my_wallet") self.nodes[0].createwallet(wallet) - assert_raises_rpc_error(-4, "Error initializing wallet database environment", - self.nodes[1].loadwallet, wallet) + assert_raises_rpc_error( + -4, + "Error initializing wallet database environment", + self.nodes[1].loadwallet, + wallet, + ) self.nodes[0].unloadwallet(wallet) self.nodes[1].loadwallet(wallet) -if __name__ == '__main__': +if __name__ == "__main__": MultiWalletTest().main() diff --git a/test/functional/wallet_reorgsrestore.py b/test/functional/wallet_reorgsrestore.py index 8433a50ac..fc9579070 100755 --- a/test/functional/wallet_reorgsrestore.py +++ b/test/functional/wallet_reorgsrestore.py @@ -1,123 +1,127 @@ #!/usr/bin/env python3 # Copyright (c) 2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test tx status in case of reorgs while wallet being shutdown. Wallet txn status rely on block connection/disconnection for its accuracy. In case of reorgs happening while wallet being shutdown block updates are not going to be received. At wallet loading, we check against chain if confirmed txn are still in chain and change their status if block in which they have been included has been disconnected. """ import os import shutil from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal class ReorgsRestoreTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 3 self.extra_args = [["-noparkdeepreorg"]] * self.num_nodes def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): # Send a tx from which to conflict outputs later txid_conflict_from = self.nodes[0].sendtoaddress( - self.nodes[0].getnewaddress(), Decimal("10000000")) + self.nodes[0].getnewaddress(), Decimal("10000000") + ) self.generate(self.nodes[0], 1) # Disconnect node1 from others to reorg its chain later self.disconnect_nodes(0, 1) self.disconnect_nodes(1, 2) self.connect_nodes(0, 2) # Send a tx to be unconfirmed later txid = self.nodes[0].sendtoaddress( - self.nodes[0].getnewaddress(), Decimal("10000000")) + self.nodes[0].getnewaddress(), Decimal("10000000") + ) tx = self.nodes[0].gettransaction(txid) self.generate(self.nodes[0], 4, sync_fun=self.no_op) tx_before_reorg = self.nodes[0].gettransaction(txid) assert_equal(tx_before_reorg["confirmations"], 4) # Disconnect node0 from node2 to broadcast a conflict on their # respective chains self.disconnect_nodes(0, 2) - nA = next(tx_out["vout"] for tx_out in self.nodes[0].gettransaction( - txid_conflict_from)["details"] if tx_out["amount"] == Decimal("10000000")) + nA = next( + tx_out["vout"] + for tx_out in self.nodes[0].gettransaction(txid_conflict_from)["details"] + if tx_out["amount"] == Decimal("10000000") + ) inputs = [] inputs.append({"txid": txid_conflict_from, "vout": nA}) outputs_1 = {} outputs_2 = {} # Create a conflicted tx broadcast on node0 chain and conflicting tx # broadcast on node1 chain. Both spend from txid_conflict_from outputs_1[self.nodes[0].getnewaddress()] = Decimal("9999980") outputs_2[self.nodes[0].getnewaddress()] = Decimal("9999980") conflicted = self.nodes[0].signrawtransactionwithwallet( - self.nodes[0].createrawtransaction(inputs, outputs_1)) + self.nodes[0].createrawtransaction(inputs, outputs_1) + ) conflicting = self.nodes[0].signrawtransactionwithwallet( - self.nodes[0].createrawtransaction(inputs, outputs_2)) + self.nodes[0].createrawtransaction(inputs, outputs_2) + ) conflicted_txid = self.nodes[0].sendrawtransaction(conflicted["hex"]) self.generate(self.nodes[0], 1, sync_fun=self.no_op) conflicting_txid = self.nodes[2].sendrawtransaction(conflicting["hex"]) self.generate(self.nodes[2], 9, sync_fun=self.no_op) # Reconnect node0 and node2 and check that conflicted_txid is # effectively conflicted self.connect_nodes(0, 2) self.sync_blocks([self.nodes[0], self.nodes[2]]) conflicted = self.nodes[0].gettransaction(conflicted_txid) conflicting = self.nodes[0].gettransaction(conflicting_txid) assert_equal(conflicted["confirmations"], -9) assert_equal(conflicted["walletconflicts"][0], conflicting["txid"]) # Node0 wallet is shutdown self.restart_node(0) # The block chain re-orgs and the tx is included in a different block self.generate(self.nodes[1], 9, sync_fun=self.no_op) self.nodes[1].sendrawtransaction(tx["hex"]) self.generate(self.nodes[1], 1, sync_fun=self.no_op) self.nodes[1].sendrawtransaction(conflicted["hex"]) self.generate(self.nodes[1], 1, sync_fun=self.no_op) # Node0 wallet file is loaded on longest sync'ed node1 self.stop_node(1) - self.nodes[0].backupwallet( - os.path.join( - self.nodes[0].datadir, - 'wallet.bak')) + self.nodes[0].backupwallet(os.path.join(self.nodes[0].datadir, "wallet.bak")) shutil.copyfile( - os.path.join( - self.nodes[0].datadir, - 'wallet.bak'), + os.path.join(self.nodes[0].datadir, "wallet.bak"), os.path.join( self.nodes[1].datadir, self.chain, self.default_wallet_name, - self.wallet_data_filename)) + self.wallet_data_filename, + ), + ) self.start_node(1) tx_after_reorg = self.nodes[1].gettransaction(txid) # Check that normal confirmed tx is confirmed again but with different # blockhash assert_equal(tx_after_reorg["confirmations"], 2) assert tx_before_reorg["blockhash"] != tx_after_reorg["blockhash"] conflicted_after_reorg = self.nodes[1].gettransaction(conflicted_txid) # Check that conflicted tx is confirmed again with blockhash different # than previously conflicting tx assert_equal(conflicted_after_reorg["confirmations"], 1) assert conflicting["blockhash"] != conflicted_after_reorg["blockhash"] -if __name__ == '__main__': +if __name__ == "__main__": ReorgsRestoreTest().main() diff --git a/test/functional/wallet_resendwallettransactions.py b/test/functional/wallet_resendwallettransactions.py index 782d78127..69e3c4d60 100755 --- a/test/functional/wallet_resendwallettransactions.py +++ b/test/functional/wallet_resendwallettransactions.py @@ -1,83 +1,88 @@ #!/usr/bin/env python3 # Copyright (c) 2017-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test that the wallet resends transactions periodically.""" import time from test_framework.blocktools import create_block, create_coinbase from test_framework.messages import ToHex from test_framework.p2p import P2PTxInvStore from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal class ResendWalletTransactionsTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): node = self.nodes[0] # alias peer_first = node.add_p2p_connection(P2PTxInvStore()) self.log.info("Create a new transaction and wait until it's broadcast") txid = node.sendtoaddress(node.getnewaddress(), 1_000_000) # Wallet rebroadcast is first scheduled 1 sec after startup (see # nNextResend in ResendWalletTransactions()). Tell scheduler to call # MaybeResendWalletTxn now to initialize nNextResend before the first # setmocktime call below. node.mockscheduler(1) # Can take a few seconds due to transaction trickling peer_first.wait_for_broadcast([txid]) # Add a second peer since txs aren't rebroadcast to the same peer (see # filterInventoryKnown) peer_second = node.add_p2p_connection(P2PTxInvStore()) self.log.info("Create a block") # Create and submit a block without the transaction. # Transactions are only rebroadcast if there has been a block at least five minutes # after the last time we tried to broadcast. Use mocktime and give an # extra minute to be sure. block_time = int(time.time()) + 6 * 60 node.setmocktime(block_time) - block = create_block(int(node.getbestblockhash(), 16), create_coinbase( - node.getblockcount() + 1), block_time) + block = create_block( + int(node.getbestblockhash(), 16), + create_coinbase(node.getblockcount() + 1), + block_time, + ) block.rehash() block.solve() node.submitblock(ToHex(block)) # Set correct m_best_block_time, which is used in # ResendWalletTransactions node.syncwithvalidationinterfacequeue() now = int(time.time()) # Transaction should not be rebroadcast within first 12 hours # Leave 2 mins for buffer twelve_hrs = 12 * 60 * 60 two_min = 2 * 60 node.setmocktime(now + twelve_hrs - two_min) # Tell scheduler to call MaybeResendWalletTxn now node.mockscheduler(1) assert_equal(int(txid, 16) in peer_second.get_invs(), False) self.log.info("Bump time & check that transaction is rebroadcast") # Transaction should be rebroadcast approximately 24 hours in the future, # but can range from 12-36. So bump 36 hours to be sure. - with node.assert_debug_log(['ResendWalletTransactions: resubmit 1 unconfirmed transactions']): + with node.assert_debug_log( + ["ResendWalletTransactions: resubmit 1 unconfirmed transactions"] + ): node.setmocktime(now + 36 * 60 * 60) # Tell scheduler to call MaybeResendWalletTxn now. node.mockscheduler(1) # Give some time for trickle to occur node.setmocktime(now + 36 * 60 * 60 + 600) peer_second.wait_for_broadcast([txid]) -if __name__ == '__main__': +if __name__ == "__main__": ResendWalletTransactionsTest().main() diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py index 2896951e9..164a58567 100755 --- a/test/functional/wallet_send.py +++ b/test/functional/wallet_send.py @@ -1,421 +1,479 @@ #!/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 the send RPC command.""" from decimal import Decimal from test_framework.authproxy import JSONRPCException from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_fee_amount, assert_greater_than, assert_raises_rpc_error, ) class WalletSendTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 # whitelist all peers to speed up tx relay / mempool sync self.extra_args = [ - ["-whitelist=127.0.0.1", ], - ["-whitelist=127.0.0.1", ], + [ + "-whitelist=127.0.0.1", + ], + [ + "-whitelist=127.0.0.1", + ], ] def skip_test_if_missing_module(self): self.skip_if_no_wallet() - def test_send(self, from_wallet, to_wallet=None, amount=None, data=None, - add_to_wallet=None, psbt=None, inputs=None, add_inputs=None, - change_address=None, change_position=None, - include_watching=None, locktime=None, lock_unspents=None, - subtract_fee_from_outputs=None, fee_rate=None, - expect_error=None): + def test_send( + self, + from_wallet, + to_wallet=None, + amount=None, + data=None, + add_to_wallet=None, + psbt=None, + inputs=None, + add_inputs=None, + change_address=None, + change_position=None, + include_watching=None, + locktime=None, + lock_unspents=None, + subtract_fee_from_outputs=None, + fee_rate=None, + expect_error=None, + ): assert (amount is None) != (data is None) from_balance_before = from_wallet.getbalance() if to_wallet is None: assert amount is None else: - to_untrusted_pending_before = \ - to_wallet.getbalances()["mine"]["untrusted_pending"] + to_untrusted_pending_before = to_wallet.getbalances()["mine"][ + "untrusted_pending" + ] if amount: dest = to_wallet.getnewaddress() outputs = {dest: amount} else: outputs = {"data": data} # Construct options dictionary options = {} if add_to_wallet is not None: options["add_to_wallet"] = add_to_wallet else: add_to_wallet = ( - False if psbt else - from_wallet.getwalletinfo()["private_keys_enabled"] + False if psbt else from_wallet.getwalletinfo()["private_keys_enabled"] ) if psbt is not None: options["psbt"] = psbt if inputs is not None: options["inputs"] = inputs if add_inputs is not None: options["add_inputs"] = add_inputs if change_address is not None: options["change_address"] = change_address if change_position is not None: options["change_position"] = change_position if include_watching is not None: options["include_watching"] = include_watching if locktime is not None: options["locktime"] = locktime if lock_unspents is not None: options["lock_unspents"] = lock_unspents if subtract_fee_from_outputs is not None: options["subtract_fee_from_outputs"] = subtract_fee_from_outputs if fee_rate is not None: options["fee_rate"] = fee_rate if len(options.keys()) == 0: options = None if expect_error is None: - res = from_wallet.send( - outputs=outputs, - options=options) + res = from_wallet.send(outputs=outputs, options=options) else: try: assert_raises_rpc_error( - expect_error[0], expect_error[1], from_wallet.send, - outputs=outputs, options=options) + expect_error[0], + expect_error[1], + from_wallet.send, + outputs=outputs, + options=options, + ) except AssertionError: # Provide debug info if the test fails self.log.error("Unexpected successful result:") self.log.error(options) - res = from_wallet.send( - outputs=outputs, - options=options) + res = from_wallet.send(outputs=outputs, options=options) self.log.error(res) if "txid" in res and add_to_wallet: self.log.error("Transaction details:") try: tx = from_wallet.gettransaction(res["txid"]) self.log.error(tx) self.log.error( - "testmempoolaccept (transaction may already be in mempool):") - self.log.error( - from_wallet.testmempoolaccept([tx["hex"]])) + "testmempoolaccept (transaction may already be in mempool):" + ) + self.log.error(from_wallet.testmempoolaccept([tx["hex"]])) except JSONRPCException as exc: self.log.error(exc) raise return if locktime: return res - if (from_wallet.getwalletinfo()["private_keys_enabled"] - and not include_watching): + if from_wallet.getwalletinfo()["private_keys_enabled"] and not include_watching: assert_equal(res["complete"], True) assert "txid" in res else: assert_equal(res["complete"], False) assert "txid" not in res assert "psbt" in res if add_to_wallet and not include_watching: # Ensure transaction exists in the wallet: tx = from_wallet.gettransaction(res["txid"]) assert tx # Ensure transaction exists in the mempool: tx = from_wallet.getrawtransaction(res["txid"], True) assert tx if amount: if subtract_fee_from_outputs: - assert_equal( - from_balance_before - - from_wallet.getbalance(), - amount) + assert_equal(from_balance_before - from_wallet.getbalance(), amount) else: assert_greater_than( - from_balance_before - from_wallet.getbalance(), amount) + from_balance_before - from_wallet.getbalance(), amount + ) else: assert next( - (out for out in tx["vout"] if out["scriptPubKey"] - ["asm"] == "OP_RETURN 35"), - None) + ( + out + for out in tx["vout"] + if out["scriptPubKey"]["asm"] == "OP_RETURN 35" + ), + None, + ) else: assert_equal(from_balance_before, from_wallet.getbalance()) if to_wallet: self.sync_mempools() if add_to_wallet: if not subtract_fee_from_outputs: assert_equal( to_wallet.getbalances()["mine"]["untrusted_pending"], - to_untrusted_pending_before + - Decimal( - amount if amount else 0)) + to_untrusted_pending_before + Decimal(amount if amount else 0), + ) else: assert_equal( to_wallet.getbalances()["mine"]["untrusted_pending"], - to_untrusted_pending_before) + to_untrusted_pending_before, + ) return res def run_test(self): self.log.info("Setup wallets...") # w0 is a wallet with coinbase rewards w0 = self.nodes[0].get_wallet_rpc(self.default_wallet_name) # w1 is a regular wallet self.nodes[1].createwallet(wallet_name="w1") w1 = self.nodes[1].get_wallet_rpc("w1") # w2 contains the private keys for w3 self.nodes[1].createwallet(wallet_name="w2") w2 = self.nodes[1].get_wallet_rpc("w2") # w3 is a watch-only wallet, based on w2 self.nodes[1].createwallet(wallet_name="w3", disable_private_keys=True) w3 = self.nodes[1].get_wallet_rpc("w3") for _ in range(3): a2_receive = w2.getnewaddress() # doesn't actually use change derivation a2_change = w2.getrawchangeaddress() - res = w3.importmulti([{ - "desc": w2.getaddressinfo(a2_receive)["desc"], - "timestamp": "now", - "keypool": True, - "watchonly": True - }, { - "desc": w2.getaddressinfo(a2_change)["desc"], - "timestamp": "now", - "keypool": True, - "internal": True, - "watchonly": True - }]) + res = w3.importmulti( + [ + { + "desc": w2.getaddressinfo(a2_receive)["desc"], + "timestamp": "now", + "keypool": True, + "watchonly": True, + }, + { + "desc": w2.getaddressinfo(a2_change)["desc"], + "timestamp": "now", + "keypool": True, + "internal": True, + "watchonly": True, + }, + ] + ) assert_equal(res, [{"success": True}, {"success": True}]) # fund w3 w0.sendtoaddress(a2_receive, 10_000_000) self.generate(self.nodes[0], 1) # w4 has private keys enabled, but only contains watch-only keys (from # w2) - self.nodes[1].createwallet( - wallet_name="w4", - disable_private_keys=False) + self.nodes[1].createwallet(wallet_name="w4", disable_private_keys=False) w4 = self.nodes[1].get_wallet_rpc("w4") for _ in range(3): a2_receive = w2.getnewaddress() - res = w4.importmulti([{ - "desc": w2.getaddressinfo(a2_receive)["desc"], - "timestamp": "now", - "keypool": False, - "watchonly": True - }]) + res = w4.importmulti( + [ + { + "desc": w2.getaddressinfo(a2_receive)["desc"], + "timestamp": "now", + "keypool": False, + "watchonly": True, + } + ] + ) assert_equal(res, [{"success": True}]) # fund w4 w0.sendtoaddress(a2_receive, 10_000_000) self.generate(self.nodes[0], 1) self.log.info("Send to address...") self.test_send(from_wallet=w0, to_wallet=w1, amount=1_000_000) self.test_send( - from_wallet=w0, - to_wallet=w1, - amount=1_000_000, - add_to_wallet=True) + from_wallet=w0, to_wallet=w1, amount=1_000_000, add_to_wallet=True + ) self.log.info("Don't broadcast...") res = self.test_send( - from_wallet=w0, - to_wallet=w1, - amount=1_000_000, - add_to_wallet=False) + from_wallet=w0, to_wallet=w1, amount=1_000_000, add_to_wallet=False + ) assert res["hex"] self.log.info("Return PSBT...") - res = self.test_send( - from_wallet=w0, - to_wallet=w1, - amount=1_000_000, - psbt=True) + res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1_000_000, psbt=True) assert res["psbt"] self.log.info( - "Create transaction that spends to address, but don't broadcast...") + "Create transaction that spends to address, but don't broadcast..." + ) self.test_send( - from_wallet=w0, - to_wallet=w1, - amount=1_000_000, - add_to_wallet=False) + from_wallet=w0, to_wallet=w1, amount=1_000_000, add_to_wallet=False + ) self.log.info("Create PSBT from watch-only wallet w3, sign with w2...") res = self.test_send(from_wallet=w3, to_wallet=w1, amount=1_000_000) res = w2.walletprocesspsbt(res["psbt"]) assert res["complete"] self.log.info( - "Create PSBT from wallet w4 with watch-only keys, sign with w2...") - self.test_send(from_wallet=w4, to_wallet=w1, amount=1_000_000, - expect_error=(-4, "Insufficient funds")) + "Create PSBT from wallet w4 with watch-only keys, sign with w2..." + ) + self.test_send( + from_wallet=w4, + to_wallet=w1, + amount=1_000_000, + expect_error=(-4, "Insufficient funds"), + ) res = self.test_send( from_wallet=w4, to_wallet=w1, amount=1_000_000, include_watching=True, - add_to_wallet=False) + add_to_wallet=False, + ) res = w2.walletprocesspsbt(res["psbt"]) assert res["complete"] self.log.info("Create OP_RETURN...") self.test_send(from_wallet=w0, to_wallet=w1, amount=1_000_000) - self.test_send(from_wallet=w0, - data="Hello World", - expect_error=(-8, - "Data must be hexadecimal string (not 'Hello World')")) + self.test_send( + from_wallet=w0, + data="Hello World", + expect_error=(-8, "Data must be hexadecimal string (not 'Hello World')"), + ) self.test_send(from_wallet=w0, data="23") res = self.test_send(from_wallet=w3, data="23") res = w2.walletprocesspsbt(res["psbt"]) assert res["complete"] self.log.info("Set fee rate...") res = self.test_send( from_wallet=w0, to_wallet=w1, amount=1_000_000, fee_rate=Decimal("20.00"), - add_to_wallet=False) + add_to_wallet=False, + ) fee = self.nodes[1].decodepsbt(res["psbt"])["fee"] - assert_fee_amount(fee, - len(res["hex"]) // 2, - Decimal("20.00")) - self.test_send(from_wallet=w0, to_wallet=w1, amount=1_000_000, fee_rate=-1, - expect_error=(-3, "Amount out of range")) + assert_fee_amount(fee, len(res["hex"]) // 2, Decimal("20.00")) + self.test_send( + from_wallet=w0, + to_wallet=w1, + amount=1_000_000, + fee_rate=-1, + expect_error=(-3, "Amount out of range"), + ) # Fee rate of 0.1 satoshi per byte should throw an error - self.test_send(from_wallet=w0, to_wallet=w1, amount=1_000_000, - fee_rate=Decimal("1.00"), - expect_error=(-4, "Fee rate (1.00 XEC/kB) is lower than the minimum fee rate setting (10.00 XEC/kB)")) + self.test_send( + from_wallet=w0, + to_wallet=w1, + amount=1_000_000, + fee_rate=Decimal("1.00"), + expect_error=( + -4, + ( + "Fee rate (1.00 XEC/kB) is lower than the minimum fee rate setting" + " (10.00 XEC/kB)" + ), + ), + ) # TODO: Return hex if fee rate is below -maxmempool # res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1_000_000, # feeRate=Decimal("1.00"), add_to_wallet=False) # assert res["hex"] # hex = res["hex"] # res = self.nodes[0].testmempoolaccept([hex]) # assert not res[0]["allowed"] # assert_equal(res[0]["reject-reason"], "...") # low fee # assert_fee_amount(fee, Decimal(len(res["hex"]) / 2), Decimal("1.00")) - self.log.info( - "If inputs are specified, do not automatically add more...") + self.log.info("If inputs are specified, do not automatically add more...") res = self.test_send( from_wallet=w0, to_wallet=w1, amount=51_000_000, inputs=[], - add_to_wallet=False) + add_to_wallet=False, + ) assert res["complete"] utxo1 = w0.listunspent()[0] assert_equal(utxo1["amount"], 50_000_000) - self.test_send(from_wallet=w0, to_wallet=w1, amount=51_000_000, - inputs=[utxo1], expect_error=(-4, "Insufficient funds")) - self.test_send(from_wallet=w0, to_wallet=w1, amount=51_000_000, - inputs=[utxo1], add_inputs=False, - expect_error=(-4, "Insufficient funds")) + self.test_send( + from_wallet=w0, + to_wallet=w1, + amount=51_000_000, + inputs=[utxo1], + expect_error=(-4, "Insufficient funds"), + ) + self.test_send( + from_wallet=w0, + to_wallet=w1, + amount=51_000_000, + inputs=[utxo1], + add_inputs=False, + expect_error=(-4, "Insufficient funds"), + ) res = self.test_send( from_wallet=w0, to_wallet=w1, amount=51_000_000, inputs=[utxo1], add_inputs=True, - add_to_wallet=False) + add_to_wallet=False, + ) assert res["complete"] self.log.info("Manual change address and position...") - self.test_send(from_wallet=w0, to_wallet=w1, amount=1_000_000, change_address="not an address", - expect_error=(-5, "Change address must be a valid bitcoin address")) + self.test_send( + from_wallet=w0, + to_wallet=w1, + amount=1_000_000, + change_address="not an address", + expect_error=(-5, "Change address must be a valid bitcoin address"), + ) change_address = w0.getnewaddress() self.test_send( from_wallet=w0, to_wallet=w1, amount=1_000_000, add_to_wallet=False, - change_address=change_address) + change_address=change_address, + ) assert res["complete"] res = self.test_send( from_wallet=w0, to_wallet=w1, amount=1_000_000, add_to_wallet=False, change_address=change_address, - change_position=0) + change_position=0, + ) assert res["complete"] assert_equal( - self.nodes[0].decodepsbt( - res["psbt"])["tx"]["vout"][0]["scriptPubKey"]["addresses"], - [change_address]) + self.nodes[0].decodepsbt(res["psbt"])["tx"]["vout"][0]["scriptPubKey"][ + "addresses" + ], + [change_address], + ) self.log.info("Set lock time...") height = self.nodes[0].getblockchaininfo()["blocks"] res = self.test_send( - from_wallet=w0, - to_wallet=w1, - amount=1_000_000, - locktime=height + 1) + from_wallet=w0, to_wallet=w1, amount=1_000_000, locktime=height + 1 + ) assert res["complete"] assert res["txid"] txid = res["txid"] # Although the wallet finishes the transaction, it can't be added to # the mempool yet: tx_hex = self.nodes[0].gettransaction(res["txid"])["hex"] res = self.nodes[0].testmempoolaccept([tx_hex]) assert not res[0]["allowed"] assert_equal(res[0]["reject-reason"], "bad-txns-nonfinal") # It shouldn't be confirmed in the next block self.generate(self.nodes[0], 1) assert_equal(self.nodes[0].gettransaction(txid)["confirmations"], 0) # The mempool should allow it now: res = self.nodes[0].testmempoolaccept([tx_hex]) assert res[0]["allowed"] # Don't wait for wallet to add it to the mempool: res = self.nodes[0].sendrawtransaction(tx_hex) self.generate(self.nodes[0], 1) assert_equal(self.nodes[0].gettransaction(txid)["confirmations"], 1) self.log.info("Lock unspents...") utxo1 = w0.listunspent()[0] assert_greater_than(utxo1["amount"], 1_000_000) res = self.test_send( from_wallet=w0, to_wallet=w1, amount=1_000_000, inputs=[utxo1], add_to_wallet=False, - lock_unspents=True) + lock_unspents=True, + ) assert res["complete"] locked_coins = w0.listlockunspent() assert_equal(len(locked_coins), 1) # Locked coins are automatically unlocked when manually selected res = self.test_send( from_wallet=w0, to_wallet=w1, amount=1_000_000, inputs=[utxo1], - add_to_wallet=False) + add_to_wallet=False, + ) assert res["complete"] self.log.info("Subtract fee from output") self.test_send( from_wallet=w0, to_wallet=w1, amount=1_000_000, - subtract_fee_from_outputs=[0]) + subtract_fee_from_outputs=[0], + ) -if __name__ == '__main__': +if __name__ == "__main__": WalletSendTest().main() diff --git a/test/functional/wallet_startup.py b/test/functional/wallet_startup.py index a2d9cf02b..19fe6b04d 100755 --- a/test/functional/wallet_startup.py +++ b/test/functional/wallet_startup.py @@ -1,59 +1,59 @@ #!/usr/bin/env python3 # Copyright (c) 2017-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test wallet load on startup. Verify that a bitcoind node can maintain list of wallets loading on startup """ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal class WalletStartupTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.supports_cli = True def skip_test_if_missing_module(self): self.skip_if_no_wallet() def setup_nodes(self): self.add_nodes(self.num_nodes) self.start_nodes() def run_test(self): - self.log.info('Should start without any wallets') + self.log.info("Should start without any wallets") assert_equal(self.nodes[0].listwallets(), []) - assert_equal(self.nodes[0].listwalletdir(), {'wallets': []}) + assert_equal(self.nodes[0].listwalletdir(), {"wallets": []}) self.log.info( - 'New default wallet should load by default when there are no other wallets') - self.nodes[0].createwallet(wallet_name='', load_on_startup=False) + "New default wallet should load by default when there are no other wallets" + ) + self.nodes[0].createwallet(wallet_name="", load_on_startup=False) self.restart_node(0) - assert_equal(self.nodes[0].listwallets(), ['']) - - self.log.info('Test load on startup behavior') - self.nodes[0].createwallet(wallet_name='w0', load_on_startup=True) - self.nodes[0].createwallet(wallet_name='w1', load_on_startup=False) - self.nodes[0].createwallet(wallet_name='w2', load_on_startup=True) - self.nodes[0].createwallet(wallet_name='w3', load_on_startup=False) - self.nodes[0].createwallet(wallet_name='w4', load_on_startup=False) - self.nodes[0].unloadwallet(wallet_name='w0', load_on_startup=False) - self.nodes[0].unloadwallet(wallet_name='w4', load_on_startup=False) - self.nodes[0].loadwallet(filename='w4', load_on_startup=True) - assert_equal(set(self.nodes[0].listwallets()), - {'', 'w1', 'w2', 'w3', 'w4'}) + assert_equal(self.nodes[0].listwallets(), [""]) + + self.log.info("Test load on startup behavior") + self.nodes[0].createwallet(wallet_name="w0", load_on_startup=True) + self.nodes[0].createwallet(wallet_name="w1", load_on_startup=False) + self.nodes[0].createwallet(wallet_name="w2", load_on_startup=True) + self.nodes[0].createwallet(wallet_name="w3", load_on_startup=False) + self.nodes[0].createwallet(wallet_name="w4", load_on_startup=False) + self.nodes[0].unloadwallet(wallet_name="w0", load_on_startup=False) + self.nodes[0].unloadwallet(wallet_name="w4", load_on_startup=False) + self.nodes[0].loadwallet(filename="w4", load_on_startup=True) + assert_equal(set(self.nodes[0].listwallets()), {"", "w1", "w2", "w3", "w4"}) self.restart_node(0) - assert_equal(set(self.nodes[0].listwallets()), {'', 'w2', 'w4'}) - self.nodes[0].unloadwallet(wallet_name='', load_on_startup=False) - self.nodes[0].unloadwallet(wallet_name='w4', load_on_startup=False) - self.nodes[0].loadwallet(filename='w3', load_on_startup=True) - self.nodes[0].loadwallet(filename='') + assert_equal(set(self.nodes[0].listwallets()), {"", "w2", "w4"}) + self.nodes[0].unloadwallet(wallet_name="", load_on_startup=False) + self.nodes[0].unloadwallet(wallet_name="w4", load_on_startup=False) + self.nodes[0].loadwallet(filename="w3", load_on_startup=True) + self.nodes[0].loadwallet(filename="") self.restart_node(0) - assert_equal(set(self.nodes[0].listwallets()), {'w2', 'w3'}) + assert_equal(set(self.nodes[0].listwallets()), {"w2", "w3"}) -if __name__ == '__main__': +if __name__ == "__main__": WalletStartupTest().main() diff --git a/test/functional/wallet_timelock.py b/test/functional/wallet_timelock.py index 3ce48c487..6826e3cf4 100755 --- a/test/functional/wallet_timelock.py +++ b/test/functional/wallet_timelock.py @@ -1,57 +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_000_000}, options={"locktime": mtp_tip - 1}, ) self.generate(node, 1, sync_fun=self.no_op) - self.log.info( - "Check that clock can not change finality of confirmed txs") + self.log.info("Check that clock can not 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.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_txn_clone.py b/test/functional/wallet_txn_clone.py index f6140d124..04606e6ad 100755 --- a/test/functional/wallet_txn_clone.py +++ b/test/functional/wallet_txn_clone.py @@ -1,145 +1,166 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet accounts properly when there are cloned transactions with malleated scriptsigs.""" import io from test_framework.messages import XEC, CTransaction from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal class TxnMallTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 3 self.extra_args = [["-noparkdeepreorg"], ["-noparkdeepreorg"], []] self.supports_cli = False def skip_test_if_missing_module(self): self.skip_if_no_wallet() def add_options(self, parser): - parser.add_argument("--mineblock", dest="mine_block", default=False, action="store_true", - help="Test double-spend of 1-confirmed transaction") + parser.add_argument( + "--mineblock", + dest="mine_block", + default=False, + action="store_true", + help="Test double-spend of 1-confirmed transaction", + ) def setup_network(self): # Start with split network: super().setup_network() self.disconnect_nodes(1, 2) def run_test(self): output_type = "legacy" # All nodes should start with 1,250,000,000 XEC: starting_balance = 1250000000 for i in range(3): assert_equal(self.nodes[i].getbalance(), starting_balance) self.nodes[0].settxfee(1000) node0_address1 = self.nodes[0].getnewaddress(address_type=output_type) node0_txid1 = self.nodes[0].sendtoaddress(node0_address1, 1219000000) node0_tx1 = self.nodes[0].gettransaction(node0_txid1) node0_address2 = self.nodes[0].getnewaddress(address_type=output_type) node0_txid2 = self.nodes[0].sendtoaddress(node0_address2, 29000000) node0_tx2 = self.nodes[0].gettransaction(node0_txid2) - assert_equal(self.nodes[0].getbalance(), - starting_balance + node0_tx1["fee"] + node0_tx2["fee"]) + assert_equal( + self.nodes[0].getbalance(), + starting_balance + node0_tx1["fee"] + node0_tx2["fee"], + ) # Coins are sent to node1_address node1_address = self.nodes[1].getnewaddress() # Send tx1, and another transaction tx2 that won't be cloned txid1 = self.nodes[0].sendtoaddress(node1_address, 40000000) txid2 = self.nodes[0].sendtoaddress(node1_address, 20000000) # Construct a clone of tx1, to be malleated rawtx1 = self.nodes[0].getrawtransaction(txid1, 1) - clone_inputs = [{"txid": rawtx1["vin"][0]["txid"], - "vout": rawtx1["vin"][0]["vout"], - "sequence": rawtx1["vin"][0]["sequence"]}] - clone_outputs = {rawtx1["vout"][0]["scriptPubKey"]["addresses"][0]: rawtx1["vout"][0]["value"], - rawtx1["vout"][1]["scriptPubKey"]["addresses"][0]: rawtx1["vout"][1]["value"]} + clone_inputs = [ + { + "txid": rawtx1["vin"][0]["txid"], + "vout": rawtx1["vin"][0]["vout"], + "sequence": rawtx1["vin"][0]["sequence"], + } + ] + clone_outputs = { + rawtx1["vout"][0]["scriptPubKey"]["addresses"][0]: rawtx1["vout"][0][ + "value" + ], + rawtx1["vout"][1]["scriptPubKey"]["addresses"][0]: rawtx1["vout"][1][ + "value" + ], + } clone_locktime = rawtx1["locktime"] clone_raw = self.nodes[0].createrawtransaction( - clone_inputs, clone_outputs, clone_locktime) + clone_inputs, clone_outputs, clone_locktime + ) # createrawtransaction randomizes the order of its outputs, so swap # them if necessary. clone_tx = CTransaction() clone_tx.deserialize(io.BytesIO(bytes.fromhex(clone_raw))) - if (rawtx1["vout"][0]["value"] == 40000000 and - clone_tx.vout[0].nValue != 40000000 * XEC or - rawtx1["vout"][0]["value"] != 40000000 and - clone_tx.vout[0].nValue == 40000000 * XEC): - (clone_tx.vout[0], clone_tx.vout[1]) = (clone_tx.vout[1], - clone_tx.vout[0]) + if ( + rawtx1["vout"][0]["value"] == 40000000 + and clone_tx.vout[0].nValue != 40000000 * XEC + or rawtx1["vout"][0]["value"] != 40000000 + and clone_tx.vout[0].nValue == 40000000 * XEC + ): + (clone_tx.vout[0], clone_tx.vout[1]) = (clone_tx.vout[1], clone_tx.vout[0]) # Use a different signature hash type to sign. This creates an equivalent but malleated clone. # Don't send the clone anywhere yet tx1_clone = self.nodes[0].signrawtransactionwithwallet( - clone_tx.serialize().hex(), None, "ALL|FORKID|ANYONECANPAY") + clone_tx.serialize().hex(), None, "ALL|FORKID|ANYONECANPAY" + ) assert_equal(tx1_clone["complete"], True) # Have node0 mine a block, if requested: - if (self.options.mine_block): + if self.options.mine_block: self.generate( - self.nodes[0], 1, sync_fun=lambda: self.sync_blocks(self.nodes[0:2])) + self.nodes[0], 1, sync_fun=lambda: self.sync_blocks(self.nodes[0:2]) + ) tx1 = self.nodes[0].gettransaction(txid1) tx2 = self.nodes[0].gettransaction(txid2) # Node0's balance should be starting balance, plus 50BTC for another # matured block, minus tx1 and tx2 amounts, and minus transaction fees: expected = starting_balance + node0_tx1["fee"] + node0_tx2["fee"] if self.options.mine_block: expected += 50000000 expected += tx1["amount"] + tx1["fee"] expected += tx2["amount"] + tx2["fee"] assert_equal(self.nodes[0].getbalance(), expected) if self.options.mine_block: assert_equal(tx1["confirmations"], 1) assert_equal(tx2["confirmations"], 1) else: assert_equal(tx1["confirmations"], 0) assert_equal(tx2["confirmations"], 0) # Send clone and its parent to miner self.nodes[2].sendrawtransaction(node0_tx1["hex"]) txid1_clone = self.nodes[2].sendrawtransaction(tx1_clone["hex"]) # ... mine a block... self.generate(self.nodes[2], 1, sync_fun=self.no_op) # Reconnect the split network, and sync chain: self.connect_nodes(1, 2) self.nodes[2].sendrawtransaction(node0_tx2["hex"]) self.nodes[2].sendrawtransaction(tx2["hex"]) # Mine another block to make sure we sync self.generate(self.nodes[2], 1) # Re-fetch transaction info: tx1 = self.nodes[0].gettransaction(txid1) tx1_clone = self.nodes[0].gettransaction(txid1_clone) tx2 = self.nodes[0].gettransaction(txid2) # Verify expected confirmations assert_equal(tx1["confirmations"], -2) assert_equal(tx1_clone["confirmations"], 2) assert_equal(tx2["confirmations"], 1) # Check node0's total balance; should be same as before the clone, + # 100,000,000 XEC for 2 matured, less possible orphaned matured subsidy expected += 100000000 - if (self.options.mine_block): + if self.options.mine_block: expected -= 50000000 assert_equal(self.nodes[0].getbalance(), expected) -if __name__ == '__main__': +if __name__ == "__main__": TxnMallTest().main() diff --git a/test/functional/wallet_txn_doublespend.py b/test/functional/wallet_txn_doublespend.py index 0a3207ddb..47067984c 100755 --- a/test/functional/wallet_txn_doublespend.py +++ b/test/functional/wallet_txn_doublespend.py @@ -1,148 +1,159 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the wallet accounts properly when there is a double-spend conflict.""" from decimal import Decimal from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, find_output class TxnMallTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 3 self.extra_args = [["-noparkdeepreorg"], ["-noparkdeepreorg"], []] self.supports_cli = False def skip_test_if_missing_module(self): self.skip_if_no_wallet() def add_options(self, parser): - parser.add_argument("--mineblock", dest="mine_block", default=False, action="store_true", - help="Test double-spend of 1-confirmed transaction") + parser.add_argument( + "--mineblock", + dest="mine_block", + default=False, + action="store_true", + help="Test double-spend of 1-confirmed transaction", + ) def setup_network(self): # Start with split network: super().setup_network() self.disconnect_nodes(1, 2) def run_test(self): # All nodes should start with 1,250,000,000 XEC: starting_balance = 1250000000 # All nodes should be out of IBD. # If the nodes are not all out of IBD, that can interfere with # blockchain sync later in the test when nodes are connected, due to # timing issues. for n in self.nodes: assert n.getblockchaininfo()["initialblockdownload"] is False for i in range(3): assert_equal(self.nodes[i].getbalance(), starting_balance) # Assign coins to foo and bar addresses: node0_address_foo = self.nodes[0].getnewaddress() - fund_foo_txid = self.nodes[0].sendtoaddress( - node0_address_foo, 1219000000) + fund_foo_txid = self.nodes[0].sendtoaddress(node0_address_foo, 1219000000) fund_foo_tx = self.nodes[0].gettransaction(fund_foo_txid) node0_address_bar = self.nodes[0].getnewaddress() - fund_bar_txid = self.nodes[0].sendtoaddress( - node0_address_bar, 29000000) + fund_bar_txid = self.nodes[0].sendtoaddress(node0_address_bar, 29000000) fund_bar_tx = self.nodes[0].gettransaction(fund_bar_txid) - assert_equal(self.nodes[0].getbalance(), - starting_balance + fund_foo_tx["fee"] + fund_bar_tx["fee"]) + assert_equal( + self.nodes[0].getbalance(), + starting_balance + fund_foo_tx["fee"] + fund_bar_tx["fee"], + ) # Coins are sent to node1_address node1_address = self.nodes[1].getnewaddress() # First: use raw transaction API to send 1,240,000,000 XEC to # node1_address, but don't broadcast: - doublespend_fee = Decimal('-20000') + doublespend_fee = Decimal("-20000") rawtx_input_0 = {} rawtx_input_0["txid"] = fund_foo_txid - rawtx_input_0["vout"] = find_output( - self.nodes[0], fund_foo_txid, 1219000000) + rawtx_input_0["vout"] = find_output(self.nodes[0], fund_foo_txid, 1219000000) rawtx_input_1 = {} rawtx_input_1["txid"] = fund_bar_txid - rawtx_input_1["vout"] = find_output( - self.nodes[0], fund_bar_txid, 29000000) + rawtx_input_1["vout"] = find_output(self.nodes[0], fund_bar_txid, 29000000) inputs = [rawtx_input_0, rawtx_input_1] change_address = self.nodes[0].getnewaddress() outputs = {} outputs[node1_address] = 1240000000 outputs[change_address] = 1248000000 - 1240000000 + doublespend_fee rawtx = self.nodes[0].createrawtransaction(inputs, outputs) doublespend = self.nodes[0].signrawtransactionwithwallet(rawtx) assert_equal(doublespend["complete"], True) # Create two spends using 1 50,000,000 XEC coin each txid1 = self.nodes[0].sendtoaddress(node1_address, 40000000) txid2 = self.nodes[0].sendtoaddress(node1_address, 20000000) # Have node0 mine a block: - if (self.options.mine_block): + if self.options.mine_block: self.generate( - self.nodes[0], 1, sync_fun=lambda: self.sync_blocks(self.nodes[0:2])) + self.nodes[0], 1, sync_fun=lambda: self.sync_blocks(self.nodes[0:2]) + ) tx1 = self.nodes[0].gettransaction(txid1) tx2 = self.nodes[0].gettransaction(txid2) # Node0's balance should be starting balance, plus 50,000,000 XEC for # another matured block, minus 40,000,000, minus 20,000,000, and minus # transaction fees: expected = starting_balance + fund_foo_tx["fee"] + fund_bar_tx["fee"] if self.options.mine_block: expected += 50000000 expected += tx1["amount"] + tx1["fee"] expected += tx2["amount"] + tx2["fee"] assert_equal(self.nodes[0].getbalance(), expected) if self.options.mine_block: assert_equal(tx1["confirmations"], 1) assert_equal(tx2["confirmations"], 1) # Node1's balance should be both transaction amounts: - assert_equal(self.nodes[1].getbalance( - ), starting_balance - tx1["amount"] - tx2["amount"]) + assert_equal( + self.nodes[1].getbalance(), + starting_balance - tx1["amount"] - tx2["amount"], + ) else: assert_equal(tx1["confirmations"], 0) assert_equal(tx2["confirmations"], 0) # Now give doublespend and its parents to miner: self.nodes[2].sendrawtransaction(fund_foo_tx["hex"]) self.nodes[2].sendrawtransaction(fund_bar_tx["hex"]) doublespend_txid = self.nodes[2].sendrawtransaction(doublespend["hex"]) # ... mine a block... self.generate(self.nodes[2], 1, sync_fun=self.no_op) # Reconnect the split network, and sync chain: self.connect_nodes(1, 2) # Mine another block to make sure we sync self.generate(self.nodes[2], 1) - assert_equal(self.nodes[0].gettransaction( - doublespend_txid)["confirmations"], 2) + assert_equal(self.nodes[0].gettransaction(doublespend_txid)["confirmations"], 2) # Re-fetch transaction info: tx1 = self.nodes[0].gettransaction(txid1) tx2 = self.nodes[0].gettransaction(txid2) # Both transactions should be conflicted assert_equal(tx1["confirmations"], -2) assert_equal(tx2["confirmations"], -2) # Node0's total balance should be starting balance, plus 100BTC for # two more matured blocks, minus 1240 for the double-spend, plus fees (which are # negative): - expected = starting_balance + 100000000 - 1240000000 + \ - fund_foo_tx["fee"] + fund_bar_tx["fee"] + doublespend_fee + expected = ( + starting_balance + + 100000000 + - 1240000000 + + fund_foo_tx["fee"] + + fund_bar_tx["fee"] + + doublespend_fee + ) assert_equal(self.nodes[0].getbalance(), expected) # Node1's balance should be its initial balance (1250 for 25 block # rewards) plus the doublespend: assert_equal(self.nodes[1].getbalance(), 1250000000 + 1240000000) -if __name__ == '__main__': +if __name__ == "__main__": TxnMallTest().main() diff --git a/test/functional/wallet_watchonly.py b/test/functional/wallet_watchonly.py index b5c2a5874..83bde733f 100755 --- a/test/functional/wallet_watchonly.py +++ b/test/functional/wallet_watchonly.py @@ -1,115 +1,113 @@ #!/usr/bin/env python3 # Copyright (c) 2018-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test createwallet arguments. """ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error class CreateWalletWatchonlyTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): node = self.nodes[0] - self.nodes[0].createwallet(wallet_name='default') - def_wallet = node.get_wallet_rpc('default') + self.nodes[0].createwallet(wallet_name="default") + def_wallet = node.get_wallet_rpc("default") a1 = def_wallet.getnewaddress() wo_change = def_wallet.getnewaddress() wo_addr = def_wallet.getnewaddress() - self.nodes[0].createwallet(wallet_name='wo', disable_private_keys=True) - wo_wallet = node.get_wallet_rpc('wo') + self.nodes[0].createwallet(wallet_name="wo", disable_private_keys=True) + wo_wallet = node.get_wallet_rpc("wo") - wo_wallet.importpubkey( - pubkey=def_wallet.getaddressinfo(wo_addr)['pubkey']) - wo_wallet.importpubkey( - pubkey=def_wallet.getaddressinfo(wo_change)['pubkey']) + wo_wallet.importpubkey(pubkey=def_wallet.getaddressinfo(wo_addr)["pubkey"]) + wo_wallet.importpubkey(pubkey=def_wallet.getaddressinfo(wo_change)["pubkey"]) # generate some btc for testing self.generatetoaddress(node, 101, a1) # send 1 btc to our watch-only address txid = def_wallet.sendtoaddress(wo_addr, 1000000) self.generate(self.nodes[0], 1) # getbalance - self.log.info( - 'include_watchonly should default to true for watch-only wallets') - self.log.info('Testing getbalance watch-only defaults') + self.log.info("include_watchonly should default to true for watch-only wallets") + self.log.info("Testing getbalance watch-only defaults") assert_equal(wo_wallet.getbalance(), 1000000) assert_equal(len(wo_wallet.listtransactions()), 1) assert_equal(wo_wallet.getbalance(include_watchonly=False), 0) - self.log.info('Testing listreceivedbyaddress watch-only defaults') + self.log.info("Testing listreceivedbyaddress watch-only defaults") result = wo_wallet.listreceivedbyaddress() assert_equal(len(result), 1) assert_equal(result[0]["involvesWatchonly"], True) result = wo_wallet.listreceivedbyaddress(include_watchonly=False) assert_equal(len(result), 0) - self.log.info('Testing listreceivedbylabel watch-only defaults') + self.log.info("Testing listreceivedbylabel watch-only defaults") result = wo_wallet.listreceivedbylabel() assert_equal(len(result), 1) assert_equal(result[0]["involvesWatchonly"], True) result = wo_wallet.listreceivedbylabel(include_watchonly=False) assert_equal(len(result), 0) - self.log.info('Testing listtransactions watch-only defaults') + self.log.info("Testing listtransactions watch-only defaults") result = wo_wallet.listtransactions() assert_equal(len(result), 1) assert_equal(result[0]["involvesWatchonly"], True) result = wo_wallet.listtransactions(include_watchonly=False) assert_equal(len(result), 0) - self.log.info('Testing listsinceblock watch-only defaults') + self.log.info("Testing listsinceblock watch-only defaults") result = wo_wallet.listsinceblock() assert_equal(len(result["transactions"]), 1) assert_equal(result["transactions"][0]["involvesWatchonly"], True) result = wo_wallet.listsinceblock(include_watchonly=False) assert_equal(len(result["transactions"]), 0) - self.log.info('Testing gettransaction watch-only defaults') + self.log.info("Testing gettransaction watch-only defaults") result = wo_wallet.gettransaction(txid) assert_equal(result["details"][0]["involvesWatchonly"], True) result = wo_wallet.gettransaction(txid=txid, include_watchonly=False) assert_equal(len(result["details"]), 0) - self.log.info('Testing walletcreatefundedpsbt watch-only defaults') + self.log.info("Testing walletcreatefundedpsbt watch-only defaults") inputs = [] outputs = [{a1: 500000}] - options = {'changeAddress': wo_change} - no_wo_options = {'changeAddress': wo_change, 'includeWatching': False} + options = {"changeAddress": wo_change} + no_wo_options = {"changeAddress": wo_change, "includeWatching": False} result = wo_wallet.walletcreatefundedpsbt( - inputs=inputs, outputs=outputs, options=options) + inputs=inputs, outputs=outputs, options=options + ) assert_equal("psbt" in result, True) - assert_raises_rpc_error(-4, - "Insufficient funds", - wo_wallet.walletcreatefundedpsbt, - inputs, - outputs, - 0, - no_wo_options) - - self.log.info('Testing fundrawtransaction watch-only defaults') + assert_raises_rpc_error( + -4, + "Insufficient funds", + wo_wallet.walletcreatefundedpsbt, + inputs, + outputs, + 0, + no_wo_options, + ) + + self.log.info("Testing fundrawtransaction watch-only defaults") rawtx = wo_wallet.createrawtransaction(inputs=inputs, outputs=outputs) result = wo_wallet.fundrawtransaction(hexstring=rawtx, options=options) assert_equal("hex" in result, True) - assert_raises_rpc_error(-4, - "Insufficient funds", - wo_wallet.fundrawtransaction, - rawtx, - no_wo_options) + assert_raises_rpc_error( + -4, "Insufficient funds", wo_wallet.fundrawtransaction, rawtx, no_wo_options + ) -if __name__ == '__main__': +if __name__ == "__main__": CreateWalletWatchonlyTest().main()