Merge bitcoin/bitcoin#33476: [28.x] backports + 28.3rc1

9968b15937 [doc] update bitcoin.conf example (glozow)
dcac36271f [doc] manpages for 28.3rc1 (glozow)
df7412803d [build] bump version to 28.3rc1 (glozow)
d3194cb8cd [doc] update release notes for 28.x (glozow)
18f6430b4a test: compare BDB dumps of test framework parser and wallet tool (Sebastian Falbesoner)
6ede736da1 test: complete BDB parser (handle internal/overflow pages, support all page sizes) (Sebastian Falbesoner)
66559d1a4a [policy] lower default minrelaytxfee and incrementalrelayfee to 100sat/kvB (glozow)
a02e0a401c [prep/test] make wallet_fundrawtransaction's minrelaytxfee assumption explicit (glozow)
f25fc092ab [prep/util] help MockMempoolMinFee handle more precise feerates (glozow)
4d809efeb9 [prep/test] replace magic number 1000 with respective feerate vars (glozow)
f7dde40c70 test: Add missing sync_mempools() to fill_mempool() (MarcoFalke)
a60281526b test: Refactor fill_mempool to extract send_batch helper (MarcoFalke)
08eeb0d342 [miner] lower default -blockmintxfee to 1sat/kvB (glozow)
b7ba016707 test: add `BulkTransaction` helper to unit test transaction utils (Sebastian Falbesoner)
27b775586e [doc] assert that default min relay feerate and incremental are the same (glozow)
e3273e03b1 [test] explicitly check default -minrelaytxfee and -incrementalrelayfee (glozow)
cf875f1559 [test] RBF rule 4 for various incrementalrelayfee settings (glozow)
308778b7b6 [test] check bypass of minrelay for various minrelaytxfee settings (glozow)
e779d59eca [test] check miner doesn't select 0fee transactions (glozow)

Pull request description:

  Includes backports of
  - #33106
  - #30125
  - #30948
  - #30784

ACKs for top commit:
  achow101:
    ACK 9968b15937
  stickies-v:
    re-ACK 9968b15937

Tree-SHA512: 1d4ec354a743ec3a894a44d2d9b98dc5169f515081071b887768afee1c42cefdaa6c5f595829dc9b579699bba3e4b4aa73a519d4c4ef8579375ad34513f26cd9
This commit is contained in:
Ava Chow 2025-10-01 10:42:26 -07:00
commit ed730c5674
No known key found for this signature in database
GPG Key ID: 17565732E08E5E41
32 changed files with 421 additions and 155 deletions

View File

@ -1,8 +1,8 @@
AC_PREREQ([2.69])
define(_CLIENT_VERSION_MAJOR, 28)
define(_CLIENT_VERSION_MINOR, 2)
define(_CLIENT_VERSION_MINOR, 3)
define(_CLIENT_VERSION_BUILD, 0)
define(_CLIENT_VERSION_RC, 0)
define(_CLIENT_VERSION_RC, 1)
define(_CLIENT_VERSION_IS_RELEASE, true)
define(_COPYRIGHT_YEAR, 2025)
define(_COPYRIGHT_HOLDERS,[The %s developers])

View File

@ -1,7 +1,7 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIN-CLI "1" "June 2025" "bitcoin-cli v28.2.0" "User Commands"
.TH BITCOIN-CLI "1" "September 2025" "bitcoin-cli v28.3.0rc1" "User Commands"
.SH NAME
bitcoin-cli \- manual page for bitcoin-cli v28.2.0
bitcoin-cli \- manual page for bitcoin-cli v28.3.0rc1
.SH SYNOPSIS
.B bitcoin-cli
[\fI\,options\/\fR] \fI\,<command> \/\fR[\fI\,params\/\fR] \fI\,Send command to Bitcoin Core\/\fR
@ -15,7 +15,7 @@ bitcoin-cli \- manual page for bitcoin-cli v28.2.0
.B bitcoin-cli
[\fI\,options\/\fR] \fI\,help <command> Get help for a command\/\fR
.SH DESCRIPTION
Bitcoin Core RPC client version v28.2.0
Bitcoin Core RPC client version v28.3.0rc1
.SH OPTIONS
.HP
\-?

View File

@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIN-QT "1" "June 2025" "bitcoin-qt v28.2.0" "User Commands"
.TH BITCOIN-QT "1" "September 2025" "bitcoin-qt v28.3.0rc1" "User Commands"
.SH NAME
bitcoin-qt \- manual page for bitcoin-qt v28.2.0
bitcoin-qt \- manual page for bitcoin-qt v28.3.0rc1
.SH SYNOPSIS
.B bitcoin-qt
[\fI\,command-line options\/\fR] [\fI\,URI\/\fR]
.SH DESCRIPTION
Bitcoin Core version v28.2.0
Bitcoin Core version v28.3.0rc1
.PP
Optional URI is a Bitcoin address in BIP21 URI format.
.SH OPTIONS
@ -705,7 +705,7 @@ replaceability signaling (default: 1)
\fB\-minrelaytxfee=\fR<amt>
.IP
Fees (in BTC/kvB) smaller than this are considered zero fee for
relaying, mining and transaction creation (default: 0.00001)
relaying, mining and transaction creation (default: 0.000001)
.HP
\fB\-permitbaremultisig\fR
.IP
@ -732,7 +732,7 @@ Set maximum BIP141 block weight (default: 3996000)
\fB\-blockmintxfee=\fR<amt>
.IP
Set lowest fee rate (in BTC/kvB) for transactions to be included in
block creation. (default: 0.00001)
block creation. (default: 0.00000001)
.PP
RPC server options:
.HP

View File

@ -1,7 +1,7 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIN-TX "1" "June 2025" "bitcoin-tx v28.2.0" "User Commands"
.TH BITCOIN-TX "1" "September 2025" "bitcoin-tx v28.3.0rc1" "User Commands"
.SH NAME
bitcoin-tx \- manual page for bitcoin-tx v28.2.0
bitcoin-tx \- manual page for bitcoin-tx v28.3.0rc1
.SH SYNOPSIS
.B bitcoin-tx
[\fI\,options\/\fR] \fI\,<hex-tx> \/\fR[\fI\,commands\/\fR] \fI\,Update hex-encoded bitcoin transaction\/\fR
@ -9,7 +9,7 @@ bitcoin-tx \- manual page for bitcoin-tx v28.2.0
.B bitcoin-tx
[\fI\,options\/\fR] \fI\,-create \/\fR[\fI\,commands\/\fR] \fI\,Create hex-encoded bitcoin transaction\/\fR
.SH DESCRIPTION
Bitcoin Core bitcoin\-tx utility version v28.2.0
Bitcoin Core bitcoin\-tx utility version v28.3.0rc1
.SH OPTIONS
.HP
\-?

View File

@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIN-UTIL "1" "June 2025" "bitcoin-util v28.2.0" "User Commands"
.TH BITCOIN-UTIL "1" "September 2025" "bitcoin-util v28.3.0rc1" "User Commands"
.SH NAME
bitcoin-util \- manual page for bitcoin-util v28.2.0
bitcoin-util \- manual page for bitcoin-util v28.3.0rc1
.SH SYNOPSIS
.B bitcoin-util
[\fI\,options\/\fR] [\fI\,commands\/\fR] \fI\,Do stuff\/\fR
.SH DESCRIPTION
Bitcoin Core bitcoin\-util utility version v28.2.0
Bitcoin Core bitcoin\-util utility version v28.3.0rc1
.SH OPTIONS
.HP
\-?

View File

@ -1,9 +1,9 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIN-WALLET "1" "June 2025" "bitcoin-wallet v28.2.0" "User Commands"
.TH BITCOIN-WALLET "1" "September 2025" "bitcoin-wallet v28.3.0rc1" "User Commands"
.SH NAME
bitcoin-wallet \- manual page for bitcoin-wallet v28.2.0
bitcoin-wallet \- manual page for bitcoin-wallet v28.3.0rc1
.SH DESCRIPTION
Bitcoin Core bitcoin\-wallet version v28.2.0
Bitcoin Core bitcoin\-wallet version v28.3.0rc1
.PP
bitcoin\-wallet is an offline tool for creating and interacting with Bitcoin Core wallet files.
By default bitcoin\-wallet will act on wallets in the default mainnet wallet directory in the datadir.

View File

