@ -5,7 +5,9 @@
""" 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
@ -16,15 +18,16 @@ import tempfile
import time
import time
import traceback
import traceback
from . authproxy import JSONRPCException
from . import coverage
from . util import (
from . util import (
PortSeed ,
MAX_NODES ,
MAX_NODES ,
bitcoind_processes ,
PortSeed ,
assert_equal ,
check_json_precision ,
check_json_precision ,
connect_nodes_bi ,
connect_nodes_bi ,
disable_mocktime ,
disable_mocktime ,
disconnect_nodes ,
disconnect_nodes ,
enable_coverage ,
enable_mocktime ,
enable_mocktime ,
get_mocktime ,
get_mocktime ,
get_rpc_proxy ,
get_rpc_proxy ,
@ -34,15 +37,9 @@ from .util import (
p2p_port ,
p2p_port ,
rpc_url ,
rpc_url ,
set_node_times ,
set_node_times ,
_start_node ,
_start_nodes ,
_stop_node ,
_stop_nodes ,
sync_blocks ,
sync_blocks ,
sync_mempools ,
sync_mempools ,
wait_for_bitcoind_start ,
)
)
from . authproxy import JSONRPCException
class TestStatus ( Enum ) :
class TestStatus ( Enum ) :
PASSED = 1
PASSED = 1
@ -53,6 +50,8 @@ TEST_EXIT_PASSED = 0
TEST_EXIT_FAILED = 1
TEST_EXIT_FAILED = 1
TEST_EXIT_SKIPPED = 77
TEST_EXIT_SKIPPED = 77
BITCOIND_PROC_WAIT_TIMEOUT = 60
class BitcoinTestFramework ( object ) :
class BitcoinTestFramework ( object ) :
""" Base class for a bitcoin test script.
""" Base class for a bitcoin test script.
@ -72,7 +71,8 @@ class BitcoinTestFramework(object):
def __init__ ( self ) :
def __init__ ( self ) :
self . num_nodes = 4
self . num_nodes = 4
self . setup_clean_chain = False
self . setup_clean_chain = False
self . nodes = None
self . nodes = [ ]
self . bitcoind_processes = { }
def add_options ( self , parser ) :
def add_options ( self , parser ) :
pass
pass
@ -98,7 +98,7 @@ class BitcoinTestFramework(object):
extra_args = None
extra_args = None
if hasattr ( self , " extra_args " ) :
if hasattr ( self , " extra_args " ) :
extra_args = 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 ) :
def run_test ( self ) :
raise NotImplementedError
raise NotImplementedError
@ -130,9 +130,6 @@ class BitcoinTestFramework(object):
self . add_options ( parser )
self . add_options ( parser )
( self . options , self . args ) = parser . parse_args ( )
( self . options , self . args ) = parser . parse_args ( )
if self . options . coveragedir :
enable_coverage ( self . options . coveragedir )
PortSeed . n = self . options . port_seed
PortSeed . n = self . options . port_seed
os . environ [ ' PATH ' ] = self . options . srcdir + " : " + self . options . srcdir + " /qt: " + os . environ [ ' PATH ' ]
os . environ [ ' PATH ' ] = self . options . srcdir + " : " + self . options . srcdir + " /qt: " + os . environ [ ' PATH ' ]
@ -209,16 +206,88 @@ class BitcoinTestFramework(object):
# Public helper methods. These can be accessed by the subclass test scripts.
# 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 ) :
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 ) :
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 ) :
self . log . debug ( " Stopping node %d " % i )
_stop_node ( self . nodes [ num_node ] , num_node )
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 ) :
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 ) :
def split_network ( self ) :
"""
"""
@ -300,9 +369,9 @@ class BitcoinTestFramework(object):
args = [ os . getenv ( " BITCOIND " , " bitcoind " ) , " -server " , " -keypool=1 " , " -datadir= " + datadir , " -discover=0 " ]
args = [ os . getenv ( " BITCOIND " , " bitcoind " ) , " -server " , " -keypool=1 " , " -datadir= " + datadir , " -discover=0 " ]
if i > 0 :
if i > 0 :
args . append ( " -connect=127.0.0.1: " + str ( p2p_port ( 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 " )
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 . log . debug ( " initialize_chain: RPC successfully started " )
self . nodes = [ ]
self . nodes = [ ]
@ -355,6 +424,30 @@ 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 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 ) :
class ComparisonTestFramework ( BitcoinTestFramework ) :
""" Test framework for doing p2p comparison testing
""" Test framework for doing p2p comparison testing