aboutsummaryrefslogtreecommitdiff
path: root/test/functional/test_framework
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional/test_framework')
-rw-r--r--test/functional/test_framework/blocktools.py112
-rwxr-xr-xtest/functional/test_framework/test_framework.py23
-rwxr-xr-xtest/functional/test_framework/test_node.py61
-rw-r--r--test/functional/test_framework/util.py1
4 files changed, 122 insertions, 75 deletions
diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py
index 43982cd09a..5c2b1815e5 100644
--- a/test/functional/test_framework/blocktools.py
+++ b/test/functional/test_framework/blocktools.py
@@ -10,7 +10,24 @@ from .address import (
script_to_p2sh_p2wsh,
script_to_p2wsh,
)
-from .mininode import *
+from .messages import (
+ CBlock,
+ COIN,
+ COutPoint,
+ CTransaction,
+ CTxIn,
+ CTxInWitness,
+ CTxOut,
+ FromHex,
+ ToHex,
+ bytes_to_hex_str,
+ hash256,
+ hex_str_to_bytes,
+ ser_string,
+ ser_uint256,
+ sha256,
+ uint256_from_str,
+)
from .script import (
CScript,
OP_0,
@@ -23,34 +40,34 @@ from .script import (
)
from .util import assert_equal
-# Create a block (with regtest difficulty)
-def create_block(hashprev, coinbase, nTime=None):
+# From BIP141
+WITNESS_COMMITMENT_HEADER = b"\xaa\x21\xa9\xed"
+
+def create_block(hashprev, coinbase, ntime=None):
+ """Create a block (with regtest difficulty)."""
block = CBlock()
- if nTime is None:
+ if ntime is None:
import time
- block.nTime = int(time.time()+600)
+ block.nTime = int(time.time() + 600)
else:
- block.nTime = nTime
+ block.nTime = ntime
block.hashPrevBlock = hashprev
- block.nBits = 0x207fffff # Will break after a difficulty adjustment...
+ block.nBits = 0x207fffff # difficulty retargeting is disabled in REGTEST chainparams
block.vtx.append(coinbase)
block.hashMerkleRoot = block.calc_merkle_root()
block.calc_sha256()
return block
-# From BIP141
-WITNESS_COMMITMENT_HEADER = b"\xaa\x21\xa9\xed"
-
-
def get_witness_script(witness_root, witness_nonce):
- witness_commitment = uint256_from_str(hash256(ser_uint256(witness_root)+ser_uint256(witness_nonce)))
+ witness_commitment = uint256_from_str(hash256(ser_uint256(witness_root) + ser_uint256(witness_nonce)))
output_data = WITNESS_COMMITMENT_HEADER + ser_uint256(witness_commitment)
return CScript([OP_RETURN, output_data])
-
-# According to BIP141, blocks with witness rules active must commit to the
-# hash of all in-block transactions including witness.
def add_witness_commitment(block, nonce=0):
+ """Add a witness commitment to the block's coinbase transaction.
+
+ According to BIP141, blocks with witness rules active must commit to the
+ hash of all in-block transactions including witness."""
# First calculate the merkle root of the block's
# transactions, with witnesses.
witness_nonce = nonce
@@ -65,7 +82,6 @@ def add_witness_commitment(block, nonce=0):
block.hashMerkleRoot = block.calc_merkle_root()
block.rehash()
-
def serialize_script_num(value):
r = bytearray(0)
if value == 0:
@@ -81,55 +97,59 @@ def serialize_script_num(value):
r[-1] |= 0x80
return r
-# Create a coinbase transaction, assuming no miner fees.
-# If pubkey is passed in, the coinbase output will be a P2PK output;
-# otherwise an anyone-can-spend output.
-def create_coinbase(height, pubkey = None):
+def create_coinbase(height, pubkey=None):
+ """Create a coinbase transaction, assuming no miner fees.
+
+ If pubkey is passed in, the coinbase output will be a P2PK output;
+ otherwise an anyone-can-spend output."""
coinbase = CTransaction()
- coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff),
- ser_string(serialize_script_num(height)), 0xffffffff))
+ coinbase.vin.append(CTxIn(COutPoint(0, 0xffffffff),
+ ser_string(serialize_script_num(height)), 0xffffffff))
coinbaseoutput = CTxOut()
coinbaseoutput.nValue = 50 * COIN
- halvings = int(height/150) # regtest
+ halvings = int(height / 150) # regtest
coinbaseoutput.nValue >>= halvings
- if (pubkey != None):
+ if (pubkey is not None):
coinbaseoutput.scriptPubKey = CScript([pubkey, OP_CHECKSIG])
else:
coinbaseoutput.scriptPubKey = CScript([OP_TRUE])
- coinbase.vout = [ coinbaseoutput ]
+ coinbase.vout = [coinbaseoutput]
coinbase.calc_sha256()
return coinbase
-# Create a transaction.
-# If the scriptPubKey is not specified, make it anyone-can-spend.
-def create_transaction(prevtx, n, sig, value, scriptPubKey=CScript()):
+def create_transaction(prevtx, n, sig, value, script_pub_key=CScript()):
+ """Create a transaction.
+
+ If the script_pub_key is not specified, make it anyone-can-spend."""
tx = CTransaction()
assert(n < len(prevtx.vout))
tx.vin.append(CTxIn(COutPoint(prevtx.sha256, n), sig, 0xffffffff))
- tx.vout.append(CTxOut(value, scriptPubKey))
+ tx.vout.append(CTxOut(value, script_pub_key))
tx.calc_sha256()
return tx
-def get_legacy_sigopcount_block(block, fAccurate=True):
+def get_legacy_sigopcount_block(block, accurate=True):
count = 0
for tx in block.vtx:
- count += get_legacy_sigopcount_tx(tx, fAccurate)
+ count += get_legacy_sigopcount_tx(tx, accurate)
return count
-def get_legacy_sigopcount_tx(tx, fAccurate=True):
+def get_legacy_sigopcount_tx(tx, accurate=True):
count = 0
for i in tx.vout:
- count += i.scriptPubKey.GetSigOpCount(fAccurate)
+ count += i.scriptPubKey.GetSigOpCount(accurate)
for j in tx.vin:
# scriptSig might be of type bytes, so convert to CScript for the moment
- count += CScript(j.scriptSig).GetSigOpCount(fAccurate)
+ count += CScript(j.scriptSig).GetSigOpCount(accurate)
return count
-# Create a scriptPubKey corresponding to either a P2WPKH output for the
-# given pubkey, or a P2WSH output of a 1-of-1 multisig for the given
-# pubkey. Returns the hex encoding of the scriptPubKey.
def witness_script(use_p2wsh, pubkey):
- if (use_p2wsh == False):
+ """Create a scriptPubKey for a pay-to-wtiness TxOut.
+
+ This is either a P2WPKH output for the given pubkey, or a P2WSH output of a
+ 1-of-1 multisig for the given pubkey. Returns the hex encoding of the
+ scriptPubKey."""
+ if not use_p2wsh:
# P2WPKH instead
pubkeyhash = hash160(hex_str_to_bytes(pubkey))
pkscript = CScript([OP_0, pubkeyhash])
@@ -140,9 +160,10 @@ def witness_script(use_p2wsh, pubkey):
pkscript = CScript([OP_0, scripthash])
return bytes_to_hex_str(pkscript)
-# Return a transaction (in hex) that spends the given utxo to a segwit output,
-# optionally wrapping the segwit output using P2SH.
def create_witness_tx(node, use_p2wsh, utxo, pubkey, encode_p2sh, amount):
+ """Return a transaction (in hex) that spends the given utxo to a segwit output.
+
+ Optionally wrap the segwit output using P2SH."""
if use_p2wsh:
program = CScript([OP_1, hex_str_to_bytes(pubkey), OP_1, OP_CHECKMULTISIG])
addr = script_to_p2sh_p2wsh(program) if encode_p2sh else script_to_p2wsh(program)
@@ -152,12 +173,13 @@ def create_witness_tx(node, use_p2wsh, utxo, pubkey, encode_p2sh, amount):
assert_equal(node.getaddressinfo(addr)['scriptPubKey'], witness_script(use_p2wsh, pubkey))
return node.createrawtransaction([utxo], {addr: amount})
-# Create a transaction spending a given utxo to a segwit output corresponding
-# to the given pubkey: use_p2wsh determines whether to use P2WPKH or P2WSH;
-# encode_p2sh determines whether to wrap in P2SH.
-# sign=True will have the given node sign the transaction.
-# insert_redeem_script will be added to the scriptSig, if given.
def send_to_witness(use_p2wsh, node, utxo, pubkey, encode_p2sh, amount, sign=True, insert_redeem_script=""):
+ """Create a transaction spending a given utxo to a segwit output.
+
+ The output corresponds to the given pubkey: use_p2wsh determines whether to
+ use P2WPKH or P2WSH; encode_p2sh determines whether to wrap in P2SH.
+ sign=True will have the given node sign the transaction.
+ insert_redeem_script will be added to the scriptSig, if given."""
tx_to_witness = create_witness_tx(node, use_p2wsh, utxo, pubkey, encode_p2sh, amount)
if (sign):
signed = node.signrawtransactionwithwallet(tx_to_witness)
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 54ff9eb038..472664a314 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -4,6 +4,7 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Base class for RPC testing."""
+import configparser
from enum import Enum
import logging
import optparse
@@ -97,10 +98,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
help="Leave bitcoinds and test.* datadir on exit or error")
parser.add_option("--noshutdown", dest="noshutdown", default=False, action="store_true",
help="Don't stop bitcoinds after the test execution")
- parser.add_option("--srcdir", dest="srcdir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../../../src"),
+ parser.add_option("--srcdir", dest="srcdir", default=os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../../../src"),
help="Source directory containing bitcoind/bitcoin-cli (default: %default)")
- parser.add_option("--cachedir", dest="cachedir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../../cache"),
- help="Directory for caching pregenerated datadirs")
+ parser.add_option("--cachedir", dest="cachedir", default=os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../../cache"),
+ help="Directory for caching pregenerated datadirs (default: %default)")
parser.add_option("--tmpdir", dest="tmpdir", help="Root directory for datadirs")
parser.add_option("-l", "--loglevel", dest="loglevel", default="INFO",
help="log events at this level and higher to the console. Can be set to DEBUG, INFO, WARNING, ERROR or CRITICAL. Passing --loglevel DEBUG will output all logs to console. Note that logs at all levels are always written to the test_framework.log file in the temporary test directory.")
@@ -111,7 +112,8 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
parser.add_option("--coveragedir", dest="coveragedir",
help="Write tested RPC commands into this directory")
parser.add_option("--configfile", dest="configfile",
- help="Location of the test framework config file")
+ default=os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../../config.ini"),
+ help="Location of the test framework config file (default: %default)")
parser.add_option("--pdbonfailure", dest="pdbonfailure", default=False, action="store_true",
help="Attach a python debugger if test fails")
parser.add_option("--usecli", dest="usecli", default=False, action="store_true",
@@ -129,6 +131,11 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.options.cachedir = os.path.abspath(self.options.cachedir)
+ config = configparser.ConfigParser()
+ config.read_file(open(self.options.configfile))
+ self.options.bitcoind = os.getenv("BITCOIND", default=config["environment"]["BUILDDIR"] + '/src/bitcoind' + config["environment"]["EXEEXT"])
+ self.options.bitcoincli = os.getenv("BITCOINCLI", default=config["environment"]["BUILDDIR"] + '/src/bitcoin-cli' + config["environment"]["EXEEXT"])
+
# Set up temp directory and start logging
if self.options.tmpdir:
self.options.tmpdir = os.path.abspath(self.options.tmpdir)
@@ -246,12 +253,12 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
if extra_args is None:
extra_args = [[]] * num_nodes
if binary is None:
- binary = [None] * num_nodes
+ binary = [self.options.bitcoind] * num_nodes
assert_equal(len(extra_confs), num_nodes)
assert_equal(len(extra_args), num_nodes)
assert_equal(len(binary), num_nodes)
for i in range(num_nodes):
- self.nodes.append(TestNode(i, get_datadir_path(self.options.tmpdir, i), rpchost=rpchost, timewait=timewait, binary=binary[i], stderr=None, mocktime=self.mocktime, coverage_dir=self.options.coveragedir, extra_conf=extra_confs[i], extra_args=extra_args[i], use_cli=self.options.usecli))
+ self.nodes.append(TestNode(i, get_datadir_path(self.options.tmpdir, i), rpchost=rpchost, timewait=timewait, bitcoind=binary[i], bitcoin_cli=self.options.bitcoincli, stderr=None, mocktime=self.mocktime, coverage_dir=self.options.coveragedir, extra_conf=extra_confs[i], extra_args=extra_args[i], use_cli=self.options.usecli))
def start_node(self, i, *args, **kwargs):
"""Start a bitcoind"""
@@ -399,10 +406,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
# Create cache directories, run bitcoinds:
for i in range(MAX_NODES):
datadir = initialize_datadir(self.options.cachedir, i)
- args = [os.getenv("BITCOIND", "bitcoind"), "-datadir=" + datadir]
+ args = [self.options.bitcoind, "-datadir=" + datadir]
if i > 0:
args.append("-connect=127.0.0.1:" + str(p2p_port(0)))
- self.nodes.append(TestNode(i, get_datadir_path(self.options.cachedir, i), extra_conf=["bind=127.0.0.1"], extra_args=[],rpchost=None, timewait=None, binary=None, stderr=None, mocktime=self.mocktime, coverage_dir=None))
+ self.nodes.append(TestNode(i, get_datadir_path(self.options.cachedir, i), extra_conf=["bind=127.0.0.1"], extra_args=[], rpchost=None, timewait=None, bitcoind=self.options.bitcoind, bitcoin_cli=self.options.bitcoincli, stderr=None, mocktime=self.mocktime, coverage_dir=None))
self.nodes[i].args = args
self.start_node(i)
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index 0f0d031f35..5a6a659392 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -10,7 +10,6 @@ from enum import Enum
import http.client
import json
import logging
-import os
import re
import subprocess
import tempfile
@@ -19,7 +18,6 @@ import time
from .authproxy import JSONRPCException
from .util import (
append_config,
- assert_equal,
delete_cookie_file,
get_rpc_proxy,
rpc_url,
@@ -57,7 +55,7 @@ class TestNode():
To make things easier for the test writer, any unrecognised messages will
be dispatched to the RPC connection."""
- def __init__(self, i, datadir, rpchost, timewait, binary, stderr, mocktime, coverage_dir, extra_conf=None, extra_args=None, use_cli=False):
+ def __init__(self, i, datadir, rpchost, timewait, bitcoind, bitcoin_cli, stderr, mocktime, coverage_dir, extra_conf=None, extra_args=None, use_cli=False):
self.index = i
self.datadir = datadir
self.rpchost = rpchost
@@ -66,10 +64,7 @@ class TestNode():
else:
# Wait for up to 60 seconds for the RPC server to respond
self.rpc_timeout = 60
- if binary is None:
- self.binary = os.getenv("BITCOIND", "bitcoind")
- else:
- self.binary = binary
+ self.binary = bitcoind
self.stderr = stderr
self.coverage_dir = coverage_dir
if extra_conf != None:
@@ -78,9 +73,19 @@ class TestNode():
# For those callers that need more flexibility, they can just set the args property directly.
# Note that common args are set in the config file (see initialize_datadir)
self.extra_args = extra_args
- self.args = [self.binary, "-datadir=" + self.datadir, "-logtimemicros", "-debug", "-debugexclude=libevent", "-debugexclude=leveldb", "-mocktime=" + str(mocktime), "-uacomment=testnode%d" % i]
-
- self.cli = TestNodeCLI(os.getenv("BITCOINCLI", "bitcoin-cli"), self.datadir)
+ self.args = [
+ self.binary,
+ "-datadir=" + self.datadir,
+ "-logtimemicros",
+ "-debug",
+ "-debugexclude=libevent",
+ "-debugexclude=leveldb",
+ "-mocktime=" + str(mocktime),
+ "-uacomment=testnode%d" % i,
+ "-noprinttoconsole"
+ ]
+
+ self.cli = TestNodeCLI(bitcoin_cli, self.datadir)
self.use_cli = use_cli
self.running = False
@@ -93,6 +98,14 @@ class TestNode():
self.p2ps = []
+ def _node_msg(self, msg: str) -> str:
+ """Return a modified msg that identifies this node by its index as a debugging aid."""
+ return "[node %d] %s" % (self.index, msg)
+
+ def _raise_assertion_error(self, msg: str):
+ """Raise an AssertionError with msg modified to identify this node."""
+ raise AssertionError(self._node_msg(msg))
+
def __del__(self):
# Ensure that we don't leave any bitcoind processes lying around after
# the test ends
@@ -100,7 +113,7 @@ class TestNode():
# Should only happen on test failure
# Avoid using logger, as that may have already been shutdown when
# this destructor is called.
- print("Cleaning up leftover process")
+ print(self._node_msg("Cleaning up leftover process"))
self.process.kill()
def __getattr__(self, name):
@@ -108,7 +121,7 @@ class TestNode():
if self.use_cli:
return getattr(self.cli, name)
else:
- assert self.rpc_connected and self.rpc is not None, "Error: no RPC connection"
+ assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection")
return getattr(self.rpc, name)
def start(self, extra_args=None, stderr=None, *args, **kwargs):
@@ -131,7 +144,8 @@ class TestNode():
poll_per_s = 4
for _ in range(poll_per_s * self.rpc_timeout):
if self.process.poll() is not None:
- raise FailedToStartError('bitcoind exited with status {} during initialization'.format(self.process.returncode))
+ raise FailedToStartError(self._node_msg(
+ 'bitcoind exited with status {} during initialization'.format(self.process.returncode)))
try:
self.rpc = get_rpc_proxy(rpc_url(self.datadir, self.index, self.rpchost), self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir)
self.rpc.getblockcount()
@@ -150,14 +164,13 @@ class TestNode():
if "No RPC credentials" not in str(e):
raise
time.sleep(1.0 / poll_per_s)
- raise AssertionError("Unable to connect to bitcoind")
+ self._raise_assertion_error("Unable to connect to bitcoind")
def get_wallet_rpc(self, wallet_name):
if self.use_cli:
return self.cli("-rpcwallet={}".format(wallet_name))
else:
- assert self.rpc_connected
- assert self.rpc
+ assert self.rpc_connected and self.rpc, self._node_msg("RPC not connected")
wallet_path = "wallet/%s" % wallet_name
return self.rpc / wallet_path
@@ -184,7 +197,8 @@ class TestNode():
return False
# process has stopped. Assert that it didn't return an error code.
- assert_equal(return_code, 0)
+ assert return_code == 0, self._node_msg(
+ "Node returned non-zero exit code (%d) when stopping" % return_code)
self.running = False
self.process = None
self.rpc_connected = False
@@ -219,19 +233,22 @@ class TestNode():
stderr = log_stderr.read().decode('utf-8').strip()
if match == ErrorMatch.PARTIAL_REGEX:
if re.search(expected_msg, stderr, flags=re.MULTILINE) is None:
- raise AssertionError('Expected message "{}" does not partially match stderr:\n"{}"'.format(expected_msg, stderr))
+ self._raise_assertion_error(
+ 'Expected message "{}" does not partially match stderr:\n"{}"'.format(expected_msg, stderr))
elif match == ErrorMatch.FULL_REGEX:
if re.fullmatch(expected_msg, stderr) is None:
- raise AssertionError('Expected message "{}" does not fully match stderr:\n"{}"'.format(expected_msg, stderr))
+ self._raise_assertion_error(
+ 'Expected message "{}" does not fully match stderr:\n"{}"'.format(expected_msg, stderr))
elif match == ErrorMatch.FULL_TEXT:
if expected_msg != stderr:
- raise AssertionError('Expected message "{}" does not fully match stderr:\n"{}"'.format(expected_msg, stderr))
+ self._raise_assertion_error(
+ 'Expected message "{}" does not fully match stderr:\n"{}"'.format(expected_msg, 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)
+ self._raise_assertion_error(assert_msg)
def node_encrypt_wallet(self, passphrase):
""""Encrypts the wallet.
@@ -262,7 +279,7 @@ class TestNode():
Convenience property - most tests only use a single p2p connection to each
node, so this saves having to write node.p2ps[0] many times."""
- assert self.p2ps, "No p2p connection"
+ assert self.p2ps, self._node_msg("No p2p connection")
return self.p2ps[0]
def disconnect_p2ps(self):
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index f22322fbbc..4ec3175cd6 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -294,6 +294,7 @@ def initialize_datadir(dirname, n):
os.makedirs(datadir)
with open(os.path.join(datadir, "bitcoin.conf"), 'w', encoding='utf8') as f:
f.write("regtest=1\n")
+ f.write("[regtest]\n")
f.write("port=" + str(p2p_port(n)) + "\n")
f.write("rpcport=" + str(rpc_port(n)) + "\n")
f.write("server=1\n")