@ -1,12 +1,12 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH BITCOIND "1" "June 2025" "bitcoind v28.2.0" "User Commands"
.TH BITCOIND "1" "September 2025" "bitcoind v28.3.0rc1" "User Commands"
.SH NAME
bitcoind \- manual page for bitcoind v28.2.0
bitcoind \- manual page for bitcoind v28.3.0rc1
.SH SYNOPSIS
.B bitcoind
[\fI\,options\/\fR] \fI\,Start Bitcoin Core\/\fR
.SH DESCRIPTION
Bitcoin Core version v28.2.0
Bitcoin Core version v28.3.0rc1
.SH OPTIONS
.HP
\-?
@ -703,7 +703,7 @@ replaceability signaling (default: 1)
\fB\-minrelaytxfee=\fR<amt>
.IP
Fees (in BTC/kvB) smaller than this are considered zero fee for
relaying, mining and transaction creation (default: 0.00001)
relaying, mining and transaction creation (default: 0.000001)
.HP
\fB\-permitbaremultisig\fR
.IP
@ -730,7 +730,7 @@ Set maximum BIP141 block weight (default: 3996000)
\fB\-blockmintxfee=\fR<amt>
.IP
Set lowest fee rate (in BTC/kvB) for transactions to be included in
block creation. (default: 0.00001)
block creation. (default: 0.00000001)
.PP
RPC server options:
.HP

View File

@ -37,6 +37,22 @@ unsupported systems.
Notable changes
===============
### Policy
The minimum block feerate (`-blockmintxfee`) has been changed to 1 satoshi per kvB. It can still be changed using the
configuration option.
- The default minimum relay feerate (`-minrelaytxfee`) and incremental relay feerate (`-incrementalrelayfee`) have been
changed to 100 satoshis per kvB. They can still be changed using their respective configuration options, but it is
recommended to change both together if you decide to do so.
- Other minimum feerates (e.g. the dust feerate, the minimum returned by the fee estimator, and all feerates used by
the wallet) remain unchanged. The mempool minimum feerate still changes in response to high volume.
- Note that unless these lower defaults are widely adopted across the network, transactions created with lower fee
rates are not guaranteed to propagate or confirm. The wallet feerates remain unchanged; `-mintxfee` must be changed
before attempting to create transactions with lower feerates using the wallet.
- #33106 policy: lower the default blockmintxfee, incrementalrelayfee, minrelaytxfee
### P2P
- #33395 net: do not apply whitelist permissions to onion inbounds
@ -45,6 +61,9 @@ Notable changes
- #32765 test: Fix list index out of range error in feature_bip68_sequence.py
- #33001 test: Do not pass tests on unhandled exceptions
- #30125 test: improve BDB parser (handle internal/overflow pages, support all page sizes)
- #30948 test: Add missing sync_mempools() to fill_mempool()
- #30784 test: add BulkTransaction helper to unit test transaction utils
### Build
@ -70,6 +89,7 @@ Credits
Thanks to everyone who directly contributed to this release:
- 0xB10C
- fanquake
- glozow
- Hennadii Stepanov
- MarcoFalke
- Martin Zumsande

View File

@ -591,7 +591,7 @@
#mempoolfullrbf=1
# Fees (in BTC/kvB) smaller than this are considered zero fee for
# relaying, mining and transaction creation (default: 0.00001)
# relaying, mining and transaction creation (default: 0.000001)
#minrelaytxfee=<amt>
# Relay transactions creating non-P2SH multisig outputs (default: 1)
@ -615,7 +615,7 @@
#blockmaxweight=<n>
# Set lowest fee rate (in BTC/kvB) for transactions to be included in
# block creation. (default: 0.00001)
# block creation. (default: 0.00000001)
#blockmintxfee=<amt>

View File

@ -56,6 +56,7 @@ util::Result<void> ApplyArgsManOptions(const ArgsManager& argsman, const CChainP
}
}
static_assert(DEFAULT_MIN_RELAY_TX_FEE == DEFAULT_INCREMENTAL_RELAY_FEE);
if (argsman.IsArgSet("-minrelaytxfee")) {
if (std::optional<CAmount> min_relay_feerate = ParseMoney(argsman.GetArg("-minrelaytxfee", ""))) {
// High fee check is done afterward in CWallet::Create()

View File

@ -22,7 +22,7 @@ class CScript;
/** Default for -blockmaxweight, which controls the range of block weights the mining code will create **/
static constexpr unsigned int DEFAULT_BLOCK_MAX_WEIGHT{MAX_BLOCK_WEIGHT - 4000};
/** Default for -blockmintxfee, which sets the minimum feerate for a transaction in blocks created by mining code **/
static constexpr unsigned int DEFAULT_BLOCK_MIN_TX_FEE{1000};
static constexpr unsigned int DEFAULT_BLOCK_MIN_TX_FEE{1};
/** The maximum weight for transactions we're willing to relay/mine */
static constexpr int32_t MAX_STANDARD_TX_WEIGHT{400000};
/** The minimum non-witness size for transactions we're willing to relay/mine: one larger than 64 */
@ -32,7 +32,7 @@ static constexpr unsigned int MAX_P2SH_SIGOPS{15};
/** The maximum number of sigops we're willing to relay/mine in a single tx */
static constexpr unsigned int MAX_STANDARD_TX_SIGOPS_COST{MAX_BLOCK_SIGOPS_COST/5};
/** Default for -incrementalrelayfee, which sets the minimum feerate increase for mempool limiting or replacement **/
static constexpr unsigned int DEFAULT_INCREMENTAL_RELAY_FEE{1000};
static constexpr unsigned int DEFAULT_INCREMENTAL_RELAY_FEE{100};
/** Default for -bytespersigop */
static constexpr unsigned int DEFAULT_BYTES_PER_SIGOP{20};
/** Default for -permitbaremultisig */
@ -54,7 +54,7 @@ static constexpr unsigned int MAX_STANDARD_SCRIPTSIG_SIZE{1650};
* outputs below the new threshold */
static constexpr unsigned int DUST_RELAY_TX_FEE{3000};
/** Default for -minrelaytxfee, minimum relay fee for transactions */
static constexpr unsigned int DEFAULT_MIN_RELAY_TX_FEE{1000};
static constexpr unsigned int DEFAULT_MIN_RELAY_TX_FEE{100};
/** Default for -limitancestorcount, max number of in-mempool ancestors */
static constexpr unsigned int DEFAULT_ANCESTOR_LIMIT{25};
/** Default for -limitancestorsize, maximum kilobytes of tx + all in-mempool ancestors */

View File

@ -443,7 +443,7 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
tx1.vout.resize(1);
tx1.vout[0].scriptPubKey = CScript() << OP_1 << OP_EQUAL;
tx1.vout[0].nValue = 10 * COIN;
pool.addUnchecked(entry.Fee(10000LL).FromTx(tx1));
pool.addUnchecked(entry.Fee(1000LL).FromTx(tx1));
CMutableTransaction tx2 = CMutableTransaction();
tx2.vin.resize(1);
@ -451,7 +451,7 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
tx2.vout.resize(1);
tx2.vout[0].scriptPubKey = CScript() << OP_2 << OP_EQUAL;
tx2.vout[0].nValue = 10 * COIN;
pool.addUnchecked(entry.Fee(5000LL).FromTx(tx2));
pool.addUnchecked(entry.Fee(500LL).FromTx(tx2));
pool.TrimToSize(pool.DynamicMemoryUsage()); // should do nothing
BOOST_CHECK(pool.exists(GenTxid::Txid(tx1.GetHash())));
@ -469,7 +469,7 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
tx3.vout.resize(1);
tx3.vout[0].scriptPubKey = CScript() << OP_3 << OP_EQUAL;
tx3.vout[0].nValue = 10 * COIN;
pool.addUnchecked(entry.Fee(20000LL).FromTx(tx3));
pool.addUnchecked(entry.Fee(2000LL).FromTx(tx3));
pool.TrimToSize(pool.DynamicMemoryUsage() * 3 / 4); // tx3 should pay for tx2 (CPFP)
BOOST_CHECK(!pool.exists(GenTxid::Txid(tx1.GetHash())));
@ -481,8 +481,8 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
BOOST_CHECK(!pool.exists(GenTxid::Txid(tx2.GetHash())));
BOOST_CHECK(!pool.exists(GenTxid::Txid(tx3.GetHash())));
CFeeRate maxFeeRateRemoved(25000, GetVirtualTransactionSize(CTransaction(tx3)) + GetVirtualTransactionSize(CTransaction(tx2)));
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + 1000);
CFeeRate maxFeeRateRemoved(2500, GetVirtualTransactionSize(CTransaction(tx3)) + GetVirtualTransactionSize(CTransaction(tx2)));
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + DEFAULT_INCREMENTAL_RELAY_FEE);
CMutableTransaction tx4 = CMutableTransaction();
tx4.vin.resize(2);
@ -532,10 +532,10 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
tx7.vout[1].scriptPubKey = CScript() << OP_7 << OP_EQUAL;
tx7.vout[1].nValue = 10 * COIN;
pool.addUnchecked(entry.Fee(7000LL).FromTx(tx4));
pool.addUnchecked(entry.Fee(1000LL).FromTx(tx5));
pool.addUnchecked(entry.Fee(1100LL).FromTx(tx6));
pool.addUnchecked(entry.Fee(9000LL).FromTx(tx7));
pool.addUnchecked(entry.Fee(700LL).FromTx(tx4));
pool.addUnchecked(entry.Fee(100LL).FromTx(tx5));
pool.addUnchecked(entry.Fee(110LL).FromTx(tx6));
pool.addUnchecked(entry.Fee(900LL).FromTx(tx7));
// we only require this to remove, at max, 2 txn, because it's not clear what we're really optimizing for aside from that
pool.TrimToSize(pool.DynamicMemoryUsage() - 1);
@ -544,8 +544,8 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
BOOST_CHECK(!pool.exists(GenTxid::Txid(tx7.GetHash())));
if (!pool.exists(GenTxid::Txid(tx5.GetHash())))
pool.addUnchecked(entry.Fee(1000LL).FromTx(tx5));
pool.addUnchecked(entry.Fee(9000LL).FromTx(tx7));
pool.addUnchecked(entry.Fee(100LL).FromTx(tx5));
pool.addUnchecked(entry.Fee(900LL).FromTx(tx7));
pool.TrimToSize(pool.DynamicMemoryUsage() / 2); // should maximize mempool size by only removing 5/7
BOOST_CHECK(pool.exists(GenTxid::Txid(tx4.GetHash())));
@ -553,34 +553,34 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
BOOST_CHECK(pool.exists(GenTxid::Txid(tx6.GetHash())));
BOOST_CHECK(!pool.exists(GenTxid::Txid(tx7.GetHash())));
pool.addUnchecked(entry.Fee(1000LL).FromTx(tx5));
pool.addUnchecked(entry.Fee(9000LL).FromTx(tx7));
pool.addUnchecked(entry.Fee(100LL).FromTx(tx5));
pool.addUnchecked(entry.Fee(900LL).FromTx(tx7));
std::vector<CTransactionRef> vtx;
SetMockTime(42);
SetMockTime(42 + CTxMemPool::ROLLING_FEE_HALFLIFE);
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + 1000);
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + DEFAULT_INCREMENTAL_RELAY_FEE);
// ... we should keep the same min fee until we get a block
pool.removeForBlock(vtx, 1);
SetMockTime(42 + 2*CTxMemPool::ROLLING_FEE_HALFLIFE);
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + 1000)/2.0));
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + DEFAULT_INCREMENTAL_RELAY_FEE)/2.0));
// ... then feerate should drop 1/2 each halflife
SetMockTime(42 + 2*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2);
BOOST_CHECK_EQUAL(pool.GetMinFee(pool.DynamicMemoryUsage() * 5 / 2).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + 1000)/4.0));
BOOST_CHECK_EQUAL(pool.GetMinFee(pool.DynamicMemoryUsage() * 5 / 2).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + DEFAULT_INCREMENTAL_RELAY_FEE)/4.0));
// ... with a 1/2 halflife when mempool is < 1/2 its target size
SetMockTime(42 + 2*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2 + CTxMemPool::ROLLING_FEE_HALFLIFE/4);
BOOST_CHECK_EQUAL(pool.GetMinFee(pool.DynamicMemoryUsage() * 9 / 2).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + 1000)/8.0));
BOOST_CHECK_EQUAL(pool.GetMinFee(pool.DynamicMemoryUsage() * 9 / 2).GetFeePerK(), llround((maxFeeRateRemoved.GetFeePerK() + DEFAULT_INCREMENTAL_RELAY_FEE)/8.0));
// ... with a 1/4 halflife when mempool is < 1/4 its target size
SetMockTime(42 + 7*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2 + CTxMemPool::ROLLING_FEE_HALFLIFE/4);
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), 1000);
// ... but feerate should never drop below 1000
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), DEFAULT_INCREMENTAL_RELAY_FEE);
// ... but feerate should never drop below DEFAULT_INCREMENTAL_RELAY_FEE
SetMockTime(42 + 8*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2 + CTxMemPool::ROLLING_FEE_HALFLIFE/4);
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), 0);
// ... unless it has gone all the way to 0 (after getting past 1000/2)
// ... unless it has gone all the way to 0 (after getting past DEFAULT_INCREMENTAL_RELAY_FEE/2)
}
inline CTransactionRef make_tx(std::vector<CAmount>&& output_values, std::vector<CTransactionRef>&& inputs=std::vector<CTransactionRef>(), std::vector<uint32_t>&& input_indices=std::vector<uint32_t>())

