Merge #10711: [tests] Introduce TestNode

789733891 [tests] Introduce TestNode (John Newbery)

Pull request description:

  Continues #10082

  TestNode is a class responsible for all state related to a bitcoind node
  under test. It stores local state, is responsible for tracking the
  bitcoind process and delegates unrecognised messages to the RPC
  connection.

  This commit changes start_nodes and stop_nodes to start and stop the
  bitcoind nodes in parallel, making test setup and teardown much faster.

  On my vm, this changeset reduces total test_runner runtime for the base set of tests
  (including building the cache) from 263s to 195s (a 25% speedup). Note that the time
  reported by test_runner does not include time spent building the cache:

  *with TestNode*:
  ```
  → date +"%T" ; ./test_runner.py -q ; date +"%T"
  12:48:04
  ..................................................................................................................................................................................................................................................................................................................................
  TEST                           | STATUS    | DURATION

  abandonconflict.py             | ✓ Passed  | 12 s
  bip68-112-113-p2p.py           | ✓ Passed  | 19 s
  blockchain.py                  | ✓ Passed  | 8 s
  bumpfee.py                     | ✓ Passed  | 13 s
  decodescript.py                | ✓ Passed  | 3 s
  disablewallet.py               | ✓ Passed  | 3 s
  disconnect_ban.py              | ✓ Passed  | 6 s
  fundrawtransaction.py          | ✓ Passed  | 37 s
  getchaintips.py                | ✓ Passed  | 4 s
  httpbasics.py                  | ✓ Passed  | 3 s
  import-rescan.py               | ✓ Passed  | 4 s
  importmulti.py                 | ✓ Passed  | 6 s
  importprunedfunds.py           | ✓ Passed  | 3 s
  invalidblockrequest.py         | ✓ Passed  | 4 s
  invalidtxrequest.py            | ✓ Passed  | 4 s
  keypool.py                     | ✓ Passed  | 7 s
  listsinceblock.py              | ✓ Passed  | 4 s
  listtransactions.py            | ✓ Passed  | 33 s
  mempool_limit.py               | ✓ Passed  | 4 s
  mempool_persist.py             | ✓ Passed  | 15 s
  mempool_reorg.py               | ✓ Passed  | 4 s
  mempool_resurrect_test.py      | ✓ Passed  | 3 s
  mempool_spendcoinbase.py       | ✓ Passed  | 3 s
  merkle_blocks.py               | ✓ Passed  | 3 s
  multi_rpc.py                   | ✓ Passed  | 4 s
  net.py                         | ✓ Passed  | 3 s
  nulldummy.py                   | ✓ Passed  | 3 s
  p2p-compactblocks.py           | ✓ Passed  | 28 s
  p2p-fullblocktest.py           | ✓ Passed  | 126 s
  p2p-leaktests.py               | ✓ Passed  | 8 s
  p2p-mempool.py                 | ✓ Passed  | 3 s
  p2p-segwit.py                  | ✓ Passed  | 59 s
  p2p-versionbits-warning.py     | ✓ Passed  | 8 s
  preciousblock.py               | ✓ Passed  | 3 s
  prioritise_transaction.py      | ✓ Passed  | 5 s
  proxy_test.py                  | ✓ Passed  | 3 s
  rawtransactions.py             | ✓ Passed  | 9 s
  receivedby.py                  | ✓ Passed  | 19 s
  reindex.py                     | ✓ Passed  | 12 s
  rest.py                        | ✓ Passed  | 9 s
  rpcnamedargs.py                | ✓ Passed  | 3 s
  segwit.py                      | ✓ Passed  | 7 s
  sendheaders.py                 | ✓ Passed  | 24 s
  signmessages.py                | ✓ Passed  | 3 s
  signrawtransactions.py         | ✓ Passed  | 3 s
  txn_clone.py                   | ✓ Passed  | 4 s
  txn_doublespend.py --mineblock | ✓ Passed  | 4 s
  uptime.py                      | ✓ Passed  | 3 s
  wallet-accounts.py             | ✓ Passed  | 3 s
  wallet-dump.py                 | ✓ Passed  | 7 s
  wallet-encryption.py           | ✓ Passed  | 8 s
  wallet-hd.py                   | ✓ Passed  | 15 s
  wallet.py                      | ✓ Passed  | 31 s
  walletbackup.py                | ✓ Passed  | 104 s
  zapwallettxes.py               | ✓ Passed  | 9 s
  zmq_test.py                    | ○ Skipped | 0 s

  ALL                            | ✓ Passed  | 735 s (accumulated)
  Runtime: 189 s

  12:51:19
  ```

  *master*:
  ```
  → date +"%T" ; ./test_runner.py -q ; date +"%T"
  12:40:13
  ..........................................................................................................................................................................................................................................................................................................................................................................................................................................
  TEST                           | STATUS    | DURATION

  abandonconflict.py             | ✓ Passed  | 15 s
  bip68-112-113-p2p.py           | ✓ Passed  | 19 s
  blockchain.py                  | ✓ Passed  | 8 s
  bumpfee.py                     | ✓ Passed  | 20 s
  decodescript.py                | ✓ Passed  | 3 s
  disablewallet.py               | ✓ Passed  | 3 s
  disconnect_ban.py              | ✓ Passed  | 8 s
  fundrawtransaction.py          | ✓ Passed  | 36 s
  getchaintips.py                | ✓ Passed  | 11 s
  httpbasics.py                  | ✓ Passed  | 7 s
  import-rescan.py               | ✓ Passed  | 16 s
  importmulti.py                 | ✓ Passed  | 10 s
  importprunedfunds.py           | ✓ Passed  | 5 s
  invalidblockrequest.py         | ✓ Passed  | 4 s
  invalidtxrequest.py            | ✓ Passed  | 3 s
  keypool.py                     | ✓ Passed  | 7 s
  listsinceblock.py              | ✓ Passed  | 11 s
  listtransactions.py            | ✓ Passed  | 37 s
  mempool_limit.py               | ✓ Passed  | 4 s
  mempool_persist.py             | ✓ Passed  | 23 s
  mempool_reorg.py               | ✓ Passed  | 7 s
  mempool_resurrect_test.py      | ✓ Passed  | 3 s
  mempool_spendcoinbase.py       | ✓ Passed  | 3 s
  merkle_blocks.py               | ✓ Passed  | 10 s
  multi_rpc.py                   | ✓ Passed  | 6 s
  net.py                         | ✓ Passed  | 6 s
  nulldummy.py                   | ✓ Passed  | 3 s
  p2p-compactblocks.py           | ✓ Passed  | 30 s
  p2p-fullblocktest.py           | ✓ Passed  | 126 s
  p2p-leaktests.py               | ✓ Passed  | 8 s
  p2p-mempool.py                 | ✓ Passed  | 3 s
  p2p-segwit.py                  | ✓ Passed  | 62 s
  p2p-versionbits-warning.py     | ✓ Passed  | 8 s
  preciousblock.py               | ✓ Passed  | 8 s
  prioritise_transaction.py      | ✓ Passed  | 7 s
  proxy_test.py                  | ✓ Passed  | 10 s
  rawtransactions.py             | ✓ Passed  | 15 s
  receivedby.py                  | ✓ Passed  | 28 s
  reindex.py                     | ✓ Passed  | 12 s
  rest.py                        | ✓ Passed  | 12 s
  rpcnamedargs.py                | ✓ Passed  | 3 s
  segwit.py                      | ✓ Passed  | 12 s
  sendheaders.py                 | ✓ Passed  | 26 s
  signmessages.py                | ✓ Passed  | 3 s
  signrawtransactions.py         | ✓ Passed  | 3 s
  txn_clone.py                   | ✓ Passed  | 10 s
  txn_doublespend.py --mineblock | ✓ Passed  | 10 s
  uptime.py                      | ✓ Passed  | 3 s
  wallet-accounts.py             | ✓ Passed  | 3 s
  wallet-dump.py                 | ✓ Passed  | 6 s
  wallet-encryption.py           | ✓ Passed  | 8 s
  wallet-hd.py                   | ✓ Passed  | 18 s
  wallet.py                      | ✓ Passed  | 69 s
  walletbackup.py                | ✓ Passed  | 130 s
  zapwallettxes.py               | ✓ Passed  | 15 s
  zmq_test.py                    | ○ Skipped | 0 s

  ALL                            | ✓ Passed  | 936 s (accumulated)
  Runtime: 242 s

  12:44:36
  ```

