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"""
21 from .authproxy
import JSONRPCException
24 """A class for representing a bitcoind node under test.
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
):
37 self
.datadir
= os
.path
.join(dirname
, "node" + str(i
))
38 self
.rpchost
= rpchost
40 self
.rpc_timeout
= timewait
42 # Wait for up to 60 seconds for the RPC server to respond
45 self
.binary
= os
.getenv("BITCOIND", "bitcoind")
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
)
58 self
.rpc_connected
= False
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):
70 if extra_args
is None:
71 extra_args
= self
.extra_args
74 self
.process
= subprocess
.Popen(self
.args
+ extra_args
, stderr
=stderr
)
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
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
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")
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
):
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
107 wallet_path
= "wallet/%s" % wallet_name
108 return self
.rpc
/ wallet_path
114 self
.log
.debug("Stopping node")
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)."""
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)
133 self
.log
.debug("Node stopped")
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():
146 self
.rpc_connected
= False
149 """Interface to bitcoin-cli for an individual node"""
151 def __init__(self
, binary
, datadir
):
153 self
.datadir
= datadir
155 def __getattr__(self
, command
):
156 def dispatcher(*args
, **kwargs
):
157 return self
.send_cli(command
, *args
, **kwargs
)
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
]
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
)