View File

@ -11,6 +11,7 @@
#include <node/miner.h>
#include <policy/policy.h>
#include <test/util/random.h>
#include <test/util/transaction_utils.h>
#include <test/util/txmempool.h>
#include <txmempool.h>
#include <uint256.h>
@ -183,6 +184,9 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
tx.vout.resize(2);
tx.vout[0].nValue = 5000000000LL - 100000000;
tx.vout[1].nValue = 100000000; // 1BTC output
// Increase size to avoid rounding errors: when the feerate is extremely small (i.e. 1sat/kvB), evaluating the fee
// at a smaller transaction size gives us a rounded value of 0.
BulkTransaction(tx, 4000);
Txid hashFreeTx2 = tx.GetHash();
tx_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx));

View File

@ -238,10 +238,10 @@ BOOST_FIXTURE_TEST_CASE(rbf_helper_functions, TestChain100Setup)
BOOST_CHECK(PaysForRBF(high_fee, high_fee - 1, 1, CFeeRate(0), unused_txid).has_value());
BOOST_CHECK(PaysForRBF(high_fee + 1, high_fee, 1, CFeeRate(0), unused_txid).has_value());
// Additional fees must cover the replacement's vsize at incremental relay fee
BOOST_CHECK(PaysForRBF(high_fee, high_fee + 1, 2, incremental_relay_feerate, unused_txid).has_value());
BOOST_CHECK(PaysForRBF(high_fee, high_fee + 2, 2, incremental_relay_feerate, unused_txid) == std::nullopt);
BOOST_CHECK(PaysForRBF(high_fee, high_fee + 2, 2, higher_relay_feerate, unused_txid).has_value());
BOOST_CHECK(PaysForRBF(high_fee, high_fee + 4, 2, higher_relay_feerate, unused_txid) == std::nullopt);
BOOST_CHECK(PaysForRBF(high_fee, high_fee + 1, 11, incremental_relay_feerate, unused_txid).has_value());
BOOST_CHECK(PaysForRBF(high_fee, high_fee + 1, 10, incremental_relay_feerate, unused_txid) == std::nullopt);
BOOST_CHECK(PaysForRBF(high_fee, high_fee + 2, 11, higher_relay_feerate, unused_txid).has_value());
BOOST_CHECK(PaysForRBF(high_fee, high_fee + 4, 20, higher_relay_feerate, unused_txid) == std::nullopt);
BOOST_CHECK(PaysForRBF(low_fee, high_fee, 99999999, incremental_relay_feerate, unused_txid).has_value());
BOOST_CHECK(PaysForRBF(low_fee, high_fee + 99999999, 99999999, incremental_relay_feerate, unused_txid) == std::nullopt);

View File

@ -41,6 +41,7 @@
#include <streams.h>
#include <test/util/net.h>
#include <test/util/random.h>
#include <test/util/transaction_utils.h>
#include <test/util/txmempool.h>
#include <txdb.h>
#include <txmempool.h>
@ -582,6 +583,9 @@ void TestChain100Setup::MockMempoolMinFee(const CFeeRate& target_feerate)
CMutableTransaction mtx = CMutableTransaction();
mtx.vin.emplace_back(COutPoint{Txid::FromUint256(g_insecure_rand_ctx.rand256()), 0});
mtx.vout.emplace_back(1 * COIN, GetScriptForDestination(WitnessV0ScriptHash(CScript() << OP_TRUE)));
// Set a large size so that the fee evaluated at target_feerate (which is usually in sats/kvB) is an integer.
// Otherwise, GetMinFee() may end up slightly different from target_feerate.
BulkTransaction(mtx, 4000);
const auto tx{MakeTransactionRef(mtx)};
LockPoints lp;
// The new mempool min feerate is equal to the removed package's feerate + incremental feerate.

View File

