You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_node.py 6.6KB

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