Tree-SHA512: 6dfc4c11fd0caf7de6954c93679cf22c3df0acc6f432e616d1151062a61f456faa8ae2fe670b427868af55bb564802df84c8fd76e90b4b338750dbc23f46ad88
This commit is contained in:
MarcoFalke 2017-08-15 23:34:07 +02:00 committed by Alexander Block
parent d6ca9a78ef
commit f55da3aa54
11 changed files with 193 additions and 98 deletions

View File

@ -138,13 +138,13 @@ class BlockchainTest(BitcoinTestFramework):
self.nodes[0].generate(6) self.nodes[0].generate(6)
assert_equal(self.nodes[0].getblockcount(), 206) assert_equal(self.nodes[0].getblockcount(), 206)
self.log.debug('Node should not stop at this height') self.log.debug('Node should not stop at this height')
assert_raises(subprocess.TimeoutExpired, lambda: self.bitcoind_processes[0].wait(timeout=3)) assert_raises(subprocess.TimeoutExpired, lambda: self.nodes[0].process.wait(timeout=3))
try: try:
self.nodes[0].generate(1) self.nodes[0].generate(1)
except (ConnectionError, http.client.BadStatusLine): except (ConnectionError, http.client.BadStatusLine):
pass # The node already shut down before response pass # The node already shut down before response
self.log.debug('Node should stop at this height...') self.log.debug('Node should stop at this height...')
self.bitcoind_processes[0].wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT) self.nodes[0].process.wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT)
self.nodes[0] = self.start_node(0, self.options.tmpdir) self.nodes[0] = self.start_node(0, self.options.tmpdir)
assert_equal(self.nodes[0].getblockcount(), 207) assert_equal(self.nodes[0].getblockcount(), 207)

