diff --git a/.arclint b/.arclint index 78e8d0869..f68f4c7f0 100644 --- a/.arclint +++ b/.arclint @@ -1,323 +1,323 @@ { "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/)" ], "flags": [ "--aggressive", "--ignore=W503,W504", "--max-line-length=88" ] }, "flake8": { "type": "flake8", "version": ">=5.0", "include": "(\\.py$)", "exclude": [ "(^contrib/gitian-builder/)", "(^contrib/apple-sdk-tools/)" ], "flags": [ - "--ignore=A001,A003,E303,E305,E501,E704,W503,W504", + "--ignore=A003,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.4.1", "include": "(^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$)" ] }, "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/abc_rpc_isfinal.py b/test/functional/abc_rpc_isfinal.py index b3d6afebb..4e9fe63bf 100755 --- a/test/functional/abc_rpc_isfinal.py +++ b/test/functional/abc_rpc_isfinal.py @@ -1,200 +1,200 @@ #!/usr/bin/env python3 # Copyright (c) 2022 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the isfinalxxx RPCS.""" import random from test_framework.address import ADDRESS_ECREG_UNSPENDABLE from test_framework.authproxy import JSONRPCException from test_framework.avatools import AvaP2PInterface, can_find_inv_in_poll from test_framework.blocktools import create_block, create_coinbase from test_framework.messages import CBlockHeader, msg_headers from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error, uint256_hex QUORUM_NODE_COUNT = 16 class AvalancheIsFinalTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 self.extra_args = [ [ '-avaproofstakeutxodustthreshold=1000000', '-avaproofstakeutxoconfirmations=1', '-avacooldown=0', '-avaminquorumstake=0', '-avaminavaproofsnodecount=0', ] ] def run_test(self): node = self.nodes[0] tip = node.getbestblockhash() assert_raises_rpc_error( -1, "Avalanche is not ready to poll yet.", self.nodes[0].isfinalblock, tip, ) assert_raises_rpc_error( -1, "Avalanche is not ready to poll yet.", self.nodes[0].isfinaltransaction, node.getblock(tip)['tx'][0], tip, ) # Build a fake quorum of nodes. def get_quorum(): return [node.add_p2p_connection(AvaP2PInterface(self, node)) for _ in range(0, QUORUM_NODE_COUNT)] # Pick one node from the quorum for polling. quorum = get_quorum() def is_quorum_established(): return node.getavalancheinfo()['ready_to_poll'] is True self.wait_until(is_quorum_established) blockhash = self.generate(node, 1, sync_fun=self.no_op)[0] cb_txid = node.getblock(blockhash)['tx'][0] assert not node.isfinalblock(blockhash) assert not node.isfinaltransaction(cb_txid, blockhash) def is_finalblock(blockhash): can_find_inv_in_poll(quorum, int(blockhash, 16)) return node.isfinalblock(blockhash) with node.assert_debug_log([f"Avalanche finalized block {blockhash}"]): self.wait_until(lambda: is_finalblock(blockhash)) assert node.isfinaltransaction(cb_txid, blockhash) self.log.info("Check block ancestors are finalized as well") tip_height = node.getblockheader(blockhash)['height'] for height in range(0, tip_height): - hash = node.getblockhash(height) - assert node.isfinalblock(hash) - txid = node.getblock(hash)['tx'][0] - assert node.isfinaltransaction(txid, hash) + blockhash = node.getblockhash(height) + assert node.isfinalblock(blockhash) + txid = node.getblock(blockhash)['tx'][0] + assert node.isfinaltransaction(txid, blockhash) if self.is_wallet_compiled(): self.log.info("Check mempool transactions are not finalized") # Mature some utxos tip = self.generate(node, 100, sync_fun=self.no_op)[-1] wallet_txid = node.sendtoaddress( ADDRESS_ECREG_UNSPENDABLE, 1_000_000) assert wallet_txid in node.getrawmempool() assert_raises_rpc_error( -5, "No such transaction found in the provided block.", node.isfinaltransaction, wallet_txid, tip, ) self.log.info( "A transaction is only finalized if the containing block is finalized") tip = self.generate(node, 1, sync_fun=self.no_op)[0] assert wallet_txid not in node.getrawmempool() assert not node.isfinaltransaction(wallet_txid, tip) self.wait_until(lambda: is_finalblock(tip)) assert node.isfinaltransaction(wallet_txid, tip) # Needs -txindex assert_raises_rpc_error( -5, "No such transaction. Use -txindex or provide a block hash to enable blockchain transaction queries.", node.isfinaltransaction, wallet_txid, ) self.log.info( "Repeat with -txindex so we don't need the blockhash") self.restart_node(0, self.extra_args[0] + ['-txindex']) quorum = get_quorum() self.wait_until(is_quorum_established) # Try to raise a -txindex not synced yet error. This is not # guaranteed because syncing is fast! try: node.isfinaltransaction( uint256_hex(random.randint(0, 2**256 - 1)), ) except JSONRPCException as e: assert_equal(e.error['code'], -5) if e.error['message'] == "No such mempool or blockchain transaction.": # If we got a regular "not found" error, the txindex should # have synced. assert node.getindexinfo()['txindex']['synced'] is True else: # Otherwise we might have successfully raised before the # indexer completed. Checking the status now is useless as # the indexer might have completed the synchronization in # the meantime and the status is no longer relevant. assert e.error['message'] == "No such transaction. Blockchain transactions are still in the process of being indexed." else: assert False, "The isfinaltransaction RPC call did not throw as expected." self.wait_until(lambda: node.getindexinfo()[ 'txindex']['synced'] is True) self.wait_until(lambda: is_finalblock(tip)) assert node.isfinaltransaction(wallet_txid) wallet_txid = node.sendtoaddress( ADDRESS_ECREG_UNSPENDABLE, 1_000_000) assert wallet_txid in node.getrawmempool() assert not node.isfinaltransaction(wallet_txid) assert_raises_rpc_error( -5, "No such mempool or blockchain transaction.", node.isfinaltransaction, uint256_hex(random.randint(0, 2**256 - 1)), ) self.log.info("Check unknown item") for _ in range(10): assert_raises_rpc_error( -8, "Block not found", node.isfinalblock, uint256_hex(random.randint(0, 2**256 - 1)), ) assert_raises_rpc_error( -8, "Block not found", node.isfinaltransaction, uint256_hex(random.randint(0, 2**256 - 1)), uint256_hex(random.randint(0, 2**256 - 1)), ) tip = node.getbestblockhash() height = node.getblockcount() + 1 time = node.getblock(tip)['time'] + 1 block = create_block(int(tip, 16), create_coinbase(height), time) block.solve() peer = node.add_p2p_connection(AvaP2PInterface()) msg = msg_headers() msg.headers = [CBlockHeader(block)] peer.send_message(msg) self.wait_until(lambda: node.getchaintips()[0]['height'] == height) assert_raises_rpc_error( -1, "Block data not downloaded yet.", node.isfinaltransaction, uint256_hex(random.randint(0, 2**256 - 1)), uint256_hex(block.sha256), ) if __name__ == '__main__': AvalancheIsFinalTest().main() diff --git a/test/functional/p2p_add_connections.py b/test/functional/p2p_add_connections.py index 49cb87678..3da8951bc 100755 --- a/test/functional/p2p_add_connections.py +++ b/test/functional/p2p_add_connections.py @@ -1,224 +1,224 @@ #!/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 add_outbound_p2p_connection test framework functionality""" import random from test_framework.messages import NODE_AVALANCHE, NODE_NETWORK from test_framework.p2p import P2PInterface from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, check_node_connections # From net.h MAX_OUTBOUND_FULL_RELAY_CONNECTIONS = 16 MAX_BLOCK_RELAY_ONLY_CONNECTIONS = 2 # Override DEFAULT_MAX_AVALANCHE_OUTBOUND_CONNECTIONS with # -maxavalancheoutbound MAX_AVALANCHE_OUTBOUND_CONNECTIONS = 12 class P2PFeelerReceiver(P2PInterface): def on_version(self, message): # The bitcoind node closes feeler connections as soon as a version # message is received from the test framework. Don't send any responses # to the node's version message since the connection will already be # closed. pass class P2PAddConnections(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 self.extra_args = [ [ "-avaproofstakeutxoconfirmations=1", f"-maxavalancheoutbound={MAX_AVALANCHE_OUTBOUND_CONNECTIONS}" ], [] ] def setup_network(self): self.setup_nodes() # Don't connect the nodes def add_outbounds(self, node, quantity, conn_type): services = NODE_NETWORK if conn_type == "avalanche": services |= NODE_AVALANCHE for _ in range(quantity): self.log.debug( f"Node {node.index}, {conn_type}: {self.p2p_idx[node.index]}") node.add_outbound_p2p_connection( P2PInterface(), p2p_idx=self.p2p_idx[node.index], connection_type=conn_type, services=services, ) self.p2p_idx[node.index] += 1 def simple_test(self): self.log.info( "Connect to various outbound peers in a predetermined way") self.p2p_idx = [0] * self.num_nodes self.log.info( f"Add {MAX_OUTBOUND_FULL_RELAY_CONNECTIONS} outbounds to node 0") self.add_outbounds( self.nodes[0], MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, "outbound-full-relay") self.log.info( f"Add {MAX_BLOCK_RELAY_ONLY_CONNECTIONS} block-relay-only connections to node 0") self.add_outbounds( self.nodes[0], MAX_BLOCK_RELAY_ONLY_CONNECTIONS, "block-relay-only") self.log.info( f"Add {MAX_BLOCK_RELAY_ONLY_CONNECTIONS} block-relay-only connections to node 1") self.add_outbounds( self.nodes[1], MAX_BLOCK_RELAY_ONLY_CONNECTIONS, "block-relay-only") self.log.info("Add 5 inbound connections to node 1") for i in range(5): self.log.info(f"inbound: {i}") self.nodes[1].add_p2p_connection(P2PInterface()) self.log.info("Add 4 outbounds to node 1") self.add_outbounds(self.nodes[1], 4, "outbound-full-relay") self.log.info("Check the connections opened as expected") check_node_connections( node=self.nodes[0], num_in=0, num_out=MAX_OUTBOUND_FULL_RELAY_CONNECTIONS + MAX_BLOCK_RELAY_ONLY_CONNECTIONS) check_node_connections( node=self.nodes[1], num_in=5, num_out=4 + MAX_BLOCK_RELAY_ONLY_CONNECTIONS) self.log.info("Disconnect p2p connections & try to re-open") self.nodes[0].disconnect_p2ps() self.p2p_idx[0] = 0 check_node_connections(node=self.nodes[0], num_in=0, num_out=0) self.log.info( f"Add {MAX_OUTBOUND_FULL_RELAY_CONNECTIONS} outbounds to node 0") self.add_outbounds( self.nodes[0], MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, "outbound-full-relay") check_node_connections( node=self.nodes[0], num_in=0, num_out=MAX_OUTBOUND_FULL_RELAY_CONNECTIONS) self.log.info( f"Add {MAX_BLOCK_RELAY_ONLY_CONNECTIONS} block-relay-only connections to node 0") self.add_outbounds( self.nodes[0], MAX_BLOCK_RELAY_ONLY_CONNECTIONS, "block-relay-only") check_node_connections( node=self.nodes[0], num_in=0, num_out=MAX_OUTBOUND_FULL_RELAY_CONNECTIONS + MAX_BLOCK_RELAY_ONLY_CONNECTIONS) self.log.info("Restart node 0 and try to reconnect to p2ps") self.restart_node(0) self.p2p_idx[0] = 0 self.log.info( f"Add {MAX_OUTBOUND_FULL_RELAY_CONNECTIONS} outbounds to node 0") self.add_outbounds( self.nodes[0], MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, "outbound-full-relay") check_node_connections( node=self.nodes[0], num_in=0, num_out=MAX_OUTBOUND_FULL_RELAY_CONNECTIONS) self.log.info( f"Add {MAX_BLOCK_RELAY_ONLY_CONNECTIONS} block-relay-only connections to node 0") self.add_outbounds( self.nodes[0], MAX_BLOCK_RELAY_ONLY_CONNECTIONS, "block-relay-only") check_node_connections( node=self.nodes[0], num_in=0, num_out=MAX_OUTBOUND_FULL_RELAY_CONNECTIONS + MAX_BLOCK_RELAY_ONLY_CONNECTIONS) check_node_connections( node=self.nodes[1], num_in=5, num_out=4 + MAX_BLOCK_RELAY_ONLY_CONNECTIONS) self.log.info("Add 1 feeler connection to node 0") feeler_conn = self.nodes[0].add_outbound_p2p_connection( P2PFeelerReceiver(), p2p_idx=self.p2p_idx[0], connection_type="feeler") # Feeler connection is closed assert not feeler_conn.is_connected # Verify version message received assert_equal(feeler_conn.message_count["version"], 1) # Feeler connections do not request tx relay assert_equal(feeler_conn.last_message["version"].relay, 0) self.log.info("Connecting avalanche outbounds") self.add_outbounds( self.nodes[0], MAX_AVALANCHE_OUTBOUND_CONNECTIONS, "avalanche") check_node_connections( node=self.nodes[0], num_in=0, num_out=MAX_OUTBOUND_FULL_RELAY_CONNECTIONS + MAX_BLOCK_RELAY_ONLY_CONNECTIONS + MAX_AVALANCHE_OUTBOUND_CONNECTIONS) def random_test(self): for node in self.nodes: node.disconnect_p2ps() self.p2p_idx = [0] * self.num_nodes remaining_outbounds = { "outbound-full-relay": MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, "block-relay-only": MAX_BLOCK_RELAY_ONLY_CONNECTIONS, "avalanche": MAX_AVALANCHE_OUTBOUND_CONNECTIONS, } max_outbounds = sum(remaining_outbounds.values()) iterations = random.randint(1, 5 * max_outbounds) self.log.info( f"Randomly insert outbounds of various types {iterations} times") for _ in range(iterations): - type = random.choice(list(remaining_outbounds)) - if remaining_outbounds[type] <= 0: + conn_type = random.choice(list(remaining_outbounds)) + if remaining_outbounds[conn_type] <= 0: continue - self.add_outbounds(self.nodes[0], 1, type) - remaining_outbounds[type] -= 1 + self.add_outbounds(self.nodes[0], 1, conn_type) + remaining_outbounds[conn_type] -= 1 check_node_connections( node=self.nodes[0], num_in=0, num_out=max_outbounds - sum(remaining_outbounds.values())) def run_test(self): self.simple_test() self.random_test() if __name__ == '__main__': P2PAddConnections().main() diff --git a/test/functional/rpc_rawtransaction.py b/test/functional/rpc_rawtransaction.py index 3875eeb58..332f192b1 100755 --- a/test/functional/rpc_rawtransaction.py +++ b/test/functional/rpc_rawtransaction.py @@ -1,726 +1,726 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2019 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the rawtranscation RPCs. Test the following RPCs: - createrawtransaction - signrawtransactionwithwallet - sendrawtransaction - decoderawtransaction - getrawtransaction """ from collections import OrderedDict from decimal import Decimal from io import BytesIO from test_framework.messages import COutPoint, CTransaction, CTxIn, CTxOut, ToHex from test_framework.script import CScript from test_framework.test_framework import BitcoinTestFramework from test_framework.txtools import pad_raw_tx from test_framework.util import ( assert_equal, assert_greater_than, assert_raises_rpc_error, find_vout_for_address, ) class multidict(dict): """Dictionary that allows duplicate keys. Constructed with a list of (key, value) tuples. When dumped by the json module, will output invalid json with repeated keys, eg: >>> json.dumps(multidict([(1,2),(1,2)]) '{"1": 2, "1": 2}' Used to test calls to rpc methods with repeated keys in the json object.""" def __init__(self, x): dict.__init__(self, x) self.x = x def items(self): return self.x # Create one-input, one-output, no-fee transaction: class RawTransactionsTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 3 self.extra_args = [["-txindex"], ["-txindex"], ["-txindex"]] # whitelist all 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 setup_network(self): super().setup_network() self.connect_nodes(0, 2) def run_test(self): self.log.info( 'prepare some coins for multiple *rawtransaction commands') self.generate(self.nodes[2], 1) self.generate(self.nodes[0], 101) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1500000) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1000000) self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 5000000) self.sync_all() self.generate(self.nodes[0], 5) self.log.info( 'Test getrawtransaction on genesis block coinbase returns an error') block = self.nodes[0].getblock(self.nodes[0].getblockhash(0)) assert_raises_rpc_error(-5, "The genesis block coinbase is not considered an ordinary transaction", self.nodes[0].getrawtransaction, block['merkleroot']) self.log.info( 'Check parameter types and required parameters of createrawtransaction') # Test `createrawtransaction` required parameters assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction) assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, []) # Test `createrawtransaction` invalid extra parameters assert_raises_rpc_error(-1, "createrawtransaction", self.nodes[0].createrawtransaction, [], {}, 0, 'foo') # Test `createrawtransaction` invalid `inputs` txid = '1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000' assert_raises_rpc_error(-3, "Expected type array", self.nodes[0].createrawtransaction, 'foo', {}) assert_raises_rpc_error(-1, "JSON value is not an object as expected", self.nodes[0].createrawtransaction, ['foo'], {}) assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[0].createrawtransaction, [{}], {}) assert_raises_rpc_error(-8, "txid must be of length 64 (not 3, for 'foo')", self.nodes[0].createrawtransaction, [{'txid': 'foo'}], {}) assert_raises_rpc_error(-8, "txid must be hexadecimal string (not 'ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844')", self.nodes[0].createrawtransaction, [{'txid': 'ZZZ7bb8b1697ea987f3b223ba7819250cae33efacb068d23dc24859824a77844'}], {}) assert_raises_rpc_error(-8, "Invalid parameter, missing vout key", self.nodes[0].createrawtransaction, [{'txid': txid}], {}) assert_raises_rpc_error(-8, "Invalid parameter, vout must be a number", self.nodes[0].createrawtransaction, [{'txid': txid, 'vout': 'foo'}], {}) assert_raises_rpc_error(-8, "Invalid parameter, vout cannot be negative", self.nodes[0].createrawtransaction, [{'txid': txid, 'vout': -1}], {}) assert_raises_rpc_error(-8, "Invalid parameter, sequence number is out of range", self.nodes[0].createrawtransaction, [{'txid': txid, 'vout': 0, 'sequence': -1}], {}) # Test `createrawtransaction` invalid `outputs` address = self.nodes[0].getnewaddress() address2 = self.nodes[0].getnewaddress() assert_raises_rpc_error(-1, "JSON value is not an array as expected", self.nodes[0].createrawtransaction, [], 'foo') # Should not throw for backwards compatibility self.nodes[0].createrawtransaction(inputs=[], outputs={}) self.nodes[0].createrawtransaction(inputs=[], outputs=[]) assert_raises_rpc_error(-8, "Data must be hexadecimal string", self.nodes[0].createrawtransaction, [], {'data': 'foo'}) assert_raises_rpc_error(-5, "Invalid Bitcoin address", self.nodes[0].createrawtransaction, [], {'foo': 0}) assert_raises_rpc_error(-3, "Invalid amount", self.nodes[0].createrawtransaction, [], {address: 'foo'}) assert_raises_rpc_error(-3, "Amount out of range", self.nodes[0].createrawtransaction, [], {address: -1}) assert_raises_rpc_error(-8, f"Invalid parameter, duplicated address: {address}", self.nodes[0].createrawtransaction, [], multidict([(address, 1), (address, 1)])) assert_raises_rpc_error(-8, f"Invalid parameter, duplicated address: {address}", self.nodes[0].createrawtransaction, [], [{address: 1}, {address: 1}]) assert_raises_rpc_error(-8, "Invalid parameter, duplicate key: data", self.nodes[0].createrawtransaction, [], [{"data": 'aa'}, {"data": "bb"}]) assert_raises_rpc_error(-8, "Invalid parameter, duplicate key: data", self.nodes[0].createrawtransaction, [], multidict([("data", 'aa'), ("data", "bb")])) assert_raises_rpc_error(-8, "Invalid parameter, key-value pair must contain exactly one key", self.nodes[0].createrawtransaction, [], [{'a': 1, 'b': 2}]) assert_raises_rpc_error(-8, "Invalid parameter, key-value pair not an object as expected", self.nodes[0].createrawtransaction, [], [['key-value pair1'], ['2']]) # Test `createrawtransaction` invalid `locktime` assert_raises_rpc_error(-3, "Expected type number", self.nodes[0].createrawtransaction, [], {}, 'foo') assert_raises_rpc_error(-8, "Invalid parameter, locktime out of range", self.nodes[0].createrawtransaction, [], {}, -1) assert_raises_rpc_error(-8, "Invalid parameter, locktime out of range", self.nodes[0].createrawtransaction, [], {}, 4294967296) self.log.info( 'Check that createrawtransaction accepts an array and object as outputs') tx = CTransaction() # One output tx.deserialize(BytesIO(bytes.fromhex(self.nodes[2].createrawtransaction( inputs=[{'txid': txid, 'vout': 9}], outputs={address: 99})))) assert_equal(len(tx.vout), 1) assert_equal( tx.serialize().hex(), self.nodes[2].createrawtransaction( inputs=[{'txid': txid, 'vout': 9}], outputs=[{address: 99}]), ) # Two outputs tx.deserialize(BytesIO(bytes.fromhex(self.nodes[2].createrawtransaction(inputs=[ {'txid': txid, 'vout': 9}], outputs=OrderedDict([(address, 99), (address2, 99)]))))) assert_equal(len(tx.vout), 2) assert_equal( tx.serialize().hex(), self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[ {address: 99}, {address2: 99}]), ) # Multiple mixed outputs tx.deserialize(BytesIO(bytes.fromhex(self.nodes[2].createrawtransaction(inputs=[ {'txid': txid, 'vout': 9}], outputs=multidict([(address, 99), (address2, 99), ('data', '99')]))))) assert_equal(len(tx.vout), 3) assert_equal( tx.serialize().hex(), self.nodes[2].createrawtransaction(inputs=[{'txid': txid, 'vout': 9}], outputs=[ {address: 99}, {address2: 99}, {'data': '99'}]), ) - for type in ["legacy"]: - addr = self.nodes[0].getnewaddress("", type) + for addr_type in ["legacy"]: + addr = self.nodes[0].getnewaddress("", addr_type) addrinfo = self.nodes[0].getaddressinfo(addr) pubkey = addrinfo["scriptPubKey"] self.log.info( - f'sendrawtransaction with missing prevtx info ({type})') + f'sendrawtransaction with missing prevtx info ({addr_type})') # Test `signrawtransactionwithwallet` invalid `prevtxs` inputs = [{'txid': txid, 'vout': 3, 'sequence': 1000}] outputs = {self.nodes[0].getnewaddress(): 1} rawtx = self.nodes[0].createrawtransaction(inputs, outputs) prevtx = {"txid": txid, "scriptPubKey": pubkey, "vout": 3, "amount": 1} succ = self.nodes[0].signrawtransactionwithwallet(rawtx, [prevtx]) assert succ["complete"] assert_raises_rpc_error(-8, "Missing amount", self.nodes[0].signrawtransactionwithwallet, rawtx, [ { "txid": txid, "scriptPubKey": pubkey, "vout": 3, } ]) assert_raises_rpc_error(-3, "Missing vout", self.nodes[0].signrawtransactionwithwallet, rawtx, [ { "txid": txid, "scriptPubKey": pubkey, "amount": 1, } ]) assert_raises_rpc_error(-3, "Missing txid", self.nodes[0].signrawtransactionwithwallet, rawtx, [ { "scriptPubKey": pubkey, "vout": 3, "amount": 1, } ]) assert_raises_rpc_error(-3, "Missing scriptPubKey", self.nodes[0].signrawtransactionwithwallet, rawtx, [ { "txid": txid, "vout": 3, "amount": 1 } ]) ######################################### # sendrawtransaction with missing input # ######################################### self.log.info('sendrawtransaction with missing input') # won't exists inputs = [ {'txid': "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'vout': 1}] outputs = {self.nodes[0].getnewaddress(): 4998000} rawtx = self.nodes[2].createrawtransaction(inputs, outputs) rawtx = pad_raw_tx(rawtx) rawtx = self.nodes[2].signrawtransactionwithwallet(rawtx) # This will raise an exception since there are missing inputs assert_raises_rpc_error(-25, "bad-txns-inputs-missingorspent", self.nodes[2].sendrawtransaction, rawtx['hex']) ##################################### # getrawtransaction with block hash # ##################################### # make a tx by sending then generate 2 blocks; block1 has the tx in it tx = self.nodes[2].sendtoaddress( self.nodes[1].getnewaddress(), 1000000) block1, block2 = self.generate(self.nodes[2], 2) self.sync_all() # We should be able to get the raw transaction by providing the correct # block gottx = self.nodes[0].getrawtransaction(tx, True, block1) assert_equal(gottx['txid'], tx) assert_equal(gottx['in_active_chain'], True) # We should not have the 'in_active_chain' flag when we don't provide a # block gottx = self.nodes[0].getrawtransaction(tx, True) assert_equal(gottx['txid'], tx) assert 'in_active_chain' not in gottx # We should not get the tx if we provide an unrelated block assert_raises_rpc_error(-5, "No such transaction found", self.nodes[0].getrawtransaction, tx, True, block2) # An invalid block hash should raise the correct errors assert_raises_rpc_error(-1, "JSON value is not a string as expected", self.nodes[0].getrawtransaction, tx, True, True) assert_raises_rpc_error(-8, "parameter 3 must be of length 64 (not 6, for 'foobar')", self.nodes[0].getrawtransaction, tx, True, "foobar") assert_raises_rpc_error(-8, "parameter 3 must be of length 64 (not 8, for 'abcd1234')", self.nodes[0].getrawtransaction, tx, True, "abcd1234") assert_raises_rpc_error( -8, "parameter 3 must be hexadecimal string (not 'ZZZ0000000000000000000000000000000000000000000000000000000000000')", self.nodes[0].getrawtransaction, tx, True, "ZZZ0000000000000000000000000000000000000000000000000000000000000") assert_raises_rpc_error(-5, "Block hash not found", self.nodes[0].getrawtransaction, tx, True, "0000000000000000000000000000000000000000000000000000000000000000") # Undo the blocks and check in_active_chain self.nodes[0].invalidateblock(block1) gottx = self.nodes[0].getrawtransaction( txid=tx, verbose=True, blockhash=block1) assert_equal(gottx['in_active_chain'], False) self.nodes[0].reconsiderblock(block1) assert_equal(self.nodes[0].getbestblockhash(), block2) if not self.options.descriptors: # The traditional multisig workflow does not work with descriptor # wallets so these are legacy only. # The multisig workflow with descriptor wallets uses PSBTs and is # tested elsewhere, no need to do them here. # # RAW TX MULTISIG TESTS # # # 2of2 test addr1 = self.nodes[2].getnewaddress() addr2 = self.nodes[2].getnewaddress() addr1Obj = self.nodes[2].getaddressinfo(addr1) addr2Obj = self.nodes[2].getaddressinfo(addr2) # Tests for createmultisig and addmultisigaddress assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 1, ["01020304"]) # createmultisig can only take public keys self.nodes[0].createmultisig( 2, [addr1Obj['pubkey'], addr2Obj['pubkey']]) # addmultisigaddress can take both pubkeys and addresses so long as # they are in the wallet, which is tested here. assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 2, [addr1Obj['pubkey'], addr1]) mSigObj = self.nodes[2].addmultisigaddress( 2, [addr1Obj['pubkey'], addr1])['address'] # use balance deltas instead of absolute values bal = self.nodes[2].getbalance() # send 1,200,000 XEC to msig adr txId = self.nodes[0].sendtoaddress(mSigObj, 1200000) self.sync_all() self.generate(self.nodes[0], 1) self.sync_all() # node2 has both keys of the 2of2 ms addr., tx should affect the # balance assert_equal(self.nodes[2].getbalance(), bal + Decimal('1200000.00')) # 2of3 test from different nodes bal = self.nodes[2].getbalance() addr1 = self.nodes[1].getnewaddress() addr2 = self.nodes[2].getnewaddress() addr3 = self.nodes[2].getnewaddress() addr1Obj = self.nodes[1].getaddressinfo(addr1) addr2Obj = self.nodes[2].getaddressinfo(addr2) addr3Obj = self.nodes[2].getaddressinfo(addr3) mSigObj = self.nodes[2].addmultisigaddress( 2, [addr1Obj['pubkey'], addr2Obj['pubkey'], addr3Obj['pubkey']] )['address'] txId = self.nodes[0].sendtoaddress(mSigObj, 2200000) decTx = self.nodes[0].gettransaction(txId) rawTx = self.nodes[0].decoderawtransaction(decTx['hex']) self.sync_all() self.generate(self.nodes[0], 1) self.sync_all() # THIS IS AN INCOMPLETE FEATURE # NODE2 HAS TWO OF THREE KEY AND THE FUNDS SHOULD BE SPENDABLE AND # COUNT AT BALANCE CALCULATION # for now, assume the funds of a 2of3 multisig tx are not marked as # spendable assert_equal(self.nodes[2].getbalance(), bal) txDetails = self.nodes[0].gettransaction(txId, True) rawTx = self.nodes[0].decoderawtransaction(txDetails['hex']) vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('2200000.00')) bal = self.nodes[0].getbalance() inputs = [{ "txid": txId, "vout": vout['n'], "scriptPubKey": vout['scriptPubKey']['hex'], "amount": vout['value'], }] outputs = {self.nodes[0].getnewaddress(): 2190000} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) rawTxPartialSigned = self.nodes[1].signrawtransactionwithwallet( rawTx, inputs) # node1 only has one key, can't comp. sign the tx assert_equal(rawTxPartialSigned['complete'], False) rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx, inputs) # node2 can sign the tx compl., own two of three keys assert_equal(rawTxSigned['complete'], True) self.nodes[2].sendrawtransaction(rawTxSigned['hex']) rawTx = self.nodes[0].decoderawtransaction(rawTxSigned['hex']) self.sync_all() self.generate(self.nodes[0], 1) self.sync_all() assert_equal(self.nodes[0].getbalance(), bal + Decimal( '50000000.00') + Decimal('2190000.00')) # block reward + tx rawTxBlock = self.nodes[0].getblock( self.nodes[0].getbestblockhash()) # 2of2 test for combining transactions bal = self.nodes[2].getbalance() addr1 = self.nodes[1].getnewaddress() addr2 = self.nodes[2].getnewaddress() addr1Obj = self.nodes[1].getaddressinfo(addr1) addr2Obj = self.nodes[2].getaddressinfo(addr2) self.nodes[1].addmultisigaddress( 2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address'] mSigObj = self.nodes[2].addmultisigaddress( 2, [addr1Obj['pubkey'], addr2Obj['pubkey']])['address'] mSigObjValid = self.nodes[2].getaddressinfo(mSigObj) txId = self.nodes[0].sendtoaddress(mSigObj, 2200000) decTx = self.nodes[0].gettransaction(txId) rawTx2 = self.nodes[0].decoderawtransaction(decTx['hex']) self.sync_all() self.generate(self.nodes[0], 1) self.sync_all() # the funds of a 2of2 multisig tx should not be marked as spendable assert_equal(self.nodes[2].getbalance(), bal) txDetails = self.nodes[0].gettransaction(txId, True) rawTx2 = self.nodes[0].decoderawtransaction(txDetails['hex']) vout = next(o for o in rawTx2['vout'] if o['value'] == Decimal('2200000.00')) bal = self.nodes[0].getbalance() inputs = [{"txid": txId, "vout": vout['n'], "scriptPubKey": vout['scriptPubKey']['hex'], "redeemScript": mSigObjValid['hex'], "amount": vout['value']}] outputs = {self.nodes[0].getnewaddress(): 2190000} rawTx2 = self.nodes[2].createrawtransaction(inputs, outputs) rawTxPartialSigned1 = self.nodes[1].signrawtransactionwithwallet( rawTx2, inputs) self.log.debug(rawTxPartialSigned1) # node1 only has one key, can't comp. sign the tx assert_equal(rawTxPartialSigned1['complete'], False) rawTxPartialSigned2 = self.nodes[2].signrawtransactionwithwallet( rawTx2, inputs) self.log.debug(rawTxPartialSigned2) # node2 only has one key, can't comp. sign the tx assert_equal(rawTxPartialSigned2['complete'], False) rawTxComb = self.nodes[2].combinerawtransaction( [rawTxPartialSigned1['hex'], rawTxPartialSigned2['hex']]) self.log.debug(rawTxComb) self.nodes[2].sendrawtransaction(rawTxComb) rawTx2 = self.nodes[0].decoderawtransaction(rawTxComb) self.sync_all() self.generate(self.nodes[0], 1) self.sync_all() # block reward + tx assert_equal(self.nodes[0].getbalance(), bal + Decimal('50000000.00') + Decimal('2190000.00')) # Sanity checks on verbose getrawtransaction output txId = rawTx["txid"] rawTxOutput = self.nodes[0].getrawtransaction(txId, True) assert_equal(rawTxOutput["hex"], rawTxSigned["hex"]) assert_equal(rawTxOutput["txid"], txId) assert_equal(rawTxOutput["hash"], txId) assert_greater_than(rawTxOutput["size"], 300) assert_equal(rawTxOutput["version"], 0x02) assert_equal(rawTxOutput["locktime"], 0) assert_equal(len(rawTxOutput["vin"]), 1) assert_equal(len(rawTxOutput["vout"]), 1) assert_equal(rawTxOutput["blockhash"], rawTxBlock["hash"]) assert_equal(rawTxOutput["confirmations"], 3) assert_equal(rawTxOutput["time"], rawTxBlock["time"]) assert_equal(rawTxOutput["blocktime"], rawTxBlock["time"]) # Basic signrawtransaction test addr = self.nodes[1].getnewaddress() txid = self.nodes[0].sendtoaddress(addr, 10_000_000) self.generate(self.nodes[0], 1) self.sync_all() vout = find_vout_for_address(self.nodes[1], txid, addr) rawTx = self.nodes[1].createrawtransaction( [{'txid': txid, 'vout': vout}], {self.nodes[1].getnewaddress(): 9_999_000}) rawTxSigned = self.nodes[1].signrawtransactionwithwallet(rawTx) txId = self.nodes[1].sendrawtransaction(rawTxSigned['hex']) self.generate(self.nodes[0], 1) self.sync_all() # getrawtransaction tests # 1. valid parameters - only supply txid assert_equal( self.nodes[0].getrawtransaction(txId), rawTxSigned['hex']) # 2. valid parameters - supply txid and 0 for non-verbose assert_equal( self.nodes[0].getrawtransaction(txId, 0), rawTxSigned['hex']) # 3. valid parameters - supply txid and False for non-verbose assert_equal(self.nodes[0].getrawtransaction(txId, False), rawTxSigned['hex']) # 4. valid parameters - supply txid and 1 for verbose. # We only check the "hex" field of the output so we don't need to # update this test every time the output format changes. assert_equal(self.nodes[0].getrawtransaction(txId, 1)["hex"], rawTxSigned['hex']) # 5. valid parameters - supply txid and True for non-verbose assert_equal(self.nodes[0].getrawtransaction(txId, True)["hex"], rawTxSigned['hex']) # 6. invalid parameters - supply txid and string "Flase" assert_raises_rpc_error(-1, "not a boolean", self.nodes[0].getrawtransaction, txId, "Flase") # 7. invalid parameters - supply txid and empty array assert_raises_rpc_error(-1, "not a boolean", self.nodes[0].getrawtransaction, txId, []) # 8. invalid parameters - supply txid and empty dict assert_raises_rpc_error( -1, "not a boolean", self.nodes[0].getrawtransaction, txId, {}) inputs = [ {'txid': "1d1d4e24ed99057e84c3f80fd8fbec79ed9e1acee37da269356ecea000000000", 'sequence': 1000}] outputs = {self.nodes[0].getnewaddress(): 1} assert_raises_rpc_error( -8, 'Invalid parameter, missing vout key', self.nodes[0].createrawtransaction, inputs, outputs) inputs[0]['vout'] = "1" assert_raises_rpc_error( -8, 'Invalid parameter, vout must be a number', self.nodes[0].createrawtransaction, inputs, outputs) inputs[0]['vout'] = -1 assert_raises_rpc_error( -8, 'Invalid parameter, vout cannot be negative', self.nodes[0].createrawtransaction, inputs, outputs) inputs[0]['vout'] = 1 rawtx = self.nodes[0].createrawtransaction(inputs, outputs) decrawtx = self.nodes[0].decoderawtransaction(rawtx) assert_equal(decrawtx['vin'][0]['sequence'], 1000) # 9. invalid parameters - sequence number out of range inputs[0]['sequence'] = -1 assert_raises_rpc_error( -8, 'Invalid parameter, sequence number is out of range', self.nodes[0].createrawtransaction, inputs, outputs) # 10. invalid parameters - sequence number out of range inputs[0]['sequence'] = 4294967296 assert_raises_rpc_error( -8, 'Invalid parameter, sequence number is out of range', self.nodes[0].createrawtransaction, inputs, outputs) inputs[0]['sequence'] = 4294967294 rawtx = self.nodes[0].createrawtransaction(inputs, outputs) decrawtx = self.nodes[0].decoderawtransaction(rawtx) assert_equal(decrawtx['vin'][0]['sequence'], 4294967294) #################################### # TRANSACTION VERSION NUMBER TESTS # #################################### # Test the minimum transaction version number that fits in a signed # 32-bit integer. # As transaction version is unsigned, this should convert to its # unsigned equivalent. tx = CTransaction() tx.nVersion = -0x80000000 rawtx = ToHex(tx) decrawtx = self.nodes[0].decoderawtransaction(rawtx) assert_equal(decrawtx['version'], 0x80000000) # Test the maximum transaction version number that fits in a signed # 32-bit integer. tx = CTransaction() tx.nVersion = 0x7fffffff rawtx = ToHex(tx) decrawtx = self.nodes[0].decoderawtransaction(rawtx) assert_equal(decrawtx['version'], 0x7fffffff) self.log.info('sendrawtransaction/testmempoolaccept with maxfeerate') # Test a transaction with a small fee. txId = self.nodes[0].sendtoaddress( self.nodes[2].getnewaddress(), 1000000) rawTx = self.nodes[0].getrawtransaction(txId, True) vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('1000000.00')) self.sync_all() inputs = [{"txid": txId, "vout": vout['n']}] # Fee 10,000 satoshis, (1,000,000 - (10000 sat * 0.01 XEC/sat)) = # 999900 outputs = {self.nodes[0].getnewaddress(): Decimal("999900.00")} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx) assert_equal(rawTxSigned['complete'], True) # Fee 10,000 satoshis, ~200 b transaction, fee rate should land around 50 sat/byte = 500 XEC/kB # Thus, testmempoolaccept should reject testres = self.nodes[2].testmempoolaccept( [rawTxSigned['hex']], 500.00)[0] assert_equal(testres['allowed'], False) assert_equal(testres['reject-reason'], 'max-fee-exceeded') # and sendrawtransaction should throw assert_raises_rpc_error(-25, 'Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)', self.nodes[2].sendrawtransaction, rawTxSigned['hex'], 10.00) # and the following calls should both succeed testres = self.nodes[2].testmempoolaccept( rawtxs=[rawTxSigned['hex']])[0] assert_equal(testres['allowed'], True) self.nodes[2].sendrawtransaction(hexstring=rawTxSigned['hex']) # Test a transaction with a large fee. txId = self.nodes[0].sendtoaddress( self.nodes[2].getnewaddress(), 1000000) rawTx = self.nodes[0].getrawtransaction(txId, True) vout = next(o for o in rawTx['vout'] if o['value'] == Decimal('1000000.00')) self.sync_all() inputs = [{"txid": txId, "vout": vout['n']}] # Fee 2,000,000 satoshis, (1,000,000 - (2,000,000 sat * 0.01 XEC/sat)) = # 980000 outputs = {self.nodes[0].getnewaddress(): Decimal("980000.00")} rawTx = self.nodes[2].createrawtransaction(inputs, outputs) rawTxSigned = self.nodes[2].signrawtransactionwithwallet(rawTx) assert_equal(rawTxSigned['complete'], True) # Fee 2,000,000 satoshis, ~100 b transaction, fee rate should land around 20,000 sat/byte = 200,000 XEC/kB # Thus, testmempoolaccept should reject testres = self.nodes[2].testmempoolaccept([rawTxSigned['hex']])[0] assert_equal(testres['allowed'], False) assert_equal(testres['reject-reason'], 'max-fee-exceeded') # and sendrawtransaction should throw assert_raises_rpc_error(-25, 'Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)', self.nodes[2].sendrawtransaction, rawTxSigned['hex']) # and the following calls should both succeed testres = self.nodes[2].testmempoolaccept( rawtxs=[rawTxSigned['hex']], maxfeerate='200000.00')[0] assert_equal(testres['allowed'], True) self.nodes[2].sendrawtransaction( hexstring=rawTxSigned['hex'], maxfeerate='200000.00') self.log.info( 'sendrawtransaction/testmempoolaccept with tx that is already in the chain') self.generate(self.nodes[2], 1) for node in self.nodes: testres = node.testmempoolaccept([rawTxSigned['hex']])[0] assert_equal(testres['allowed'], False) assert_equal(testres['reject-reason'], 'txn-already-known') assert_raises_rpc_error( -27, 'Transaction already in block chain', node.sendrawtransaction, rawTxSigned['hex']) ########################################## # Decoding weird scripts in transactions # ########################################## self.log.info('Decode correctly-formatted but weird transactions') tx = CTransaction() # empty self.nodes[0].decoderawtransaction(ToHex(tx)) # truncated push tx.vin.append(CTxIn(COutPoint(42, 0), b'\x4e\x00\x00')) tx.vin.append(CTxIn(COutPoint(42, 0), b'\x4c\x10TRUNC')) tx.vout.append(CTxOut(0, b'\x4e\x00\x00')) tx.vout.append(CTxOut(0, b'\x4c\x10TRUNC')) self.nodes[0].decoderawtransaction(ToHex(tx)) # giant pushes and long scripts tx.vin.append( CTxIn(COutPoint(42, 0), CScript([b'giant push' * 10000]))) tx.vout.append(CTxOut(0, CScript([b'giant push' * 10000]))) self.nodes[0].decoderawtransaction(ToHex(tx)) self.log.info('Refuse garbage after transaction') assert_raises_rpc_error(-22, 'TX decode failed', self.nodes[0].decoderawtransaction, f"{ToHex(tx)}00") if __name__ == '__main__': RawTransactionsTest().main() diff --git a/test/functional/wallet_coinbase_category.py b/test/functional/wallet_coinbase_category.py index b48d3b8fe..91628456c 100755 --- a/test/functional/wallet_coinbase_category.py +++ b/test/functional/wallet_coinbase_category.py @@ -1,59 +1,59 @@ #!/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}) def run_test(self): # Generate one block to an address address = self.nodes[0].getnewaddress() self.generatetoaddress(self.nodes[0], 1, address) - hash = self.nodes[0].getbestblockhash() - txid = self.nodes[0].getblock(hash)["tx"][0] + 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(hash) + self.nodes[0].invalidateblock(blockhash) # Coinbase transaction is now orphaned self.assert_category("orphan", address, txid, 100) if __name__ == '__main__': CoinbaseCategoryTest().main() diff --git a/test/functional/wallet_send.py b/test/functional/wallet_send.py index 0b8eab9fb..2896951e9 100755 --- a/test/functional/wallet_send.py +++ b/test/functional/wallet_send.py @@ -1,421 +1,421 @@ #!/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", ], ] 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): 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"] 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"] ) 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) else: try: assert_raises_rpc_error( 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) 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"]])) 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): 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) else: assert_greater_than( from_balance_before - from_wallet.getbalance(), amount) else: assert next( (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)) else: assert_equal( to_wallet.getbalances()["mine"]["untrusted_pending"], 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 }]) 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) 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 }]) 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) self.log.info("Don't broadcast...") res = self.test_send( 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) assert res["psbt"] self.log.info( "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) 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")) res = self.test_send( from_wallet=w4, to_wallet=w1, amount=1_000_000, include_watching=True, 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="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) 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")) # 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)")) # 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...") res = self.test_send( from_wallet=w0, to_wallet=w1, amount=51_000_000, inputs=[], 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")) res = self.test_send( from_wallet=w0, to_wallet=w1, amount=51_000_000, inputs=[utxo1], add_inputs=True, 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")) 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) 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) assert res["complete"] assert_equal( 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) assert res["complete"] assert res["txid"] txid = res["txid"] # Although the wallet finishes the transaction, it can't be added to # the mempool yet: - hex = self.nodes[0].gettransaction(res["txid"])["hex"] - res = self.nodes[0].testmempoolaccept([hex]) + 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([hex]) + 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(hex) + 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) 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) 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]) if __name__ == '__main__': WalletSendTest().main()