@ -5,7 +5,9 @@
@@ -5,7 +5,9 @@
""" Base class for RPC testing. """
from collections import deque
import errno
from enum import Enum
import http . client
import logging
import optparse
import os
@ -16,15 +18,16 @@ import tempfile
@@ -16,15 +18,16 @@ import tempfile
import time
import traceback
from . authproxy import JSONRPCException
from . import coverage
from . util import (
PortSeed ,
MAX_NODES ,
bitcoind_processes ,
PortSeed ,
assert_equal ,
check_json_precision ,
connect_nodes_bi ,
disable_mocktime ,
disconnect_nodes ,
enable_coverage ,
enable_mocktime ,
get_mocktime ,
get_rpc_proxy ,
@ -34,15 +37,9 @@ from .util import (
@@ -34,15 +37,9 @@ from .util import (
p2p_port ,
rpc_url ,
set_node_times ,
_start_node ,
_start_nodes ,
_stop_node ,
_stop_nodes ,
sync_blocks ,
sync_mempools ,
wait_for_bitcoind_start ,
)
from . authproxy import JSONRPCException
class TestStatus ( Enum ) :
PASSED = 1
@ -53,6 +50,8 @@ TEST_EXIT_PASSED = 0
@@ -53,6 +50,8 @@ TEST_EXIT_PASSED = 0
TEST_EXIT_FAILED = 1
TEST_EXIT_SKIPPED = 77
BITCOIND_PROC_WAIT_TIMEOUT = 60
class BitcoinTestFramework ( object ) :
""" Base class for a bitcoin test script.
@ -72,7 +71,8 @@ class BitcoinTestFramework(object):
@@ -72,7 +71,8 @@ class BitcoinTestFramework(object):
def __init__ ( self ) :
self . num_nodes = 4
self . setup_clean_chain = False
self . nodes = None
self . nodes = [ ]
self . bitcoind_processes = { }
def add_options ( self , parser ) :
pass
@ -98,7 +98,7 @@ class BitcoinTestFramework(object):
@@ -98,7 +98,7 @@ class BitcoinTestFramework(object):
extra_args = None
if hasattr ( self , " extra_args " ) :
extra_args = self . extra_args
self . nodes = _ start_nodes( self . num_nodes , self . options . tmpdir , extra_args )
self . nodes = self . start_nodes ( self . num_nodes , self . options . tmpdir , extra_args )
def run_test ( self ) :
raise NotImplementedError
@ -130,9 +130,6 @@ class BitcoinTestFramework(object):
@@ -130,9 +130,6 @@ class BitcoinTestFramework(object):
self . add_options ( parser )
( self . options , self . args ) = parser . parse_args ( )
if self . options . coveragedir :
enable_coverage ( self . options . coveragedir )
PortSeed . n = self . options . port_seed
os . environ [ ' PATH ' ] = self . options . srcdir + " : " + self . options . srcdir + " /qt: " + os . environ [ ' PATH ' ]
@ -209,16 +206,88 @@ class BitcoinTestFramework(object):
@@ -209,16 +206,88 @@ class BitcoinTestFramework(object):
# Public helper methods. These can be accessed by the subclass test scripts.
def start_node ( self , i , dirname , extra_args = None , rpchost = None , timewait = None , binary = None , stderr = None ) :
return _start_node ( i , dirname , extra_args , rpchost , timewait , binary , stderr )
""" Start a bitcoind and return RPC connection to it """
datadir = os . path . join ( dirname , " node " + str ( i ) )
if binary is None :
binary = os . getenv ( " BITCOIND " , " bitcoind " )
args = [ binary , " -datadir= " + datadir , " -server " , " -keypool=1 " , " -discover=0 " , " -rest " , " -logtimemicros " , " -debug " , " -debugexclude=libevent " , " -debugexclude=leveldb " , " -mocktime= " + str ( get_mocktime ( ) ) , " -uacomment=testnode %d " % i ]
if extra_args is not None :
args . extend ( extra_args )
self . bitcoind_processes [ i ] = subprocess . Popen ( args , stderr = stderr )
self . log . debug ( " initialize_chain: bitcoind 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 :
coverage . write_all_rpc_commands ( self . options . coveragedir , proxy )
return proxy
def start_nodes ( self , num_nodes , dirname , extra_args = None , rpchost = None , timewait = None , binary = None ) :
return _start_nodes ( num_nodes , dirname , extra_args , rpchost , timewait , binary )
""" Start multiple bitcoinds, return RPC connections to them """
if extra_args is None :
extra_args = [ None ] * num_nodes
if binary is None :
binary = [ None ] * num_nodes
assert_equal ( len ( extra_args ) , num_nodes )
assert_equal ( len ( binary ) , num_nodes )
rpcs = [ ]
try :
for i in range ( num_nodes ) :
rpcs . append ( self . start_node ( i , dirname , extra_args [ i ] , rpchost , timewait = timewait , binary = binary [ i ] ) )
except :
# 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 . nodes = [ ]
raise
return rpcs
def stop_node ( self , i ) :
""" Stop a bitcoind test node """
def stop_node ( self , num_node ) :
_stop_node ( self . nodes [ num_node ] , num_node )
self . log . debug ( " Stopping node %d " % i )
try :
self . nodes [ i ] . stop ( )
except http . client . CannotSendRequest as e :
self . log . exception ( " Unable to stop node " )
return_code = self . bitcoind_processes [ i ] . wait ( timeout = BITCOIND_PROC_WAIT_TIMEOUT )
assert_equal ( return_code , 0 )
del self . bitcoind_processes [ i ]
def stop_nodes ( self ) :
_stop_nodes ( self . nodes )
""" Stop multiple bitcoind test nodes """
for i in range ( len ( self . nodes ) ) :
self . stop_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 ) :
with tempfile . SpooledTemporaryFile ( max_size = 2 * * 16 ) as log_stderr :
try :
self . start_node ( i , dirname , extra_args , stderr = log_stderr )
self . stop_node ( i )
except Exception as e :
assert ' bitcoind exited ' in str ( e ) # node must have shutdown
if expected_msg is not None :
log_stderr . seek ( 0 )
stderr = log_stderr . read ( ) . decode ( ' utf-8 ' )
if expected_msg not in stderr :
raise AssertionError ( " Expected error \" " + expected_msg + " \" not found in: \n " + stderr )
else :
if expected_msg is None :
assert_msg = " bitcoind should have exited with an error "
else :
assert_msg = " bitcoind should have exited with expected error " + expected_msg
raise AssertionError ( assert_msg )
def wait_for_node_exit ( self , i , timeout ) :
self . bitcoind_processes [ i ] . wait ( timeout )
def split_network ( self ) :
"""
@ -300,9 +369,9 @@ class BitcoinTestFramework(object):
@@ -300,9 +369,9 @@ class BitcoinTestFramework(object):
args = [ os . getenv ( " BITCOIND " , " bitcoind " ) , " -server " , " -keypool=1 " , " -datadir= " + datadir , " -discover=0 " ]
if i > 0 :
args . append ( " -connect=127.0.0.1: " + str ( p2p_port ( 0 ) ) )
bitcoind_processes [ i ] = subprocess . Popen ( args )
self . bitcoind_processes [ i ] = subprocess . Popen ( args )
self . log . debug ( " initialize_chain: bitcoind started, waiting for RPC to come up " )
wait_for_bitcoind_start ( bitcoind_processes [ i ] , datadir , i )
self . _ wait_for_bitcoind_start( self . bitcoind_processes [ i ] , datadir , i )
self . log . debug ( " initialize_chain: RPC successfully started " )
self . nodes = [ ]
@ -355,6 +424,30 @@ class BitcoinTestFramework(object):
@@ -355,6 +424,30 @@ class BitcoinTestFramework(object):
for i in range ( num_nodes ) :
initialize_datadir ( test_dir , i )
def _wait_for_bitcoind_start ( self , process , datadir , i , rpchost = None ) :
""" Wait for bitcoind to start.
This means that RPC is accessible and fully initialized .
Raise an exception if bitcoind exits during initialization . """
while True :
if process . poll ( ) is not None :
raise Exception ( ' bitcoind 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. bitcoind still starting
if " No RPC credentials " not in str ( e ) :
raise
time . sleep ( 0.25 )
class ComparisonTestFramework ( BitcoinTestFramework ) :
""" Test framework for doing p2p comparison testing