View File

@ -452,8 +452,7 @@ class RawTransactionsTest(BitcoinTestFramework):
self.stop_node(0) self.stop_node(0)
self.stop_node(2) self.stop_node(2)
self.stop_node(3) self.stop_node(3)
self.nodes[1].encryptwallet("test") self.nodes[1].node_encrypt_wallet("test")
self.bitcoind_processes[1].wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT)
self.nodes = self.start_nodes(self.num_nodes, self.options.tmpdir, [['-usehd=0']] * self.num_nodes) self.nodes = self.start_nodes(self.num_nodes, self.options.tmpdir, [['-usehd=0']] * self.num_nodes)
# This test is not meant to test fee estimation and we'd like # This test is not meant to test fee estimation and we'd like

View File

@ -17,7 +17,7 @@ class LongpollThread(threading.Thread):
self.longpollid = templat['longpollid'] self.longpollid = templat['longpollid']
# create a new connection to the node, we can't use the same # create a new connection to the node, we can't use the same
# connection from two threads # connection from two threads
self.node = get_rpc_proxy(node.url, 1, timeout=600) self.node = get_rpc_proxy(node.url, 1, timeout=600, coveragedir=node.coverage_dir)
def run(self): def run(self):
self.node.getblocktemplate({'longpollid':self.longpollid}) self.node.getblocktemplate({'longpollid':self.longpollid})

View File

@ -18,8 +18,7 @@ class KeyPoolTest(BitcoinTestFramework):
nodes = self.nodes nodes = self.nodes
# Encrypt wallet and wait to terminate # Encrypt wallet and wait to terminate
nodes[0].encryptwallet('test') nodes[0].node_encrypt_wallet('test')
self.bitcoind_processes[0].wait()
# Restart node 0 # Restart node 0
nodes[0] = self.start_node(0, self.options.tmpdir, ['-usehd=0']) nodes[0] = self.start_node(0, self.options.tmpdir, ['-usehd=0'])
# Keep creating keys # Keep creating keys

View File