@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <coins.h>
#include <consensus/validation.h>
#include <script/signingprovider.h>
#include <test/util/transaction_utils.h>
@ -69,3 +70,23 @@ std::vector<CMutableTransaction> SetupDummyInputs(FillableSigningProvider& keyst
return dummyTransactions;
}
void BulkTransaction(CMutableTransaction& tx, int32_t target_weight)
{
tx.vout.emplace_back(0, CScript() << OP_RETURN);
auto unpadded_weight{GetTransactionWeight(CTransaction(tx))};
assert(target_weight >= unpadded_weight);
// determine number of needed padding bytes by converting weight difference to vbytes
auto dummy_vbytes = (target_weight - unpadded_weight + (WITNESS_SCALE_FACTOR - 1)) / WITNESS_SCALE_FACTOR;
// compensate for the increase of the compact-size encoded script length
// (note that the length encoding of the unpadded output script needs one byte)
dummy_vbytes -= GetSizeOfCompactSize(dummy_vbytes) - 1;
// pad transaction by repeatedly appending a dummy opcode to the output script
tx.vout[0].scriptPubKey.insert(tx.vout[0].scriptPubKey.end(), dummy_vbytes, OP_1);
// actual weight should be at most 3 higher than target weight
assert(GetTransactionWeight(CTransaction(tx)) >= target_weight);
assert(GetTransactionWeight(CTransaction(tx)) <= target_weight + 3);
}

View File

@ -26,4 +26,8 @@ CMutableTransaction BuildSpendingTransaction(const CScript& scriptSig, const CSc
// the second nValues[2] and nValues[3] outputs paid to a TxoutType::PUBKEYHASH.
std::vector<CMutableTransaction> SetupDummyInputs(FillableSigningProvider& keystoreRet, CCoinsViewCache& coinsRet, const std::array<CAmount,4>& nValues);
// bulk transaction to reach a certain target weight,
// by appending a single output with padded output script
void BulkTransaction(CMutableTransaction& tx, int32_t target_weight);
#endif // BITCOIN_TEST_UTIL_TRANSACTION_UTILS_H

View File

@ -14,7 +14,10 @@ from test_framework.messages import (
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than,
assert_greater_than_or_equal,
assert_raises_rpc_error,
get_fee,
)
from test_framework.wallet import MiniWallet
from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE
@ -87,6 +90,9 @@ class ReplaceByFeeTest(BitcoinTestFramework):
self.log.info("Running test full replace by fee...")
self.test_fullrbf()
self.log.info("Running test incremental relay feerates...")
self.test_incremental_relay_feerates()
self.log.info("Passed")
def make_utxo(self, node, amount, *, confirmed=True, scriptPubKey=None):
@ -697,10 +703,42 @@ class ReplaceByFeeTest(BitcoinTestFramework):
# 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.
assert_equal(self.nodes[0].getmempoolinfo()["incrementalrelayfee"], Decimal("0.00001"))
assert_equal(self.nodes[0].getmempoolinfo()["incrementalrelayfee"], Decimal("0.000001"))
tx.vout[0].nValue -= 1
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex())
def test_incremental_relay_feerates(self):
self.log.info("Test that incremental relay fee is applied correctly in RBF for various settings...")
node = self.nodes[0]
for incremental_setting in (0, 5, 10, 50, 100, 234, 1000, 5000, 21000):
incremental_setting_decimal = incremental_setting / Decimal(COIN)
self.log.info(f"-> Test -incrementalrelayfee={incremental_setting_decimal:.8f}sat/kvB...")
self.restart_node(0, extra_args=[f"-incrementalrelayfee={incremental_setting_decimal:.8f}", "-datacarriersize=5000", "-persistmempool=0"])
# When incremental relay feerate is higher than min relay feerate, min relay feerate is automatically increased.
min_relay_feerate = node.getmempoolinfo()["minrelaytxfee"]
assert_greater_than_or_equal(min_relay_feerate, incremental_setting_decimal)
low_feerate = min_relay_feerate * 2
confirmed_utxo = self.wallet.get_utxo(confirmed_only=True)
replacee_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, fee_rate=low_feerate, target_weight=20000)
node.sendrawtransaction(replacee_tx['hex'])
replacement_placeholder_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo)
replacement_expected_size = replacement_placeholder_tx['tx'].get_vsize()
replacement_required_fee = get_fee(replacement_expected_size, incremental_setting_decimal) + replacee_tx['fee']
# Should always be required to pay additional fees
if incremental_setting > 0:
assert_greater_than(replacement_required_fee, replacee_tx['fee'])
# 1 satoshi shy of the required fee
failed_replacement_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, fee=replacement_required_fee - Decimal("0.00000001"))
assert_raises_rpc_error(-26, "insufficient fee", node.sendrawtransaction, failed_replacement_tx['hex'])
replacement_tx = self.wallet.create_self_transfer(utxo_to_spend=confirmed_utxo, fee=replacement_required_fee)
node.sendrawtransaction(replacement_tx['hex'])
def test_fullrbf(self):
confirmed_utxo = self.make_utxo(self.nodes[0], int(2 * COIN))

View File

@ -9,6 +9,10 @@ from decimal import Decimal
import math
from test_framework.test_framework import BitcoinTestFramework
from test_framework.mempool_util import (
DEFAULT_MIN_RELAY_TX_FEE,
DEFAULT_INCREMENTAL_RELAY_FEE,
)
from test_framework.messages import (
MAX_BIP125_RBF_SEQUENCE,
COIN,
@ -80,6 +84,11 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
assert_equal(node.getblockcount(), 200)
assert_equal(node.getmempoolinfo()['size'], self.mempool_size)
self.log.info("Check default settings")
# Settings are listed in BTC/kvB
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN)
assert_equal(node.getmempoolinfo()['incrementalrelayfee'], Decimal(DEFAULT_INCREMENTAL_RELAY_FEE) / COIN)
self.log.info('Should not accept garbage to testmempoolaccept')
assert_raises_rpc_error(-3, 'JSON value of type string is not of expected type array', lambda: node.testmempoolaccept(rawtxs='ff00baar'))
assert_raises_rpc_error(-8, 'Array must contain between 1 and 25 transactions.', lambda: node.testmempoolaccept(rawtxs=['ff22']*26))

View File

@ -92,8 +92,7 @@ class MempoolLimitTest(BitcoinTestFramework):
self.restart_node(0, extra_args=self.extra_args[0])
# Restarting the node resets mempool minimum feerate
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000'))
assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))
assert_equal(node.getmempoolinfo()['minrelaytxfee'], node.getmempoolinfo()["mempoolminfee"])
fill_mempool(self, node)
current_info = node.getmempoolinfo()
@ -122,7 +121,7 @@ class MempoolLimitTest(BitcoinTestFramework):
# coin is no longer available, but the cache could still contains the tx.
cpfp_parent = self.wallet.create_self_transfer(
utxo_to_spend=mempool_evicted_tx["new_utxo"],
fee_rate=mempoolmin_feerate - Decimal('0.00001'),
fee_rate=mempoolmin_feerate / 2,
confirmed_only=True)
package_hex.append(cpfp_parent["hex"])
parent_utxos.append(cpfp_parent["new_utxo"])
@ -154,7 +153,7 @@ class MempoolLimitTest(BitcoinTestFramework):
# Specific number of satoshis to fit within a small window. The parent_cpfp + child package needs to be
# - When there is mid-package eviction, high enough feerate to meet the new mempoolminfee
# - When there is no mid-package eviction, low enough feerate to be evicted immediately after submission.
magic_satoshis = 1200
magic_satoshis = 120
cpfp_satoshis = int(cpfp_fee * COIN) + magic_satoshis
child = self.wallet.create_self_transfer_multi(utxos_to_spend=parent_utxos, fee_per_output=cpfp_satoshis)
@ -182,8 +181,7 @@ class MempoolLimitTest(BitcoinTestFramework):
self.restart_node(0, extra_args=self.extra_args[0])
# Restarting the node resets mempool minimum feerate
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000'))
assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))
assert_equal(node.getmempoolinfo()['minrelaytxfee'], node.getmempoolinfo()["mempoolminfee"])
fill_mempool(self, node)
current_info = node.getmempoolinfo()
@ -208,7 +206,7 @@ class MempoolLimitTest(BitcoinTestFramework):
# coin is no longer available, but the cache could still contain the tx.
cpfp_parent = self.wallet.create_self_transfer(
utxo_to_spend=replaced_tx["new_utxo"],
fee_rate=mempoolmin_feerate - Decimal('0.00001'),
fee_rate=mempoolmin_feerate - Decimal('0.000001'),
confirmed_only=True)
self.wallet.rescan_utxos()
@ -256,8 +254,7 @@ class MempoolLimitTest(BitcoinTestFramework):
relayfee = node.getnetworkinfo()['relayfee']
self.log.info('Check that mempoolminfee is minrelaytxfee')
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000'))
assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))
assert_equal(node.getmempoolinfo()['minrelaytxfee'], node.getmempoolinfo()["mempoolminfee"])
fill_mempool(self, node)
@ -314,9 +311,9 @@ class MempoolLimitTest(BitcoinTestFramework):
target_weight_each = 200000
assert_greater_than(target_weight_each * 2, node.getmempoolinfo()["maxmempool"] - node.getmempoolinfo()["bytes"])
# Should be a true CPFP: parent's feerate is just below mempool min feerate
parent_feerate = mempoolmin_feerate - Decimal("0.000001") # 0.1 sats/vbyte below min feerate
parent_feerate = mempoolmin_feerate - Decimal("0.0000001") # 0.01 sats/vbyte below min feerate
# Parent + child is above mempool minimum feerate
child_feerate = (worst_feerate_btcvb * 1000) - Decimal("0.000001") # 0.1 sats/vbyte below worst feerate
child_feerate = (worst_feerate_btcvb * 1000) - Decimal("0.0000001") # 0.01 sats/vbyte below worst feerate
# However, when eviction is triggered, these transactions should be at the bottom.
# This assertion assumes parent and child are the same size.
miniwallet.rescan_utxos()

