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