@ -35,11 +35,15 @@ class MultiWalletTest(BitcoinTestFramework):
self.nodes[0] = self.start_node(0, self.options.tmpdir, self.extra_args[0]) self.nodes[0] = self.start_node(0, self.options.tmpdir, self.extra_args[0])
w1 = self.nodes[0] / "wallet/w1" w1 = self.nodes[0].get_wallet_rpc("w1")
w2 = self.nodes[0].get_wallet_rpc("w2")
w3 = self.nodes[0].get_wallet_rpc("w3")
wallet_bad = self.nodes[0].get_wallet_rpc("bad")
w1.generate(1) w1.generate(1)
# accessing invalid wallet fails # accessing invalid wallet fails
assert_raises_jsonrpc(-18, "Requested wallet does not exist or is not loaded", (self.nodes[0] / "wallet/bad").getwalletinfo) assert_raises_jsonrpc(-18, "Requested wallet does not exist or is not loaded", wallet_bad.getwalletinfo)
# accessing wallet RPC without using wallet endpoint fails # accessing wallet RPC without using wallet endpoint fails
assert_raises_jsonrpc(-19, "Wallet file not specified", self.nodes[0].getwalletinfo) assert_raises_jsonrpc(-19, "Wallet file not specified", self.nodes[0].getwalletinfo)
@ -50,14 +54,12 @@ class MultiWalletTest(BitcoinTestFramework):
w1_name = w1_info['walletname'] w1_name = w1_info['walletname']
assert_equal(w1_name, "w1") assert_equal(w1_name, "w1")
# check w1 wallet balance # check w2 wallet balance
w2 = self.nodes[0] / "wallet/w2"
w2_info = w2.getwalletinfo() w2_info = w2.getwalletinfo()
assert_equal(w2_info['immature_balance'], 0) assert_equal(w2_info['immature_balance'], 0)
w2_name = w2_info['walletname'] w2_name = w2_info['walletname']
assert_equal(w2_name, "w2") assert_equal(w2_name, "w2")
w3 = self.nodes[0] / "wallet/w3"
w3_name = w3.getwalletinfo()['walletname'] w3_name = w3.getwalletinfo()['walletname']
assert_equal(w3_name, "w3") assert_equal(w3_name, "w3")

View File

@ -37,7 +37,7 @@ class RPCBindTest(BitcoinTestFramework):
base_args += ['-rpcallowip=' + x for x in allow_ips] base_args += ['-rpcallowip=' + x for x in allow_ips]
binds = ['-rpcbind='+addr for addr in addresses] binds = ['-rpcbind='+addr for addr in addresses]
self.nodes = self.start_nodes(self.num_nodes, self.options.tmpdir, [base_args + binds], connect_to) self.nodes = self.start_nodes(self.num_nodes, self.options.tmpdir, [base_args + binds], connect_to)
pid = self.bitcoind_processes[0].pid pid = self.nodes[0].process.pid
assert_equal(set(get_bind_addrs(pid)), set(expected)) assert_equal(set(get_bind_addrs(pid)), set(expected))
self.stop_nodes() self.stop_nodes()
@ -49,7 +49,7 @@ class RPCBindTest(BitcoinTestFramework):
base_args = ['-disablewallet', '-nolisten'] + ['-rpcallowip='+x for x in allow_ips] base_args = ['-disablewallet', '-nolisten'] + ['-rpcallowip='+x for x in allow_ips]
self.nodes = self.start_nodes(self.num_nodes, self.options.tmpdir, [base_args]) self.nodes = self.start_nodes(self.num_nodes, self.options.tmpdir, [base_args])
# connect to node through non-loopback interface # connect to node through non-loopback interface
node = get_rpc_proxy(rpc_url(get_datadir_path(self.options.tmpdir, 0), 0, "%s:%d" % (rpchost, rpcport)), 0) node = get_rpc_proxy(rpc_url(get_datadir_path(self.options.tmpdir, 0), 0, "%s:%d" % (rpchost, rpcport)), 0, coveragedir=self.options.coveragedir)
node.getnetworkinfo() node.getnetworkinfo()
self.stop_nodes() self.stop_nodes()

View File