View File

@ -170,13 +170,13 @@ class PackageRBFTest(BitcoinTestFramework):
self.log.info("Check replacement pays for incremental bandwidth")
_, placeholder_txns3 = self.create_simple_package(coin)
package_3_size = sum([tx.get_vsize() for tx in placeholder_txns3])
incremental_sats_required = Decimal(package_3_size) / COIN
incremental_sats_short = incremental_sats_required - Decimal("0.00000001")
incremental_sats_required = (Decimal(package_3_size * 0.1) / COIN).quantize(Decimal("0.00000001"))
incremental_sats_short = incremental_sats_required - Decimal("0.00000005")
# Recreate the package with slightly higher fee once we know the size of the new package, but still short of required fee
failure_package_hex3, failure_package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE + incremental_sats_short)
assert_equal(package_3_size, sum([tx.get_vsize() for tx in failure_package_txns3]))
pkg_results3 = node.submitpackage(failure_package_hex3)
assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {failure_package_txns3[1].rehash()}, not enough additional fees to relay; {incremental_sats_short} < {incremental_sats_required}", pkg_results3["package_msg"])
assert_equal(f"package RBF failed: insufficient anti-DoS fees, rejecting replacement {failure_package_txns3[1].rehash()}, not enough additional fees to relay; {incremental_sats_short:.8f} < {incremental_sats_required:.8f}", pkg_results3["package_msg"])
self.assert_mempool_contents(expected=package_txns1)
success_package_hex3, success_package_txns3 = self.create_simple_package(coin, parent_fee=DEFAULT_FEE, child_fee=DEFAULT_CHILD_FEE + incremental_sats_required)
@ -554,7 +554,7 @@ class PackageRBFTest(BitcoinTestFramework):
self.generate(node, 1)
def test_child_conflicts_parent_mempool_ancestor(self):
fill_mempool(self, self.nodes[0])
fill_mempool(self, self.nodes[0], tx_sync_fun=self.no_op)
# Reset coins since we filled the mempool with current coins
self.coins = self.wallet.get_utxos(mark_as_spent=False, confirmed_only=True)
@ -570,12 +570,13 @@ class PackageRBFTest(BitcoinTestFramework):
)
node.sendrawtransaction(grandparent_result["hex"])
minrelayfeerate = node.getnetworkinfo()["relayfee"]
# Now make package of two descendants that looks
# like a cpfp where the parent can't get in on its own
self.ctr += 1
parent_result = self.wallet.create_self_transfer(
fee_rate=Decimal('0.00001000'),
fee_rate=minrelayfeerate,
utxo_to_spend=grandparent_result["new_utxo"],
sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
)

View File

@ -14,6 +14,7 @@ from test_framework.util import (
assert_greater_than,
assert_greater_than_or_equal,
assert_raises_rpc_error,
get_fee,
)
from test_framework.wallet import (
COIN,
@ -621,12 +622,57 @@ class MempoolTRUC(BitcoinTestFramework):
)
self.check_mempool([tx_with_multi_children["txid"], tx_with_sibling3_rbf["txid"], tx_with_sibling2["txid"]])
@cleanup(extra_args=None)
def test_minrelay_in_package_combos(self):
node = self.nodes[0]
self.log.info("Test that only TRUC transactions can be under minrelaytxfee for various settings...")
for minrelay_setting in (0, 5, 10, 100, 500, 1000, 5000, 333333, 2500000):
self.log.info(f"-> Test -minrelaytxfee={minrelay_setting}sat/kvB...")
setting_decimal = minrelay_setting / Decimal(COIN)
self.restart_node(0, extra_args=[f"-minrelaytxfee={setting_decimal:.8f}", "-persistmempool=0"])
minrelayfeerate = node.getmempoolinfo()["minrelaytxfee"]
high_feerate = minrelayfeerate * 50
tx_v3_0fee_parent = self.wallet.create_self_transfer(fee=0, fee_rate=0, confirmed_only=True, version=3)
tx_v3_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_0fee_parent["new_utxo"], fee_rate=high_feerate, version=3)
total_v3_fee = tx_v3_child["fee"] + tx_v3_0fee_parent["fee"]
total_v3_size = tx_v3_child["tx"].get_vsize() + tx_v3_0fee_parent["tx"].get_vsize()
assert_greater_than_or_equal(total_v3_fee, get_fee(total_v3_size, minrelayfeerate))
if minrelayfeerate > 0:
assert_greater_than(get_fee(tx_v3_0fee_parent["tx"].get_vsize(), minrelayfeerate), 0)
# Always need to pay at least 1 satoshi for entry, even if minimum feerate is very low
assert_greater_than(total_v3_fee, 0)
tx_v2_0fee_parent = self.wallet.create_self_transfer(fee=0, fee_rate=0, confirmed_only=True, version=2)
tx_v2_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v2_0fee_parent["new_utxo"], fee_rate=high_feerate, version=2)
total_v2_fee = tx_v2_child["fee"] + tx_v2_0fee_parent["fee"]
total_v2_size = tx_v2_child["tx"].get_vsize() + tx_v2_0fee_parent["tx"].get_vsize()
assert_greater_than_or_equal(total_v2_fee, get_fee(total_v2_size, minrelayfeerate))
if minrelayfeerate > 0:
assert_greater_than(get_fee(tx_v2_0fee_parent["tx"].get_vsize(), minrelayfeerate), 0)
# Always need to pay at least 1 satoshi for entry, even if minimum feerate is very low
assert_greater_than(total_v2_fee, 0)
result_truc = node.submitpackage([tx_v3_0fee_parent["hex"], tx_v3_child["hex"]], maxfeerate=0)
assert_equal(result_truc["package_msg"], "success")
result_non_truc = node.submitpackage([tx_v2_0fee_parent["hex"], tx_v2_child["hex"]], maxfeerate=0)
if minrelayfeerate > 0:
assert_equal(result_non_truc["package_msg"], "transaction failed")
min_fee_parent = int(get_fee(tx_v2_0fee_parent["tx"].get_vsize(), minrelayfeerate) * COIN)
assert_equal(result_non_truc["tx-results"][tx_v2_0fee_parent["wtxid"]]["error"], f"min relay fee not met, 0 < {min_fee_parent}")
self.check_mempool([tx_v3_0fee_parent["txid"], tx_v3_child["txid"]])
else:
assert_equal(result_non_truc["package_msg"], "success")
self.check_mempool([tx_v2_0fee_parent["txid"], tx_v2_child["txid"], tx_v3_0fee_parent["txid"], tx_v3_child["txid"]])
def run_test(self):
self.log.info("Generate blocks to create UTXOs")
node = self.nodes[0]
self.wallet = MiniWallet(node)
self.generate(self.wallet, 120)
self.generate(self.wallet, 200)
self.test_truc_max_vsize()
self.test_truc_acceptance()
self.test_truc_replacement()
@ -641,6 +687,7 @@ class MempoolTRUC(BitcoinTestFramework):
self.test_reorg_2child_rbf()
self.test_truc_sibling_eviction()
self.test_reorg_sibling_eviction_1p2c()
self.test_minrelay_in_package_combos()
if __name__ == "__main__":

View File

