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"""
22 from .authproxy
import JSONRPCException
24 BITCOIND_PROC_WAIT_TIMEOUT
= 60
27 """A class for representing a bitcoind node under test.
31 - state about the node (whether it's running, etc)
32 - a Python subprocess.Popen object representing the running process
33 - an RPC connection to the node
35 To make things easier for the test writer, a bit of magic is happening under the covers.
36 Any unrecognised messages will be dispatched to the RPC connection."""
38 def __init__(self
, i
, dirname
, extra_args
, rpchost
, timewait
, binary
, stderr
, mocktime
, coverage_dir
):
40 self
.datadir
= os
.path
.join(dirname
, "node" + str(i
))
41 self
.rpchost
= rpchost
43 self
.rpc_timeout
= timewait
45 # Wait for up to 60 seconds for the RPC server to respond
48 self
.binary
= os
.getenv("BITCOIND", "bitcoind")
52 self
.coverage_dir
= coverage_dir
53 # 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.
54 self
.extra_args
= extra_args
55 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
]
57 self
.cli
= TestNodeCLI(os
.getenv("BITCOINCLI", "bitcoin-cli"), self
.datadir
)
61 self
.rpc_connected
= False
64 self
.log
= logging
.getLogger('TestFramework.node%d' % i
)
66 def __getattr__(self
, name
):
67 """Dispatches any unrecognised messages to the RPC connection."""
68 assert self
.rpc_connected
and self
.rpc
is not None, "Error: no RPC connection"
69 return getattr(self
.rpc
, name
)
71 def start(self
, extra_args
=None, stderr
=None):
73 if extra_args
is None:
74 extra_args
= self
.extra_args
77 self
.process
= subprocess
.Popen(self
.args
+ extra_args
, stderr
=stderr
)
79 self
.log
.debug("bitcoind started, waiting for RPC to come up")
81 def wait_for_rpc_connection(self
):
82 """Sets up an RPC connection to the bitcoind process. Returns False if unable to connect."""
83 # Poll at a rate of four times per second
85 for _
in range(poll_per_s
* self
.rpc_timeout
):
86 assert self
.process
.poll() is None, "bitcoind exited with status %i during initialization" % self
.process
.returncode
88 self
.rpc
= get_rpc_proxy(rpc_url(self
.datadir
, self
.index
, self
.rpchost
), self
.index
, timeout
=self
.rpc_timeout
, coveragedir
=self
.coverage_dir
)
89 self
.rpc
.getblockcount()
90 # If the call to getblockcount() succeeds then the RPC connection is up
91 self
.rpc_connected
= True
92 self
.url
= self
.rpc
.url
93 self
.log
.debug("RPC successfully started")
96 if e
.errno
!= errno
.ECONNREFUSED
: # Port not yet open?
97 raise # unknown IO error
98 except JSONRPCException
as e
: # Initialization phase
99 if e
.error
['code'] != -28: # RPC in warmup?
100 raise # unknown JSON RPC exception
101 except ValueError as e
: # cookie file not found and no rpcuser or rpcassword. bitcoind still starting
102 if "No RPC credentials" not in str(e
):
104 time
.sleep(1.0 / poll_per_s
)
105 raise AssertionError("Unable to connect to bitcoind")
107 def get_wallet_rpc(self
, wallet_name
):
108 assert self
.rpc_connected
110 wallet_path
= "wallet/%s" % wallet_name
111 return self
.rpc
/ wallet_path
117 self
.log
.debug("Stopping node")
120 except http
.client
.CannotSendRequest
:
121 self
.log
.exception("Unable to stop node.")
123 def is_node_stopped(self
):
124 """Checks whether the node has stopped.
126 Returns True if the node has stopped. False otherwise.
127 This method is responsible for freeing resources (self.process)."""
130 return_code
= self
.process
.poll()
131 if return_code
is None:
134 # process has stopped. Assert that it didn't return an error code.
135 assert_equal(return_code
, 0)
138 self
.rpc_connected
= False
140 self
.log
.debug("Node stopped")
143 def wait_until_stopped(self
, timeout
=BITCOIND_PROC_WAIT_TIMEOUT
):
144 wait_until(self
.is_node_stopped
, timeout
=timeout
)
146 def node_encrypt_wallet(self
, passphrase
):
147 """"Encrypts the wallet.
149 This causes bitcoind to shutdown, so this method takes
150 care of cleaning up resources."""
151 self
.encryptwallet(passphrase
)
152 self
.wait_until_stopped()
155 """Interface to bitcoin-cli for an individual node"""
157 def __init__(self
, binary
, datadir
):
160 self
.datadir
= datadir
163 def __call__(self
, *args
, input=None):
164 # TestNodeCLI is callable with bitcoin-cli command-line args
165 self
.args
= [str(arg
) for arg
in args
]
169 def __getattr__(self
, command
):
170 def dispatcher(*args
, **kwargs
):
171 return self
.send_cli(command
, *args
, **kwargs
)
174 def send_cli(self
, command
, *args
, **kwargs
):
175 """Run bitcoin-cli command. Deserializes returned string as python object."""
177 pos_args
= [str(arg
) for arg
in args
]
178 named_args
= [str(key
) + "=" + str(value
) for (key
, value
) in kwargs
.items()]
179 assert not (pos_args
and named_args
), "Cannot use positional arguments and named arguments in the same bitcoin-cli call"
180 p_args
= [self
.binary
, "-datadir=" + self
.datadir
] + self
.args
183 p_args
+= [command
] + pos_args
+ named_args
184 process
= subprocess
.Popen(p_args
, stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
, universal_newlines
=True)
185 cli_stdout
, cli_stderr
= process
.communicate(input=self
.input)
186 returncode
= process
.poll()
188 # Ignore cli_stdout, raise with cli_stderr
189 raise subprocess
.CalledProcessError(returncode
, self
.binary
, output
=cli_stderr
)
190 return json
.loads(cli_stdout
, parse_float
=decimal
.Decimal
)