aboutsummaryrefslogtreecommitdiff
path: root/test/functional/feature_rbf.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional/feature_rbf.py')
-rwxr-xr-xtest/functional/feature_rbf.py234
1 files changed, 155 insertions, 79 deletions
diff --git a/test/functional/feature_rbf.py b/test/functional/feature_rbf.py
index 344db5f652..ed944274e3 100755
--- a/test/functional/feature_rbf.py
+++ b/test/functional/feature_rbf.py
@@ -7,16 +7,22 @@
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 (
+ BIP125_SEQUENCE_NUMBER,
+ COIN,
+ COutPoint,
+ CTransaction,
+ CTxIn,
+ CTxOut,
+)
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 +32,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'])))
@@ -41,7 +47,7 @@ def make_utxo(node, amount, confirmed=True, scriptPubKey=DUMMY_P2WPKH_SCRIPT):
tx2.vout = [CTxOut(amount, scriptPubKey)]
tx2.rehash()
- signed_tx = node.signrawtransactionwithwallet(txToHex(tx2))
+ signed_tx = node.signrawtransactionwithwallet(tx2.serialize().hex())
txid = node.sendrawtransaction(signed_tx['hex'], 0)
@@ -78,10 +84,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()
@@ -116,11 +119,17 @@ class ReplaceByFeeTest(BitcoinTestFramework):
self.log.info("Running test prioritised transactions...")
self.test_prioritised_transactions()
+ self.log.info("Running test no inherited signaling...")
+ self.test_no_inherited_signaling()
+
+ self.log.info("Running test replacement relay fee...")
+ self.test_replacement_relay_fee()
+
self.log.info("Passed")
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
@@ -130,7 +139,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tx1a = CTransaction()
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0)]
tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1a_hex = txToHex(tx1a)
+ tx1a_hex = tx1a.serialize().hex()
tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0)
self.sync_all()
@@ -139,7 +148,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tx1b = CTransaction()
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
tx1b.vout = [CTxOut(1 * COIN, DUMMY_2_P2WPKH_SCRIPT)]
- tx1b_hex = txToHex(tx1b)
+ tx1b_hex = tx1b.serialize().hex()
# This will raise an exception due to insufficient fee
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
@@ -148,7 +157,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tx1b = CTransaction()
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
tx1b.vout = [CTxOut(int(0.9 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx1b_hex = txToHex(tx1b)
+ tx1b_hex = tx1b.serialize().hex()
# Works when enabled
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0)
@@ -162,18 +171,18 @@ 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]))]
- tx_hex = txToHex(tx)
+ tx_hex = tx.serialize().hex()
txid = self.nodes[0].sendrawtransaction(tx_hex, 0)
chain_txids.append(txid)
prevout = COutPoint(int(txid, 16), 0)
@@ -183,7 +192,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
dbl_tx = CTransaction()
dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
dbl_tx.vout = [CTxOut(initial_nValue - 30 * COIN, DUMMY_P2WPKH_SCRIPT)]
- dbl_tx_hex = txToHex(dbl_tx)
+ dbl_tx_hex = dbl_tx.serialize().hex()
# This will raise an exception due to insufficient fee
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0)
@@ -192,7 +201,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
dbl_tx = CTransaction()
dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
dbl_tx.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- dbl_tx_hex = txToHex(dbl_tx)
+ dbl_tx_hex = dbl_tx.serialize().hex()
self.nodes[0].sendrawtransaction(dbl_tx_hex, 0)
mempool = self.nodes[0].getrawmempool()
@@ -202,10 +211,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:
@@ -220,7 +229,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tx = CTransaction()
tx.vin = [CTxIn(prevout, nSequence=0)]
tx.vout = vout
- tx_hex = txToHex(tx)
+ tx_hex = tx.serialize().hex()
assert len(tx.serialize()) < 100000
txid = self.nodes[0].sendrawtransaction(tx_hex, 0)
@@ -236,7 +245,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)
@@ -245,7 +254,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
dbl_tx = CTransaction()
dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
dbl_tx.vout = [CTxOut(initial_nValue - fee * n, DUMMY_P2WPKH_SCRIPT)]
- dbl_tx_hex = txToHex(dbl_tx)
+ dbl_tx_hex = dbl_tx.serialize().hex()
# This will raise an exception due to insufficient fee
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0)
@@ -253,7 +262,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
dbl_tx = CTransaction()
dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
dbl_tx.vout = [CTxOut(initial_nValue - fee * n - 1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- dbl_tx_hex = txToHex(dbl_tx)
+ dbl_tx_hex = dbl_tx.serialize().hex()
self.nodes[0].sendrawtransaction(dbl_tx_hex, 0)
mempool = self.nodes[0].getrawmempool()
@@ -264,8 +273,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)
@@ -273,7 +282,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
dbl_tx = CTransaction()
dbl_tx.vin = [CTxIn(tx0_outpoint, nSequence=0)]
dbl_tx.vout = [CTxOut(initial_nValue - 2 * fee * n, DUMMY_P2WPKH_SCRIPT)]
- dbl_tx_hex = txToHex(dbl_tx)
+ dbl_tx_hex = dbl_tx.serialize().hex()
# This will raise an exception
assert_raises_rpc_error(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, dbl_tx_hex, 0)
@@ -283,33 +292,33 @@ 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)]
tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1a_hex = txToHex(tx1a)
+ tx1a_hex = tx1a.serialize().hex()
self.nodes[0].sendrawtransaction(tx1a_hex, 0)
# Higher fee, but the fee per KB is much lower, so the replacement is
# rejected.
tx1b = CTransaction()
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
- tx1b.vout = [CTxOut(int(0.001*COIN), CScript([b'a'*999000]))]
- tx1b_hex = txToHex(tx1b)
+ tx1b.vout = [CTxOut(int(0.001 * COIN), CScript([b'a' * 999000]))]
+ tx1b_hex = tx1b.serialize().hex()
# This will raise an exception due to insufficient fee
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx1b_hex, 0)
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)]
tx1a.vout = [CTxOut(int(1.1 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx1a_hex = txToHex(tx1a)
+ tx1a_hex = tx1a.serialize().hex()
tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0)
tx1a_txid = int(tx1a_txid, 16)
@@ -319,7 +328,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tx2.vin = [CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0)]
tx2.vin.append(CTxIn(COutPoint(tx1a_txid, 0), nSequence=0))
tx2.vout = tx1a.vout
- tx2_hex = txToHex(tx2)
+ tx2_hex = tx2.serialize().hex()
# This will raise an exception
assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, tx2_hex, 0)
@@ -328,7 +337,7 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tx1b = CTransaction()
tx1b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)]
tx1b.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1b_hex = txToHex(tx1b)
+ tx1b_hex = tx1b.serialize().hex()
tx1b_txid = self.nodes[0].sendrawtransaction(tx1b_hex, 0)
tx1b_txid = int(tx1b_txid, 16)
@@ -336,26 +345,26 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tx2.vin = [CTxIn(utxo1, nSequence=0), CTxIn(utxo2, nSequence=0),
CTxIn(COutPoint(tx1b_txid, 0))]
tx2.vout = tx1a.vout
- tx2_hex = txToHex(tx2)
+ tx2_hex = tx2.serialize().hex()
# This will raise an exception
assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, tx2_hex, 0)
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)]
tx1.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1_hex = txToHex(tx1)
+ tx1_hex = tx1.serialize().hex()
self.nodes[0].sendrawtransaction(tx1_hex, 0)
tx2 = CTransaction()
tx2.vin = [CTxIn(confirmed_utxo), CTxIn(unconfirmed_utxo)]
tx2.vout = tx1.vout
- tx2_hex = txToHex(tx2)
+ tx2_hex = tx2.serialize().hex()
# This will raise an exception
assert_raises_rpc_error(-26, "replacement-adds-unconfirmed", self.nodes[0].sendrawtransaction, tx2_hex, 0)
@@ -366,42 +375,42 @@ 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()
splitting_tx.vin = [CTxIn(utxo, nSequence=0)]
splitting_tx.vout = outputs
- splitting_tx_hex = txToHex(splitting_tx)
+ splitting_tx_hex = splitting_tx.serialize().hex()
txid = self.nodes[0].sendrawtransaction(splitting_tx_hex, 0)
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)]
- tx_i_hex = txToHex(tx_i)
+ tx_i_hex = tx_i.serialize().hex()
self.nodes[0].sendrawtransaction(tx_i_hex, 0)
# 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
double_tx.vout = [CTxOut(double_spend_value, CScript([b'a']))]
- double_tx_hex = txToHex(double_tx)
+ double_tx_hex = double_tx.serialize().hex()
# This will raise an exception
assert_raises_rpc_error(-26, "too many potential replacements", self.nodes[0].sendrawtransaction, double_tx_hex, 0)
@@ -410,18 +419,18 @@ class ReplaceByFeeTest(BitcoinTestFramework):
double_tx = CTransaction()
double_tx.vin = inputs[0:-1]
double_tx.vout = [CTxOut(double_spend_value, CScript([b'a']))]
- double_tx_hex = txToHex(double_tx)
+ double_tx_hex = double_tx.serialize().hex()
self.nodes[0].sendrawtransaction(double_tx_hex, 0)
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()
tx1a.vin = [CTxIn(tx0_outpoint, nSequence=0xffffffff)]
tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1a_hex = txToHex(tx1a)
+ tx1a_hex = tx1a.serialize().hex()
tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0)
# This transaction isn't shown as replaceable
@@ -431,25 +440,25 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tx1b = CTransaction()
tx1b.vin = [CTxIn(tx0_outpoint, nSequence=0)]
tx1b.vout = [CTxOut(int(0.9 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx1b_hex = txToHex(tx1b)
+ tx1b_hex = tx1b.serialize().hex()
# 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()
tx2a.vin = [CTxIn(tx1_outpoint, nSequence=0xfffffffe)]
tx2a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx2a_hex = txToHex(tx2a)
+ tx2a_hex = tx2a.serialize().hex()
tx2a_txid = self.nodes[0].sendrawtransaction(tx2a_hex, 0)
# Still shouldn't be able to double-spend
tx2b = CTransaction()
tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)]
tx2b.vout = [CTxOut(int(0.9 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx2b_hex = txToHex(tx2b)
+ tx2b_hex = tx2b.serialize().hex()
# This will raise an exception
assert_raises_rpc_error(-26, "txn-mempool-conflict", self.nodes[0].sendrawtransaction, tx2b_hex, 0)
@@ -464,8 +473,8 @@ 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_hex = txToHex(tx3a)
+ tx3a.vout = [CTxOut(int(0.9 * COIN), CScript([b'c'])), CTxOut(int(0.9 * COIN), CScript([b'd']))]
+ tx3a_hex = tx3a.serialize().hex()
tx3a_txid = self.nodes[0].sendrawtransaction(tx3a_hex, 0)
@@ -475,12 +484,12 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tx3b = CTransaction()
tx3b.vin = [CTxIn(COutPoint(tx1a_txid, 0), nSequence=0)]
tx3b.vout = [CTxOut(int(0.5 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx3b_hex = txToHex(tx3b)
+ tx3b_hex = tx3b.serialize().hex()
tx3c = CTransaction()
tx3c.vin = [CTxIn(COutPoint(tx2a_txid, 0), nSequence=0)]
tx3c.vout = [CTxOut(int(0.5 * COIN), DUMMY_P2WPKH_SCRIPT)]
- tx3c_hex = txToHex(tx3c)
+ tx3c_hex = tx3c.serialize().hex()
self.nodes[0].sendrawtransaction(tx3b_hex, 0)
# If tx3b was accepted, tx3c won't look like a replacement,
@@ -492,25 +501,25 @@ 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)]
tx1a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx1a_hex = txToHex(tx1a)
+ tx1a_hex = tx1a.serialize().hex()
tx1a_txid = self.nodes[0].sendrawtransaction(tx1a_hex, 0)
# 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_hex = txToHex(tx1b)
+ tx1b.vout = [CTxOut(int(0.001 * COIN), CScript([b'a' * 740000]))]
+ tx1b_hex = tx1b.serialize().hex()
# 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)
@@ -518,12 +527,12 @@ 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)]
tx2a.vout = [CTxOut(1 * COIN, DUMMY_P2WPKH_SCRIPT)]
- tx2a_hex = txToHex(tx2a)
+ tx2a_hex = tx2a.serialize().hex()
self.nodes[0].sendrawtransaction(tx2a_hex, 0)
# Lower fee, but we'll prioritise it
@@ -531,13 +540,13 @@ class ReplaceByFeeTest(BitcoinTestFramework):
tx2b.vin = [CTxIn(tx1_outpoint, nSequence=0)]
tx2b.vout = [CTxOut(int(1.01 * COIN), DUMMY_P2WPKH_SCRIPT)]
tx2b.rehash()
- tx2b_hex = txToHex(tx2b)
+ tx2b_hex = tx2b.serialize().hex()
# Verify tx2b cannot replace tx2a.
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)
@@ -547,11 +556,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)
@@ -559,10 +568,77 @@ 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):
+ 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 = 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']])[0]
+
+ # Parent can be replaced.
+ assert_equal(res['allowed'], True)
+
+ # Create an opt-out child tx spending the opt-in parent
+ 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_tx`) does.
+ # The replacement transaction (`replacement_child_tx`) should be able to replace the original transaction.
+ # See CVE-2021-31876 for further explanations.
+ 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)
+
+ def test_replacement_relay_fee(self):
+ wallet = MiniWallet(self.nodes[0])
+ wallet.scan_blocks(start=77, num=1)
+ tx = wallet.send_self_transfer(from_node=self.nodes[0])['tx']
+
+ # Higher fee, higher feerate, different txid, but the replacement does not provide a relay
+ # fee conforming to node's `incrementalrelayfee` policy of 1000 sat per KB.
+ tx.vout[0].nValue -= 1
+ assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex())
+
if __name__ == '__main__':
ReplaceByFeeTest().main()