1#!/usr/bin/env python3 2# Copyright (c) 2017-2019 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"""Class for bitcoind node under test""" 6 7import contextlib 8import decimal 9import errno 10from enum import Enum 11import http.client 12import json 13import logging 14import os 15import re 16import subprocess 17import tempfile 18import time 19import urllib.parse 20import collections 21import shlex 22import sys 23 24from .authproxy import JSONRPCException 25from .util import ( 26 MAX_NODES, 27 append_config, 28 delete_cookie_file, 29 get_rpc_proxy, 30 rpc_url, 31 wait_until, 32 p2p_port, 33 EncodeDecimal, 34) 35from .qtum import convert_btc_address_to_qtum 36from .qtumconfig import ENABLE_REDUCED_BLOCK_TIME 37 38BITCOIND_PROC_WAIT_TIMEOUT = 60 39 40 41class FailedToStartError(Exception): 42 """Raised when a node fails to start correctly.""" 43 44 45class ErrorMatch(Enum): 46 FULL_TEXT = 1 47 FULL_REGEX = 2 48 PARTIAL_REGEX = 3 49 50 51class TestNode(): 52 """A class for representing a bitcoind node under test. 53 54 This class contains: 55 56 - state about the node (whether it's running, etc) 57 - a Python subprocess.Popen object representing the running process 58 - an RPC connection to the node 59 - one or more P2P connections to the node 60 61 62 To make things easier for the test writer, any unrecognised messages will 63 be dispatched to the RPC connection.""" 64 65 def __init__(self, i, datadir, *, chain, rpchost, timewait, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False, use_valgrind=False, version=None, enable_wallet=False): 66 """ 67 Kwargs: 68 start_perf (bool): If True, begin profiling the node with `perf` as soon as 69 the node starts. 70 """ 71 72 self.index = i 73 self.datadir = datadir 74 self.bitcoinconf = os.path.join(self.datadir, "qtum.conf") 75 self.stdout_dir = os.path.join(self.datadir, "stdout") 76 self.stderr_dir = os.path.join(self.datadir, "stderr") 77 self.chain = chain 78 self.rpchost = rpchost 79 self.rpc_timeout = timewait 80 self.binary = bitcoind 81 self.coverage_dir = coverage_dir 82 self.cwd = cwd 83 self.enable_wallet = enable_wallet 84 if extra_conf is not None: 85 append_config(datadir, extra_conf) 86 # Most callers will just need to add extra args to the standard list below. 87 # For those callers that need more flexibility, they can just set the args property directly. 88 # Note that common args are set in the config file (see initialize_datadir) 89 self.extra_args = extra_args 90 self.version = version 91 # Configuration for logging is set as command-line args rather than in the bitcoin.conf file. 92 # This means that starting a bitcoind using the temp dir to debug a failed test won't 93 # spam debug.log. 94 self.args = [ 95 self.binary, 96 "-datadir=" + self.datadir, 97 "-logtimemicros", 98 "-debug", 99 "-debugexclude=libevent", 100 "-debugexclude=leveldb", 101 "-uacomment=testnode%d" % i, 102 ] 103 if use_valgrind: 104 default_suppressions_file = os.path.join( 105 os.path.dirname(os.path.realpath(__file__)), 106 "..", "..", "..", "contrib", "valgrind.supp") 107 suppressions_file = os.getenv("VALGRIND_SUPPRESSIONS_FILE", 108 default_suppressions_file) 109 self.args = ["valgrind", "--suppressions={}".format(suppressions_file), 110 "--gen-suppressions=all", "--exit-on-first-error=yes", 111 "--error-exitcode=1", "--quiet"] + self.args 112 113 if self.version is None or self.version >= 190000: 114 self.args.append("-logthreadnames") 115 116 self.cli = TestNodeCLI(bitcoin_cli, self.datadir) 117 self.use_cli = use_cli 118 self.start_perf = start_perf 119 120 self.running = False 121 self.process = None 122 self.rpc_connected = False 123 self.rpc = None 124 self.url = None 125 self.log = logging.getLogger('TestFramework.node%d' % i) 126 self.cleanup_on_exit = True # Whether to kill the node when this object goes away 127 # Cache perf subprocesses here by their data output filename. 128 self.perf_subprocesses = {} 129 130 self.p2ps = [] 131 132 AddressKeyPair = collections.namedtuple('AddressKeyPair', ['address', 'key']) 133 PRIV_KEYS = [ 134 # address , privkey 135 AddressKeyPair(convert_btc_address_to_qtum('mjTkW3DjgyZck4KbiRusZsqTgaYTxdSz6z'), 'cVpF924EspNh8KjYsfhgY96mmxvT6DgdWiTYMtMjuM74hJaU5psW'), 136 AddressKeyPair(convert_btc_address_to_qtum('msX6jQXvxiNhx3Q62PKeLPrhrqZQdSimTg'), 'cUxsWyKyZ9MAQTaAhUQWJmBbSvHMwSmuv59KgxQV7oZQU3PXN3KE'), 137 AddressKeyPair(convert_btc_address_to_qtum('mnonCMyH9TmAsSj3M59DsbH8H63U3RKoFP'), 'cTrh7dkEAeJd6b3MRX9bZK8eRmNqVCMH3LSUkE3dSFDyzjU38QxK'), 138 AddressKeyPair(convert_btc_address_to_qtum('mqJupas8Dt2uestQDvV2NH3RU8uZh2dqQR'), 'cVuKKa7gbehEQvVq717hYcbE9Dqmq7KEBKqWgWrYBa2CKKrhtRim'), 139 AddressKeyPair(convert_btc_address_to_qtum('msYac7Rvd5ywm6pEmkjyxhbCDKqWsVeYws'), 'cQDCBuKcjanpXDpCqacNSjYfxeQj8G6CAtH1Dsk3cXyqLNC4RPuh'), 140 AddressKeyPair(convert_btc_address_to_qtum('n2rnuUnwLgXqf9kk2kjvVm8R5BZK1yxQBi'), 'cQakmfPSLSqKHyMFGwAqKHgWUiofJCagVGhiB4KCainaeCSxeyYq'), 141 AddressKeyPair(convert_btc_address_to_qtum('myzuPxRwsf3vvGzEuzPfK9Nf2RfwauwYe6'), 'cQMpDLJwA8DBe9NcQbdoSb1BhmFxVjWD5gRyrLZCtpuF9Zi3a9RK'), 142 AddressKeyPair(convert_btc_address_to_qtum('mumwTaMtbxEPUswmLBBN3vM9oGRtGBrys8'), 'cSXmRKXVcoouhNNVpcNKFfxsTsToY5pvB9DVsFksF1ENunTzRKsy'), 143 AddressKeyPair(convert_btc_address_to_qtum('mpV7aGShMkJCZgbW7F6iZgrvuPHjZjH9qg'), 'cSoXt6tm3pqy43UMabY6eUTmR3eSUYFtB2iNQDGgb3VUnRsQys2k'), 144 AddressKeyPair(convert_btc_address_to_qtum('mq4fBNdckGtvY2mijd9am7DRsbRB4KjUkf'), 'cN55daf1HotwBAgAKWVgDcoppmUNDtQSfb7XLutTLeAgVc3u8hik'), 145 AddressKeyPair(convert_btc_address_to_qtum('mpFAHDjX7KregM3rVotdXzQmkbwtbQEnZ6'), 'cT7qK7g1wkYEMvKowd2ZrX1E5f6JQ7TM246UfqbCiyF7kZhorpX3'), 146 AddressKeyPair(convert_btc_address_to_qtum('mzRe8QZMfGi58KyWCse2exxEFry2sfF2Y7'), 'cPiRWE8KMjTRxH1MWkPerhfoHFn5iHPWVK5aPqjW8NxmdwenFinJ'), 147 ] 148 149 def get_deterministic_priv_key(self): 150 """Return a deterministic priv key in base58, that only depends on the node's index""" 151 assert len(self.PRIV_KEYS) == MAX_NODES 152 return self.PRIV_KEYS[self.index] 153 154 def _node_msg(self, msg: str) -> str: 155 """Return a modified msg that identifies this node by its index as a debugging aid.""" 156 return "[node %d] %s" % (self.index, msg) 157 158 def _raise_assertion_error(self, msg: str): 159 """Raise an AssertionError with msg modified to identify this node.""" 160 raise AssertionError(self._node_msg(msg)) 161 162 def __del__(self): 163 # Ensure that we don't leave any bitcoind processes lying around after 164 # the test ends 165 if self.process and self.cleanup_on_exit: 166 # Should only happen on test failure 167 # Avoid using logger, as that may have already been shutdown when 168 # this destructor is called. 169 print(self._node_msg("Cleaning up leftover process")) 170 self.process.kill() 171 172 def __getattr__(self, name): 173 """Dispatches any unrecognised messages to the RPC connection or a CLI instance.""" 174 if self.use_cli: 175 return getattr(self.cli, name) 176 else: 177 assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection") 178 return getattr(self.rpc, name) 179 180 def start(self, extra_args=None, *, cwd=None, stdout=None, stderr=None, **kwargs): 181 """Start the node.""" 182 if extra_args is None: 183 extra_args = self.extra_args 184 185 # Add a new stdout and stderr file each time bitcoind is started 186 if stderr is None: 187 stderr = tempfile.NamedTemporaryFile(dir=self.stderr_dir, delete=False) 188 if stdout is None: 189 stdout = tempfile.NamedTemporaryFile(dir=self.stdout_dir, delete=False) 190 self.stderr = stderr 191 self.stdout = stdout 192 193 if cwd is None: 194 cwd = self.cwd 195 196 # Delete any existing cookie file -- if such a file exists (eg due to 197 # unclean shutdown), it will get overwritten anyway by bitcoind, and 198 # potentially interfere with our attempt to authenticate 199 delete_cookie_file(self.datadir, self.chain) 200 201 # add environment variable LIBC_FATAL_STDERR_=1 so that libc errors are written to stderr and not the terminal 202 subp_env = dict(os.environ, LIBC_FATAL_STDERR_="1") 203 204 if self.enable_wallet and not any(arg.startswith('-staking=') for arg in extra_args): 205 extra_args.append('-staking=0') 206 207 if not ENABLE_REDUCED_BLOCK_TIME and not any(arg.startswith('-reduceblocktimeheight=') for arg in extra_args): 208 extra_args.append('-reduceblocktimeheight=10000000') 209 210 if self.enable_wallet and not any(arg.startswith('-offlinestakingheight=') for arg in extra_args): 211 extra_args.append('-offlinestakingheight=1') 212 213 # Disable the spam filter as it may interfere with come tests sending lots and lots of blocks 214 if not any(arg.startswith('-headerspamfilter') for arg in extra_args): 215 extra_args.append('-headerspamfilter=0') 216 217 self.process = subprocess.Popen(self.args + extra_args, env=subp_env, stdout=stdout, stderr=stderr, cwd=cwd, **kwargs) 218 219 self.running = True 220 self.log.debug("bitcoind started, waiting for RPC to come up") 221 222 if self.start_perf: 223 self._start_perf() 224 225 def wait_for_rpc_connection(self): 226 """Sets up an RPC connection to the bitcoind process. Returns False if unable to connect.""" 227 # Poll at a rate of four times per second 228 poll_per_s = 4 229 for _ in range(poll_per_s * self.rpc_timeout): 230 if self.process.poll() is not None: 231 raise FailedToStartError(self._node_msg( 232 'bitcoind exited with status {} during initialization'.format(self.process.returncode))) 233 try: 234 rpc = get_rpc_proxy(rpc_url(self.datadir, self.index, self.chain, self.rpchost), self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir) 235 rpc.getblockcount() 236 # If the call to getblockcount() succeeds then the RPC connection is up 237 self.log.debug("RPC successfully started") 238 if self.use_cli: 239 return 240 self.rpc = rpc 241 self.rpc_connected = True 242 self.url = self.rpc.url 243 return 244 except IOError as e: 245 if e.errno != errno.ECONNREFUSED: # Port not yet open? 246 raise # unknown IO error 247 except JSONRPCException as e: # Initialization phase 248 # -28 RPC in warmup 249 # -342 Service unavailable, RPC server started but is shutting down due to error 250 if e.error['code'] != -28 and e.error['code'] != -342: 251 raise # unknown JSON RPC exception 252 except ConnectionResetError: 253 # This might happen when the RPC server is in warmup, but shut down before the call to getblockcount 254 # succeeds. Try again to properly raise the FailedToStartError 255 pass 256 except ValueError as e: # cookie file not found and no rpcuser or rpcassword. bitcoind still starting 257 if "No RPC credentials" not in str(e): 258 raise 259 time.sleep(1.0 / poll_per_s) 260 self._raise_assertion_error("Unable to connect to bitcoind") 261 262 def generate(self, nblocks, maxtries=1000000): 263 self.log.debug("TestNode.generate() dispatches `generate` call to `generatetoaddress`") 264 return self.generatetoaddress(nblocks=nblocks, address=self.get_deterministic_priv_key().address, maxtries=maxtries) 265 266 def get_wallet_rpc(self, wallet_name): 267 if self.use_cli: 268 return self.cli("-rpcwallet={}".format(wallet_name)) 269 else: 270 assert self.rpc_connected and self.rpc, self._node_msg("RPC not connected") 271 wallet_path = "wallet/{}".format(urllib.parse.quote(wallet_name)) 272 return self.rpc / wallet_path 273 274 def stop_node(self, expected_stderr='', wait=0): 275 """Stop the node.""" 276 if not self.running: 277 return 278 self.log.debug("Stopping node") 279 try: 280 # Do not use wait argument when testing older nodes, e.g. in feature_backwards_compatibility.py 281 if self.version is None or self.version >= 180000: 282 self.stop(wait=wait) 283 else: 284 self.stop() 285 except http.client.CannotSendRequest: 286 self.log.exception("Unable to stop node.") 287 288 # If there are any running perf processes, stop them. 289 for profile_name in tuple(self.perf_subprocesses.keys()): 290 self._stop_perf(profile_name) 291 292 # Check that stderr is as expected 293 self.stderr.seek(0) 294 stderr = self.stderr.read().decode('utf-8').strip() 295 if stderr != expected_stderr: 296 raise AssertionError("Unexpected stderr {} != {}".format(stderr, expected_stderr)) 297 298 self.stdout.close() 299 self.stderr.close() 300 301 del self.p2ps[:] 302 303 def is_node_stopped(self): 304 """Checks whether the node has stopped. 305 306 Returns True if the node has stopped. False otherwise. 307 This method is responsible for freeing resources (self.process).""" 308 if not self.running: 309 return True 310 return_code = self.process.poll() 311 if return_code is None: 312 return False 313 314 # process has stopped. Assert that it didn't return an error code. 315 assert return_code == 0, self._node_msg( 316 "Node returned non-zero exit code (%d) when stopping" % return_code) 317 self.running = False 318 self.process = None 319 self.rpc_connected = False 320 self.rpc = None 321 self.log.debug("Node stopped") 322 return True 323 324 def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT): 325 wait_until(self.is_node_stopped, timeout=timeout) 326 327 @contextlib.contextmanager 328 def assert_debug_log(self, expected_msgs, unexpected_msgs=None, timeout=2): 329 if unexpected_msgs is None: 330 unexpected_msgs = [] 331 time_end = time.time() + timeout 332 debug_log = os.path.join(self.datadir, self.chain, 'debug.log') 333 with open(debug_log, encoding='utf-8') as dl: 334 dl.seek(0, 2) 335 prev_size = dl.tell() 336 337 yield 338 339 while True: 340 found = True 341 with open(debug_log, encoding='utf-8') as dl: 342 dl.seek(prev_size) 343 log = dl.read() 344 print_log = " - " + "\n - ".join(log.splitlines()) 345 for unexpected_msg in unexpected_msgs: 346 if re.search(re.escape(unexpected_msg), log, flags=re.MULTILINE): 347 self._raise_assertion_error('Unexpected message "{}" partially matches log:\n\n{}\n\n'.format(unexpected_msg, print_log)) 348 for expected_msg in expected_msgs: 349 if re.search(re.escape(expected_msg), log, flags=re.MULTILINE) is None: 350 found = False 351 if found: 352 return 353 if time.time() >= time_end: 354 break 355 time.sleep(0.05) 356 self._raise_assertion_error('Expected messages "{}" does not partially match log:\n\n{}\n\n'.format(str(expected_msgs), print_log)) 357 358 @contextlib.contextmanager 359 def profile_with_perf(self, profile_name): 360 """ 361 Context manager that allows easy profiling of node activity using `perf`. 362 363 See `test/functional/README.md` for details on perf usage. 364 365 Args: 366 profile_name (str): This string will be appended to the 367 profile data filename generated by perf. 368 """ 369 subp = self._start_perf(profile_name) 370 371 yield 372 373 if subp: 374 self._stop_perf(profile_name) 375 376 def _start_perf(self, profile_name=None): 377 """Start a perf process to profile this node. 378 379 Returns the subprocess running perf.""" 380 subp = None 381 382 def test_success(cmd): 383 return subprocess.call( 384 # shell=True required for pipe use below 385 cmd, shell=True, 386 stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL) == 0 387 388 if not sys.platform.startswith('linux'): 389 self.log.warning("Can't profile with perf; only available on Linux platforms") 390 return None 391 392 if not test_success('which perf'): 393 self.log.warning("Can't profile with perf; must install perf-tools") 394 return None 395 396 if not test_success('readelf -S {} | grep .debug_str'.format(shlex.quote(self.binary))): 397 self.log.warning( 398 "perf output won't be very useful without debug symbols compiled into bitcoind") 399 400 output_path = tempfile.NamedTemporaryFile( 401 dir=self.datadir, 402 prefix="{}.perf.data.".format(profile_name or 'test'), 403 delete=False, 404 ).name 405 406 cmd = [ 407 'perf', 'record', 408 '-g', # Record the callgraph. 409 '--call-graph', 'dwarf', # Compatibility for gcc's --fomit-frame-pointer. 410 '-F', '101', # Sampling frequency in Hz. 411 '-p', str(self.process.pid), 412 '-o', output_path, 413 ] 414 subp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 415 self.perf_subprocesses[profile_name] = subp 416 417 return subp 418 419 def _stop_perf(self, profile_name): 420 """Stop (and pop) a perf subprocess.""" 421 subp = self.perf_subprocesses.pop(profile_name) 422 output_path = subp.args[subp.args.index('-o') + 1] 423 424 subp.terminate() 425 subp.wait(timeout=10) 426 427 stderr = subp.stderr.read().decode() 428 if 'Consider tweaking /proc/sys/kernel/perf_event_paranoid' in stderr: 429 self.log.warning( 430 "perf couldn't collect data! Try " 431 "'sudo sysctl -w kernel.perf_event_paranoid=-1'") 432 else: 433 report_cmd = "perf report -i {}".format(output_path) 434 self.log.info("See perf output by running '{}'".format(report_cmd)) 435 436 def assert_start_raises_init_error(self, extra_args=None, expected_msg=None, match=ErrorMatch.FULL_TEXT, *args, **kwargs): 437 """Attempt to start the node and expect it to raise an error. 438 439 extra_args: extra arguments to pass through to bitcoind 440 expected_msg: regex that stderr should match when bitcoind fails 441 442 Will throw if bitcoind starts without an error. 443 Will throw if an expected_msg is provided and it does not match bitcoind's stdout.""" 444 with tempfile.NamedTemporaryFile(dir=self.stderr_dir, delete=False) as log_stderr, \ 445 tempfile.NamedTemporaryFile(dir=self.stdout_dir, delete=False) as log_stdout: 446 try: 447 self.start(extra_args, stdout=log_stdout, stderr=log_stderr, *args, **kwargs) 448 self.wait_for_rpc_connection() 449 self.stop_node() 450 self.wait_until_stopped() 451 except FailedToStartError as e: 452 self.log.debug('bitcoind failed to start: %s', e) 453 self.running = False 454 self.process = None 455 # Check stderr for expected message 456 if expected_msg is not None: 457 log_stderr.seek(0) 458 stderr = log_stderr.read().decode('utf-8').strip() 459 if match == ErrorMatch.PARTIAL_REGEX: 460 if re.search(expected_msg, stderr, flags=re.MULTILINE) is None: 461 self._raise_assertion_error( 462 'Expected message "{}" does not partially match stderr:\n"{}"'.format(expected_msg, stderr)) 463 elif match == ErrorMatch.FULL_REGEX: 464 if re.fullmatch(expected_msg, stderr) is None: 465 self._raise_assertion_error( 466 'Expected message "{}" does not fully match stderr:\n"{}"'.format(expected_msg, stderr)) 467 elif match == ErrorMatch.FULL_TEXT: 468 if expected_msg != stderr: 469 self._raise_assertion_error( 470 'Expected message "{}" does not fully match stderr:\n"{}"'.format(expected_msg, stderr)) 471 else: 472 if expected_msg is None: 473 assert_msg = "bitcoind should have exited with an error" 474 else: 475 assert_msg = "bitcoind should have exited with expected error " + expected_msg 476 self._raise_assertion_error(assert_msg) 477 478 def add_p2p_connection(self, p2p_conn, *, wait_for_verack=True, **kwargs): 479 """Add a p2p connection to the node. 480 481 This method adds the p2p connection to the self.p2ps list and also 482 returns the connection to the caller.""" 483 if 'dstport' not in kwargs: 484 kwargs['dstport'] = p2p_port(self.index) 485 if 'dstaddr' not in kwargs: 486 kwargs['dstaddr'] = '127.0.0.1' 487 488 p2p_conn.peer_connect(**kwargs, net=self.chain)() 489 self.p2ps.append(p2p_conn) 490 if wait_for_verack: 491 # Wait for the node to send us the version and verack 492 p2p_conn.wait_for_verack() 493 # At this point we have sent our version message and received the version and verack, however the full node 494 # has not yet received the verack from us (in reply to their version). So, the connection is not yet fully 495 # established (fSuccessfullyConnected). 496 # 497 # This shouldn't lead to any issues when sending messages, since the verack will be in-flight before the 498 # message we send. However, it might lead to races where we are expecting to receive a message. E.g. a 499 # transaction that will be added to the mempool as soon as we return here. 500 # 501 # So syncing here is redundant when we only want to send a message, but the cost is low (a few milliseconds) 502 # in comparision to the upside of making tests less fragile and unexpected intermittent errors less likely. 503 p2p_conn.sync_with_ping() 504 505 return p2p_conn 506 507 @property 508 def p2p(self): 509 """Return the first p2p connection 510 511 Convenience property - most tests only use a single p2p connection to each 512 node, so this saves having to write node.p2ps[0] many times.""" 513 assert self.p2ps, self._node_msg("No p2p connection") 514 return self.p2ps[0] 515 516 def disconnect_p2ps(self): 517 """Close all p2p connections to the node.""" 518 for p in self.p2ps: 519 p.peer_disconnect() 520 del self.p2ps[:] 521 522 523class TestNodeCLIAttr: 524 def __init__(self, cli, command): 525 self.cli = cli 526 self.command = command 527 528 def __call__(self, *args, **kwargs): 529 return self.cli.send_cli(self.command, *args, **kwargs) 530 531 def get_request(self, *args, **kwargs): 532 return lambda: self(*args, **kwargs) 533 534 535def arg_to_cli(arg): 536 if isinstance(arg, bool): 537 return str(arg).lower() 538 elif isinstance(arg, dict) or isinstance(arg, list): 539 return json.dumps(arg, default=EncodeDecimal) 540 else: 541 return str(arg) 542 543 544class TestNodeCLI(): 545 """Interface to bitcoin-cli for an individual node""" 546 547 def __init__(self, binary, datadir): 548 self.options = [] 549 self.binary = binary 550 self.datadir = datadir 551 self.input = None 552 self.log = logging.getLogger('TestFramework.bitcoincli') 553 554 def __call__(self, *options, input=None): 555 # TestNodeCLI is callable with bitcoin-cli command-line options 556 cli = TestNodeCLI(self.binary, self.datadir) 557 cli.options = [str(o) for o in options] 558 cli.input = input 559 return cli 560 561 def __getattr__(self, command): 562 return TestNodeCLIAttr(self, command) 563 564 def batch(self, requests): 565 results = [] 566 for request in requests: 567 try: 568 results.append(dict(result=request())) 569 except JSONRPCException as e: 570 results.append(dict(error=e)) 571 return results 572 573 def send_cli(self, command=None, *args, **kwargs): 574 """Run bitcoin-cli command. Deserializes returned string as python object.""" 575 pos_args = [arg_to_cli(arg) for arg in args] 576 named_args = [str(key) + "=" + arg_to_cli(value) for (key, value) in kwargs.items()] 577 assert not (pos_args and named_args), "Cannot use positional arguments and named arguments in the same bitcoin-cli call" 578 p_args = [self.binary, "-datadir=" + self.datadir] + self.options 579 if named_args: 580 p_args += ["-named"] 581 if command is not None: 582 p_args += [command] 583 p_args += pos_args + named_args 584 self.log.debug("Running bitcoin-cli command: %s" % command) 585 process = subprocess.Popen(p_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) 586 cli_stdout, cli_stderr = process.communicate(input=self.input) 587 returncode = process.poll() 588 if returncode: 589 match = re.match(r'error code: ([-0-9]+)\nerror message:\n(.*)', cli_stderr) 590 if match: 591 code, message = match.groups() 592 raise JSONRPCException(dict(code=int(code), message=message)) 593 # Ignore cli_stdout, raise with cli_stderr 594 raise subprocess.CalledProcessError(returncode, self.binary, output=cli_stderr) 595 try: 596 return json.loads(cli_stdout, parse_float=decimal.Decimal) 597 except json.JSONDecodeError: 598 return cli_stdout.rstrip("\n") 599