aboutsummaryrefslogtreecommitdiff
path: root/test/functional
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional')
-rwxr-xr-xtest/functional/feature_coinstatsindex.py1
-rwxr-xr-xtest/functional/feature_rbf.py170
-rwxr-xr-xtest/functional/interface_bitcoin_cli.py8
-rwxr-xr-xtest/functional/p2p_compactblocks_hb.py97
-rwxr-xr-xtest/functional/rpc_setban.py31
-rw-r--r--test/functional/test_framework/blocktools.py5
-rwxr-xr-xtest/functional/test_framework/messages.py2
-rwxr-xr-xtest/functional/test_framework/test_framework.py4
-rw-r--r--test/functional/test_framework/wallet.py32
-rwxr-xr-xtest/functional/test_runner.py1
10 files changed, 236 insertions, 115 deletions
diff --git a/test/functional/feature_coinstatsindex.py b/test/functional/feature_coinstatsindex.py
index cf270b25ee..7488e3cb42 100755
--- a/test/functional/feature_coinstatsindex.py
+++ b/test/functional/feature_coinstatsindex.py
@@ -281,6 +281,7 @@ class CoinStatsIndexTest(BitcoinTestFramework):
# Add another block, so we don't depend on reconsiderblock remembering which
# blocks were touched by invalidateblock
index_node.generate(1)
+ self.sync_all()
# Ensure that removing and re-adding blocks yields consistent results
block = index_node.getblockhash(99)
diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py
index 0bb04ae267..b99b64e16d 100755
--- a/test/functional/feature_rbf.py
+++ b/test/functional/feature_rbf.py
@@ -7,17 +7,20 @@
from decimal import Decimal
from test_framework.blocktools import COINBASE_MATURITY
-from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut
+from test_framework.messages import COIN, COutPoint, CTransaction, CTxIn, CTxOut, BIP125_SEQUENCE_NUMBER
from test_framework.script import CScript, OP_DROP
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error, satoshi_round
from test_framework.script_util import DUMMY_P2WPKH_SCRIPT, DUMMY_2_P2WPKH_SCRIPT
+from test_framework.wallet import MiniWallet
MAX_REPLACEMENT_LIMIT = 100
+
def txToHex(tx):
return tx.serialize().hex()
+
def make_utxo(node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
"""Create a txout with a given amount and scriptPubKey
@@ -26,12 +29,12 @@ def make_utxo(node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
confirmed - txouts created will be confirmed in the blockchain;
unconfirmed otherwise.
"""
- fee = 1*COIN
- while node.getbalance() < satoshi_round((amount + fee)/COIN):
+ fee = 1 * COIN
+ while node.getbalance() < satoshi_round((amount + fee) / COIN):
node.generate(COINBASE_MATURITY)
new_addr = node.getnewaddress()
- txid = node.sendtoaddress(new_addr, satoshi_round((amount+fee)/COIN))
+ txid = node.sendtoaddress(new_addr, satoshi_round((amount + fee) / COIN))
tx1 = node.getrawtransaction(txid, 1)
txid = int(txid, 16)
i, _ = next(filter(lambda vout: new_addr == vout[1]['scriptPubKey']['address'], enumerate(tx1['vout'])))
@@ -78,10 +81,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
self.skip_if_no_wallet()
def run_test(self):
- # Leave IBD
- self.nodes[0].generate(1)
-
- make_utxo(self.nodes[0], 1*COIN)
+ make_utxo(self.nodes[0], 1 * COIN)
# Ensure nodes are synced
self.sync_all()
@@ -123,7 +123,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_simple_doublespend(self):
"""Simple doublespend"""
- tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+ tx0_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
# make_utxo may have generated a bunch of blocks, so we need to sync
# before we can spend the coins generated, or else the resulting
@@ -165,14 +165,14 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_doublespend_chain(self):
"""Doublespend of a long chain"""
- initial_nValue = 50*COIN
+ initial_nValue = 50 * COIN
tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
prevout = tx0_outpoint
remaining_value = initial_nValue
chain_txids = []
- while remaining_value > 10*COIN:
- remaining_value -= 1*COIN
+ while remaining_value > 10 * COIN:
+ remaining_value -= 1 * COIN
tx = CTransaction()
tx.vin = [CTxIn(prevout, nSequence=0)]
tx.vout = [CTxOut(remaining_value, CScript([1, OP_DROP] * 15 + [1]))]
@@ -205,10 +205,10 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_doublespend_tree(self):
"""Doublespend of a big tree of transactions"""
- initial_nValue = 50*COIN
+ initial_nValue = 50 * COIN
tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
- def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.0001*COIN, _total_txs=None):
+ def branch(prevout, initial_value, max_txs, tree_width=5, fee=0.0001 * COIN, _total_txs=None):
if _total_txs is None:
_total_txs = [0]
if _total_txs[0] >= max_txs:
@@ -239,7 +239,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
_total_txs=_total_txs):
yield x
- fee = int(0.0001*COIN)
+ fee = int(0.0001 * COIN)
n = MAX_REPLACEMENT_LIMIT
tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
assert_equal(len(tree_txs), n)
@@ -267,8 +267,8 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# Try again, but with more total transactions than the "max txs
# double-spent at once" anti-DoS limit.
- for n in (MAX_REPLACEMENT_LIMIT+1, MAX_REPLACEMENT_LIMIT*2):
- fee = int(0.0001*COIN)
+ for n in (MAX_REPLACEMENT_LIMIT + 1, MAX_REPLACEMENT_LIMIT * 2):
+ fee = int(0.0001 * COIN)
tx0_outpoint = make_utxo(self.nodes[0], initial_nValue)
tree_txs = list(branch(tx0_outpoint, initial_nValue, n, fee=fee))
assert_equal(len(tree_txs), n)
@@ -286,7 +286,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_replacement_feeperkb(self):
"""Replacement requires fee-per-KB to be higher"""
- tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+ tx0_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
tx1a = CTransaction()
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
@@ -298,7 +298,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# rejected.
tx1b = CTransaction()
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- tx1b.vout = [CTxOut(int(0.001*COIN), CScript([b'a'*999000]))]
+ tx1b.vout = [CTxOut(int(0.001 * COIN), CScript([b'a' * 999000]))]
tx1b_hex = txToHex(tx1b)
# This will raise an exception due to insufficient fee
@@ -306,8 +306,8 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_spends_of_conflicting_outputs(self):
"""Replacements that spend conflicting tx outputs are rejected"""
- utxo1 = make_utxo(self.nodes[0], int(1.2*COIN))
- utxo2 = make_utxo(self.nodes[0], 3*COIN)
+ utxo1 = make_utxo(self.nodes[0], int(1.2 * COIN))
+ utxo2 = make_utxo(self.nodes[0], 3 * COIN)
tx1a = CTransaction()
tx1a.vin = [CTxIn(utxo1, nSequence=0)]
@@ -346,8 +346,8 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_new_unconfirmed_inputs(self):
"""Replacements that add new unconfirmed inputs are rejected"""
- confirmed_utxo = make_utxo(self.nodes[0], int(1.1*COIN))
- unconfirmed_utxo = make_utxo(self.nodes[0], int(0.1*COIN), False)
+ confirmed_utxo = make_utxo(self.nodes[0], int(1.1 * COIN))
+ unconfirmed_utxo = make_utxo(self.nodes[0], int(0.1 * COIN), False)
tx1 = CTransaction()
tx1.vin = [CTxIn(confirmed_utxo)]
@@ -369,13 +369,13 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# transactions
# Start by creating a single transaction with many outputs
- initial_nValue = 10*COIN
+ initial_nValue = 10 * COIN
utxo = make_utxo(self.nodes[0], initial_nValue)
- fee = int(0.0001*COIN)
- split_value = int((initial_nValue-fee)/(MAX_REPLACEMENT_LIMIT+1))
+ fee = int(0.0001 * COIN)
+ split_value = int((initial_nValue - fee) / (MAX_REPLACEMENT_LIMIT + 1))
outputs = []
- for _ in range(MAX_REPLACEMENT_LIMIT+1):
+ for _ in range(MAX_REPLACEMENT_LIMIT + 1):
outputs.append(CTxOut(split_value, CScript([1])))
splitting_tx = CTransaction()
@@ -387,7 +387,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
txid = int(txid, 16)
# Now spend each of those outputs individually
- for i in range(MAX_REPLACEMENT_LIMIT+1):
+ for i in range(MAX_REPLACEMENT_LIMIT + 1):
tx_i = CTransaction()
tx_i.vin = [CTxIn(COutPoint(txid, i), nSequence=0)]
tx_i.vout = [CTxOut(split_value - fee, DUMMY_P2WPKH_SCRIPT)]
@@ -397,9 +397,9 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# Now create doublespend of the whole lot; should fail.
# Need a big enough fee to cover all spending transactions and have
# a higher fee rate
- double_spend_value = (split_value-100*fee)*(MAX_REPLACEMENT_LIMIT+1)
+ double_spend_value = (split_value - 100 * fee) * (MAX_REPLACEMENT_LIMIT + 1)
inputs = []
- for i in range(MAX_REPLACEMENT_LIMIT+1):
+ for i in range(MAX_REPLACEMENT_LIMIT + 1):
inputs.append(CTxIn(COutPoint(txid, i), nSequence=0))
double_tx = CTransaction()
double_tx.vin = inputs
@@ -418,7 +418,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_opt_in(self):
"""Replacing should only work if orig tx opted in"""
- tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+ tx0_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
# Create a non-opting in transaction
tx1a = CTransaction()
@@ -439,7 +439,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# This will raise an exception
assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
- tx1_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+ tx1_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
# Create a different non-opting in transaction
tx2a = CTransaction()
@@ -467,7 +467,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tx3a = CTransaction()
tx3a.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0xffffffff),
CTxIn(COutPoint(tx2a_txid, 0), nSequence=0xfffffffd)]
- tx3a.vout = [CTxOut(int(0.9*COIN), CScript([b'c'])), CTxOut(int(0.9*COIN), CScript([b'd']))]
+ tx3a.vout = [CTxOut(int(0.9 * COIN), CScript([b'c'])), CTxOut(int(0.9 * COIN), CScript([b'd']))]
tx3a_hex = txToHex(tx3a)
tx3a_txid = self.nodes[0].sendrawtransaction(tx3a_hex, 0)
@@ -495,7 +495,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# correctly used by replacement logic
# 1. Check that feeperkb uses modified fees
- tx0_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+ tx0_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
tx1a = CTransaction()
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
@@ -506,14 +506,14 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# Higher fee, but the actual fee per KB is much lower.
tx1b = CTransaction()
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- tx1b.vout = [CTxOut(int(0.001*COIN), CScript([b'a'*740000]))]
+ tx1b.vout = [CTxOut(int(0.001 * COIN), CScript([b'a' * 740000]))]
tx1b_hex = txToHex(tx1b)
# Verify tx1b cannot replace tx1a.
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
# Use prioritisetransaction to set tx1a's fee to 0.
- self.nodes[0].prioritisetransaction(txid=tx1a_txid, fee_delta=int(-0.1*COIN))
+ self.nodes[0].prioritisetransaction(txid=tx1a_txid, fee_delta=int(-0.1 * COIN))
# Now tx1b should be able to replace tx1a
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0)
@@ -521,7 +521,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
assert tx1b_txid in self.nodes[0].getrawmempool()
# 2. Check that absolute fee checks use modified fee.
- tx1_outpoint = make_utxo(self.nodes[0], int(1.1*COIN))
+ tx1_outpoint = make_utxo(self.nodes[0], int(1.1 * COIN))
tx2a = CTransaction()
tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0)]
@@ -540,7 +540,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx2b_hex, 0)
# Now prioritise tx2b to have a higher modified fee
- self.nodes[0].prioritisetransaction(txid=tx2b.hash, fee_delta=int(0.1*COIN))
+ self.nodes[0].prioritisetransaction(txid=tx2b.hash, fee_delta=int(0.1 * COIN))
# tx2b should now be accepted
tx2b_txid = self.nodes[0].sendrawtransaction(tx2b_hex, 0)
@@ -550,11 +550,11 @@ class ReplaceByFeeTest(BitcoinTestFramework):
def test_rpc(self):
us0 = self.nodes[0].listunspent()[0]
ins = [us0]
- outs = {self.nodes[0].getnewaddress() : Decimal(1.0000000)}
+ outs = {self.nodes[0].getnewaddress(): Decimal(1.0000000)}
rawtx0 = self.nodes[0].createrawtransaction(ins, outs, 0, True)
rawtx1 = self.nodes[0].createrawtransaction(ins, outs, 0, False)
- json0 = self.nodes[0].decoderawtransaction(rawtx0)
- json1 = self.nodes[0].decoderawtransaction(rawtx1)
+ json0 = self.nodes[0].decoderawtransaction(rawtx0)
+ json1 = self.nodes[0].decoderawtransaction(rawtx1)
assert_equal(json0["vin"][0]["sequence"], 4294967293)
assert_equal(json1["vin"][0]["sequence"], 4294967295)
@@ -562,74 +562,68 @@ class ReplaceByFeeTest(BitcoinTestFramework):
frawtx2a = self.nodes[0].fundrawtransaction(rawtx2, {"replaceable": True})
frawtx2b = self.nodes[0].fundrawtransaction(rawtx2, {"replaceable": False})
- json0 = self.nodes[0].decoderawtransaction(frawtx2a['hex'])
- json1 = self.nodes[0].decoderawtransaction(frawtx2b['hex'])
+ json0 = self.nodes[0].decoderawtransaction(frawtx2a['hex'])
+ json1 = self.nodes[0].decoderawtransaction(frawtx2b['hex'])
assert_equal(json0["vin"][0]["sequence"], 4294967293)
assert_equal(json1["vin"][0]["sequence"], 4294967294)
def test_no_inherited_signaling(self):
- # Send tx from which to conflict outputs later
- base_txid = self.nodes[0].sendtoaddress(self.nodes[0].getnewaddress(), Decimal("10"))
- self.nodes[0].generate(1)
- self.sync_blocks()
+ wallet = MiniWallet(self.nodes[0])
+ wallet.scan_blocks(start=76, num=1)
+ confirmed_utxo = wallet.get_utxo()
# Create an explicitly opt-in parent transaction
- optin_parent_tx = self.nodes[0].createrawtransaction([{
- 'txid': base_txid,
- 'vout': 0,
- "sequence": 0xfffffffd,
- }], {self.nodes[0].getnewaddress(): Decimal("9.99998")})
-
- optin_parent_tx = self.nodes[0].signrawtransactionwithwallet(optin_parent_tx)
-
- # Broadcast parent tx
- optin_parent_txid = self.nodes[0].sendrawtransaction(hexstring=optin_parent_tx["hex"], maxfeerate=0)
- assert optin_parent_txid in self.nodes[0].getrawmempool()
-
- replacement_parent_tx = self.nodes[0].createrawtransaction([{
- 'txid': base_txid,
- 'vout': 0,
- "sequence": 0xfffffffd,
- }], {self.nodes[0].getnewaddress(): Decimal("9.90000")})
-
- replacement_parent_tx = self.nodes[0].signrawtransactionwithwallet(replacement_parent_tx)
+ optin_parent_tx = wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=confirmed_utxo,
+ sequence=BIP125_SEQUENCE_NUMBER,
+ fee_rate=Decimal('0.01'),
+ )
+ assert_equal(True, self.nodes[0].getmempoolentry(optin_parent_tx['txid'])['bip125-replaceable'])
+
+ replacement_parent_tx = wallet.create_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=confirmed_utxo,
+ sequence=BIP125_SEQUENCE_NUMBER,
+ fee_rate=Decimal('0.02'),
+ )
# Test if parent tx can be replaced.
- res = self.nodes[0].testmempoolaccept(rawtxs=[replacement_parent_tx['hex']], maxfeerate=0)[0]
+ res = self.nodes[0].testmempoolaccept(rawtxs=[replacement_parent_tx['hex']])[0]
# Parent can be replaced.
assert_equal(res['allowed'], True)
# Create an opt-out child tx spending the opt-in parent
- optout_child_tx = self.nodes[0].createrawtransaction([{
- 'txid': optin_parent_txid,
- 'vout': 0,
- "sequence": 0xffffffff,
- }], {self.nodes[0].getnewaddress(): Decimal("9.99990")})
-
- optout_child_tx = self.nodes[0].signrawtransactionwithwallet(optout_child_tx)
-
- # Broadcast child tx
- optout_child_txid = self.nodes[0].sendrawtransaction(hexstring=optout_child_tx["hex"], maxfeerate=0)
- assert optout_child_txid in self.nodes[0].getrawmempool()
-
- replacement_child_tx = self.nodes[0].createrawtransaction([{
- 'txid': optin_parent_txid,
- 'vout': 0,
- "sequence": 0xffffffff,
- }], {self.nodes[0].getnewaddress(): Decimal("9.00000")})
-
- replacement_child_tx = self.nodes[0].signrawtransactionwithwallet(replacement_child_tx)
+ parent_utxo = wallet.get_utxo(txid=optin_parent_tx['txid'])
+ optout_child_tx = wallet.send_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=parent_utxo,
+ sequence=0xffffffff,
+ fee_rate=Decimal('0.01'),
+ )
+
+ # Reports true due to inheritance
+ assert_equal(True, self.nodes[0].getmempoolentry(optout_child_tx['txid'])['bip125-replaceable'])
+
+ replacement_child_tx = wallet.create_self_transfer(
+ from_node=self.nodes[0],
+ utxo_to_spend=parent_utxo,
+ sequence=0xffffffff,
+ fee_rate=Decimal('0.02'),
+ mempool_valid=False,
+ )
# Broadcast replacement child tx
# BIP 125 :
# 1. The original transactions signal replaceability explicitly or through inheritance as described in the above
# Summary section.
- # The original transaction (`optout_child_tx`) doesn't signal RBF but its parent (`optin_parent_txid`) does.
+ # The original transaction (`optout_child_tx`) doesn't signal RBF but its parent (`optin_parent_tx`) does.
# The replacement transaction (`replacement_child_tx`) should be able to replace the original transaction.
# See CVE-2021-31876 for further explanations.
- assert optin_parent_txid in self.nodes[0].getrawmempool()
+ assert_equal(True, self.nodes[0].getmempoolentry(optin_parent_tx['txid'])['bip125-replaceable'])
assert_raises_rpc_error(-26, 'txn-mempool-conflict', self.nodes[0].sendrawtransaction, replacement_child_tx["hex"], 0)
+
if __name__ == '__main__':
ReplaceByFeeTest().main()
diff --git a/test/functional/interface_bitcoin_cli.py b/test/functional/interface_bitcoin_cli.py
index 30cd499b3f..22eec59600 100755
--- a/test/functional/interface_bitcoin_cli.py
+++ b/test/functional/interface_bitcoin_cli.py
@@ -10,10 +10,12 @@ from test_framework.blocktools import COINBASE_MATURITY
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
+ assert_greater_than_or_equal,
assert_raises_process_error,
assert_raises_rpc_error,
get_auth_cookie,
)
+import time
# The block reward of coinbaseoutput.nValue (50) BTC/block matures after
# COINBASE_MATURITY (100) blocks. Therefore, after mining 101 blocks we expect
@@ -248,6 +250,12 @@ class TestBitcoinCli(BitcoinTestFramework):
self.nodes[0].wait_for_rpc_connection()
assert_equal(blocks, BLOCKS + 25)
+ self.log.info("Test -rpcwait option waits at most -rpcwaittimeout seconds for startup")
+ self.stop_node(0) # stop the node so we time out
+ start_time = time.time()
+ assert_raises_process_error(1, "Could not connect to the server", self.nodes[0].cli('-rpcwait', '-rpcwaittimeout=5').echo)
+ assert_greater_than_or_equal(time.time(), start_time + 5)
+
if __name__ == '__main__':
TestBitcoinCli().main()
diff --git a/test/functional/p2p_compactblocks_hb.py b/test/functional/p2p_compactblocks_hb.py
new file mode 100755
index 0000000000..a3d30a6f04
--- /dev/null
+++ b/test/functional/p2p_compactblocks_hb.py
@@ -0,0 +1,97 @@
+#!/usr/bin/env python3
+# Copyright (c) 2021 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Test compact blocks HB selection logic."""
+
+from test_framework.test_framework import BitcoinTestFramework
+from test_framework.util import assert_equal
+
+
+class CompactBlocksConnectionTest(BitcoinTestFramework):
+ """Test class for verifying selection of HB peer connections."""
+
+ def set_test_params(self):
+ self.setup_clean_chain = True
+ self.num_nodes = 6
+
+ def peer_info(self, from_node, to_node):
+ """Query from_node for its getpeerinfo about to_node."""
+ for peerinfo in self.nodes[from_node].getpeerinfo():
+ if "(testnode%i)" % to_node in peerinfo['subver']:
+ return peerinfo
+ return None
+
+ def setup_network(self):
+ self.setup_nodes()
+ # Start network with everyone disconnected
+ self.sync_all()
+
+ def relay_block_through(self, peer):
+ """Relay a new block through peer peer, and return HB status between 1 and [2,3,4,5]."""
+ self.connect_nodes(peer, 0)
+ self.nodes[0].generate(1)
+ self.sync_blocks()
+ self.disconnect_nodes(peer, 0)
+ status_to = [self.peer_info(1, i)['bip152_hb_to'] for i in range(2, 6)]
+ status_from = [self.peer_info(i, 1)['bip152_hb_from'] for i in range(2, 6)]
+ assert_equal(status_to, status_from)
+ return status_to
+
+ def run_test(self):
+ self.log.info("Testing reserved high-bandwidth mode slot for outbound peer...")
+
+ # Connect everyone to node 0, and mine some blocks to get all nodes out of IBD.
+ for i in range(1, 6):
+ self.connect_nodes(i, 0)
+ self.nodes[0].generate(2)
+ self.sync_blocks()
+ for i in range(1, 6):
+ self.disconnect_nodes(i, 0)
+
+ # Construct network topology:
+ # - Node 0 is the block producer
+ # - Node 1 is the "target" node being tested
+ # - Nodes 2-5 are intermediaries.
+ # - Node 1 has an outbound connection to node 2
+ # - Node 1 has inbound connections from nodes 3-5
+ self.connect_nodes(3, 1)
+ self.connect_nodes(4, 1)
+ self.connect_nodes(5, 1)
+ self.connect_nodes(1, 2)
+
+ # Mine blocks subsequently relaying through nodes 3,4,5 (inbound to node 1)
+ for nodeid in range(3, 6):
+ status = self.relay_block_through(nodeid)
+ assert_equal(status, [False, nodeid >= 3, nodeid >= 4, nodeid >= 5])
+
+ # And again through each. This should not change HB status.
+ for nodeid in range(3, 6):
+ status = self.relay_block_through(nodeid)
+ assert_equal(status, [False, True, True, True])
+
+ # Now relay one block through peer 2 (outbound from node 1), so it should take HB status
+ # from one of the inbounds.
+ status = self.relay_block_through(2)
+ assert_equal(status[0], True)
+ assert_equal(sum(status), 3)
+
+ # Now relay again through nodes 3,4,5. Since 2 is outbound, it should remain HB.
+ for nodeid in range(3, 6):
+ status = self.relay_block_through(nodeid)
+ assert status[0]
+ assert status[nodeid - 2]
+ assert_equal(sum(status), 3)
+
+ # Reconnect peer 2, and retry. Now the three inbounds should be HB again.
+ self.disconnect_nodes(1, 2)
+ self.connect_nodes(1, 2)
+ for nodeid in range(3, 6):
+ status = self.relay_block_through(nodeid)
+ assert not status[0]
+ assert status[nodeid - 2]
+ assert_equal(status, [False, True, True, True])
+
+
+if __name__ == '__main__':
+ CompactBlocksConnectionTest().main()
diff --git a/test/functional/rpc_setban.py b/test/functional/rpc_setban.py
index fd5f8aa098..36873f964b 100755
--- a/test/functional/rpc_setban.py
+++ b/test/functional/rpc_setban.py
@@ -22,7 +22,7 @@ class SetBanTests(BitcoinTestFramework):
# Node 0 connects to Node 1, check that the noban permission is not granted
self.connect_nodes(0, 1)
peerinfo = self.nodes[1].getpeerinfo()[0]
- assert(not 'noban' in peerinfo['permissions'])
+ assert not "noban" in peerinfo["permissions"]
# Node 0 get banned by Node 1
self.nodes[1].setban("127.0.0.1", "add")
@@ -36,27 +36,40 @@ class SetBanTests(BitcoinTestFramework):
self.restart_node(1, ['-whitelist=127.0.0.1'])
self.connect_nodes(0, 1)
peerinfo = self.nodes[1].getpeerinfo()[0]
- assert('noban' in peerinfo['permissions'])
+ assert "noban" in peerinfo["permissions"]
# If we remove the ban, Node 0 should be able to reconnect even without noban permission
self.nodes[1].setban("127.0.0.1", "remove")
self.restart_node(1, [])
self.connect_nodes(0, 1)
peerinfo = self.nodes[1].getpeerinfo()[0]
- assert(not 'noban' in peerinfo['permissions'])
+ assert not "noban" in peerinfo["permissions"]
self.log.info("Test that a non-IP address can be banned/unbanned")
node = self.nodes[1]
tor_addr = "pg6mmjiyjmcrsslvykfwnntlaru7p5svn6y2ymmju6nubxndf4pscryd.onion"
ip_addr = "1.2.3.4"
- assert(not self.is_banned(node, tor_addr))
- assert(not self.is_banned(node, ip_addr))
+ assert not self.is_banned(node, tor_addr)
+ assert not self.is_banned(node, ip_addr)
+
node.setban(tor_addr, "add")
- assert(self.is_banned(node, tor_addr))
- assert(not self.is_banned(node, ip_addr))
+ assert self.is_banned(node, tor_addr)
+ assert not self.is_banned(node, ip_addr)
+
+ self.log.info("Test the ban list is preserved through restart")
+
+ self.restart_node(1)
+ assert self.is_banned(node, tor_addr)
+ assert not self.is_banned(node, ip_addr)
+
node.setban(tor_addr, "remove")
- assert(not self.is_banned(self.nodes[1], tor_addr))
- assert(not self.is_banned(node, ip_addr))
+ assert not self.is_banned(self.nodes[1], tor_addr)
+ assert not self.is_banned(node, ip_addr)
+
+ self.restart_node(1)
+ assert not self.is_banned(node, tor_addr)
+ assert not self.is_banned(node, ip_addr)
+
if __name__ == '__main__':
SetBanTests().main()
diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py
index f35ea6c122..e91b44e776 100644
--- a/test/functional/test_framework/blocktools.py
+++ b/test/functional/test_framework/blocktools.py
@@ -181,11 +181,6 @@ def create_raw_transaction(node, txid, to_address, *, amount):
signed_psbt = wrpc.walletprocesspsbt(psbt)
psbt = signed_psbt['psbt']
final_psbt = node.finalizepsbt(psbt)
- if not final_psbt["complete"]:
- node.log.info(f'final_psbt={final_psbt}')
- for w in node.listwallets():
- wrpc = node.get_wallet_rpc(w)
- node.log.info(f'listunspent={wrpc.listunspent()}')
assert_equal(final_psbt["complete"], True)
return final_psbt['hex']
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index 5a9736a7a3..3d79174356 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -39,7 +39,7 @@ MAX_BLOOM_HASH_FUNCS = 50
COIN = 100000000 # 1 btc in satoshis
MAX_MONEY = 21000000 * COIN
-BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is BIP 125 opt-in and BIP 68-opt-out
+BIP125_SEQUENCE_NUMBER = 0xfffffffd # Sequence number that is rbf-opt-in (BIP 125) and csv-opt-out (BIP 68)
MAX_PROTOCOL_MESSAGE_LENGTH = 4000000 # Maximum length of incoming protocol messages
MAX_HEADERS_RESULTS = 2000 # Number of headers sent in one getheaders result
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index a89a26caea..40360c54a0 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -194,6 +194,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
help="Run test using legacy wallets", dest='descriptors')
self.add_options(parser)
+ # Running TestShell in a Jupyter notebook causes an additional -f argument
+ # To keep TestShell from failing with an "unrecognized argument" error, we add a dummy "-f" argument
+ # source: https://stackoverflow.com/questions/48796169/how-to-fix-ipykernel-launcher-py-error-unrecognized-arguments-in-jupyter/56349168#56349168
+ parser.add_argument("-f", "--fff", help="a dummy argument to fool ipython", default="1")
self.options = parser.parse_args()
self.options.previous_releases_path = previous_releases_path
diff --git a/test/functional/test_framework/wallet.py b/test/functional/test_framework/wallet.py
index 0962a5cb54..47ec6b0be2 100644
--- a/test/functional/test_framework/wallet.py
+++ b/test/functional/test_framework/wallet.py
@@ -88,12 +88,20 @@ class MiniWallet:
if out['scriptPubKey']['hex'] == self._scriptPubKey.hex():
self._utxos.append({'txid': tx['txid'], 'vout': out['n'], 'value': out['value']})
- def sign_tx(self, tx):
+ def sign_tx(self, tx, fixed_length=True):
"""Sign tx that has been created by MiniWallet in P2PK mode"""
assert self._priv_key is not None
(sighash, err) = LegacySignatureHash(CScript(self._scriptPubKey), tx, 0, SIGHASH_ALL)
assert err is None
- tx.vin[0].scriptSig = CScript([self._priv_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL]))])
+ # for exact fee calculation, create only signatures with fixed size by default (>49.89% probability):
+ # 65 bytes: high-R val (33 bytes) + low-S val (32 bytes)
+ # with the DER header/skeleton data of 6 bytes added, this leads to a target size of 71 bytes
+ der_sig = b''
+ while not len(der_sig) == 71:
+ der_sig = self._priv_key.sign_ecdsa(sighash)
+ if not fixed_length:
+ break
+ tx.vin[0].scriptSig = CScript([der_sig + bytes(bytearray([SIGHASH_ALL]))])
def generate(self, num_blocks):
"""Generate blocks with coinbase outputs to the internal address, and append the outputs to the internal list"""
@@ -124,23 +132,26 @@ class MiniWallet:
else:
return self._utxos[index]
- def send_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, locktime=0):
+ def send_self_transfer(self, **kwargs):
"""Create and send a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
- tx = self.create_self_transfer(fee_rate=fee_rate, from_node=from_node, utxo_to_spend=utxo_to_spend)
- self.sendrawtransaction(from_node=from_node, tx_hex=tx['hex'])
+ tx = self.create_self_transfer(**kwargs)
+ self.sendrawtransaction(from_node=kwargs['from_node'], tx_hex=tx['hex'])
return tx
- def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, mempool_valid=True, locktime=0):
+ def create_self_transfer(self, *, fee_rate=Decimal("0.003"), from_node, utxo_to_spend=None, mempool_valid=True, locktime=0, sequence=0):
"""Create and return a tx with the specified fee_rate. Fee may be exact or at most one satoshi higher than needed."""
self._utxos = sorted(self._utxos, key=lambda k: k['value'])
utxo_to_spend = utxo_to_spend or self._utxos.pop() # Pick the largest utxo (if none provided) and hope it covers the fee
- vsize = Decimal(96)
+ if self._priv_key is None:
+ vsize = Decimal(96) # anyone-can-spend
+ else:
+ vsize = Decimal(168) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other)
send_value = satoshi_round(utxo_to_spend['value'] - fee_rate * (vsize / 1000))
fee = utxo_to_spend['value'] - send_value
assert send_value > 0
tx = CTransaction()
- tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']))]
+ tx.vin = [CTxIn(COutPoint(int(utxo_to_spend['txid'], 16), utxo_to_spend['vout']), nSequence=sequence)]
tx.vout = [CTxOut(int(send_value * COIN), self._scriptPubKey)]
tx.nLockTime = locktime
if not self._address:
@@ -159,10 +170,7 @@ class MiniWallet:
tx_info = from_node.testmempoolaccept([tx_hex])[0]
assert_equal(mempool_valid, tx_info['allowed'])
if mempool_valid:
- # TODO: for P2PK, vsize is not constant due to varying scriptSig length,
- # so only check this for anyone-can-spend outputs right now
- if self._priv_key is None:
- assert_equal(tx_info['vsize'], vsize)
+ assert_equal(tx_info['vsize'], vsize)
assert_equal(tx_info['fees']['base'], fee)
return {'txid': tx_info['txid'], 'wtxid': tx_info['wtxid'], 'hex': tx_hex, 'tx': tx}
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 76347b052d..ad1acd2e2f 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -174,6 +174,7 @@ BASE_SCRIPTS = [
'wallet_groups.py --legacy-wallet',
'p2p_addrv2_relay.py',
'wallet_groups.py --descriptors',
+ 'p2p_compactblocks_hb.py',
'p2p_disconnect_ban.py',
'rpc_decodescript.py',
'rpc_blockchain.py',