Merge #11121: TestNode tidyups
[bitcoinplatinum.git] / test / functional / test_framework / test_node.py
blobefb3ac9d16599cd8415293a4f8400ad3f12ecd1c
1 #!/usr/bin/env python3
2 # Copyright (c) 2017 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"""
7 import decimal
8 import errno
9 import http.client
10 import json
11 import logging
12 import os
13 import subprocess
14 import time
16 from .util import (
17 assert_equal,
18 get_rpc_proxy,
19 rpc_url,
21 from .authproxy import JSONRPCException
23 class TestNode():
24 """A class for representing a bitcoind node under test.
26 This class contains:
28 - state about the node (whether it's running, etc)
29 - a Python subprocess.Popen object representing the running process
30 - an RPC connection to the node
32 To make things easier for the test writer, a bit of magic is happening under the covers.
33 Any unrecognised messages will be dispatched to the RPC connection."""
35 def __init__(self, i, dirname, extra_args, rpchost, timewait, binary, stderr, mocktime, coverage_dir):
36 self.index = i
37 self.datadir = os.path.join(dirname, "node" + str(i))
38 self.rpchost = rpchost
39 if timewait:
40 self.rpc_timeout = timewait
41 else:
42 # Wait for up to 60 seconds for the RPC server to respond
43 self.rpc_timeout = 60
44 if binary is None:
45 self.binary = os.getenv("BITCOIND", "bitcoind")
46 else:
47 self.binary = binary
48 self.stderr = stderr
49 self.coverage_dir = coverage_dir
50 # Most callers will just need to add extra args to the standard list below. For those callers that need more flexibity, they can just set the args property directly.
51 self.extra_args = extra_args
52 self.args = [self.binary, "-datadir=" + self.datadir, "-server", "-keypool=1", "-discover=0", "-rest", "-logtimemicros", "-debug", "-debugexclude=libevent", "-debugexclude=leveldb", "-mocktime=" + str(mocktime), "-uacomment=testnode%d" % i]
54 self.cli = TestNodeCLI(os.getenv("BITCOINCLI", "bitcoin-cli"), self.datadir)
56 self.running = False
57 self.process = None
58 self.rpc_connected = False
59 self.rpc = None
60 self.url = None
61 self.log = logging.getLogger('TestFramework.node%d' % i)
63 def __getattr__(self, *args, **kwargs):
64 """Dispatches any unrecognised messages to the RPC connection."""
65 assert self.rpc_connected and self.rpc is not None, "Error: no RPC connection"
66 return self.rpc.__getattr__(*args, **kwargs)
68 def start(self, extra_args=None, stderr=None):
69 """Start the node."""
70 if extra_args is None:
71 extra_args = self.extra_args
72 if stderr is None:
73 stderr = self.stderr
74 self.process = subprocess.Popen(self.args + extra_args, stderr=stderr)
75 self.running = True
76 self.log.debug("bitcoind started, waiting for RPC to come up")
78 def wait_for_rpc_connection(self):
79 """Sets up an RPC connection to the bitcoind process. Returns False if unable to connect."""
80 # Poll at a rate of four times per second
81 poll_per_s = 4
82 for _ in range(poll_per_s * self.rpc_timeout):
83 assert self.process.poll() is None, "bitcoind exited with status %i during initialization" % self.process.returncode
84 try:
85 self.rpc = get_rpc_proxy(rpc_url(self.datadir, self.index, self.rpchost), self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir)
86 self.rpc.getblockcount()
87 # If the call to getblockcount() succeeds then the RPC connection is up
88 self.rpc_connected = True
89 self.url = self.rpc.url
90 self.log.debug("RPC successfully started")
91 return
92 except IOError as e:
93 if e.errno != errno.ECONNREFUSED: # Port not yet open?
94 raise # unknown IO error
95 except JSONRPCException as e: # Initialization phase
96 if e.error['code'] != -28: # RPC in warmup?
97 raise # unknown JSON RPC exception
98 except ValueError as e: # cookie file not found and no rpcuser or rpcassword. bitcoind still starting
99 if "No RPC credentials" not in str(e):
100 raise
101 time.sleep(1.0 / poll_per_s)
102 raise AssertionError("Unable to connect to bitcoind")
104 def get_wallet_rpc(self, wallet_name):
105 assert self.rpc_connected
106 assert self.rpc
107 wallet_path = "wallet/%s" % wallet_name
108 return self.rpc / wallet_path
110 def stop_node(self):
111 """Stop the node."""
112 if not self.running:
113 return
114 self.log.debug("Stopping node")
115 try:
116 self.stop()
117 except http.client.CannotSendRequest:
118 self.log.exception("Unable to stop node.")
120 def is_node_stopped(self):
121 """Checks whether the node has stopped.
123 Returns True if the node has stopped. False otherwise.
124 This method is responsible for freeing resources (self.process)."""
125 if not self.running:
126 return True
127 return_code = self.process.poll()
128 if return_code is not None:
129 # process has stopped. Assert that it didn't return an error code.
130 assert_equal(return_code, 0)
131 self.running = False
132 self.process = None
133 self.log.debug("Node stopped")
134 return True
135 return False
137 def node_encrypt_wallet(self, passphrase):
138 """"Encrypts the wallet.
140 This causes bitcoind to shutdown, so this method takes
141 care of cleaning up resources."""
142 self.encryptwallet(passphrase)
143 while not self.is_node_stopped():
144 time.sleep(0.1)
145 self.rpc = None
146 self.rpc_connected = False
148 class TestNodeCLI():
149 """Interface to bitcoin-cli for an individual node"""
151 def __init__(self, binary, datadir):
152 self.binary = binary
153 self.datadir = datadir
155 def __getattr__(self, command):
156 def dispatcher(*args, **kwargs):
157 return self.send_cli(command, *args, **kwargs)
158 return dispatcher
160 def send_cli(self, command, *args, **kwargs):
161 """Run bitcoin-cli command. Deserializes returned string as python object."""
163 pos_args = [str(arg) for arg in args]
164 named_args = [str(key) + "=" + str(value) for (key, value) in kwargs.items()]
165 assert not (pos_args and named_args), "Cannot use positional arguments and named arguments in the same bitcoin-cli call"
166 p_args = [self.binary, "-datadir=" + self.datadir]
167 if named_args:
168 p_args += ["-named"]
169 p_args += [command] + pos_args + named_args
170 cli_output = subprocess.check_output(p_args, universal_newlines=True)
171 return json.loads(cli_output, parse_float=decimal.Decimal)