1 #!/usr/bin/env python3
2 # Copyright (c) 2014-present The Bitcoin Core developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 """Helpful routines for regression testing."""
6 7 from base64 import b64encode
8 from copy import copy
9 from decimal import Decimal
10 from subprocess import CalledProcessError
11 import hashlib
12 import inspect
13 import json
14 import logging
15 import os
16 import pathlib
17 import platform
18 import random
19 import re
20 import shlex
21 import time
22 import types
23 24 from .descriptors import descsum_create
25 from collections.abc import Callable
26 from typing import Optional, Union
27 28 SATOSHI_PRECISION = Decimal('0.00000001')
29 30 logger = logging.getLogger("TestFramework.utils")
31 32 class JSONRPCException(Exception):
33 def __init__(self, rpc_error, http_status=None):
34 self.error = rpc_error
35 self.http_status = http_status
36 37 # throw KeyError if any required fields are missing
38 copied_error = copy(rpc_error)
39 message = copied_error.pop("message")
40 code = copied_error.pop("code")
41 extra = f'{copied_error}' if copied_error else ''
42 super().__init__(f"{message} ({code}) {extra} [http_status={http_status}]")
43 44 # Assert functions
45 ##################
46 47 48 def assert_approx(v, vexp, vspan=0.00001):
49 """Assert that `v` is within `vspan` of `vexp`"""
50 if isinstance(v, Decimal) or isinstance(vexp, Decimal):
51 v=Decimal(v)
52 vexp=Decimal(vexp)
53 vspan=Decimal(vspan)
54 if v < vexp - vspan:
55 raise AssertionError("%s < [%s..%s]" % (str(v), str(vexp - vspan), str(vexp + vspan)))
56 if v > vexp + vspan:
57 raise AssertionError("%s > [%s..%s]" % (str(v), str(vexp - vspan), str(vexp + vspan)))
58 59 60 def assert_fee_amount(fee, tx_size, feerate_BTC_kvB):
61 """Assert the fee is in range."""
62 assert isinstance(tx_size, int)
63 target_fee = get_fee(tx_size, feerate_BTC_kvB)
64 if fee < target_fee:
65 raise AssertionError("Fee of %s BTC too low! (Should be %s BTC)" % (str(fee), str(target_fee)))
66 # allow the wallet's estimation to be at most 2 bytes off
67 high_fee = get_fee(tx_size + 2, feerate_BTC_kvB)
68 if fee > high_fee:
69 raise AssertionError("Fee of %s BTC too high! (Should be %s BTC)" % (str(fee), str(target_fee)))
70 71 72 def summarise_dict_differences(thing1, thing2):
73 if not isinstance(thing1, dict) or not isinstance(thing2, dict):
74 return thing1, thing2
75 d1, d2 = {}, {}
76 for k in sorted(thing1.keys()):
77 if k not in thing2:
78 d1[k] = thing1[k]
79 elif thing1[k] != thing2[k]:
80 d1[k], d2[k] = summarise_dict_differences(thing1[k], thing2[k])
81 for k in sorted(thing2.keys()):
82 if k not in thing1:
83 d2[k] = thing2[k]
84 return d1, d2
85 86 def assert_equal(thing1, thing2, *args):
87 if thing1 != thing2 and not args and isinstance(thing1, dict) and isinstance(thing2, dict):
88 d1,d2 = summarise_dict_differences(thing1, thing2)
89 if d1 != thing1 or d2 != thing2:
90 raise AssertionError(f"not({thing1!s} == {thing2!s})\n in particular not({d1!s} == {d2!s})")
91 else:
92 raise AssertionError(f"not({thing1!s} == {thing2!s})")
93 if thing1 != thing2 or any(thing1 != arg for arg in args):
94 raise AssertionError("not(%s)" % " == ".join(str(arg) for arg in (thing1, thing2) + args))
95 96 def assert_not_equal(thing1, thing2, *, error_message=""):
97 if thing1 == thing2:
98 raise AssertionError(f"Both values are {thing1}{f', {error_message}' if error_message else ''}")
99 100 101 def assert_greater_than(thing1, thing2):
102 if thing1 <= thing2:
103 raise AssertionError("%s <= %s" % (str(thing1), str(thing2)))
104 105 106 def assert_greater_than_or_equal(thing1, thing2):
107 if thing1 < thing2:
108 raise AssertionError("%s < %s" % (str(thing1), str(thing2)))
109 110 111 def assert_raises(exc, fun, *args, **kwds):
112 assert_raises_message(exc, None, fun, *args, **kwds)
113 114 115 def assert_raises_message(exc, message, fun, *args, **kwds):
116 try:
117 fun(*args, **kwds)
118 except JSONRPCException:
119 raise AssertionError("Use assert_raises_rpc_error() to test RPC failures")
120 except exc as e:
121 if message is not None and message not in str(e):
122 raise AssertionError("Expected substring not found in exception:\n"
123 f"substring: '{message}'\nexception: {e!r}.")
124 except Exception as e:
125 raise AssertionError("Unexpected exception raised: " + type(e).__name__)
126 else:
127 raise AssertionError("No exception raised")
128 129 130 def assert_raises_process_error(returncode: int, output: str, fun: Callable, *args, **kwds):
131 """Execute a process and asserts the process return code and output.
132 133 Calls function `fun` with arguments `args` and `kwds`. Catches a CalledProcessError
134 and verifies that the return code and output are as expected. Throws AssertionError if
135 no CalledProcessError was raised or if the return code and output are not as expected.
136 137 Args:
138 returncode: the process return code.
139 output: [a substring of] the process output.
140 fun: the function to call. This should execute a process.
141 args*: positional arguments for the function.
142 kwds**: named arguments for the function.
143 """
144 try:
145 fun(*args, **kwds)
146 except CalledProcessError as e:
147 if returncode != e.returncode:
148 raise AssertionError("Unexpected returncode %i" % e.returncode)
149 if output not in e.output:
150 raise AssertionError(f"Expected substring not found in: {e.output!r}")
151 else:
152 raise AssertionError("No exception raised")
153 154 155 def assert_raises_rpc_error(code: Optional[int], message: Optional[str], fun: Callable, *args, **kwds):
156 """Run an RPC and verify that a specific JSONRPC exception code and message is raised.
157 158 Calls function `fun` with arguments `args` and `kwds`. Catches a JSONRPCException
159 and verifies that the error code and message are as expected. Throws AssertionError if
160 no JSONRPCException was raised or if the error code/message are not as expected.
161 162 Args:
163 code: the error code returned by the RPC call (defined in src/rpc/protocol.h).
164 Set to None if checking the error code is not required.
165 message: [a substring of] the error string returned by the RPC call.
166 Set to None if checking the error string is not required.
167 fun: the function to call. This should be the name of an RPC.
168 args*: positional arguments for the function.
169 kwds**: named arguments for the function.
170 """
171 assert try_rpc(code, message, fun, *args, **kwds), "No exception raised"
172 173 174 def try_rpc(code, message, fun, *args, **kwds):
175 """Tries to run an rpc command.
176 177 Test against error code and message if the rpc fails.
178 Returns whether a JSONRPCException was raised."""
179 try:
180 fun(*args, **kwds)
181 except JSONRPCException as e:
182 # JSONRPCException was thrown as expected. Check the message and code values are correct.
183 if (message is not None) and (message not in e.error['message']):
184 raise AssertionError(
185 "Expected substring not found in error message:\nsubstring: '{}'\nerror message: '{}'.".format(
186 message, e.error['message']))
187 if (code is not None) and (code != e.error["code"]):
188 raise AssertionError("Unexpected JSONRPC error code %i" % e.error["code"])
189 return True
190 except Exception as e:
191 raise AssertionError("Unexpected exception raised: " + type(e).__name__)
192 else:
193 return False
194 195 196 def assert_is_hex_string(string):
197 try:
198 int(string, 16)
199 except Exception as e:
200 raise AssertionError("Couldn't interpret %r as hexadecimal; raised: %s" % (string, e))
201 202 203 def assert_is_hash_string(string, length=64):
204 if not isinstance(string, str):
205 raise AssertionError("Expected a string, got type %r" % type(string))
206 elif length and len(string) != length:
207 raise AssertionError("String of length %d expected; got %d" % (length, len(string)))
208 elif not re.match('[abcdef0-9]+$', string):
209 raise AssertionError("String %r contains invalid characters for a hash." % string)
210 211 212 def assert_array_result(object_array, to_match, expected, should_not_find=False):
213 """
214 Pass in array of JSON objects, a dictionary with key/value pairs
215 to match against, and another dictionary with expected key/value
216 pairs.
217 If the should_not_find flag is true, to_match should not be found
218 in object_array
219 """
220 if should_not_find:
221 assert_equal(expected, {})
222 num_matched = 0
223 for item in object_array:
224 all_match = True
225 for key, value in to_match.items():
226 if item[key] != value:
227 all_match = False
228 if not all_match:
229 continue
230 elif should_not_find:
231 num_matched = num_matched + 1
232 for key, value in expected.items():
233 if item[key] != value:
234 raise AssertionError("%s : expected %s=%s" % (str(item), str(key), str(value)))
235 num_matched = num_matched + 1
236 if num_matched == 0 and not should_not_find:
237 raise AssertionError("No objects matched %s" % (str(to_match)))
238 if num_matched > 0 and should_not_find:
239 raise AssertionError("Objects were found %s" % (str(to_match)))
240 241 242 # Utility functions
243 ###################
244 245 246 def check_json_precision():
247 """Make sure json library being used does not lose precision converting BTC values"""
248 n = Decimal("20000000.00000003")
249 satoshis = int(json.loads(json.dumps(float(n))) * 1.0e8)
250 if satoshis != 2000000000000003:
251 raise RuntimeError("JSON encode/decode loses precision")
252 253 254 class Binaries:
255 """Helper class to provide information about bitcoin binaries
256 257 Attributes:
258 paths: Object returned from get_binary_paths() containing information
259 which binaries and command lines to use from environment variables and
260 the config file.
261 bin_dir: An optional string containing a directory path to look for
262 binaries, which takes precedence over the paths above, if specified.
263 This is used by tests calling binaries from previous releases.
264 """
265 def __init__(self, paths, bin_dir, *, use_valgrind=False):
266 self.paths = paths
267 self.bin_dir = bin_dir
268 suppressions_file = pathlib.Path(__file__).resolve().parents[3] / "test" / "sanitizer_suppressions" / "valgrind.supp"
269 self.valgrind_cmd = [
270 "valgrind",
271 f"--suppressions={suppressions_file}",
272 "--gen-suppressions=all",
273 "--trace-children=yes", # Needed for 'bitcoin' wrapper
274 "--exit-on-first-error=yes",
275 "--error-exitcode=1",
276 "--quiet",
277 ] if use_valgrind else []
278 279 def node_argv(self, **kwargs):
280 "Return argv array that should be used to invoke bitcoind"
281 return self._argv("node", self.paths.bitcoind, **kwargs)
282 283 def rpc_argv(self):
284 "Return argv array that should be used to invoke bitcoin-cli"
285 # Add -nonamed because "bitcoin rpc" enables -named by default, but bitcoin-cli doesn't
286 return self._argv("rpc", self.paths.bitcoincli) + ["-nonamed"]
287 288 def bench_argv(self):
289 "Return argv array that should be used to invoke bench_bitcoin"
290 return self._argv("bench", self.paths.bitcoin_bench)
291 292 def tx_argv(self):
293 "Return argv array that should be used to invoke bitcoin-tx"
294 return self._argv("tx", self.paths.bitcointx)
295 296 def util_argv(self):
297 "Return argv array that should be used to invoke bitcoin-util"
298 return self._argv("util", self.paths.bitcoinutil)
299 300 def wallet_argv(self):
301 "Return argv array that should be used to invoke bitcoin-wallet"
302 return self._argv("wallet", self.paths.bitcoinwallet)
303 304 def chainstate_argv(self):
305 "Return argv array that should be used to invoke bitcoin-chainstate"
306 return self._argv("chainstate", self.paths.bitcoinchainstate)
307 308 def _argv(self, command, bin_path, need_ipc=False):
309 """Return argv array that should be used to invoke the command.
310 311 It either uses the bitcoin wrapper executable (if BITCOIN_CMD is set or
312 need_ipc is True), or the direct binary path (bitcoind, etc). When
313 bin_dir is set (by tests calling binaries from previous releases) it
314 always uses the direct path.
315 316 The returned args include valgrind, except when bin_dir is set
317 (previous releases). Also, valgrind will only apply to the bitcoin
318 wrapper executable directly, not to the commands that `bitcoin` calls.
319 """
320 if self.bin_dir is not None:
321 return [os.path.join(self.bin_dir, os.path.basename(bin_path))]
322 elif self.paths.bitcoin_cmd is not None or need_ipc:
323 # If the current test needs IPC functionality, use the bitcoin
324 # wrapper binary and append -m so it calls multiprocess binaries.
325 bitcoin_cmd = self.paths.bitcoin_cmd or [self.paths.bitcoin_bin]
326 return self.valgrind_cmd + bitcoin_cmd + (["-m"] if need_ipc else []) + [command]
327 else:
328 return self.valgrind_cmd + [bin_path]
329 330 331 def get_binary_paths(config):
332 """Get paths of all binaries from environment variables or their default values"""
333 334 paths = types.SimpleNamespace()
335 binaries = {
336 "bitcoin": "BITCOIN_BIN",
337 "bitcoind": "BITCOIND",
338 "bench_bitcoin": "BITCOIN_BENCH",
339 "bitcoin-cli": "BITCOINCLI",
340 "bitcoin-util": "BITCOINUTIL",
341 "bitcoin-tx": "BITCOINTX",
342 "bitcoin-chainstate": "BITCOINCHAINSTATE",
343 "bitcoin-wallet": "BITCOINWALLET",
344 }
345 # Set paths to bitcoin core binaries allowing overrides with environment
346 # variables.
347 for binary, env_variable_name in binaries.items():
348 default_filename = os.path.join(
349 config["environment"]["BUILDDIR"],
350 "bin",
351 binary + config["environment"]["EXEEXT"],
352 )
353 setattr(paths, env_variable_name.lower(), os.getenv(env_variable_name, default=default_filename))
354 # BITCOIN_CMD environment variable can be specified to invoke bitcoin
355 # wrapper binary instead of other executables.
356 paths.bitcoin_cmd = shlex.split(os.getenv("BITCOIN_CMD", "")) or None
357 return paths
358 359 360 def export_env_build_path(config):
361 os.environ["PATH"] = os.pathsep.join([
362 os.path.join(config["environment"]["BUILDDIR"], "bin"),
363 os.environ["PATH"],
364 ])
365 366 367 def count_bytes(hex_string):
368 return len(bytearray.fromhex(hex_string))
369 370 371 def str_to_b64str(string):
372 return b64encode(string.encode('utf-8')).decode('ascii')
373 374 375 def ceildiv(a, b):
376 """
377 Divide 2 ints and round up to next int rather than round down
378 Implementation requires python integers, which have a // operator that does floor division.
379 Other types like decimal.Decimal whose // operator truncates towards 0 will not work.
380 """
381 assert isinstance(a, int)
382 assert isinstance(b, int)
383 return -(-a // b)
384 385 386 def random_bitflip(data):
387 data = list(data)
388 data[random.randrange(len(data))] ^= (1 << (random.randrange(8)))
389 return bytes(data)
390 391 392 def get_fee(tx_size, feerate_btc_kvb):
393 """Calculate the fee in BTC given a feerate is BTC/kvB. Reflects CFeeRate::GetFee"""
394 feerate_sat_kvb = int(feerate_btc_kvb * Decimal(1e8)) # Fee in sat/kvb as an int to avoid float precision errors
395 target_fee_sat = ceildiv(feerate_sat_kvb * tx_size, 1000) # Round calculated fee up to nearest sat
396 return target_fee_sat / Decimal(1e8) # Return result in BTC
397 398 399 def satoshi_round(amount: Union[int, float, str], *, rounding: str) -> Decimal:
400 """Rounds a Decimal amount to the nearest satoshi using the specified rounding mode."""
401 return Decimal(amount).quantize(SATOSHI_PRECISION, rounding=rounding)
402 403 404 def ensure_for(*, duration, f, check_interval=0.2):
405 """Check if the predicate keeps returning True for duration.
406 407 check_interval can be used to configure the wait time between checks.
408 Setting check_interval to 0 will allow to have two checks: one in the
409 beginning and one after duration.
410 """
411 # If check_interval is 0 or negative or larger than duration, we fall back
412 # to checking once in the beginning and once at the end of duration
413 if check_interval <= 0 or check_interval > duration:
414 check_interval = duration
415 time_end = time.time() + duration
416 predicate_source = "''''\n" + inspect.getsource(f) + "'''"
417 while True:
418 if not f():
419 raise AssertionError(f"Predicate {predicate_source} became false within {duration} seconds")
420 if time.time() > time_end:
421 return
422 time.sleep(check_interval)
423 424 425 def wait_until_helper_internal(predicate, *, timeout=60, lock=None, timeout_factor=1.0, check_interval=0.05):
426 """Sleep until the predicate resolves to be True.
427 428 Warning: Note that this method is not recommended to be used in tests as it is
429 not aware of the context of the test framework. Using the `wait_until()` members
430 from `BitcoinTestFramework` or `P2PInterface` class ensures the timeout is
431 properly scaled. Furthermore, `wait_until()` from `P2PInterface` class in
432 `p2p.py` has a preset lock.
433 """
434 timeout = timeout * timeout_factor
435 time_end = time.time() + timeout
436 437 while time.time() < time_end:
438 if lock:
439 with lock:
440 if predicate():
441 return
442 else:
443 if predicate():
444 return
445 time.sleep(check_interval)
446 447 # Print the cause of the timeout
448 predicate_source = "''''\n" + inspect.getsource(predicate) + "'''"
449 logger.error("wait_until() failed. Predicate: {}".format(predicate_source))
450 raise AssertionError("Predicate {} not true after {} seconds".format(predicate_source, timeout))
451 452 453 def bpf_cflags():
454 return [
455 "-Wno-error=implicit-function-declaration",
456 "-Wno-duplicate-decl-specifier", # https://github.com/bitcoin/bitcoin/issues/32322
457 ]
458 459 460 def sha256sum_file(filename):
461 h = hashlib.sha256()
462 with open(filename, 'rb') as f:
463 d = f.read(4096)
464 while len(d) > 0:
465 h.update(d)
466 d = f.read(4096)
467 return h.digest()
468 469 470 def util_xor(data, key, *, offset):
471 data = bytearray(data)
472 for i in range(len(data)):
473 data[i] ^= key[(i + offset) % len(key)]
474 return bytes(data)
475 476 477 # RPC/P2P connection constants and functions
478 ############################################
479 480 # The maximum number of nodes a single test can spawn
481 MAX_NODES = 12
482 # Don't assign p2p, rpc or tor ports lower than this
483 PORT_MIN = int(os.getenv('TEST_RUNNER_PORT_MIN', default=11000))
484 # The number of ports to "reserve" for p2p, rpc and tor, each
485 PORT_RANGE = 5000
486 487 488 class PortSeed:
489 # Must be initialized with a unique integer for each process
490 n = None
491 492 def p2p_port(n):
493 assert n <= MAX_NODES
494 return PORT_MIN + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
495 496 497 def rpc_port(n):
498 return p2p_port(n) + PORT_RANGE
499 500 501 def tor_port(n):
502 return p2p_port(n) + PORT_RANGE * 2
503 504 505 # Node functions
506 ################
507 508 509 def initialize_datadir(dirname, n, chain, disable_autoconnect=True):
510 datadir = get_datadir_path(dirname, n)
511 if not os.path.isdir(datadir):
512 os.makedirs(datadir)
513 write_config(os.path.join(datadir, "bitcoin.conf"), n=n, chain=chain, disable_autoconnect=disable_autoconnect)
514 os.makedirs(os.path.join(datadir, 'stderr'), exist_ok=True)
515 os.makedirs(os.path.join(datadir, 'stdout'), exist_ok=True)
516 return datadir
517 518 519 def write_config(config_path, *, n, chain, extra_config="", disable_autoconnect=True):
520 # Translate chain subdirectory name to config name
521 if chain == 'testnet3':
522 chain_name_conf_arg = 'testnet'
523 chain_name_conf_section = 'test'
524 else:
525 chain_name_conf_arg = chain
526 chain_name_conf_section = chain
527 with open(config_path, 'w') as f:
528 if chain_name_conf_arg:
529 f.write("{}=1\n".format(chain_name_conf_arg))
530 if chain_name_conf_section:
531 f.write("[{}]\n".format(chain_name_conf_section))
532 f.write("port=" + str(p2p_port(n)) + "\n")
533 f.write("rpcport=" + str(rpc_port(n)) + "\n")
534 # Disable server-side timeouts to avoid intermittent issues
535 f.write("rpcservertimeout=99000\n")
536 f.write("rpcdoccheck=1\n")
537 f.write("rpcthreads=2\n")
538 f.write("fallbackfee=0.0002\n")
539 f.write("server=1\n")
540 f.write("keypool=1\n")
541 f.write("discover=0\n")
542 f.write("dnsseed=0\n")
543 f.write("fixedseeds=0\n")
544 f.write("listenonion=0\n")
545 # Increase peertimeout to avoid disconnects while using mocktime.
546 # peertimeout is measured in mock time, so setting it large enough to
547 # cover any duration in mock time is sufficient. It can be overridden
548 # in tests.
549 f.write("peertimeout=999999999\n")
550 f.write("printtoconsole=0\n")
551 f.write("natpmp=0\n") # Avoid non-loopback network traffic during tests.
552 f.write("shrinkdebugfile=0\n")
553 # To improve SQLite wallet performance so that the tests don't timeout, use -unsafesqlitesync
554 f.write("unsafesqlitesync=1\n")
555 if disable_autoconnect:
556 f.write("connect=0\n")
557 # Limit max connections to mitigate test failures on some systems caused by the warning:
558 # "Warning: Reducing -maxconnections from <...> to <...> due to system limitations".
559 # The value is calculated as follows:
560 # available_fds = 256 // Same as FD_SETSIZE on NetBSD.
561 # MIN_CORE_FDS = 151 // Number of file descriptors required for core functionality.
562 # MAX_ADDNODE_CONNECTIONS = 8 // Maximum number of -addnode outgoing nodes.
563 # nBind == 3 // Maximum number of bound interfaces used in a test.
564 #
565 # min_required_fds = MIN_CORE_FDS + MAX_ADDNODE_CONNECTIONS + nBind = 151 + 8 + 3 = 162;
566 # nMaxConnections = available_fds - min_required_fds = 256 - 161 = 94;
567 f.write("maxconnections=94\n")
568 f.write("par=" + str(min(2, os.cpu_count())) + "\n")
569 # Use a single prevoutfetch worker thread to keep per-node resource usage low.
570 f.write("prevoutfetchthreads=1\n")
571 f.write(extra_config)
572 573 574 def get_datadir_path(dirname, n):
575 return pathlib.Path(dirname) / f"node{n}"
576 577 578 def get_temp_default_datadir(temp_dir: pathlib.Path) -> tuple[dict, pathlib.Path]:
579 """Return os-specific environment variables that can be set to make the
580 GetDefaultDataDir() function return a datadir path under the provided
581 temp_dir, as well as the complete path it would return."""
582 if platform.system() == "Windows":
583 env = dict(APPDATA=str(temp_dir))
584 datadir = temp_dir / "Bitcoin"
585 else:
586 env = dict(HOME=str(temp_dir))
587 if platform.system() == "Darwin":
588 datadir = temp_dir / "Library/Application Support/Bitcoin"
589 else:
590 datadir = temp_dir / ".bitcoin"
591 return env, datadir
592 593 594 def append_config(datadir, options):
595 with open(os.path.join(datadir, "bitcoin.conf"), 'a') as f:
596 for option in options:
597 f.write(option + "\n")
598 599 600 def get_auth_cookie(datadir, chain):
601 user = None
602 password = None
603 if os.path.isfile(os.path.join(datadir, "bitcoin.conf")):
604 with open(os.path.join(datadir, "bitcoin.conf"), 'r') as f:
605 for line in f:
606 if line.startswith("rpcuser="):
607 assert user is None # Ensure that there is only one rpcuser line
608 user = line.split("=")[1].strip("\n")
609 if line.startswith("rpcpassword="):
610 assert password is None # Ensure that there is only one rpcpassword line
611 password = line.split("=")[1].strip("\n")
612 try:
613 with open(os.path.join(datadir, chain, ".cookie"), 'r') as f:
614 userpass = f.read()
615 split_userpass = userpass.split(':')
616 user = split_userpass[0]
617 password = split_userpass[1]
618 except OSError:
619 pass
620 if user is None or password is None:
621 raise ValueError("No RPC credentials")
622 return user, password
623 624 625 # If a cookie file exists in the given datadir, delete it.
626 def delete_cookie_file(datadir, chain):
627 if os.path.isfile(os.path.join(datadir, chain, ".cookie")):
628 logger.debug("Deleting leftover cookie file")
629 os.remove(os.path.join(datadir, chain, ".cookie"))
630 631 632 def softfork_active(node, key):
633 """Return whether a softfork is active."""
634 return node.getdeploymentinfo()['deployments'][key]['active']
635 636 637 def set_node_times(nodes, t):
638 for node in nodes:
639 node.setmocktime(t)
640 641 642 def check_node_connections(*, node, num_in, num_out):
643 info = node.getnetworkinfo()
644 assert_equal(info["connections_in"], num_in)
645 assert_equal(info["connections_out"], num_out)
646 647 648 # Transaction/Block functions
649 #############################
650 651 652 # Create large OP_RETURN txouts that can be appended to a transaction
653 # to make it large (helper for constructing large transactions). The
654 # total serialized size of the txouts is about 66k vbytes.
655 def gen_return_txouts():
656 from .messages import CTxOut
657 from .script import CScript, OP_RETURN
658 txouts = [CTxOut(nValue=0, scriptPubKey=CScript([OP_RETURN, b'\x01'*67437]))]
659 assert_equal(sum([len(txout.serialize()) for txout in txouts]), 67456)
660 return txouts
661 662 663 # Create a spend of each passed-in utxo, splicing in "txouts" to each raw
664 # transaction to make it large. See gen_return_txouts() above.
665 def create_lots_of_big_transactions(mini_wallet, node, fee, tx_batch_size, txouts, utxos=None):
666 txids = []
667 use_internal_utxos = utxos is None
668 for _ in range(tx_batch_size):
669 tx = mini_wallet.create_self_transfer(
670 utxo_to_spend=None if use_internal_utxos else utxos.pop(),
671 fee=fee,
672 )["tx"]
673 tx.vout.extend(txouts)
674 res = node.testmempoolaccept([tx.serialize().hex()])[0]
675 assert_equal(res['fees']['base'], fee)
676 txids.append(node.sendrawtransaction(tx.serialize().hex()))
677 return txids
678 679 680 def mine_large_block(test_framework, mini_wallet, node):
681 # generate a 66k transaction,
682 # and 14 of them is close to the 1MB block limit
683 txouts = gen_return_txouts()
684 fee = 100 * node.getnetworkinfo()["relayfee"]
685 create_lots_of_big_transactions(mini_wallet, node, fee, 14, txouts)
686 test_framework.generate(node, 1)
687 688 689 def find_vout_for_address(node, txid, addr):
690 """
691 Locate the vout index of the given transaction sending to the
692 given address. Raises runtime error exception if not found.
693 """
694 tx = node.getrawtransaction(txid, True)
695 for i in range(len(tx["vout"])):
696 if addr == tx["vout"][i]["scriptPubKey"]["address"]:
697 return i
698 raise RuntimeError("Vout not found for address: txid=%s, addr=%s" % (txid, addr))
699 700 701 def dumb_sync_blocks(*, src, dst, height=None):
702 """Sync blocks between `src` and `dst` nodes via RPC submitblock up to height."""
703 height = height or src.getblockcount()
704 for i in range(dst.getblockcount() + 1, height + 1):
705 block_hash = src.getblockhash(i)
706 block = src.getblock(blockhash=block_hash, verbosity=0)
707 dst.submitblock(block)
708 assert_equal(dst.getblockcount(), height)
709 710 711 def sync_txindex(test_framework, node):
712 test_framework.log.debug("Waiting for node txindex to sync")
713 sync_start = int(time.time())
714 test_framework.wait_until(lambda: node.getindexinfo("txindex")["txindex"]["synced"])
715 test_framework.log.debug(f"Synced in {time.time() - sync_start} seconds")
716 717 def wallet_importprivkey(wallet_rpc, privkey, timestamp, *, label=""):
718 desc = descsum_create("combo(" + privkey + ")")
719 req = [{
720 "desc": desc,
721 "timestamp": timestamp,
722 "label": label,
723 }]
724 import_res = wallet_rpc.importdescriptors(req)
725 assert_equal(import_res[0]["success"], True)
726 727 def is_dir_writable(dir_path: pathlib.Path) -> bool:
728 """Return True if we can create a file in the directory, False otherwise"""
729 try:
730 tmp = dir_path / f".tmp_{random.randrange(1 << 32)}"
731 tmp.touch()
732 tmp.unlink()
733 return True
734 except OSError:
735 return False
736