@ -28,6 +28,7 @@ from test_framework.p2p import P2PDataStore
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than,
assert_greater_than_or_equal,
assert_raises_rpc_error,
get_fee,
@ -40,7 +41,7 @@ MAX_FUTURE_BLOCK_TIME = 2 * 3600
MAX_TIMEWARP = 600
VERSIONBITS_TOP_BITS = 0x20000000
VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 28
DEFAULT_BLOCK_MIN_TX_FEE = 1000 # default `-blockmintxfee` setting [sat/kvB]
DEFAULT_BLOCK_MIN_TX_FEE = 1 # default `-blockmintxfee` setting [sat/kvB]
def assert_template(node, block, expect, rehash=True):
@ -86,7 +87,7 @@ class MiningTest(BitcoinTestFramework):
node = self.nodes[0]
# test default (no parameter), zero and a bunch of arbitrary blockmintxfee rates [sat/kvB]
for blockmintxfee_sat_kvb in (DEFAULT_BLOCK_MIN_TX_FEE, 0, 50, 100, 500, 2500, 5000, 21000, 333333, 2500000):
for blockmintxfee_sat_kvb in (DEFAULT_BLOCK_MIN_TX_FEE, 0, 5, 10, 50, 100, 500, 1000, 2500, 5000, 21000, 333333, 2500000):
blockmintxfee_btc_kvb = blockmintxfee_sat_kvb / Decimal(COIN)
if blockmintxfee_sat_kvb == DEFAULT_BLOCK_MIN_TX_FEE:
self.log.info(f"-> Default -blockmintxfee setting ({blockmintxfee_sat_kvb} sat/kvB)...")
@ -97,19 +98,27 @@ class MiningTest(BitcoinTestFramework):
self.wallet.rescan_utxos() # to avoid spending outputs of txs that are not in mempool anymore after restart
# submit one tx with exactly the blockmintxfee rate, and one slightly below
tx_with_min_feerate = self.wallet.send_self_transfer(from_node=node, fee_rate=blockmintxfee_btc_kvb)
tx_with_min_feerate = self.wallet.send_self_transfer(from_node=node, fee_rate=blockmintxfee_btc_kvb, confirmed_only=True)
assert_equal(tx_with_min_feerate["fee"], get_fee(tx_with_min_feerate["tx"].get_vsize(), blockmintxfee_btc_kvb))
if blockmintxfee_btc_kvb > 0:
if blockmintxfee_sat_kvb > 5:
lowerfee_btc_kvb = blockmintxfee_btc_kvb - Decimal(10)/COIN # 0.01 sat/vbyte lower
tx_below_min_feerate = self.wallet.send_self_transfer(from_node=node, fee_rate=lowerfee_btc_kvb)
tx_below_min_feerate = self.wallet.send_self_transfer(from_node=node, fee_rate=lowerfee_btc_kvb, confirmed_only=True)
assert_equal(tx_below_min_feerate["fee"], get_fee(tx_below_min_feerate["tx"].get_vsize(), lowerfee_btc_kvb))
else: # go below zero fee by using modified fees
tx_below_min_feerate = self.wallet.send_self_transfer(from_node=node, fee_rate=blockmintxfee_btc_kvb)
tx_below_min_feerate = self.wallet.send_self_transfer(from_node=node, fee_rate=blockmintxfee_btc_kvb, confirmed_only=True)
node.prioritisetransaction(tx_below_min_feerate["txid"], 0, -1)
# check that tx below specified fee-rate is neither in template nor in the actual block
block_template = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
block_template_txids = [tx['txid'] for tx in block_template['transactions']]
# Unless blockmintxfee is 0, the template shouldn't contain free transactions.
# Note that the real block assembler uses package feerates, but we didn't create dependent transactions so it's ok to use base feerate.
if blockmintxfee_btc_kvb > 0:
for txid in block_template_txids:
tx = node.getmempoolentry(txid)
assert_greater_than(tx['fees']['base'], 0)
self.generate(self.wallet, 1, sync_fun=self.no_op)
block = node.getblock(node.getbestblockhash(), verbosity=2)
block_txids = [tx['txid'] for tx in block['tx']]

View File

