aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rwxr-xr-xtest/functional/feature_framework_unit_tests.py1
-rwxr-xr-xtest/functional/feature_rbf.py1
-rwxr-xr-xtest/functional/interface_rpc.py217
-rwxr-xr-xtest/functional/mempool_package_onemore.py1
-rwxr-xr-xtest/functional/mempool_packages.py2
-rwxr-xr-xtest/functional/rpc_packages.py32
-rw-r--r--test/functional/test_framework/authproxy.py9
-rw-r--r--test/functional/test_framework/crypto/secp256k1.py8
-rwxr-xr-xtest/functional/test_framework/test_node.py5
-rwxr-xr-xtest/functional/test_runner.py2
-rwxr-xr-xtest/functional/tool_wallet.py101
-rwxr-xr-xtest/fuzz/test_runner.py4
12 files changed, 338 insertions, 45 deletions
diff --git a/test/functional/feature_framework_unit_tests.py b/test/functional/feature_framework_unit_tests.py
index c9754e083c..f03f084bed 100755
--- a/test/functional/feature_framework_unit_tests.py
+++ b/test/functional/feature_framework_unit_tests.py
@@ -25,6 +25,7 @@ TEST_FRAMEWORK_MODULES = [
"crypto.muhash",
"crypto.poly1305",
"crypto.ripemd160",
+ "crypto.secp256k1",
"script",
"segwit_addr",
"wallet_util",
diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py
index c5eeaf66e0..739b9b9bb9 100755
--- a/test/functional/feature_rbf.py
+++ b/test/functional/feature_rbf.py
@@ -28,7 +28,6 @@ class ReplaceByFeeTest(BitcoinTestFramework):
self.num_nodes = 2
self.extra_args = [
[
- "-maxorphantx=1000",
"-limitancestorcount=50",
"-limitancestorsize=101",
"-limitdescendantcount=200",
diff --git a/test/functional/interface_rpc.py b/test/functional/interface_rpc.py
index e873e2da0b..b08ca42796 100755
--- a/test/functional/interface_rpc.py
+++ b/test/functional/interface_rpc.py
@@ -4,22 +4,80 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Tests some generic aspects of the RPC interface."""
+import json
import os
-from test_framework.authproxy import JSONRPCException
+from dataclasses import dataclass
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_greater_than_or_equal
from threading import Thread
+from typing import Optional
import subprocess
-def expect_http_status(expected_http_status, expected_rpc_code,
- fcn, *args):
- try:
- fcn(*args)
- raise AssertionError(f"Expected RPC error {expected_rpc_code}, got none")
- except JSONRPCException as exc:
- assert_equal(exc.error["code"], expected_rpc_code)
- assert_equal(exc.http_status, expected_http_status)
+RPC_INVALID_ADDRESS_OR_KEY = -5
+RPC_INVALID_PARAMETER = -8
+RPC_METHOD_NOT_FOUND = -32601
+RPC_INVALID_REQUEST = -32600
+RPC_PARSE_ERROR = -32700
+
+
+@dataclass
+class BatchOptions:
+ version: Optional[int] = None
+ notification: bool = False
+ request_fields: Optional[dict] = None
+ response_fields: Optional[dict] = None
+
+
+def format_request(options, idx, fields):
+ request = {}
+ if options.version == 1:
+ request.update(version="1.1")
+ elif options.version == 2:
+ request.update(jsonrpc="2.0")
+ elif options.version is not None:
+ raise NotImplementedError(f"Unknown JSONRPC version {options.version}")
+ if not options.notification:
+ request.update(id=idx)
+ request.update(fields)
+ if options.request_fields:
+ request.update(options.request_fields)
+ return request
+
+
+def format_response(options, idx, fields):
+ if options.version == 2 and options.notification:
+ return None
+ response = {}
+ if not options.notification:
+ response.update(id=idx)
+ if options.version == 2:
+ response.update(jsonrpc="2.0")
+ else:
+ response.update(result=None, error=None)
+ response.update(fields)
+ if options.response_fields:
+ response.update(options.response_fields)
+ return response
+
+
+def send_raw_rpc(node, raw_body: bytes) -> tuple[object, int]:
+ return node._request("POST", "/", raw_body)
+
+
+def send_json_rpc(node, body: object) -> tuple[object, int]:
+ raw = json.dumps(body).encode("utf-8")
+ return send_raw_rpc(node, raw)
+
+
+def expect_http_rpc_status(expected_http_status, expected_rpc_error_code, node, method, params, version=1, notification=False):
+ req = format_request(BatchOptions(version, notification), 0, {"method": method, "params": params})
+ response, status = send_json_rpc(node, req)
+
+ if expected_rpc_error_code is not None:
+ assert_equal(response["error"]["code"], expected_rpc_error_code)
+
+ assert_equal(status, expected_http_status)
def test_work_queue_getblock(node, got_exceeded_error):
@@ -48,37 +106,126 @@ class RPCInterfaceTest(BitcoinTestFramework):
assert_greater_than_or_equal(command['duration'], 0)
assert_equal(info['logpath'], os.path.join(self.nodes[0].chain_path, 'debug.log'))
- def test_batch_request(self):
- self.log.info("Testing basic JSON-RPC batch request...")
-
- results = self.nodes[0].batch([
+ def test_batch_request(self, call_options):
+ calls = [
# A basic request that will work fine.
- {"method": "getblockcount", "id": 1},
+ {"method": "getblockcount"},
# Request that will fail. The whole batch request should still
# work fine.
- {"method": "invalidmethod", "id": 2},
+ {"method": "invalidmethod"},
# Another call that should succeed.
- {"method": "getblockhash", "id": 3, "params": [0]},
- ])
-
- result_by_id = {}
- for res in results:
- result_by_id[res["id"]] = res
-
- assert_equal(result_by_id[1]['error'], None)
- assert_equal(result_by_id[1]['result'], 0)
-
- assert_equal(result_by_id[2]['error']['code'], -32601)
- assert_equal(result_by_id[2]['result'], None)
-
- assert_equal(result_by_id[3]['error'], None)
- assert result_by_id[3]['result'] is not None
+ {"method": "getblockhash", "params": [0]},
+ # Invalid request format
+ {"pizza": "sausage"}
+ ]
+ results = [
+ {"result": 0},
+ {"error": {"code": RPC_METHOD_NOT_FOUND, "message": "Method not found"}},
+ {"result": "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"},
+ {"error": {"code": RPC_INVALID_REQUEST, "message": "Missing method"}},
+ ]
+
+ request = []
+ response = []
+ for idx, (call, result) in enumerate(zip(calls, results), 1):
+ options = call_options(idx)
+ if options is None:
+ continue
+ request.append(format_request(options, idx, call))
+ r = format_response(options, idx, result)
+ if r is not None:
+ response.append(r)
+
+ rpc_response, http_status = send_json_rpc(self.nodes[0], request)
+ if len(response) == 0 and len(request) > 0:
+ assert_equal(http_status, 204)
+ assert_equal(rpc_response, None)
+ else:
+ assert_equal(http_status, 200)
+ assert_equal(rpc_response, response)
+
+ def test_batch_requests(self):
+ self.log.info("Testing empty batch request...")
+ self.test_batch_request(lambda idx: None)
+
+ self.log.info("Testing basic JSON-RPC 2.0 batch request...")
+ self.test_batch_request(lambda idx: BatchOptions(version=2))
+
+ self.log.info("Testing JSON-RPC 2.0 batch with notifications...")
+ self.test_batch_request(lambda idx: BatchOptions(version=2, notification=idx < 2))
+
+ self.log.info("Testing JSON-RPC 2.0 batch of ALL notifications...")
+ self.test_batch_request(lambda idx: BatchOptions(version=2, notification=True))
+
+ # JSONRPC 1.1 does not support batch requests, but test them for backwards compatibility.
+ self.log.info("Testing nonstandard JSON-RPC 1.1 batch request...")
+ self.test_batch_request(lambda idx: BatchOptions(version=1))
+
+ self.log.info("Testing nonstandard mixed JSON-RPC 1.1/2.0 batch request...")
+ self.test_batch_request(lambda idx: BatchOptions(version=2 if idx % 2 else 1))
+
+ self.log.info("Testing nonstandard batch request without version numbers...")
+ self.test_batch_request(lambda idx: BatchOptions())
+
+ self.log.info("Testing nonstandard batch request without version numbers or ids...")
+ self.test_batch_request(lambda idx: BatchOptions(notification=True))
+
+ self.log.info("Testing nonstandard jsonrpc 1.0 version number is accepted...")
+ self.test_batch_request(lambda idx: BatchOptions(request_fields={"jsonrpc": "1.0"}))
+
+ self.log.info("Testing unrecognized jsonrpc version number is rejected...")
+ self.test_batch_request(lambda idx: BatchOptions(
+ request_fields={"jsonrpc": "2.1"},
+ response_fields={"result": None, "error": {"code": RPC_INVALID_REQUEST, "message": "JSON-RPC version not supported"}}))
def test_http_status_codes(self):
- self.log.info("Testing HTTP status codes for JSON-RPC requests...")
-
- expect_http_status(404, -32601, self.nodes[0].invalidmethod)
- expect_http_status(500, -8, self.nodes[0].getblockhash, 42)
+ self.log.info("Testing HTTP status codes for JSON-RPC 1.1 requests...")
+ # OK
+ expect_http_rpc_status(200, None, self.nodes[0], "getblockhash", [0])
+ # Errors
+ expect_http_rpc_status(404, RPC_METHOD_NOT_FOUND, self.nodes[0], "invalidmethod", [])
+ expect_http_rpc_status(500, RPC_INVALID_PARAMETER, self.nodes[0], "getblockhash", [42])
+ # force-send empty request
+ response, status = send_raw_rpc(self.nodes[0], b"")
+ assert_equal(response, {"id": None, "result": None, "error": {"code": RPC_PARSE_ERROR, "message": "Parse error"}})
+ assert_equal(status, 500)
+ # force-send invalidly formatted request
+ response, status = send_raw_rpc(self.nodes[0], b"this is bad")
+ assert_equal(response, {"id": None, "result": None, "error": {"code": RPC_PARSE_ERROR, "message": "Parse error"}})
+ assert_equal(status, 500)
+
+ self.log.info("Testing HTTP status codes for JSON-RPC 2.0 requests...")
+ # OK
+ expect_http_rpc_status(200, None, self.nodes[0], "getblockhash", [0], 2, False)
+ # RPC errors but not HTTP errors
+ expect_http_rpc_status(200, RPC_METHOD_NOT_FOUND, self.nodes[0], "invalidmethod", [], 2, False)
+ expect_http_rpc_status(200, RPC_INVALID_PARAMETER, self.nodes[0], "getblockhash", [42], 2, False)
+ # force-send invalidly formatted requests
+ response, status = send_json_rpc(self.nodes[0], {"jsonrpc": 2, "method": "getblockcount"})
+ assert_equal(response, {"result": None, "error": {"code": RPC_INVALID_REQUEST, "message": "jsonrpc field must be a string"}})
+ assert_equal(status, 400)
+ response, status = send_json_rpc(self.nodes[0], {"jsonrpc": "3.0", "method": "getblockcount"})
+ assert_equal(response, {"result": None, "error": {"code": RPC_INVALID_REQUEST, "message": "JSON-RPC version not supported"}})
+ assert_equal(status, 400)
+
+ self.log.info("Testing HTTP status codes for JSON-RPC 2.0 notifications...")
+ # Not notification: id exists
+ response, status = send_json_rpc(self.nodes[0], {"jsonrpc": "2.0", "id": None, "method": "getblockcount"})
+ assert_equal(response["result"], 0)
+ assert_equal(status, 200)
+ # Not notification: JSON 1.1
+ expect_http_rpc_status(200, None, self.nodes[0], "getblockcount", [], 1)
+ # Not notification: has "id" field
+ expect_http_rpc_status(200, None, self.nodes[0], "getblockcount", [], 2, False)
+ block_count = self.nodes[0].getblockcount()
+ # Notification response status code: HTTP_NO_CONTENT
+ expect_http_rpc_status(204, None, self.nodes[0], "generatetoaddress", [1, "bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqdku202"], 2, True)
+ # The command worked even though there was no response
+ assert_equal(block_count + 1, self.nodes[0].getblockcount())
+ # No error response for notifications even if they are invalid
+ expect_http_rpc_status(204, None, self.nodes[0], "generatetoaddress", [1, "invalid_address"], 2, True)
+ # Sanity check: command was not executed
+ assert_equal(block_count + 1, self.nodes[0].getblockcount())
def test_work_queue_exceeded(self):
self.log.info("Testing work queue exceeded...")
@@ -94,7 +241,7 @@ class RPCInterfaceTest(BitcoinTestFramework):
def run_test(self):
self.test_getrpcinfo()
- self.test_batch_request()
+ self.test_batch_requests()
self.test_http_status_codes()
self.test_work_queue_exceeded()
diff --git a/test/functional/mempool_package_onemore.py b/test/functional/mempool_package_onemore.py
index 921c190668..98b397e32b 100755
--- a/test/functional/mempool_package_onemore.py
+++ b/test/functional/mempool_package_onemore.py
@@ -21,7 +21,6 @@ from test_framework.wallet import MiniWallet
class MempoolPackagesTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
- self.extra_args = [["-maxorphantx=1000"]]
def chain_tx(self, utxos_to_spend, *, num_outputs=1):
return self.wallet.send_self_transfer_multi(
diff --git a/test/functional/mempool_packages.py b/test/functional/mempool_packages.py
index e83c62915e..4be6594de6 100755
--- a/test/functional/mempool_packages.py
+++ b/test/functional/mempool_packages.py
@@ -31,10 +31,8 @@ class MempoolPackagesTest(BitcoinTestFramework):
self.noban_tx_relay = True
self.extra_args = [
[
- "-maxorphantx=1000",
],
[
- "-maxorphantx=1000",
"-limitancestorcount={}".format(CUSTOM_ANCESTOR_LIMIT),
"-limitdescendantcount={}".format(CUSTOM_DESCENDANT_LIMIT),
],
diff --git a/test/functional/rpc_packages.py b/test/functional/rpc_packages.py
index 8ac0afdaaa..113424c0a6 100755
--- a/test/functional/rpc_packages.py
+++ b/test/functional/rpc_packages.py
@@ -23,6 +23,7 @@ from test_framework.util import (
assert_raises_rpc_error,
)
from test_framework.wallet import (
+ COIN,
DEFAULT_FEE,
MiniWallet,
)
@@ -242,6 +243,37 @@ class RPCPackagesTest(BitcoinTestFramework):
{"txid": tx2["txid"], "wtxid": tx2["wtxid"], "package-error": "conflict-in-package"}
])
+ # Add a child that spends both at high feerate to submit via submitpackage
+ tx_child = self.wallet.create_self_transfer_multi(
+ fee_per_output=int(DEFAULT_FEE * 5 * COIN),
+ utxos_to_spend=[tx1["new_utxo"], tx2["new_utxo"]],
+ )
+
+ testres = node.testmempoolaccept([tx1["hex"], tx2["hex"], tx_child["hex"]])
+
+ assert_equal(testres, [
+ {"txid": tx1["txid"], "wtxid": tx1["wtxid"], "package-error": "conflict-in-package"},
+ {"txid": tx2["txid"], "wtxid": tx2["wtxid"], "package-error": "conflict-in-package"},
+ {"txid": tx_child["txid"], "wtxid": tx_child["wtxid"], "package-error": "conflict-in-package"}
+ ])
+
+ submitres = node.submitpackage([tx1["hex"], tx2["hex"], tx_child["hex"]])
+ assert_equal(submitres, {'package_msg': 'conflict-in-package', 'tx-results': {}, 'replaced-transactions': []})
+
+ # Submit tx1 to mempool, then try the same package again
+ node.sendrawtransaction(tx1["hex"])
+
+ submitres = node.submitpackage([tx1["hex"], tx2["hex"], tx_child["hex"]])
+ assert_equal(submitres, {'package_msg': 'conflict-in-package', 'tx-results': {}, 'replaced-transactions': []})
+ assert tx_child["txid"] not in node.getrawmempool()
+
+ # ... and without the in-mempool ancestor tx1 included in the call
+ submitres = node.submitpackage([tx2["hex"], tx_child["hex"]])
+ assert_equal(submitres, {'package_msg': 'package-not-child-with-unconfirmed-parents', 'tx-results': {}, 'replaced-transactions': []})
+
+ # Regardless of error type, the child can never enter the mempool
+ assert tx_child["txid"] not in node.getrawmempool()
+
def test_rbf(self):
node = self.nodes[0]
diff --git a/test/functional/test_framework/authproxy.py b/test/functional/test_framework/authproxy.py
index 03042877b2..7edf9f3679 100644
--- a/test/functional/test_framework/authproxy.py
+++ b/test/functional/test_framework/authproxy.py
@@ -160,6 +160,15 @@ class AuthServiceProxy():
raise JSONRPCException({
'code': -342, 'message': 'missing HTTP response from server'})
+ # Check for no-content HTTP status code, which can be returned when an
+ # RPC client requests a JSON-RPC 2.0 "notification" with no response.
+ # Currently this is only possible if clients call the _request() method
+ # directly to send a raw request.
+ if http_response.status == HTTPStatus.NO_CONTENT:
+ if len(http_response.read()) != 0:
+ raise JSONRPCException({'code': -342, 'message': 'Content received with NO CONTENT status code'})
+ return None, http_response.status
+
content_type = http_response.getheader('Content-Type')
if content_type != 'application/json':
raise JSONRPCException(
diff --git a/test/functional/test_framework/crypto/secp256k1.py b/test/functional/test_framework/crypto/secp256k1.py
index 2e9e419da5..50a46dce37 100644
--- a/test/functional/test_framework/crypto/secp256k1.py
+++ b/test/functional/test_framework/crypto/secp256k1.py
@@ -15,6 +15,8 @@ Exports:
* G: the secp256k1 generator point
"""
+import unittest
+from hashlib import sha256
class FE:
"""Objects of this class represent elements of the field GF(2**256 - 2**32 - 977).
@@ -344,3 +346,9 @@ class FastGEMul:
# Precomputed table with multiples of G for fast multiplication
FAST_G = FastGEMul(G)
+
+class TestFrameworkSecp256k1(unittest.TestCase):
+ def test_H(self):
+ H = sha256(G.to_bytes_uncompressed()).digest()
+ assert GE.lift_x(FE.from_bytes(H)) is not None
+ self.assertEqual(H.hex(), "50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0")
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index d228bd8991..4ba92a7b1f 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -419,8 +419,9 @@ class TestNode():
return True
def wait_until_stopped(self, *, timeout=BITCOIND_PROC_WAIT_TIMEOUT, expect_error=False, **kwargs):
- expected_ret_code = 1 if expect_error else 0 # Whether node shutdown return EXIT_FAILURE or EXIT_SUCCESS
- self.wait_until(lambda: self.is_node_stopped(expected_ret_code=expected_ret_code, **kwargs), timeout=timeout)
+ if "expected_ret_code" not in kwargs:
+ kwargs["expected_ret_code"] = 1 if expect_error else 0 # Whether node shutdown return EXIT_FAILURE or EXIT_SUCCESS
+ self.wait_until(lambda: self.is_node_stopped(**kwargs), timeout=timeout)
def replace_in_config(self, replacements):
"""
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 690ab64c83..725b116281 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -183,6 +183,8 @@ BASE_SCRIPTS = [
'mempool_resurrect.py',
'wallet_txn_doublespend.py --mineblock',
'tool_wallet.py --legacy-wallet',
+ 'tool_wallet.py --legacy-wallet --bdbro',
+ 'tool_wallet.py --legacy-wallet --bdbro --swap-bdb-endian',
'tool_wallet.py --descriptors',
'tool_signet_miner.py --legacy-wallet',
'tool_signet_miner.py --descriptors',
diff --git a/test/functional/tool_wallet.py b/test/functional/tool_wallet.py
index fc042bca66..dcf74f6075 100755
--- a/test/functional/tool_wallet.py
+++ b/test/functional/tool_wallet.py
@@ -5,6 +5,7 @@
"""Test bitcoin-wallet."""
import os
+import platform
import stat
import subprocess
import textwrap
@@ -14,6 +15,7 @@ from collections import OrderedDict
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
+ assert_greater_than,
sha256sum_file,
)
@@ -21,11 +23,15 @@ from test_framework.util import (
class ToolWalletTest(BitcoinTestFramework):
def add_options(self, parser):
self.add_wallet_options(parser)
+ parser.add_argument("--bdbro", action="store_true", help="Use the BerkeleyRO internal parser when dumping a Berkeley DB wallet file")
+ parser.add_argument("--swap-bdb-endian", action="store_true",help="When making Legacy BDB wallets, always make then byte swapped internally")
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True
self.rpc_timeout = 120
+ if self.options.swap_bdb_endian:
+ self.extra_args = [["-swapbdbendian"]]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
@@ -35,15 +41,21 @@ class ToolWalletTest(BitcoinTestFramework):
default_args = ['-datadir={}'.format(self.nodes[0].datadir_path), '-chain=%s' % self.chain]
if not self.options.descriptors and 'create' in args:
default_args.append('-legacy')
+ if "dump" in args and self.options.bdbro:
+ default_args.append("-withinternalbdb")
return subprocess.Popen([self.options.bitcoinwallet] + default_args + list(args), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
def assert_raises_tool_error(self, error, *args):
p = self.bitcoin_wallet_process(*args)
stdout, stderr = p.communicate()
- assert_equal(p.poll(), 1)
assert_equal(stdout, '')
- assert_equal(stderr.strip(), error)
+ if isinstance(error, tuple):
+ assert_equal(p.poll(), error[0])
+ assert error[1] in stderr.strip()
+ else:
+ assert_equal(p.poll(), 1)
+ assert error in stderr.strip()
def assert_tool_output(self, output, *args):
p = self.bitcoin_wallet_process(*args)
@@ -451,6 +463,88 @@ class ToolWalletTest(BitcoinTestFramework):
''')
self.assert_tool_output(expected_output, "-wallet=conflicts", "info")
+ def test_dump_endianness(self):
+ self.log.info("Testing dumps of the same contents with different BDB endianness")
+
+ self.start_node(0)
+ self.nodes[0].createwallet("endian")
+ self.stop_node(0)
+
+ wallet_dump = self.nodes[0].datadir_path / "endian.dump"
+ self.assert_tool_output("The dumpfile may contain private keys. To ensure the safety of your Bitcoin, do not share the dumpfile.\n", "-wallet=endian", f"-dumpfile={wallet_dump}", "dump")
+ expected_dump = self.read_dump(wallet_dump)
+
+ self.do_tool_createfromdump("native_endian", "endian.dump", "bdb")
+ native_dump = self.read_dump(self.nodes[0].datadir_path / "rt-native_endian.dump")
+ self.assert_dump(expected_dump, native_dump)
+
+ self.do_tool_createfromdump("other_endian", "endian.dump", "bdb_swap")
+ other_dump = self.read_dump(self.nodes[0].datadir_path / "rt-other_endian.dump")
+ self.assert_dump(expected_dump, other_dump)
+
+ def test_dump_very_large_records(self):
+ self.log.info("Test that wallets with large records are successfully dumped")
+
+ self.start_node(0)
+ self.nodes[0].createwallet("bigrecords")
+ wallet = self.nodes[0].get_wallet_rpc("bigrecords")
+
+ # Both BDB and sqlite have maximum page sizes of 65536 bytes, with defaults of 4096
+ # When a record exceeds some size threshold, both BDB and SQLite will store the data
+ # in one or more overflow pages. We want to make sure that our tooling can dump such
+ # records, even when they span multiple pages. To make a large record, we just need
+ # to make a very big transaction.
+ self.generate(self.nodes[0], 101)
+ def_wallet = self.nodes[0].get_wallet_rpc(self.default_wallet_name)
+ outputs = {}
+ for i in range(500):
+ outputs[wallet.getnewaddress(address_type="p2sh-segwit")] = 0.01
+ def_wallet.sendmany(amounts=outputs)
+ self.generate(self.nodes[0], 1)
+ send_res = wallet.sendall([def_wallet.getnewaddress()])
+ self.generate(self.nodes[0], 1)
+ assert_equal(send_res["complete"], True)
+ tx = wallet.gettransaction(txid=send_res["txid"], verbose=True)
+ assert_greater_than(tx["decoded"]["size"], 70000)
+
+ self.stop_node(0)
+
+ wallet_dump = self.nodes[0].datadir_path / "bigrecords.dump"
+ self.assert_tool_output("The dumpfile may contain private keys. To ensure the safety of your Bitcoin, do not share the dumpfile.\n", "-wallet=bigrecords", f"-dumpfile={wallet_dump}", "dump")
+ dump = self.read_dump(wallet_dump)
+ for k,v in dump.items():
+ if tx["hex"] in v:
+ break
+ else:
+ assert False, "Big transaction was not found in wallet dump"
+
+ def test_dump_unclean_lsns(self):
+ if not self.options.bdbro:
+ return
+ self.log.info("Test that a legacy wallet that has not been compacted is not dumped by bdbro")
+
+ self.start_node(0, extra_args=["-flushwallet=0"])
+ self.nodes[0].createwallet("unclean_lsn")
+ wallet = self.nodes[0].get_wallet_rpc("unclean_lsn")
+ # First unload and load normally to make sure everything is written
+ wallet.unloadwallet()
+ self.nodes[0].loadwallet("unclean_lsn")
+ # Next cause a bunch of writes by filling the keypool
+ wallet.keypoolrefill(wallet.getwalletinfo()["keypoolsize"] + 100)
+ # Lastly kill bitcoind so that the LSNs don't get reset
+ self.nodes[0].process.kill()
+ self.nodes[0].wait_until_stopped(expected_ret_code=1 if platform.system() == "Windows" else -9)
+ assert self.nodes[0].is_node_stopped()
+
+ wallet_dump = self.nodes[0].datadir_path / "unclean_lsn.dump"
+ self.assert_raises_tool_error("LSNs are not reset, this database is not completely flushed. Please reopen then close the database with a version that has BDB support", "-wallet=unclean_lsn", f"-dumpfile={wallet_dump}", "dump")
+
+ # File can be dumped after reload it normally
+ self.start_node(0)
+ self.nodes[0].loadwallet("unclean_lsn")
+ self.stop_node(0)
+ self.assert_tool_output("The dumpfile may contain private keys. To ensure the safety of your Bitcoin, do not share the dumpfile.\n", "-wallet=unclean_lsn", f"-dumpfile={wallet_dump}", "dump")
+
def run_test(self):
self.wallet_path = self.nodes[0].wallets_path / self.default_wallet_name / self.wallet_data_filename
self.test_invalid_tool_commands_and_args()
@@ -462,8 +556,11 @@ class ToolWalletTest(BitcoinTestFramework):
if not self.options.descriptors:
# Salvage is a legacy wallet only thing
self.test_salvage()
+ self.test_dump_endianness()
+ self.test_dump_unclean_lsns()
self.test_dump_createfromdump()
self.test_chainless_conflicts()
+ self.test_dump_very_large_records()
if __name__ == '__main__':
ToolWalletTest().main()
diff --git a/test/fuzz/test_runner.py b/test/fuzz/test_runner.py
index a635175e7c..c74246ef45 100755
--- a/test/fuzz/test_runner.py
+++ b/test/fuzz/test_runner.py
@@ -215,12 +215,12 @@ def transform_process_message_target(targets, src_dir):
p2p_msg_target = "process_message"
if (p2p_msg_target, {}) in targets:
lines = subprocess.run(
- ["git", "grep", "--function-context", "g_all_net_message_types{", src_dir / "src" / "protocol.cpp"],
+ ["git", "grep", "--function-context", "ALL_NET_MESSAGE_TYPES{", src_dir / "src" / "protocol.h"],
check=True,
stdout=subprocess.PIPE,
text=True,
).stdout.splitlines()
- lines = [l.split("::", 1)[1].split(",")[0].lower() for l in lines if l.startswith("src/protocol.cpp- NetMsgType::")]
+ lines = [l.split("::", 1)[1].split(",")[0].lower() for l in lines if l.startswith("src/protocol.h- NetMsgType::")]
assert len(lines)
targets += [(p2p_msg_target, {"LIMIT_TO_MESSAGE_TYPE": m}) for m in lines]
return targets