mirror of https://github.com/bitcoin/bitcoin.git
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: ACK9968b15937
stickies-v: re-ACK9968b15937
Tree-SHA512: 1d4ec354a743ec3a894a44d2d9b98dc5169f515081071b887768afee1c42cefdaa6c5f595829dc9b579699bba3e4b4aa73a519d4c4ef8579375ad34513f26cd9
This commit is contained in:
commit
ed730c5674
|
@ -1,8 +1,8 @@
|
||||||
AC_PREREQ([2.69])
|
AC_PREREQ([2.69])
|
||||||
define(_CLIENT_VERSION_MAJOR, 28)
|
define(_CLIENT_VERSION_MAJOR, 28)
|
||||||
define(_CLIENT_VERSION_MINOR, 2)
|
define(_CLIENT_VERSION_MINOR, 3)
|
||||||
define(_CLIENT_VERSION_BUILD, 0)
|
define(_CLIENT_VERSION_BUILD, 0)
|
||||||
define(_CLIENT_VERSION_RC, 0)
|
define(_CLIENT_VERSION_RC, 1)
|
||||||
define(_CLIENT_VERSION_IS_RELEASE, true)
|
define(_CLIENT_VERSION_IS_RELEASE, true)
|
||||||
define(_COPYRIGHT_YEAR, 2025)
|
define(_COPYRIGHT_YEAR, 2025)
|
||||||
define(_COPYRIGHT_HOLDERS,[The %s developers])
|
define(_COPYRIGHT_HOLDERS,[The %s developers])
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
.\" 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
|
.SH NAME
|
||||||
bitcoin-cli \- manual page for bitcoin-cli v28.2.0
|
bitcoin-cli \- manual page for bitcoin-cli v28.3.0rc1
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B bitcoin-cli
|
.B bitcoin-cli
|
||||||
[\fI\,options\/\fR] \fI\,<command> \/\fR[\fI\,params\/\fR] \fI\,Send command to Bitcoin Core\/\fR
|
[\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
|
.B bitcoin-cli
|
||||||
[\fI\,options\/\fR] \fI\,help <command> Get help for a command\/\fR
|
[\fI\,options\/\fR] \fI\,help <command> Get help for a command\/\fR
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Bitcoin Core RPC client version v28.2.0
|
Bitcoin Core RPC client version v28.3.0rc1
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.HP
|
.HP
|
||||||
\-?
|
\-?
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
.\" 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
|
.SH NAME
|
||||||
bitcoin-qt \- manual page for bitcoin-qt v28.2.0
|
bitcoin-qt \- manual page for bitcoin-qt v28.3.0rc1
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B bitcoin-qt
|
.B bitcoin-qt
|
||||||
[\fI\,command-line options\/\fR] [\fI\,URI\/\fR]
|
[\fI\,command-line options\/\fR] [\fI\,URI\/\fR]
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Bitcoin Core version v28.2.0
|
Bitcoin Core version v28.3.0rc1
|
||||||
.PP
|
.PP
|
||||||
Optional URI is a Bitcoin address in BIP21 URI format.
|
Optional URI is a Bitcoin address in BIP21 URI format.
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
|
@ -705,7 +705,7 @@ replaceability signaling (default: 1)
|
||||||
\fB\-minrelaytxfee=\fR<amt>
|
\fB\-minrelaytxfee=\fR<amt>
|
||||||
.IP
|
.IP
|
||||||
Fees (in BTC/kvB) smaller than this are considered zero fee for
|
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
|
.HP
|
||||||
\fB\-permitbaremultisig\fR
|
\fB\-permitbaremultisig\fR
|
||||||
.IP
|
.IP
|
||||||
|
@ -732,7 +732,7 @@ Set maximum BIP141 block weight (default: 3996000)
|
||||||
\fB\-blockmintxfee=\fR<amt>
|
\fB\-blockmintxfee=\fR<amt>
|
||||||
.IP
|
.IP
|
||||||
Set lowest fee rate (in BTC/kvB) for transactions to be included in
|
Set lowest fee rate (in BTC/kvB) for transactions to be included in
|
||||||
block creation. (default: 0.00001)
|
block creation. (default: 0.00000001)
|
||||||
.PP
|
.PP
|
||||||
RPC server options:
|
RPC server options:
|
||||||
.HP
|
.HP
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
.\" 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
|
.SH NAME
|
||||||
bitcoin-tx \- manual page for bitcoin-tx v28.2.0
|
bitcoin-tx \- manual page for bitcoin-tx v28.3.0rc1
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B bitcoin-tx
|
.B bitcoin-tx
|
||||||
[\fI\,options\/\fR] \fI\,<hex-tx> \/\fR[\fI\,commands\/\fR] \fI\,Update hex-encoded bitcoin transaction\/\fR
|
[\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
|
.B bitcoin-tx
|
||||||
[\fI\,options\/\fR] \fI\,-create \/\fR[\fI\,commands\/\fR] \fI\,Create hex-encoded bitcoin transaction\/\fR
|
[\fI\,options\/\fR] \fI\,-create \/\fR[\fI\,commands\/\fR] \fI\,Create hex-encoded bitcoin transaction\/\fR
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Bitcoin Core bitcoin\-tx utility version v28.2.0
|
Bitcoin Core bitcoin\-tx utility version v28.3.0rc1
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.HP
|
.HP
|
||||||
\-?
|
\-?
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
.\" 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
|
.SH NAME
|
||||||
bitcoin-util \- manual page for bitcoin-util v28.2.0
|
bitcoin-util \- manual page for bitcoin-util v28.3.0rc1
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B bitcoin-util
|
.B bitcoin-util
|
||||||
[\fI\,options\/\fR] [\fI\,commands\/\fR] \fI\,Do stuff\/\fR
|
[\fI\,options\/\fR] [\fI\,commands\/\fR] \fI\,Do stuff\/\fR
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Bitcoin Core bitcoin\-util utility version v28.2.0
|
Bitcoin Core bitcoin\-util utility version v28.3.0rc1
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.HP
|
.HP
|
||||||
\-?
|
\-?
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
.\" 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
|
.SH NAME
|
||||||
bitcoin-wallet \- manual page for bitcoin-wallet v28.2.0
|
bitcoin-wallet \- manual page for bitcoin-wallet v28.3.0rc1
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Bitcoin Core bitcoin\-wallet version v28.2.0
|
Bitcoin Core bitcoin\-wallet version v28.3.0rc1
|
||||||
.PP
|
.PP
|
||||||
bitcoin\-wallet is an offline tool for creating and interacting with Bitcoin Core wallet files.
|
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.
|
By default bitcoin\-wallet will act on wallets in the default mainnet wallet directory in the datadir.
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
|
.\" 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
|
.SH NAME
|
||||||
bitcoind \- manual page for bitcoind v28.2.0
|
bitcoind \- manual page for bitcoind v28.3.0rc1
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
.B bitcoind
|
.B bitcoind
|
||||||
[\fI\,options\/\fR] \fI\,Start Bitcoin Core\/\fR
|
[\fI\,options\/\fR] \fI\,Start Bitcoin Core\/\fR
|
||||||
.SH DESCRIPTION
|
.SH DESCRIPTION
|
||||||
Bitcoin Core version v28.2.0
|
Bitcoin Core version v28.3.0rc1
|
||||||
.SH OPTIONS
|
.SH OPTIONS
|
||||||
.HP
|
.HP
|
||||||
\-?
|
\-?
|
||||||
|
@ -703,7 +703,7 @@ replaceability signaling (default: 1)
|
||||||
\fB\-minrelaytxfee=\fR<amt>
|
\fB\-minrelaytxfee=\fR<amt>
|
||||||
.IP
|
.IP
|
||||||
Fees (in BTC/kvB) smaller than this are considered zero fee for
|
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
|
.HP
|
||||||
\fB\-permitbaremultisig\fR
|
\fB\-permitbaremultisig\fR
|
||||||
.IP
|
.IP
|
||||||
|
@ -730,7 +730,7 @@ Set maximum BIP141 block weight (default: 3996000)
|
||||||
\fB\-blockmintxfee=\fR<amt>
|
\fB\-blockmintxfee=\fR<amt>
|
||||||
.IP
|
.IP
|
||||||
Set lowest fee rate (in BTC/kvB) for transactions to be included in
|
Set lowest fee rate (in BTC/kvB) for transactions to be included in
|
||||||
block creation. (default: 0.00001)
|
block creation. (default: 0.00000001)
|
||||||
.PP
|
.PP
|
||||||
RPC server options:
|
RPC server options:
|
||||||
.HP
|
.HP
|
||||||
|
|
|
@ -37,6 +37,22 @@ unsupported systems.
|
||||||
Notable changes
|
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
|
### P2P
|
||||||
|
|
||||||
- #33395 net: do not apply whitelist permissions to onion inbounds
|
- #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
|
- #32765 test: Fix list index out of range error in feature_bip68_sequence.py
|
||||||
- #33001 test: Do not pass tests on unhandled exceptions
|
- #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
|
### Build
|
||||||
|
|
||||||
|
@ -70,6 +89,7 @@ Credits
|
||||||
Thanks to everyone who directly contributed to this release:
|
Thanks to everyone who directly contributed to this release:
|
||||||
- 0xB10C
|
- 0xB10C
|
||||||
- fanquake
|
- fanquake
|
||||||
|
- glozow
|
||||||
- Hennadii Stepanov
|
- Hennadii Stepanov
|
||||||
- MarcoFalke
|
- MarcoFalke
|
||||||
- Martin Zumsande
|
- Martin Zumsande
|
||||||
|
|
|
@ -591,7 +591,7 @@
|
||||||
#mempoolfullrbf=1
|
#mempoolfullrbf=1
|
||||||
|
|
||||||
# Fees (in BTC/kvB) smaller than this are considered zero fee for
|
# 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>
|
#minrelaytxfee=<amt>
|
||||||
|
|
||||||
# Relay transactions creating non-P2SH multisig outputs (default: 1)
|
# Relay transactions creating non-P2SH multisig outputs (default: 1)
|
||||||
|
@ -615,7 +615,7 @@
|
||||||
#blockmaxweight=<n>
|
#blockmaxweight=<n>
|
||||||
|
|
||||||
# Set lowest fee rate (in BTC/kvB) for transactions to be included in
|
# 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>
|
#blockmintxfee=<amt>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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 (argsman.IsArgSet("-minrelaytxfee")) {
|
||||||
if (std::optional<CAmount> min_relay_feerate = ParseMoney(argsman.GetArg("-minrelaytxfee", ""))) {
|
if (std::optional<CAmount> min_relay_feerate = ParseMoney(argsman.GetArg("-minrelaytxfee", ""))) {
|
||||||
// High fee check is done afterward in CWallet::Create()
|
// High fee check is done afterward in CWallet::Create()
|
||||||
|
|
|
@ -22,7 +22,7 @@ class CScript;
|
||||||
/** Default for -blockmaxweight, which controls the range of block weights the mining code will create **/
|
/** 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};
|
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 **/
|
/** 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 */
|
/** The maximum weight for transactions we're willing to relay/mine */
|
||||||
static constexpr int32_t MAX_STANDARD_TX_WEIGHT{400000};
|
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 */
|
/** 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 */
|
/** 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};
|
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 **/
|
/** 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 */
|
/** Default for -bytespersigop */
|
||||||
static constexpr unsigned int DEFAULT_BYTES_PER_SIGOP{20};
|
static constexpr unsigned int DEFAULT_BYTES_PER_SIGOP{20};
|
||||||
/** Default for -permitbaremultisig */
|
/** Default for -permitbaremultisig */
|
||||||
|
@ -54,7 +54,7 @@ static constexpr unsigned int MAX_STANDARD_SCRIPTSIG_SIZE{1650};
|
||||||
* outputs below the new threshold */
|
* outputs below the new threshold */
|
||||||
static constexpr unsigned int DUST_RELAY_TX_FEE{3000};
|
static constexpr unsigned int DUST_RELAY_TX_FEE{3000};
|
||||||
/** Default for -minrelaytxfee, minimum relay fee for transactions */
|
/** 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 */
|
/** Default for -limitancestorcount, max number of in-mempool ancestors */
|
||||||
static constexpr unsigned int DEFAULT_ANCESTOR_LIMIT{25};
|
static constexpr unsigned int DEFAULT_ANCESTOR_LIMIT{25};
|
||||||
/** Default for -limitancestorsize, maximum kilobytes of tx + all in-mempool ancestors */
|
/** Default for -limitancestorsize, maximum kilobytes of tx + all in-mempool ancestors */
|
||||||
|
|
|
@ -443,7 +443,7 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
|
||||||
tx1.vout.resize(1);
|
tx1.vout.resize(1);
|
||||||
tx1.vout[0].scriptPubKey = CScript() << OP_1 << OP_EQUAL;
|
tx1.vout[0].scriptPubKey = CScript() << OP_1 << OP_EQUAL;
|
||||||
tx1.vout[0].nValue = 10 * COIN;
|
tx1.vout[0].nValue = 10 * COIN;
|
||||||
pool.addUnchecked(entry.Fee(10000LL).FromTx(tx1));
|
pool.addUnchecked(entry.Fee(1000LL).FromTx(tx1));
|
||||||
|
|
||||||
CMutableTransaction tx2 = CMutableTransaction();
|
CMutableTransaction tx2 = CMutableTransaction();
|
||||||
tx2.vin.resize(1);
|
tx2.vin.resize(1);
|
||||||
|
@ -451,7 +451,7 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
|
||||||
tx2.vout.resize(1);
|
tx2.vout.resize(1);
|
||||||
tx2.vout[0].scriptPubKey = CScript() << OP_2 << OP_EQUAL;
|
tx2.vout[0].scriptPubKey = CScript() << OP_2 << OP_EQUAL;
|
||||||
tx2.vout[0].nValue = 10 * COIN;
|
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
|
pool.TrimToSize(pool.DynamicMemoryUsage()); // should do nothing
|
||||||
BOOST_CHECK(pool.exists(GenTxid::Txid(tx1.GetHash())));
|
BOOST_CHECK(pool.exists(GenTxid::Txid(tx1.GetHash())));
|
||||||
|
@ -469,7 +469,7 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
|
||||||
tx3.vout.resize(1);
|
tx3.vout.resize(1);
|
||||||
tx3.vout[0].scriptPubKey = CScript() << OP_3 << OP_EQUAL;
|
tx3.vout[0].scriptPubKey = CScript() << OP_3 << OP_EQUAL;
|
||||||
tx3.vout[0].nValue = 10 * COIN;
|
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)
|
pool.TrimToSize(pool.DynamicMemoryUsage() * 3 / 4); // tx3 should pay for tx2 (CPFP)
|
||||||
BOOST_CHECK(!pool.exists(GenTxid::Txid(tx1.GetHash())));
|
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(tx2.GetHash())));
|
||||||
BOOST_CHECK(!pool.exists(GenTxid::Txid(tx3.GetHash())));
|
BOOST_CHECK(!pool.exists(GenTxid::Txid(tx3.GetHash())));
|
||||||
|
|
||||||
CFeeRate maxFeeRateRemoved(25000, GetVirtualTransactionSize(CTransaction(tx3)) + GetVirtualTransactionSize(CTransaction(tx2)));
|
CFeeRate maxFeeRateRemoved(2500, GetVirtualTransactionSize(CTransaction(tx3)) + GetVirtualTransactionSize(CTransaction(tx2)));
|
||||||
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + 1000);
|
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), maxFeeRateRemoved.GetFeePerK() + DEFAULT_INCREMENTAL_RELAY_FEE);
|
||||||
|
|
||||||
CMutableTransaction tx4 = CMutableTransaction();
|
CMutableTransaction tx4 = CMutableTransaction();
|
||||||
tx4.vin.resize(2);
|
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].scriptPubKey = CScript() << OP_7 << OP_EQUAL;
|
||||||
tx7.vout[1].nValue = 10 * COIN;
|
tx7.vout[1].nValue = 10 * COIN;
|
||||||
|
|
||||||
pool.addUnchecked(entry.Fee(7000LL).FromTx(tx4));
|
pool.addUnchecked(entry.Fee(700LL).FromTx(tx4));
|
||||||
pool.addUnchecked(entry.Fee(1000LL).FromTx(tx5));
|
pool.addUnchecked(entry.Fee(100LL).FromTx(tx5));
|
||||||
pool.addUnchecked(entry.Fee(1100LL).FromTx(tx6));
|
pool.addUnchecked(entry.Fee(110LL).FromTx(tx6));
|
||||||
pool.addUnchecked(entry.Fee(9000LL).FromTx(tx7));
|
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
|
// 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);
|
pool.TrimToSize(pool.DynamicMemoryUsage() - 1);
|
||||||
|
@ -544,8 +544,8 @@ BOOST_AUTO_TEST_CASE(MempoolSizeLimitTest)
|
||||||
BOOST_CHECK(!pool.exists(GenTxid::Txid(tx7.GetHash())));
|
BOOST_CHECK(!pool.exists(GenTxid::Txid(tx7.GetHash())));
|
||||||
|
|
||||||
if (!pool.exists(GenTxid::Txid(tx5.GetHash())))
|
if (!pool.exists(GenTxid::Txid(tx5.GetHash())))
|
||||||
pool.addUnchecked(entry.Fee(1000LL).FromTx(tx5));
|
pool.addUnchecked(entry.Fee(100LL).FromTx(tx5));
|
||||||
pool.addUnchecked(entry.Fee(9000LL).FromTx(tx7));
|
pool.addUnchecked(entry.Fee(900LL).FromTx(tx7));
|
||||||
|
|
||||||
pool.TrimToSize(pool.DynamicMemoryUsage() / 2); // should maximize mempool size by only removing 5/7
|
pool.TrimToSize(pool.DynamicMemoryUsage() / 2); // should maximize mempool size by only removing 5/7
|
||||||
BOOST_CHECK(pool.exists(GenTxid::Txid(tx4.GetHash())));
|
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(tx6.GetHash())));
|
||||||
BOOST_CHECK(!pool.exists(GenTxid::Txid(tx7.GetHash())));
|
BOOST_CHECK(!pool.exists(GenTxid::Txid(tx7.GetHash())));
|
||||||
|
|
||||||
pool.addUnchecked(entry.Fee(1000LL).FromTx(tx5));
|
pool.addUnchecked(entry.Fee(100LL).FromTx(tx5));
|
||||||
pool.addUnchecked(entry.Fee(9000LL).FromTx(tx7));
|
pool.addUnchecked(entry.Fee(900LL).FromTx(tx7));
|
||||||
|
|
||||||
std::vector<CTransactionRef> vtx;
|
std::vector<CTransactionRef> vtx;
|
||||||
SetMockTime(42);
|
SetMockTime(42);
|
||||||
SetMockTime(42 + CTxMemPool::ROLLING_FEE_HALFLIFE);
|
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
|
// ... we should keep the same min fee until we get a block
|
||||||
pool.removeForBlock(vtx, 1);
|
pool.removeForBlock(vtx, 1);
|
||||||
SetMockTime(42 + 2*CTxMemPool::ROLLING_FEE_HALFLIFE);
|
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
|
// ... then feerate should drop 1/2 each halflife
|
||||||
|
|
||||||
SetMockTime(42 + 2*CTxMemPool::ROLLING_FEE_HALFLIFE + CTxMemPool::ROLLING_FEE_HALFLIFE/2);
|
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
|
// ... 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);
|
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
|
// ... 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);
|
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);
|
BOOST_CHECK_EQUAL(pool.GetMinFee(1).GetFeePerK(), DEFAULT_INCREMENTAL_RELAY_FEE);
|
||||||
// ... but feerate should never drop below 1000
|
// ... 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);
|
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);
|
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>())
|
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>())
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <node/miner.h>
|
#include <node/miner.h>
|
||||||
#include <policy/policy.h>
|
#include <policy/policy.h>
|
||||||
#include <test/util/random.h>
|
#include <test/util/random.h>
|
||||||
|
#include <test/util/transaction_utils.h>
|
||||||
#include <test/util/txmempool.h>
|
#include <test/util/txmempool.h>
|
||||||
#include <txmempool.h>
|
#include <txmempool.h>
|
||||||
#include <uint256.h>
|
#include <uint256.h>
|
||||||
|
@ -183,6 +184,9 @@ void MinerTestingSetup::TestPackageSelection(const CScript& scriptPubKey, const
|
||||||
tx.vout.resize(2);
|
tx.vout.resize(2);
|
||||||
tx.vout[0].nValue = 5000000000LL - 100000000;
|
tx.vout[0].nValue = 5000000000LL - 100000000;
|
||||||
tx.vout[1].nValue = 100000000; // 1BTC output
|
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();
|
Txid hashFreeTx2 = tx.GetHash();
|
||||||
tx_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx));
|
tx_mempool.addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx));
|
||||||
|
|
||||||
|
|
|
@ -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, 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());
|
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
|
// 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 + 1, 11, 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 + 1, 10, 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 + 2, 11, 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 + 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, incremental_relay_feerate, unused_txid).has_value());
|
||||||
BOOST_CHECK(PaysForRBF(low_fee, high_fee + 99999999, 99999999, incremental_relay_feerate, unused_txid) == std::nullopt);
|
BOOST_CHECK(PaysForRBF(low_fee, high_fee + 99999999, 99999999, incremental_relay_feerate, unused_txid) == std::nullopt);
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
#include <streams.h>
|
#include <streams.h>
|
||||||
#include <test/util/net.h>
|
#include <test/util/net.h>
|
||||||
#include <test/util/random.h>
|
#include <test/util/random.h>
|
||||||
|
#include <test/util/transaction_utils.h>
|
||||||
#include <test/util/txmempool.h>
|
#include <test/util/txmempool.h>
|
||||||
#include <txdb.h>
|
#include <txdb.h>
|
||||||
#include <txmempool.h>
|
#include <txmempool.h>
|
||||||
|
@ -582,6 +583,9 @@ void TestChain100Setup::MockMempoolMinFee(const CFeeRate& target_feerate)
|
||||||
CMutableTransaction mtx = CMutableTransaction();
|
CMutableTransaction mtx = CMutableTransaction();
|
||||||
mtx.vin.emplace_back(COutPoint{Txid::FromUint256(g_insecure_rand_ctx.rand256()), 0});
|
mtx.vin.emplace_back(COutPoint{Txid::FromUint256(g_insecure_rand_ctx.rand256()), 0});
|
||||||
mtx.vout.emplace_back(1 * COIN, GetScriptForDestination(WitnessV0ScriptHash(CScript() << OP_TRUE)));
|
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)};
|
const auto tx{MakeTransactionRef(mtx)};
|
||||||
LockPoints lp;
|
LockPoints lp;
|
||||||
// The new mempool min feerate is equal to the removed package's feerate + incremental feerate.
|
// The new mempool min feerate is equal to the removed package's feerate + incremental feerate.
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
#include <coins.h>
|
#include <coins.h>
|
||||||
|
#include <consensus/validation.h>
|
||||||
#include <script/signingprovider.h>
|
#include <script/signingprovider.h>
|
||||||
#include <test/util/transaction_utils.h>
|
#include <test/util/transaction_utils.h>
|
||||||
|
|
||||||
|
@ -69,3 +70,23 @@ std::vector<CMutableTransaction> SetupDummyInputs(FillableSigningProvider& keyst
|
||||||
|
|
||||||
return dummyTransactions;
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -26,4 +26,8 @@ CMutableTransaction BuildSpendingTransaction(const CScript& scriptSig, const CSc
|
||||||
// the second nValues[2] and nValues[3] outputs paid to a TxoutType::PUBKEYHASH.
|
// 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);
|
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
|
#endif // BITCOIN_TEST_UTIL_TRANSACTION_UTILS_H
|
||||||
|
|
|
@ -14,7 +14,10 @@ from test_framework.messages import (
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import (
|
from test_framework.util import (
|
||||||
assert_equal,
|
assert_equal,
|
||||||
|
assert_greater_than,
|
||||||
|
assert_greater_than_or_equal,
|
||||||
assert_raises_rpc_error,
|
assert_raises_rpc_error,
|
||||||
|
get_fee,
|
||||||
)
|
)
|
||||||
from test_framework.wallet import MiniWallet
|
from test_framework.wallet import MiniWallet
|
||||||
from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE
|
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.log.info("Running test full replace by fee...")
|
||||||
self.test_fullrbf()
|
self.test_fullrbf()
|
||||||
|
|
||||||
|
self.log.info("Running test incremental relay feerates...")
|
||||||
|
self.test_incremental_relay_feerates()
|
||||||
|
|
||||||
self.log.info("Passed")
|
self.log.info("Passed")
|
||||||
|
|
||||||
def make_utxo(self, node, amount, *, confirmed=True, scriptPubKey=None):
|
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
|
# 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.
|
# 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
|
tx.vout[0].nValue -= 1
|
||||||
assert_raises_rpc_error(-26, "insufficient fee", self.nodes[0].sendrawtransaction, tx.serialize().hex())
|
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):
|
def test_fullrbf(self):
|
||||||
|
|
||||||
confirmed_utxo = self.make_utxo(self.nodes[0], int(2 * COIN))
|
confirmed_utxo = self.make_utxo(self.nodes[0], int(2 * COIN))
|
||||||
|
|
|
@ -9,6 +9,10 @@ from decimal import Decimal
|
||||||
import math
|
import math
|
||||||
|
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
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 (
|
from test_framework.messages import (
|
||||||
MAX_BIP125_RBF_SEQUENCE,
|
MAX_BIP125_RBF_SEQUENCE,
|
||||||
COIN,
|
COIN,
|
||||||
|
@ -80,6 +84,11 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
|
||||||
assert_equal(node.getblockcount(), 200)
|
assert_equal(node.getblockcount(), 200)
|
||||||
assert_equal(node.getmempoolinfo()['size'], self.mempool_size)
|
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')
|
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(-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))
|
assert_raises_rpc_error(-8, 'Array must contain between 1 and 25 transactions.', lambda: node.testmempoolaccept(rawtxs=['ff22']*26))
|
||||||
|
|
|
@ -92,8 +92,7 @@ class MempoolLimitTest(BitcoinTestFramework):
|
||||||
self.restart_node(0, extra_args=self.extra_args[0])
|
self.restart_node(0, extra_args=self.extra_args[0])
|
||||||
|
|
||||||
# Restarting the node resets mempool minimum feerate
|
# Restarting the node resets mempool minimum feerate
|
||||||
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000'))
|
assert_equal(node.getmempoolinfo()['minrelaytxfee'], node.getmempoolinfo()["mempoolminfee"])
|
||||||
assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))
|
|
||||||
|
|
||||||
fill_mempool(self, node)
|
fill_mempool(self, node)
|
||||||
current_info = node.getmempoolinfo()
|
current_info = node.getmempoolinfo()
|
||||||
|
@ -122,7 +121,7 @@ class MempoolLimitTest(BitcoinTestFramework):
|
||||||
# coin is no longer available, but the cache could still contains the tx.
|
# coin is no longer available, but the cache could still contains the tx.
|
||||||
cpfp_parent = self.wallet.create_self_transfer(
|
cpfp_parent = self.wallet.create_self_transfer(
|
||||||
utxo_to_spend=mempool_evicted_tx["new_utxo"],
|
utxo_to_spend=mempool_evicted_tx["new_utxo"],
|
||||||
fee_rate=mempoolmin_feerate - Decimal('0.00001'),
|
fee_rate=mempoolmin_feerate / 2,
|
||||||
confirmed_only=True)
|
confirmed_only=True)
|
||||||
package_hex.append(cpfp_parent["hex"])
|
package_hex.append(cpfp_parent["hex"])
|
||||||
parent_utxos.append(cpfp_parent["new_utxo"])
|
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
|
# 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 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.
|
# - 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
|
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)
|
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])
|
self.restart_node(0, extra_args=self.extra_args[0])
|
||||||
|
|
||||||
# Restarting the node resets mempool minimum feerate
|
# Restarting the node resets mempool minimum feerate
|
||||||
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000'))
|
assert_equal(node.getmempoolinfo()['minrelaytxfee'], node.getmempoolinfo()["mempoolminfee"])
|
||||||
assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))
|
|
||||||
|
|
||||||
fill_mempool(self, node)
|
fill_mempool(self, node)
|
||||||
current_info = node.getmempoolinfo()
|
current_info = node.getmempoolinfo()
|
||||||
|
@ -208,7 +206,7 @@ class MempoolLimitTest(BitcoinTestFramework):
|
||||||
# coin is no longer available, but the cache could still contain the tx.
|
# coin is no longer available, but the cache could still contain the tx.
|
||||||
cpfp_parent = self.wallet.create_self_transfer(
|
cpfp_parent = self.wallet.create_self_transfer(
|
||||||
utxo_to_spend=replaced_tx["new_utxo"],
|
utxo_to_spend=replaced_tx["new_utxo"],
|
||||||
fee_rate=mempoolmin_feerate - Decimal('0.00001'),
|
fee_rate=mempoolmin_feerate - Decimal('0.000001'),
|
||||||
confirmed_only=True)
|
confirmed_only=True)
|
||||||
|
|
||||||
self.wallet.rescan_utxos()
|
self.wallet.rescan_utxos()
|
||||||
|
@ -256,8 +254,7 @@ class MempoolLimitTest(BitcoinTestFramework):
|
||||||
|
|
||||||
relayfee = node.getnetworkinfo()['relayfee']
|
relayfee = node.getnetworkinfo()['relayfee']
|
||||||
self.log.info('Check that mempoolminfee is minrelaytxfee')
|
self.log.info('Check that mempoolminfee is minrelaytxfee')
|
||||||
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000'))
|
assert_equal(node.getmempoolinfo()['minrelaytxfee'], node.getmempoolinfo()["mempoolminfee"])
|
||||||
assert_equal(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))
|
|
||||||
|
|
||||||
fill_mempool(self, node)
|
fill_mempool(self, node)
|
||||||
|
|
||||||
|
@ -314,9 +311,9 @@ class MempoolLimitTest(BitcoinTestFramework):
|
||||||
target_weight_each = 200000
|
target_weight_each = 200000
|
||||||
assert_greater_than(target_weight_each * 2, node.getmempoolinfo()["maxmempool"] - node.getmempoolinfo()["bytes"])
|
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
|
# 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
|
# 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.
|
# However, when eviction is triggered, these transactions should be at the bottom.
|
||||||
# This assertion assumes parent and child are the same size.
|
# This assertion assumes parent and child are the same size.
|
||||||
miniwallet.rescan_utxos()
|
miniwallet.rescan_utxos()
|
||||||
|
|
|
@ -170,13 +170,13 @@ class PackageRBFTest(BitcoinTestFramework):
|
||||||
self.log.info("Check replacement pays for incremental bandwidth")
|
self.log.info("Check replacement pays for incremental bandwidth")
|
||||||
_, placeholder_txns3 = self.create_simple_package(coin)
|
_, placeholder_txns3 = self.create_simple_package(coin)
|
||||||
package_3_size = sum([tx.get_vsize() for tx in placeholder_txns3])
|
package_3_size = sum([tx.get_vsize() for tx in placeholder_txns3])
|
||||||
incremental_sats_required = Decimal(package_3_size) / COIN
|
incremental_sats_required = (Decimal(package_3_size * 0.1) / COIN).quantize(Decimal("0.00000001"))
|
||||||
incremental_sats_short = incremental_sats_required - 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
|
# 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)
|
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]))
|
assert_equal(package_3_size, sum([tx.get_vsize() for tx in failure_package_txns3]))
|
||||||
pkg_results3 = node.submitpackage(failure_package_hex3)
|
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)
|
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)
|
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)
|
self.generate(node, 1)
|
||||||
|
|
||||||
def test_child_conflicts_parent_mempool_ancestor(self):
|
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
|
# Reset coins since we filled the mempool with current coins
|
||||||
self.coins = self.wallet.get_utxos(mark_as_spent=False, confirmed_only=True)
|
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"])
|
node.sendrawtransaction(grandparent_result["hex"])
|
||||||
|
minrelayfeerate = node.getnetworkinfo()["relayfee"]
|
||||||
|
|
||||||
# Now make package of two descendants that looks
|
# Now make package of two descendants that looks
|
||||||
# like a cpfp where the parent can't get in on its own
|
# like a cpfp where the parent can't get in on its own
|
||||||
self.ctr += 1
|
self.ctr += 1
|
||||||
parent_result = self.wallet.create_self_transfer(
|
parent_result = self.wallet.create_self_transfer(
|
||||||
fee_rate=Decimal('0.00001000'),
|
fee_rate=minrelayfeerate,
|
||||||
utxo_to_spend=grandparent_result["new_utxo"],
|
utxo_to_spend=grandparent_result["new_utxo"],
|
||||||
sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
|
sequence=MAX_BIP125_RBF_SEQUENCE - self.ctr,
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,6 +14,7 @@ from test_framework.util import (
|
||||||
assert_greater_than,
|
assert_greater_than,
|
||||||
assert_greater_than_or_equal,
|
assert_greater_than_or_equal,
|
||||||
assert_raises_rpc_error,
|
assert_raises_rpc_error,
|
||||||
|
get_fee,
|
||||||
)
|
)
|
||||||
from test_framework.wallet import (
|
from test_framework.wallet import (
|
||||||
COIN,
|
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"]])
|
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):
|
def run_test(self):
|
||||||
self.log.info("Generate blocks to create UTXOs")
|
self.log.info("Generate blocks to create UTXOs")
|
||||||
node = self.nodes[0]
|
node = self.nodes[0]
|
||||||
self.wallet = MiniWallet(node)
|
self.wallet = MiniWallet(node)
|
||||||
self.generate(self.wallet, 120)
|
self.generate(self.wallet, 200)
|
||||||
self.test_truc_max_vsize()
|
self.test_truc_max_vsize()
|
||||||
self.test_truc_acceptance()
|
self.test_truc_acceptance()
|
||||||
self.test_truc_replacement()
|
self.test_truc_replacement()
|
||||||
|
@ -641,6 +687,7 @@ class MempoolTRUC(BitcoinTestFramework):
|
||||||
self.test_reorg_2child_rbf()
|
self.test_reorg_2child_rbf()
|
||||||
self.test_truc_sibling_eviction()
|
self.test_truc_sibling_eviction()
|
||||||
self.test_reorg_sibling_eviction_1p2c()
|
self.test_reorg_sibling_eviction_1p2c()
|
||||||
|
self.test_minrelay_in_package_combos()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -28,6 +28,7 @@ from test_framework.p2p import P2PDataStore
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import (
|
from test_framework.util import (
|
||||||
assert_equal,
|
assert_equal,
|
||||||
|
assert_greater_than,
|
||||||
assert_greater_than_or_equal,
|
assert_greater_than_or_equal,
|
||||||
assert_raises_rpc_error,
|
assert_raises_rpc_error,
|
||||||
get_fee,
|
get_fee,
|
||||||
|
@ -40,7 +41,7 @@ MAX_FUTURE_BLOCK_TIME = 2 * 3600
|
||||||
MAX_TIMEWARP = 600
|
MAX_TIMEWARP = 600
|
||||||
VERSIONBITS_TOP_BITS = 0x20000000
|
VERSIONBITS_TOP_BITS = 0x20000000
|
||||||
VERSIONBITS_DEPLOYMENT_TESTDUMMY_BIT = 28
|
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):
|
def assert_template(node, block, expect, rehash=True):
|
||||||
|
@ -86,7 +87,7 @@ class MiningTest(BitcoinTestFramework):
|
||||||
node = self.nodes[0]
|
node = self.nodes[0]
|
||||||
|
|
||||||
# test default (no parameter), zero and a bunch of arbitrary blockmintxfee rates [sat/kvB]
|
# 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)
|
blockmintxfee_btc_kvb = blockmintxfee_sat_kvb / Decimal(COIN)
|
||||||
if blockmintxfee_sat_kvb == DEFAULT_BLOCK_MIN_TX_FEE:
|
if blockmintxfee_sat_kvb == DEFAULT_BLOCK_MIN_TX_FEE:
|
||||||
self.log.info(f"-> Default -blockmintxfee setting ({blockmintxfee_sat_kvb} sat/kvB)...")
|
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
|
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
|
# 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))
|
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
|
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))
|
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
|
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)
|
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
|
# 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 = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
|
||||||
block_template_txids = [tx['txid'] for tx in block_template['transactions']]
|
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)
|
self.generate(self.wallet, 1, sync_fun=self.no_op)
|
||||||
block = node.getblock(node.getbestblockhash(), verbosity=2)
|
block = node.getblock(node.getbestblockhash(), verbosity=2)
|
||||||
block_txids = [tx['txid'] for tx in block['tx']]
|
block_txids = [tx['txid'] for tx in block['tx']]
|
||||||
|
|
|
@ -13,9 +13,11 @@ from decimal import Decimal
|
||||||
from math import ceil
|
from math import ceil
|
||||||
|
|
||||||
from test_framework.mempool_util import (
|
from test_framework.mempool_util import (
|
||||||
|
DEFAULT_MIN_RELAY_TX_FEE,
|
||||||
fill_mempool,
|
fill_mempool,
|
||||||
)
|
)
|
||||||
from test_framework.messages import (
|
from test_framework.messages import (
|
||||||
|
COIN,
|
||||||
msg_tx,
|
msg_tx,
|
||||||
)
|
)
|
||||||
from test_framework.p2p import (
|
from test_framework.p2p import (
|
||||||
|
@ -31,9 +33,6 @@ from test_framework.wallet import (
|
||||||
MiniWalletMode,
|
MiniWalletMode,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 1sat/vB feerate denominated in BTC/KvB
|
|
||||||
FEERATE_1SAT_VB = Decimal("0.00001000")
|
|
||||||
|
|
||||||
class PackageRelayTest(BitcoinTestFramework):
|
class PackageRelayTest(BitcoinTestFramework):
|
||||||
def set_test_params(self):
|
def set_test_params(self):
|
||||||
self.setup_clean_chain = True
|
self.setup_clean_chain = True
|
||||||
|
@ -49,17 +48,14 @@ class PackageRelayTest(BitcoinTestFramework):
|
||||||
def raise_network_minfee(self):
|
def raise_network_minfee(self):
|
||||||
fill_mempool(self, self.nodes[0])
|
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")
|
self.log.debug("Check that all nodes' mempool minimum feerates are above min relay feerate")
|
||||||
for node in self.nodes:
|
for node in self.nodes:
|
||||||
assert_equal(node.getmempoolinfo()['minrelaytxfee'], FEERATE_1SAT_VB)
|
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN)
|
||||||
assert_greater_than(node.getmempoolinfo()['mempoolminfee'], FEERATE_1SAT_VB)
|
assert_greater_than(node.getmempoolinfo()['mempoolminfee'], Decimal(DEFAULT_MIN_RELAY_TX_FEE) / COIN)
|
||||||
|
|
||||||
def create_basic_1p1c(self, wallet):
|
def create_basic_1p1c(self, wallet):
|
||||||
low_fee_parent = wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB, confirmed_only=True)
|
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*FEERATE_1SAT_VB)
|
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"]]
|
package_hex_basic = [low_fee_parent["hex"], high_fee_child["hex"]]
|
||||||
return package_hex_basic, low_fee_parent["tx"], high_fee_child["tx"]
|
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"]
|
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):
|
def create_package_2p1c(self, wallet):
|
||||||
parent1 = wallet.create_self_transfer(fee_rate=FEERATE_1SAT_VB*10, 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=FEERATE_1SAT_VB*20, 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(
|
child = wallet.create_self_transfer_multi(
|
||||||
utxos_to_spend=[parent1["new_utxo"], parent2["new_utxo"]],
|
utxos_to_spend=[parent1["new_utxo"], parent2["new_utxo"]],
|
||||||
fee_per_output=999*parent1["tx"].get_vsize(),
|
fee_per_output=999*parent1["tx"].get_vsize(),
|
||||||
|
|
|
@ -28,8 +28,8 @@ from test_framework.p2p import (
|
||||||
)
|
)
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
|
|
||||||
MAX_FEE_FILTER = Decimal(9170997) / COIN
|
MAX_FEE_FILTER = Decimal(9936506) / COIN
|
||||||
NORMAL_FEE_FILTER = Decimal(100) / COIN
|
NORMAL_FEE_FILTER = Decimal(10) / COIN
|
||||||
|
|
||||||
|
|
||||||
class P2PIBDTxRelayTest(BitcoinTestFramework):
|
class P2PIBDTxRelayTest(BitcoinTestFramework):
|
||||||
|
@ -37,8 +37,8 @@ class P2PIBDTxRelayTest(BitcoinTestFramework):
|
||||||
self.setup_clean_chain = True
|
self.setup_clean_chain = True
|
||||||
self.num_nodes = 2
|
self.num_nodes = 2
|
||||||
self.extra_args = [
|
self.extra_args = [
|
||||||
["-minrelaytxfee={}".format(NORMAL_FEE_FILTER)],
|
["-minrelaytxfee={:.8f}".format(NORMAL_FEE_FILTER)],
|
||||||
["-minrelaytxfee={}".format(NORMAL_FEE_FILTER)],
|
["-minrelaytxfee={:.8f}".format(NORMAL_FEE_FILTER)],
|
||||||
]
|
]
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
|
|
|
@ -9,10 +9,12 @@ Test opportunistic 1p1c package submission logic.
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
import time
|
import time
|
||||||
from test_framework.mempool_util import (
|
from test_framework.mempool_util import (
|
||||||
|
DEFAULT_MIN_RELAY_TX_FEE,
|
||||||
fill_mempool,
|
fill_mempool,
|
||||||
)
|
)
|
||||||
from test_framework.messages import (
|
from test_framework.messages import (
|
||||||
CInv,
|
CInv,
|
||||||
|
COIN,
|
||||||
CTxInWitness,
|
CTxInWitness,
|
||||||
MAX_BIP125_RBF_SEQUENCE,
|
MAX_BIP125_RBF_SEQUENCE,
|
||||||
MSG_WTX,
|
MSG_WTX,
|
||||||
|
@ -65,13 +67,13 @@ class PackageRelayTest(BitcoinTestFramework):
|
||||||
self.supports_cli = False
|
self.supports_cli = False
|
||||||
|
|
||||||
def create_tx_below_mempoolminfee(self, wallet):
|
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 so that subsequent calls to this function result in unique transactions."""
|
||||||
|
|
||||||
self.sequence -= 1
|
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
|
@cleanup
|
||||||
def test_basic_child_then_parent(self):
|
def test_basic_child_then_parent(self):
|
||||||
|
|
|
@ -250,7 +250,7 @@ class TxDownloadTest(BitcoinTestFramework):
|
||||||
def test_rejects_filter_reset(self):
|
def test_rejects_filter_reset(self):
|
||||||
self.log.info('Check that rejected tx is not requested again')
|
self.log.info('Check that rejected tx is not requested again')
|
||||||
node = self.nodes[0]
|
node = self.nodes[0]
|
||||||
fill_mempool(self, node)
|
fill_mempool(self, node, tx_sync_fun=self.no_op)
|
||||||
self.wallet.rescan_utxos()
|
self.wallet.rescan_utxos()
|
||||||
mempoolminfee = node.getmempoolinfo()['mempoolminfee']
|
mempoolminfee = node.getmempoolinfo()['mempoolminfee']
|
||||||
peer = node.add_p2p_connection(TestP2PConn())
|
peer = node.add_p2p_connection(TestP2PConn())
|
||||||
|
|
|
@ -6,20 +6,18 @@
|
||||||
Utilities for working directly with the wallet's BDB database file
|
Utilities for working directly with the wallet's BDB database file
|
||||||
|
|
||||||
This is specific to the configuration of BDB used in this project:
|
This is specific to the configuration of BDB used in this project:
|
||||||
- pagesize: 4096 bytes
|
|
||||||
- Outer database contains single subdatabase named 'main'
|
- Outer database contains single subdatabase named 'main'
|
||||||
- btree
|
- 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
|
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
|
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
|
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
|
`db_dump -da wallet.dat` is useful to see the data in a wallet.dat BDB file
|
||||||
"""
|
"""
|
||||||
|
@ -27,23 +25,36 @@ transactions.
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
# Important constants
|
# Important constants
|
||||||
PAGESIZE = 4096
|
PAGE_HEADER_SIZE = 26
|
||||||
OUTER_META_PAGE = 0
|
OUTER_META_PAGE = 0
|
||||||
INNER_META_PAGE = 2
|
|
||||||
|
|
||||||
# Page type values
|
# Page type values
|
||||||
BTREE_INTERNAL = 3
|
BTREE_INTERNAL = 3
|
||||||
BTREE_LEAF = 5
|
BTREE_LEAF = 5
|
||||||
|
OVERFLOW_DATA = 7
|
||||||
BTREE_META = 9
|
BTREE_META = 9
|
||||||
|
|
||||||
|
# Record type values
|
||||||
|
RECORD_KEYDATA = 1
|
||||||
|
RECORD_OVERFLOW_DATA = 3
|
||||||
|
|
||||||
# Some magic numbers for sanity checking
|
# Some magic numbers for sanity checking
|
||||||
BTREE_MAGIC = 0x053162
|
BTREE_MAGIC = 0x053162
|
||||||
DB_VERSION = 9
|
DB_VERSION = 9
|
||||||
|
SUBDATABASE_NAME = b'main'
|
||||||
|
|
||||||
# Deserializes a leaf page into a dict.
|
# Deserializes an internal, leaf or overflow page into a dict.
|
||||||
# Btree internal pages have the same header, for those, return None.
|
# In addition to the common page header fields, the result contains an 'entries'
|
||||||
# For the btree leaf pages, deserialize them and put all the data into a dict
|
# array of dicts with the following fields, depending on the page type:
|
||||||
def dump_leaf_page(data):
|
# 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_info = {}
|
||||||
page_header = data[0:26]
|
page_header = data[0:26]
|
||||||
_, pgno, prev_pgno, next_pgno, entries, hf_offset, level, pg_type = struct.unpack('QIIIHHBB', page_header)
|
_, 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['entry_offsets'] = struct.unpack('{}H'.format(entries), data[26:26 + entries * 2])
|
||||||
page_info['entries'] = []
|
page_info['entries'] = []
|
||||||
|
|
||||||
if pg_type == BTREE_INTERNAL:
|
assert pg_type in (BTREE_INTERNAL, BTREE_LEAF, OVERFLOW_DATA)
|
||||||
# Skip internal pages. These are the internal nodes of the btree and don't contain anything relevant to us
|
|
||||||
return None
|
|
||||||
|
|
||||||
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):
|
for i in range(0, entries):
|
||||||
|
entry = {}
|
||||||
offset = page_info['entry_offsets'][i]
|
offset = page_info['entry_offsets'][i]
|
||||||
entry = {'offset': offset}
|
record_header = data[offset:offset + 3]
|
||||||
page_data_header = data[offset:offset + 3]
|
offset += 3
|
||||||
e_len, pg_type = struct.unpack('HB', page_data_header)
|
e_len, record_type = struct.unpack('HB', record_header)
|
||||||
entry['len'] = e_len
|
|
||||||
entry['pg_type'] = pg_type
|
if pg_type == BTREE_INTERNAL:
|
||||||
entry['data'] = data[offset + 3:offset + 3 + e_len]
|
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)
|
page_info['entries'].append(entry)
|
||||||
|
|
||||||
return page_info
|
return page_info
|
||||||
|
@ -115,16 +141,27 @@ def dump_meta_page(page):
|
||||||
return metadata
|
return metadata
|
||||||
|
|
||||||
# Given the dict from dump_leaf_page, get the key-value pairs and put them into a dict
|
# 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 = {}
|
out = {}
|
||||||
last_key = None
|
last_key = None
|
||||||
for i, entry in enumerate(page_data['entries']):
|
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
|
# By virtue of these all being pairs, even number entries are keys, and odd are values
|
||||||
if i % 2 == 0:
|
if i % 2 == 0:
|
||||||
out[entry['data']] = b''
|
out[entry['data']] = b''
|
||||||
last_key = entry['data']
|
last_key = data
|
||||||
else:
|
else:
|
||||||
out[last_key] = entry['data']
|
out[last_key] = data
|
||||||
return out
|
return out
|
||||||
|
|
||||||
# Extract the key-value pairs of the BDB file given in filename
|
# 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
|
# Read in the BDB file and start deserializing it
|
||||||
pages = []
|
pages = []
|
||||||
with open(filename, 'rb') as f:
|
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:
|
while len(data) > 0:
|
||||||
pages.append(data)
|
pages.append(data)
|
||||||
data = f.read(PAGESIZE)
|
data = f.read(pagesize)
|
||||||
|
|
||||||
# Sanity check the meta pages
|
# Sanity check the meta pages, read root page
|
||||||
dump_meta_page(pages[OUTER_META_PAGE])
|
outer_meta_info = dump_meta_page(pages[OUTER_META_PAGE])
|
||||||
dump_meta_page(pages[INNER_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 = {}
|
kv = {}
|
||||||
for i in range(3, len(pages)):
|
pages_to_process = [inner_meta_info['root']]
|
||||||
info = dump_leaf_page(pages[i])
|
while len(pages_to_process) > 0:
|
||||||
if info is not None:
|
curr_page_no = pages_to_process.pop()
|
||||||
info_kv = extract_kv_pairs(info)
|
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}
|
kv = {**kv, **info_kv}
|
||||||
return kv
|
return kv
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
# Distributed under the MIT software license, see the accompanying
|
# Distributed under the MIT software license, see the accompanying
|
||||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
"""Helpful routines for mempool testing."""
|
"""Helpful routines for mempool testing."""
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
from .blocktools import (
|
from .blocktools import (
|
||||||
COINBASE_MATURITY,
|
COINBASE_MATURITY,
|
||||||
|
@ -18,24 +17,23 @@ from .wallet import (
|
||||||
MiniWallet,
|
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.
|
"""Fill mempool until eviction.
|
||||||
|
|
||||||
Allows for simpler testing of scenarios with floating mempoolminfee > minrelay
|
Allows for simpler testing of scenarios with floating mempoolminfee > minrelay
|
||||||
Requires -datacarriersize=100000 and
|
Requires -datacarriersize=100000 and -maxmempool=5 and assumes -minrelaytxfee
|
||||||
-maxmempool=5.
|
is 100 sat/kvB.
|
||||||
It will not ensure mempools become synced as it
|
|
||||||
is based on a single node and assumes -minrelaytxfee
|
|
||||||
is 1 sat/vbyte.
|
|
||||||
To avoid unintentional tx dependencies, the mempool filling txs are created with a
|
To avoid unintentional tx dependencies, the mempool filling txs are created with a
|
||||||
tagged ephemeral miniwallet instance.
|
tagged ephemeral miniwallet instance.
|
||||||
"""
|
"""
|
||||||
test_framework.log.info("Fill the mempool until eviction is triggered and the mempoolminfee rises")
|
test_framework.log.info("Fill the mempool until eviction is triggered and the mempoolminfee rises")
|
||||||
txouts = gen_return_txouts()
|
txouts = gen_return_txouts()
|
||||||
relayfee = node.getnetworkinfo()['relayfee']
|
minrelayfee = node.getnetworkinfo()['relayfee']
|
||||||
|
|
||||||
assert_equal(relayfee, Decimal('0.00001000'))
|
|
||||||
|
|
||||||
tx_batch_size = 1
|
tx_batch_size = 1
|
||||||
num_of_batches = 75
|
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")
|
test_framework.log.debug("Create a mempool tx that will be evicted")
|
||||||
tx_to_be_evicted_id = ephemeral_miniwallet.send_self_transfer(
|
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
|
# 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)
|
# 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
|
# 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")
|
test_framework.log.debug("Fill up the mempool with txs with higher fee rate")
|
||||||
with node.assert_debug_log(["rolling minimum fee bumped"]):
|
for fee in batch_fees[:-3]:
|
||||||
for batch_of_txid in range(num_of_batches):
|
send_batch(fee)
|
||||||
fee = (batch_of_txid + 1) * base_fee
|
tx_sync_fun() if tx_sync_fun else test_framework.sync_mempools() # sync before any eviction
|
||||||
utxos = confirmed_utxos[:tx_batch_size]
|
assert_equal(node.getmempoolinfo()["mempoolminfee"], minrelayfee)
|
||||||
create_lots_of_big_transactions(ephemeral_miniwallet, node, fee, tx_batch_size, txouts, utxos)
|
for fee in batch_fees[-3:]:
|
||||||
del confirmed_utxos[:tx_batch_size]
|
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")
|
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
|
# 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()
|
assert tx_to_be_evicted_id not in node.getrawmempool()
|
||||||
|
|
||||||
test_framework.log.debug("Check that mempoolminfee is larger than minrelaytxfee")
|
test_framework.log.debug("Check that mempoolminfee is larger than minrelaytxfee")
|
||||||
assert_equal(node.getmempoolinfo()['minrelaytxfee'], Decimal('0.00001000'))
|
assert_equal(node.getmempoolinfo()['minrelaytxfee'], minrelayfee)
|
||||||
assert_greater_than(node.getmempoolinfo()['mempoolminfee'], Decimal('0.00001000'))
|
assert_greater_than(node.getmempoolinfo()['mempoolminfee'], minrelayfee)
|
||||||
|
|
|
@ -6,18 +6,23 @@
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
import random
|
||||||
import stat
|
import stat
|
||||||
|
import string
|
||||||
import subprocess
|
import subprocess
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
from collections import OrderedDict
|
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.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import (
|
from test_framework.util import (
|
||||||
assert_equal,
|
assert_equal,
|
||||||
assert_greater_than,
|
assert_greater_than,
|
||||||
sha256sum_file,
|
sha256sum_file,
|
||||||
)
|
)
|
||||||
|
from test_framework.wallet import getnewdestination
|
||||||
|
|
||||||
|
|
||||||
class ToolWalletTest(BitcoinTestFramework):
|
class ToolWalletTest(BitcoinTestFramework):
|
||||||
|
@ -545,6 +550,44 @@ class ToolWalletTest(BitcoinTestFramework):
|
||||||
self.stop_node(0)
|
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")
|
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):
|
def run_test(self):
|
||||||
self.wallet_path = self.nodes[0].wallets_path / self.default_wallet_name / self.wallet_data_filename
|
self.wallet_path = self.nodes[0].wallets_path / self.default_wallet_name / self.wallet_data_filename
|
||||||
self.test_invalid_tool_commands_and_args()
|
self.test_invalid_tool_commands_and_args()
|
||||||
|
@ -561,6 +604,9 @@ class ToolWalletTest(BitcoinTestFramework):
|
||||||
self.test_dump_createfromdump()
|
self.test_dump_createfromdump()
|
||||||
self.test_chainless_conflicts()
|
self.test_chainless_conflicts()
|
||||||
self.test_dump_very_large_records()
|
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__':
|
if __name__ == '__main__':
|
||||||
ToolWalletTest(__file__).main()
|
ToolWalletTest(__file__).main()
|
||||||
|
|
|
@ -534,7 +534,7 @@ def test_dust_to_fee(self, rbf_node, dest_address):
|
||||||
|
|
||||||
def test_settxfee(self, rbf_node, dest_address):
|
def test_settxfee(self, rbf_node, dest_address):
|
||||||
self.log.info('Test settxfee')
|
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'))
|
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)
|
# check that bumpfee reacts correctly to the use of settxfee (paytxfee)
|
||||||
rbfid = spend_one_input(rbf_node, dest_address)
|
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
|
# 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)
|
# 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)
|
# 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})
|
rbf_node.bumpfee(tx["txid"], {"fee_rate": 3})
|
||||||
|
|
|
@ -44,6 +44,9 @@ class RawTransactionsTest(BitcoinTestFramework):
|
||||||
|
|
||||||
def set_test_params(self):
|
def set_test_params(self):
|
||||||
self.num_nodes = 4
|
self.num_nodes = 4
|
||||||
|
self.extra_args = [[
|
||||||
|
"-minrelaytxfee=0.00001000",
|
||||||
|
] for i in range(self.num_nodes)]
|
||||||
self.setup_clean_chain = True
|
self.setup_clean_chain = True
|
||||||
# whitelist peers to speed up tx relay / mempool sync
|
# whitelist peers to speed up tx relay / mempool sync
|
||||||
self.noban_tx_relay = True
|
self.noban_tx_relay = True
|
||||||
|
|
Loading…
Reference in New Issue