@ -13,9 +13,11 @@ from decimal import Decimal
from math import ceil
from test_framework.mempool_util import (
DEFAULT_MIN_RELAY_TX_FEE,
fill_mempool,
)
from test_framework.messages import (
COIN,
msg_tx,
)
from test_framework.p2p import (
@ -31,9 +33,6 @@ from test_framework.wallet import (
MiniWalletMode,
)
# 1sat/vB feerate denominated in BTC/KvB
FEERATE_1SAT_VB = Decimal("0.00001000")
class PackageRelayTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
@ -49,17 +48,14 @@ class PackageRelayTest(BitcoinTestFramework):
def raise_network_minfee(self):
fill_mempool(self, self.nodes[0])
self.log.debug("Wait for the network to sync mempools")
self.sync_mempools()
self.log.debug("Check that all nodes' mempool minimum feerates are above min relay feerate")
for node in self.nodes:
assert_equal(node.getmempoolinfo()['minrelaytxfee'], FEERATE_1SAT_VB)
assert_greater_than(node.getmempoolinfo()['mempoolminfee'], FEERATE_1SAT_VB)
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN)
assert_greater_than(node.getmempoolinfo()['mempoolminfee'], Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN)
def create_basic_1p1c(self, wallet):
low_fee_parent = wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB, confirmed_only=True)
high_fee_child = wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=999*FEERATE_1SAT_VB)
low_fee_parent = wallet.create_self_transfer(fee_rate=Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN, confirmed_only=True)
high_fee_child = wallet.create_self_transfer(utxo_to_spend=low_fee_parent["new_utxo"], fee_rate=999*Decimal(DEFAULT_MIN_RELAY_TX_FEE)/ COIN)
package_hex_basic = [low_fee_parent["hex"], high_fee_child["hex"]]
return package_hex_basic, low_fee_parent["tx"], high_fee_child["tx"]
@ -90,8 +86,8 @@ class PackageRelayTest(BitcoinTestFramework):
return [low_fee_parent_2outs["hex"], high_fee_child_2outs["hex"]], low_fee_parent_2outs["tx"], high_fee_child_2outs["tx"]
def create_package_2p1c(self, wallet):
parent1 = wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB*10, confirmed_only=True)
parent2 = wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB*20, confirmed_only=True)
parent1 = wallet.create_self_transfer(fee_rate=Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN * 10, confirmed_only=True)
parent2 = wallet.create_self_transfer(fee_rate=Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN * 20, confirmed_only=True)
child = wallet.create_self_transfer_multi(
utxos_to_spend=[parent1["new_utxo"], parent2["new_utxo"]],
fee_per_output=999*parent1["tx"].get_vsize(),

View File

@ -28,8 +28,8 @@ from test_framework.p2p import (
)
from test_framework.test_framework import BitcoinTestFramework
MAX_FEE_FILTER = Decimal(9170997) / COIN
NORMAL_FEE_FILTER = Decimal(100) / COIN
MAX_FEE_FILTER = Decimal(9936506) / COIN
NORMAL_FEE_FILTER = Decimal(10) / COIN
class P2PIBDTxRelayTest(BitcoinTestFramework):
@ -37,8 +37,8 @@ class P2PIBDTxRelayTest(BitcoinTestFramework):
self.setup_clean_chain = True
self.num_nodes = 2
self.extra_args = [
["-minrelaytxfee={}".format(NORMAL_FEE_FILTER)],
["-minrelaytxfee={}".format(NORMAL_FEE_FILTER)],
["-minrelaytxfee={:.8f}".format(NORMAL_FEE_FILTER)],
["-minrelaytxfee={:.8f}".format(NORMAL_FEE_FILTER)],
]
def run_test(self):

View File

@ -9,10 +9,12 @@ Test opportunistic 1p1c package submission logic.
from decimal import Decimal
import time
from test_framework.mempool_util import (
DEFAULT_MIN_RELAY_TX_FEE,
fill_mempool,
)
from test_framework.messages import (
CInv,
COIN,
CTxInWitness,
MAX_BIP125_RBF_SEQUENCE,
MSG_WTX,
@ -65,13 +67,13 @@ class PackageRelayTest(BitcoinTestFramework):
self.supports_cli = False
def create_tx_below_mempoolminfee(self, wallet):
"""Create a 1-input 1sat/vB transaction using a confirmed UTXO. Decrement and use
"""Create a 1-input 0.1sat/vB transaction using a confirmed UTXO. Decrement and use
self.sequence so that subsequent calls to this function result in unique transactions."""
self.sequence -= 1
assert_greater_than(self.nodes[0].getmempoolinfo()["mempoolminfee"], FEERATE_1SAT_VB)
assert_greater_than(self.nodes[0].getmempoolinfo()["mempoolminfee"], Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN)
return wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB, sequence=self.sequence, confirmed_only=True)
return wallet.create_self_transfer(fee_rate=Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN, sequence=self.sequence, confirmed_only=True)
@cleanup
def test_basic_child_then_parent(self):

View File

@ -250,7 +250,7 @@ class TxDownloadTest(BitcoinTestFramework):
def test_rejects_filter_reset(self):
self.log.info('Check that rejected tx is not requested again')
node = self.nodes[0]
fill_mempool(self, node)
fill_mempool(self, node, tx_sync_fun=self.no_op)
self.wallet.rescan_utxos()
mempoolminfee = node.getmempoolinfo()['mempoolminfee']
peer = node.add_p2p_connection(TestP2PConn())

View File

@ -6,20 +6,18 @@
Utilities for working directly with the wallet's BDB database file
This is specific to the configuration of BDB used in this project:
- pagesize: 4096 bytes
- Outer database contains single subdatabase named 'main'
- btree
- btree leaf pages
- btree internal, leaf and overflow pages
Each key-value pair is two entries in a btree leaf. The first is the key, the one that follows
Each key-value pair is two entries in a btree leaf, which optionally refers to overflow pages
if the data doesn't fit into a single page. The first entry is the key, the one that follows
is the value. And so on. Note that the entry data is itself not in the correct order. Instead
entry offsets are stored in the correct order and those offsets are needed to then retrieve
the data itself.
the data itself. Note that this implementation currently only supports reading databases that
are in the same endianness as the host.
Page format can be found in BDB source code dbinc/db_page.h
This only implements the deserialization of btree metadata pages and normal btree pages. Overflow
pages are not implemented but may be needed in the future if dealing with wallets with large
transactions.
`db_dump -da wallet.dat` is useful to see the data in a wallet.dat BDB file
"""
@ -27,23 +25,36 @@ transactions.
import struct
# Important constants
PAGESIZE = 4096
PAGE_HEADER_SIZE = 26
OUTER_META_PAGE = 0
INNER_META_PAGE = 2
# Page type values
BTREE_INTERNAL = 3
BTREE_LEAF = 5
OVERFLOW_DATA = 7
BTREE_META = 9
# Record type values
RECORD_KEYDATA = 1
RECORD_OVERFLOW_DATA = 3
# Some magic numbers for sanity checking
BTREE_MAGIC = 0x053162
DB_VERSION = 9
SUBDATABASE_NAME = b'main'
# Deserializes a leaf page into a dict.
# Btree internal pages have the same header, for those, return None.
# For the btree leaf pages, deserialize them and put all the data into a dict
def dump_leaf_page(data):
# Deserializes an internal, leaf or overflow page into a dict.
# In addition to the common page header fields, the result contains an 'entries'
# array of dicts with the following fields, depending on the page type:
# internal page [BTREE_INTERNAL]:
# - 'page_num': referenced page number (used to find further pages to process)
# leaf page [BTREE_LEAF]:
# - 'record_type': record type, must be RECORD_KEYDATA or RECORD_OVERFLOW_DATA
# - 'data': binary data (key or value payload), if record type is RECORD_KEYDATA
# - 'page_num': referenced overflow page number, if record type is RECORD_OVERFLOW_DATA
# overflow page [OVERFLOW_DATA]:
# - 'data': binary data (part of key or value payload)
def dump_page(data):
page_info = {}
page_header = data[0:26]
_, pgno, prev_pgno, next_pgno, entries, hf_offset, level, pg_type = struct.unpack('QIIIHHBB', page_header)
@ -56,20 +67,35 @@ def dump_leaf_page(data):
page_info['entry_offsets'] = struct.unpack('{}H'.format(entries), data[26:26 + entries * 2])
page_info['entries'] = []
if pg_type == BTREE_INTERNAL:
# Skip internal pages. These are the internal nodes of the btree and don't contain anything relevant to us
return None
assert pg_type in (BTREE_INTERNAL, BTREE_LEAF, OVERFLOW_DATA)
assert pg_type == BTREE_LEAF, 'A non-btree leaf page has been encountered while dumping leaves'
if pg_type == OVERFLOW_DATA:
assert entries == 1
page_info['entries'].append({'data': data[26:26 + hf_offset]})
return page_info
for i in range(0, entries):
entry = {}
offset = page_info['entry_offsets'][i]
entry = {'offset': offset}
page_data_header = data[offset:offset + 3]
e_len, pg_type = struct.unpack('HB', page_data_header)
entry['len'] = e_len
entry['pg_type'] = pg_type
entry['data'] = data[offset + 3:offset + 3 + e_len]
record_header = data[offset:offset + 3]
offset += 3
e_len, record_type = struct.unpack('HB', record_header)
if pg_type == BTREE_INTERNAL:
assert record_type == RECORD_KEYDATA
internal_record_data = data[offset:offset + 9]
_, page_num, _ = struct.unpack('=BII', internal_record_data)
entry['page_num'] = page_num
elif pg_type == BTREE_LEAF:
assert record_type in (RECORD_KEYDATA, RECORD_OVERFLOW_DATA)
entry['record_type'] = record_type
if record_type == RECORD_KEYDATA:
entry['data'] = data[offset:offset + e_len]
elif record_type == RECORD_OVERFLOW_DATA:
overflow_record_data = data[offset:offset + 9]
_, page_num, _ = struct.unpack('=BII', overflow_record_data)
entry['page_num'] = page_num
page_info['entries'].append(entry)
return page_info
@ -115,16 +141,27 @@ def dump_meta_page(page):
return metadata
# Given the dict from dump_leaf_page, get the key-value pairs and put them into a dict
def extract_kv_pairs(page_data):
def extract_kv_pairs(page_data, pages):
out = {}
last_key = None
for i, entry in enumerate(page_data['entries']):
data = b''
if entry['record_type'] == RECORD_KEYDATA:
data = entry['data']
elif entry['record_type'] == RECORD_OVERFLOW_DATA:
next_page = entry['page_num']
while next_page != 0:
opage = pages[next_page]
opage_info = dump_page(opage)
data += opage_info['entries'][0]['data']
next_page = opage_info['next_pgno']
# By virtue of these all being pairs, even number entries are keys, and odd are values
if i % 2 == 0:
out[entry['data']] = b''
last_key = entry['data']
last_key = data
else:
out[last_key] = entry['data']
out[last_key] = data
return out
# Extract the key-value pairs of the BDB file given in filename
@ -132,20 +169,42 @@ def dump_bdb_kv(filename):
# Read in the BDB file and start deserializing it
pages = []
with open(filename, 'rb') as f:
data = f.read(PAGESIZE)
# Determine pagesize first
data = f.read(PAGE_HEADER_SIZE)
pagesize = struct.unpack('I', data[20:24])[0]
assert pagesize in (512, 1024, 2048, 4096, 8192, 16384, 32768, 65536)
# Read rest of first page
data += f.read(pagesize - PAGE_HEADER_SIZE)
assert len(data) == pagesize
# Read all remaining pages
while len(data) > 0:
pages.append(data)
data = f.read(PAGESIZE)
data = f.read(pagesize)
# Sanity check the meta pages
dump_meta_page(pages[OUTER_META_PAGE])
dump_meta_page(pages[INNER_META_PAGE])
# Sanity check the meta pages, read root page
outer_meta_info = dump_meta_page(pages[OUTER_META_PAGE])
root_page_info = dump_page(pages[outer_meta_info['root']])
assert root_page_info['pg_type'] == BTREE_LEAF
assert len(root_page_info['entries']) == 2
assert root_page_info['entries'][0]['data'] == SUBDATABASE_NAME
assert len(root_page_info['entries'][1]['data']) == 4
inner_meta_page = int.from_bytes(root_page_info['entries'][1]['data'], 'big')
inner_meta_info = dump_meta_page(pages[inner_meta_page])
# Fetch the kv pairs from the leaf pages
# Fetch the kv pairs from the pages
kv = {}
for i in range(3, len(pages)):
info = dump_leaf_page(pages[i])
if info is not None:
info_kv = extract_kv_pairs(info)
pages_to_process = [inner_meta_info['root']]
while len(pages_to_process) > 0:
curr_page_no = pages_to_process.pop()
assert curr_page_no <= outer_meta_info['last_pgno']
info = dump_page(pages[curr_page_no])
assert info['pg_type'] in (BTREE_INTERNAL, BTREE_LEAF)
if info['pg_type'] == BTREE_INTERNAL:
for entry in info['entries']:
pages_to_process.append(entry['page_num'])
elif info['pg_type'] == BTREE_LEAF:
info_kv = extract_kv_pairs(info, pages)
kv = {**kv, **info_kv}
return kv

View File

@ -3,7 +3,6 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Helpful routines for mempool testing."""
from decimal import Decimal
from .blocktools import (
COINBASE_MATURITY,
@ -18,24 +17,23 @@ from .wallet import (
MiniWallet,
)
# Default for -minrelaytxfee in sat/kvB
DEFAULT_MIN_RELAY_TX_FEE = 100
# Default for -incrementalrelayfee in sat/kvB
DEFAULT_INCREMENTAL_RELAY_FEE = 100
def fill_mempool(test_framework, node):
def fill_mempool(test_framework, node, *, tx_sync_fun=None):
"""Fill mempool until eviction.
Allows for simpler testing of scenarios with floating mempoolminfee > minrelay
Requires -datacarriersize=100000 and
-maxmempool=5.
It will not ensure mempools become synced as it
is based on a single node and assumes -minrelaytxfee
is 1 sat/vbyte.
Requires -datacarriersize=100000 and -maxmempool=5 and assumes -minrelaytxfee
is 100 sat/kvB.
To avoid unintentional tx dependencies, the mempool filling txs are created with a
tagged ephemeral miniwallet instance.
"""
test_framework.log.info("Fill the mempool until eviction is triggered and the mempoolminfee rises")
txouts = gen_return_txouts()
relayfee = node.getnetworkinfo()['relayfee']
assert_equal(relayfee, Decimal('0.00001000'))
minrelayfee = node.getnetworkinfo()['relayfee']
tx_batch_size = 1
num_of_batches = 75
@ -55,20 +53,27 @@ def fill_mempool(test_framework, node):
test_framework.log.debug("Create a mempool tx that will be evicted")
tx_to_be_evicted_id = ephemeral_miniwallet.send_self_transfer(
from_node=node, utxo_to_spend=confirmed_utxos.pop(0), fee_rate=relayfee)["txid"]
from_node=node, utxo_to_spend=confirmed_utxos.pop(0), fee_rate=minrelayfee)["txid"]
def send_batch(fee):
utxos = confirmed_utxos[:tx_batch_size]
create_lots_of_big_transactions(ephemeral_miniwallet, node, fee, tx_batch_size, txouts, utxos)
del confirmed_utxos[:tx_batch_size]
# Increase the tx fee rate to give the subsequent transactions a higher priority in the mempool
# The tx has an approx. vsize of 65k, i.e. multiplying the previous fee rate (in sats/kvB)
# by 130 should result in a fee that corresponds to 2x of that fee rate
base_fee = relayfee * 130
base_fee = minrelayfee * 130
batch_fees = [(i + 1) * base_fee for i in range(num_of_batches)]
test_framework.log.debug("Fill up the mempool with txs with higher fee rate")
with node.assert_debug_log(["rolling minimum fee bumped"]):
for batch_of_txid in range(num_of_batches):
fee = (batch_of_txid + 1) * base_fee
utxos = confirmed_utxos[:tx_batch_size]
create_lots_of_big_transactions(ephemeral_miniwallet, node, fee, tx_batch_size, txouts, utxos)
del confirmed_utxos[:tx_batch_size]
for fee in batch_fees[:-3]:
send_batch(fee)
tx_sync_fun() if tx_sync_fun else test_framework.sync_mempools() # sync before any eviction
assert_equal(node.getmempoolinfo()["mempoolminfee"], minrelayfee)
for fee in batch_fees[-3:]:
send_batch(fee)
tx_sync_fun() if tx_sync_fun else test_framework.sync_mempools() # sync after all evictions
test_framework.log.debug("The tx should be evicted by now")
# The number of transactions created should be greater than the ones present in the mempool
@ -77,5 +82,5 @@ def fill_mempool(test_framework, node):
assert tx_to_be_evicted_id not in node.getrawmempool()
test_framework.log.debug("Check that mempoolminfee is larger than minrelaytxfee")
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000'))
assert_greater_than(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))
assert_equal(node.getmempoolinfo()['minrelaytxfee'], minrelayfee)
assert_greater_than(node.getmempoolinfo()['mempoolminfee'], minrelayfee)

View File

@ -6,18 +6,23 @@
import os
import platform
import random
import stat
import string
import subprocess
import textwrap
from collections import OrderedDict
from test_framework.bdb import dump_bdb_kv
from test_framework.messages import ser_string
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_greater_than,
sha256sum_file,
)
from test_framework.wallet import getnewdestination
class ToolWalletTest(BitcoinTestFramework):
@ -545,6 +550,44 @@ class ToolWalletTest(BitcoinTestFramework):
self.stop_node(0)
self.assert_tool_output("The dumpfile may contain private keys. To ensure the safety of your Bitcoin, do not share the dumpfile.\n", "-wallet=unclean_lsn", f"-dumpfile={wallet_dump}", "dump")
def test_compare_legacy_dump_with_framework_bdb_parser(self):
self.log.info("Verify that legacy wallet database dump matches the one from the test framework's BDB parser")
wallet_name = "bdb_ro_test"
self.start_node(0)
# add some really large labels (above twice the largest valid page size) to create BDB overflow pages
self.nodes[0].createwallet(wallet_name)
wallet_rpc = self.nodes[0].get_wallet_rpc(wallet_name)
generated_labels = {}
for i in range(10):
address = getnewdestination()[2]
large_label = ''.join([random.choice(string.ascii_letters) for _ in range(150000)])
wallet_rpc.setlabel(address, large_label)
generated_labels[address] = large_label
# fill the keypool to create BDB internal pages
wallet_rpc.keypoolrefill(1000)
self.stop_node(0)
wallet_dumpfile = self.nodes[0].datadir_path / "bdb_ro_test.dump"
self.assert_tool_output("The dumpfile may contain private keys. To ensure the safety of your Bitcoin, do not share the dumpfile.\n", "-wallet={}".format(wallet_name), "-dumpfile={}".format(wallet_dumpfile), "dump")
expected_dump = self.read_dump(wallet_dumpfile)
# remove extra entries from wallet tool dump that are not actual key/value pairs from the database
del expected_dump['BITCOIN_CORE_WALLET_DUMP']
del expected_dump['format']
del expected_dump['checksum']
bdb_ro_parser_dump_raw = dump_bdb_kv(self.nodes[0].wallets_path / wallet_name / "wallet.dat")
bdb_ro_parser_dump = OrderedDict()
assert any([len(bytes.fromhex(value)) >= 150000 for value in expected_dump.values()])
for key, value in sorted(bdb_ro_parser_dump_raw.items()):
bdb_ro_parser_dump[key.hex()] = value.hex()
assert_equal(bdb_ro_parser_dump, expected_dump)
# check that all labels were created with the correct address
for address, label in generated_labels.items():
key_bytes = b'\x04name' + ser_string(address.encode())
assert key_bytes in bdb_ro_parser_dump_raw
assert_equal(bdb_ro_parser_dump_raw[key_bytes], ser_string(label.encode()))
def run_test(self):
self.wallet_path = self.nodes[0].wallets_path / self.default_wallet_name / self.wallet_data_filename
self.test_invalid_tool_commands_and_args()
@ -561,6 +604,9 @@ class ToolWalletTest(BitcoinTestFramework):
self.test_dump_createfromdump()
self.test_chainless_conflicts()
self.test_dump_very_large_records()
if not self.options.descriptors and self.is_bdb_compiled() and not self.options.swap_bdb_endian:
self.test_compare_legacy_dump_with_framework_bdb_parser()
if __name__ == '__main__':
ToolWalletTest(__file__).main()

View File

@ -534,7 +534,7 @@ def test_dust_to_fee(self, rbf_node, dest_address):
def test_settxfee(self, rbf_node, dest_address):
self.log.info('Test settxfee')
assert_raises_rpc_error(-8, "txfee cannot be less than min relay tx fee", rbf_node.settxfee, Decimal('0.000005'))
assert_raises_rpc_error(-8, "txfee cannot be less than min relay tx fee", rbf_node.settxfee, Decimal('0.0000005'))
assert_raises_rpc_error(-8, "txfee cannot be less than wallet min fee", rbf_node.settxfee, Decimal('0.000015'))
# check that bumpfee reacts correctly to the use of settxfee (paytxfee)
rbfid = spend_one_input(rbf_node, dest_address)
@ -846,7 +846,7 @@ def test_bumpfee_with_feerate_ignores_walletincrementalrelayfee(self, rbf_node,
# Ensure you can not fee bump if the fee_rate is more than original fee_rate but the total fee from new fee_rate is
# less than (original fee + incrementalrelayfee)
assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, tx["txid"], {"fee_rate": 2.8})
assert_raises_rpc_error(-8, "Insufficient total fee", rbf_node.bumpfee, tx["txid"], {"fee_rate": 2.05})
# You can fee bump as long as the new fee set from fee_rate is at least (original fee + incrementalrelayfee)
rbf_node.bumpfee(tx["txid"], {"fee_rate": 3})

View File

@ -44,6 +44,9 @@ class RawTransactionsTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 4
self.extra_args = [[
"-minrelaytxfee=0.00001000",
] for i in range(self.num_nodes)]
self.setup_clean_chain = True
# whitelist peers to speed up tx relay / mempool sync
self.noban_tx_relay = True