@ -6,15 +6,12 @@
"""Base class for RPC testing.""" """Base class for RPC testing."""
from collections import deque from collections import deque
import errno
from enum import Enum from enum import Enum
import http.client
import logging import logging
import optparse import optparse
import os import os
import pdb import pdb
import shutil import shutil
import subprocess
import sys import sys
import tempfile import tempfile
import time import time
@ -23,6 +20,7 @@ from concurrent.futures import ThreadPoolExecutor
from .authproxy import JSONRPCException from .authproxy import JSONRPCException
from . import coverage from . import coverage
from .test_node import TestNode
from .util import ( from .util import (
PortSeed, PortSeed,
MAX_NODES, MAX_NODES,
@ -32,12 +30,9 @@ from .util import (
connect_nodes, connect_nodes,
copy_datadir, copy_datadir,
disconnect_nodes, disconnect_nodes,
get_rpc_proxy,
initialize_datadir, initialize_datadir,
get_datadir_path,
log_filename, log_filename,
p2p_port, p2p_port,
rpc_url,
set_node_times, set_node_times,
satoshi_round, satoshi_round,
sync_blocks, sync_blocks,
@ -78,7 +73,6 @@ class BitcoinTestFramework(object):
self.num_nodes = 4 self.num_nodes = 4
self.setup_clean_chain = False self.setup_clean_chain = False
self.nodes = [] self.nodes = []
self.bitcoind_processes = {}
self.mocktime = 0 self.mocktime = 0
def add_options(self, parser): def add_options(self, parser):
@ -227,68 +221,64 @@ class BitcoinTestFramework(object):
def start_node(self, i, dirname, extra_args=None, rpchost=None, timewait=None, binary=None, stderr=None): def start_node(self, i, dirname, extra_args=None, rpchost=None, timewait=None, binary=None, stderr=None):
"""Start a dashd and return RPC connection to it""" """Start a dashd and return RPC connection to it"""
datadir = os.path.join(dirname, "node" + str(i)) if extra_args is None:
extra_args = []
if binary is None: if binary is None:
binary = os.getenv("BITCOIND", "dashd") binary = os.getenv("BITCOIND", "dashd")
args = [binary, "-datadir=" + datadir, "-server", "-keypool=1", "-discover=0", "-rest", "-logtimemicros", "-debug", "-debugexclude=libevent", "-debugexclude=leveldb", "-mocktime=" + str(self.mocktime), "-uacomment=testnode%d" % i]
# Don't try auto backups (they fail a lot when running tests) # Don't try auto backups (they fail a lot when running tests)
args += [ "-createwalletbackups=0" ] extra_args = extra_args + [ "-createwalletbackups=0" ]
if extra_args is not None: node = TestNode(i, dirname, extra_args, rpchost, timewait, binary, stderr, self.mocktime, coverage_dir=self.options.coveragedir)
args.extend(extra_args) node.start()
self.bitcoind_processes[i] = subprocess.Popen(args, stderr=stderr) node.wait_for_rpc_connection()
self.log.debug("initialize_chain: dashd started, waiting for RPC to come up")
self._wait_for_bitcoind_start(self.bitcoind_processes[i], datadir, i, rpchost)
self.log.debug("initialize_chain: RPC successfully started")
proxy = get_rpc_proxy(rpc_url(datadir, i, rpchost), i, timeout=timewait)
if self.options.coveragedir: if self.options.coveragedir is not None:
coverage.write_all_rpc_commands(self.options.coveragedir, proxy) coverage.write_all_rpc_commands(self.options.coveragedir, node.rpc)
return proxy return node
def start_nodes(self, num_nodes, dirname, extra_args=None, rpchost=None, timewait=None, binary=None, stderr=None): def start_nodes(self, num_nodes, dirname, extra_args=None, rpchost=None, timewait=None, binary=None, stderr=None):
"""Start multiple dashds, return RPC connections to them""" """Start multiple dashds, return RPC connections to them"""
if extra_args is None: if extra_args is None:
extra_args = [None] * num_nodes extra_args = [[]] * num_nodes
if binary is None: if binary is None:
binary = [None] * num_nodes binary = [None] * num_nodes
assert_equal(len(extra_args), num_nodes) assert_equal(len(extra_args), num_nodes)
assert_equal(len(binary), num_nodes) assert_equal(len(binary), num_nodes)
rpcs = [] nodes = []
try: try:
for i in range(num_nodes): for i in range(num_nodes):
rpcs.append(self.start_node(i, dirname, extra_args[i], rpchost, timewait=timewait, binary=binary[i], stderr=stderr)) nodes.append(TestNode(i, dirname, extra_args[i], rpchost, timewait=timewait, binary=binary[i], stderr=stderr, mocktime=self.mocktime, coverage_dir=self.options.coveragedir))
nodes[i].start()
for node in nodes:
node.wait_for_rpc_connection()
except: except:
# If one node failed to start, stop the others # If one node failed to start, stop the others
# TODO: abusing self.nodes in this way is a little hacky.
# Eventually we should do a better job of tracking nodes
self.nodes.extend(rpcs)
self.stop_nodes() self.stop_nodes()
self.nodes = []
raise raise
return rpcs
if self.options.coveragedir is not None:
for node in nodes:
coverage.write_all_rpc_commands(self.options.coveragedir, node.rpc)
return nodes
def stop_node(self, i, wait=True): def stop_node(self, i, wait=True):
"""Stop a dashd test node""" """Stop a dashd test node"""
self.nodes[i].stop_node()
self.log.debug("Stopping node %d" % i) while not self.nodes[i].is_node_stopped():
try: time.sleep(0.1)
self.nodes[i].stop()
except http.client.CannotSendRequest as e:
self.log.exception("Unable to stop node")
if wait:
self.wait_node(i)
def stop_nodes(self, fast=True): def stop_nodes(self, fast=True):
"""Stop multiple dashd test nodes""" """Stop multiple dashd test nodes"""
for node in self.nodes:
# Issue RPC to stop nodes
node.stop_node()
for i in range(len(self.nodes)): for node in self.nodes:
self.stop_node(i, not fast) # Wait for nodes to stop
if fast: while not node.is_node_stopped():
for i in range(len(self.nodes)): time.sleep(0.1)
self.wait_node(i)
assert not self.bitcoind_processes.values() # All connections must be gone now
def assert_start_raises_init_error(self, i, dirname, extra_args=None, expected_msg=None): def assert_start_raises_init_error(self, i, dirname, extra_args=None, expected_msg=None):
with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr: with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr:
@ -297,6 +287,8 @@ class BitcoinTestFramework(object):
self.stop_node(i) self.stop_node(i)
except Exception as e: except Exception as e:
assert 'dashd exited' in str(e) # node must have shutdown assert 'dashd exited' in str(e) # node must have shutdown
self.nodes[i].running = False
self.nodes[i].process = None
if expected_msg is not None: if expected_msg is not None:
log_stderr.seek(0) log_stderr.seek(0)
stderr = log_stderr.read().decode('utf-8') stderr = log_stderr.read().decode('utf-8')
@ -315,7 +307,7 @@ class BitcoinTestFramework(object):
assert_equal(return_code, 0) assert_equal(return_code, 0)
def wait_for_node_exit(self, i, timeout): def wait_for_node_exit(self, i, timeout):
self.bitcoind_processes[i].wait(timeout) self.nodes[i].process.wait(timeout)
def split_network(self): def split_network(self):
""" """
@ -415,18 +407,13 @@ class BitcoinTestFramework(object):
args.append("-connect=127.0.0.1:" + str(p2p_port(0))) args.append("-connect=127.0.0.1:" + str(p2p_port(0)))
if extra_args is not None: if extra_args is not None:
args.extend(extra_args) args.extend(extra_args)
self.bitcoind_processes[i] = subprocess.Popen(args, stderr=stderr) self.nodes.append(TestNode(i, cachedir, extra_args=[], rpchost=None, timewait=None, binary=None, stderr=stderr, mocktime=self.mocktime, coverage_dir=None))
self.log.debug("initialize_chain: dashd started, waiting for RPC to come up") self.nodes[i].args = args
self._wait_for_bitcoind_start(self.bitcoind_processes[i], datadir, i) self.nodes[i].start()
self.log.debug("initialize_chain: RPC successfully started")
self.nodes = [] # Wait for RPC connections to be ready
for i in range(MAX_NODES): for node in self.nodes:
try: node.wait_for_rpc_connection()
self.nodes.append(get_rpc_proxy(rpc_url(get_datadir_path(cachedir, i), i), i))
except:
self.log.exception("Error connecting to node %d" % i)
sys.exit(1)
# Create a 200-block-long chain; each of the 4 first nodes # Create a 200-block-long chain; each of the 4 first nodes
# gets 25 mature blocks and 25 immature. # gets 25 mature blocks and 25 immature.
@ -469,30 +456,6 @@ class BitcoinTestFramework(object):
for i in range(num_nodes): for i in range(num_nodes):
initialize_datadir(test_dir, i) initialize_datadir(test_dir, i)
def _wait_for_bitcoind_start(self, process, datadir, i, rpchost=None):
"""Wait for dashd to start.
This means that RPC is accessible and fully initialized.
Raise an exception if dashd exits during initialization."""
while True:
if process.poll() is not None:
raise Exception('dashd exited with status %i during initialization' % process.returncode)
try:
# Check if .cookie file to be created
rpc = get_rpc_proxy(rpc_url(datadir, i, rpchost), i, coveragedir=self.options.coveragedir)
rpc.getblockcount()
break # break out of loop on success
except IOError as e:
if e.errno != errno.ECONNREFUSED: # Port not yet open?
raise # unknown IO error
except JSONRPCException as e: # Initialization phase
if e.error['code'] != -28: # RPC in warmup?
raise # unknown JSON RPC exception
except ValueError as e: # cookie file not found and no rpcuser or rpcassword. dashd still starting
if "No RPC credentials" not in str(e):
raise
time.sleep(0.25)
MASTERNODE_COLLATERAL = 1000 MASTERNODE_COLLATERAL = 1000

View File

@ -0,0 +1,134 @@
#!/usr/bin/env python3
# Copyright (c) 2017 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Class for bitcoind node under test"""
import errno
import http.client
import logging
import os
import subprocess
import time
from .util import (
assert_equal,
get_rpc_proxy,
rpc_url,
)
from .authproxy import JSONRPCException
class TestNode():
"""A class for representing a bitcoind node under test.
This class contains:
- state about the node (whether it's running, etc)
- a Python subprocess.Popen object representing the running process
- an RPC connection to the node
To make things easier for the test writer, a bit of magic is happening under the covers.
Any unrecognised messages will be dispatched to the RPC connection."""
def __init__(self, i, dirname, extra_args, rpchost, timewait, binary, stderr, mocktime, coverage_dir):
self.index = i
self.datadir = os.path.join(dirname, "node" + str(i))
self.rpchost = rpchost
self.rpc_timeout = timewait
if binary is None:
self.binary = os.getenv("BITCOIND", "bitcoind")
else:
self.binary = binary
self.stderr = stderr
self.coverage_dir = coverage_dir
# 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.
self.extra_args = extra_args
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]
self.running = False
self.process = None
self.rpc_connected = False
self.rpc = None
self.url = None
self.log = logging.getLogger('TestFramework.node%d' % i)
def __getattr__(self, *args, **kwargs):
"""Dispatches any unrecognised messages to the RPC connection."""
assert self.rpc_connected and self.rpc is not None, "Error: no RPC connection"
return self.rpc.__getattr__(*args, **kwargs)
def start(self):
"""Start the node."""
self.process = subprocess.Popen(self.args + self.extra_args, stderr=self.stderr)
self.running = True
self.log.debug("bitcoind started, waiting for RPC to come up")
def wait_for_rpc_connection(self):
"""Sets up an RPC connection to the bitcoind process. Returns False if unable to connect."""
# Wait for up to 10 seconds for the RPC server to respond
for _ in range(40):
assert not self.process.poll(), "bitcoind exited with status %i during initialization" % self.process.returncode
try:
self.rpc = get_rpc_proxy(rpc_url(self.datadir, self.index, self.rpchost), self.index, coveragedir=self.coverage_dir)
self.rpc.getblockcount()
# If the call to getblockcount() succeeds then the RPC connection is up
self.rpc_connected = True
self.url = self.rpc.url
self.log.debug("RPC successfully started")
return
except IOError as e:
if e.errno != errno.ECONNREFUSED: # Port not yet open?
raise # unknown IO error
except JSONRPCException as e: # Initialization phase
if e.error['code'] != -28: # RPC in warmup?
raise # unknown JSON RPC exception
except ValueError as e: # cookie file not found and no rpcuser or rpcassword. bitcoind still starting
if "No RPC credentials" not in str(e):
raise
time.sleep(0.25)
raise AssertionError("Unable to connect to bitcoind")
def get_wallet_rpc(self, wallet_name):
assert self.rpc_connected
assert self.rpc
wallet_path = "wallet/%s" % wallet_name
return self.rpc / wallet_path
def stop_node(self):
"""Stop the node."""
if not self.running:
return
self.log.debug("Stopping node")
try:
self.stop()
except http.client.CannotSendRequest:
self.log.exception("Unable to stop node.")
def is_node_stopped(self):
"""Checks whether the node has stopped.
Returns True if the node has stopped. False otherwise.
This method is responsible for freeing resources (self.process)."""
if not self.running:
return True
return_code = self.process.poll()
if return_code is not None:
# process has stopped. Assert that it didn't return an error code.
assert_equal(return_code, 0)
self.running = False
self.process = None
self.log.debug("Node stopped")
return True
return False
def node_encrypt_wallet(self, passphrase):
""""Encrypts the wallet.
This causes bitcoind to shutdown, so this method takes
care of cleaning up resources."""
self.encryptwallet(passphrase)
while not self.is_node_stopped():
time.sleep(0.1)
self.rpc = None
self.rpc_connected = False

View File

@ -220,7 +220,7 @@ def rpc_port(n):
return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES) return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
def rpc_url(datadir, i, rpchost=None): def rpc_url(datadir, i, rpchost=None):
rpc_u, rpc_p = get_auth_cookie(datadir, i) rpc_u, rpc_p = get_auth_cookie(datadir)
host = '127.0.0.1' host = '127.0.0.1'
port = rpc_port(i) port = rpc_port(i)
if rpchost: if rpchost:
@ -248,7 +248,7 @@ def initialize_datadir(dirname, n):
def get_datadir_path(dirname, n): def get_datadir_path(dirname, n):
return os.path.join(dirname, "node" + str(n)) return os.path.join(dirname, "node" + str(n))
def get_auth_cookie(datadir, n): def get_auth_cookie(datadir):
user = None user = None
password = None password = None
if os.path.isfile(os.path.join(datadir, "dash.conf")): if os.path.isfile(os.path.join(datadir, "dash.conf")):

View File

@ -96,8 +96,7 @@ class WalletDumpTest(BitcoinTestFramework):
assert_equal(found_addr_rsv, 180) # keypool size (external+internal) assert_equal(found_addr_rsv, 180) # keypool size (external+internal)
#encrypt wallet, restart, unlock and dump #encrypt wallet, restart, unlock and dump
self.nodes[0].encryptwallet('test') self.nodes[0].node_encrypt_wallet('test')
self.bitcoind_processes[0].wait()
self.nodes[0] = self.start_node(0, tmpdir, self.extra_args[0]) self.nodes[0] = self.start_node(0, tmpdir, self.extra_args[0])
self.nodes[0].walletpassphrase('test', 10) self.nodes[0].walletpassphrase('test', 10)
# Should be a no-op: # Should be a no-op:

View File

@ -30,8 +30,7 @@ class WalletEncryptionTest(BitcoinTestFramework):
assert_equal(len(privkey), 52) assert_equal(len(privkey), 52)
# Encrypt the wallet # Encrypt the wallet
self.nodes[0].encryptwallet(passphrase) self.nodes[0].node_encrypt_wallet(passphrase)
self.bitcoind_processes[0].wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT)
self.nodes[0] = self.start_node(0, self.options.tmpdir) self.nodes[0] = self.start_node(0, self.options.tmpdir)
# Test that the wallet is encrypted # Test that the wallet is encrypted