0
0
Fork 0
mirror of https://github.com/bitcoin/bitcoin.git synced 2025-03-05 14:06:27 -05:00

tx fees, policy: CBlockPolicyEstimator update from CValidationInterface notifications

`CBlockPolicyEstimator` will implement `CValidationInterface` and
subscribe to its notification to process transactions added and removed
from the mempool.

Re-delegate calculation of `validForFeeEstimation` from validation to fee estimator.

Also clean up the validForFeeEstimation arg thats no longer needed in `CTxMempool`.

Co-authored-by: Matt Corallo <git@bluematt.me>
This commit is contained in:
ismaelsadeeq 2023-11-03 18:34:58 +01:00
parent dff5ad3b99
commit 714523918b
13 changed files with 158 additions and 74 deletions

View file

@ -954,7 +954,6 @@ libbitcoinkernel_la_SOURCES = \
node/chainstate.cpp \ node/chainstate.cpp \
node/utxo_snapshot.cpp \ node/utxo_snapshot.cpp \
policy/feerate.cpp \ policy/feerate.cpp \
policy/fees.cpp \
policy/packages.cpp \ policy/packages.cpp \
policy/policy.cpp \ policy/policy.cpp \
policy/rbf.cpp \ policy/rbf.cpp \

View file

@ -285,8 +285,12 @@ void Shutdown(NodeContext& node)
DumpMempool(*node.mempool, MempoolPath(*node.args)); DumpMempool(*node.mempool, MempoolPath(*node.args));
} }
// Drop transactions we were still watching, and record fee estimations. // Drop transactions we were still watching, record fee estimations and Unregister
if (node.fee_estimator) node.fee_estimator->Flush(); // fee estimator from validation interface.
if (node.fee_estimator) {
node.fee_estimator->Flush();
UnregisterValidationInterface(node.fee_estimator.get());
}
// FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing // FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing
if (node.chainman) { if (node.chainman) {
@ -1258,6 +1262,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
// Flush estimates to disk periodically // Flush estimates to disk periodically
CBlockPolicyEstimator* fee_estimator = node.fee_estimator.get(); CBlockPolicyEstimator* fee_estimator = node.fee_estimator.get();
node.scheduler->scheduleEvery([fee_estimator] { fee_estimator->FlushFeeEstimates(); }, FEE_FLUSH_INTERVAL); node.scheduler->scheduleEvery([fee_estimator] { fee_estimator->FlushFeeEstimates(); }, FEE_FLUSH_INTERVAL);
RegisterValidationInterface(fee_estimator);
} }
// Check port numbers // Check port numbers
@ -1471,7 +1476,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
assert(!node.chainman); assert(!node.chainman);
CTxMemPool::Options mempool_opts{ CTxMemPool::Options mempool_opts{
.estimator = node.fee_estimator.get(),
.check_ratio = chainparams.DefaultConsistencyChecks() ? 1 : 0, .check_ratio = chainparams.DefaultConsistencyChecks() ? 1 : 0,
}; };
auto result{ApplyArgsManOptions(args, chainparams, mempool_opts)}; auto result{ApplyArgsManOptions(args, chainparams, mempool_opts)};

View file

@ -13,8 +13,6 @@
#include <cstdint> #include <cstdint>
#include <optional> #include <optional>
class CBlockPolicyEstimator;
/** Default for -maxmempool, maximum megabytes of mempool memory usage */ /** Default for -maxmempool, maximum megabytes of mempool memory usage */
static constexpr unsigned int DEFAULT_MAX_MEMPOOL_SIZE_MB{300}; static constexpr unsigned int DEFAULT_MAX_MEMPOOL_SIZE_MB{300};
/** Default for -maxmempool when blocksonly is set */ /** Default for -maxmempool when blocksonly is set */
@ -37,8 +35,6 @@ namespace kernel {
* Most of the time, this struct should be referenced as CTxMemPool::Options. * Most of the time, this struct should be referenced as CTxMemPool::Options.
*/ */
struct MemPoolOptions { struct MemPoolOptions {
/* Used to estimate appropriate transaction fees. */
CBlockPolicyEstimator* estimator{nullptr};
/* The ratio used to determine how often sanity checks will run. */ /* The ratio used to determine how often sanity checks will run. */
int check_ratio{0}; int check_ratio{0};
int64_t max_size_bytes{DEFAULT_MAX_MEMPOOL_SIZE_MB * 1'000'000}; int64_t max_size_bytes{DEFAULT_MAX_MEMPOOL_SIZE_MB * 1'000'000};

View file

@ -515,15 +515,10 @@ void TxConfirmStats::removeTx(unsigned int entryHeight, unsigned int nBestSeenHe
} }
} }
// This function is called from CTxMemPool::removeUnchecked to ensure bool CBlockPolicyEstimator::removeTx(uint256 hash)
// txs removed from the mempool for any reason are no longer
// tracked. Txs that were part of a block have already been removed in
// processBlockTx to ensure they are never double tracked, but it is
// of no harm to try to remove them again.
bool CBlockPolicyEstimator::removeTx(uint256 hash, bool inBlock)
{ {
LOCK(m_cs_fee_estimator); LOCK(m_cs_fee_estimator);
return _removeTx(hash, inBlock); return _removeTx(hash, /*inBlock=*/false);
} }
bool CBlockPolicyEstimator::_removeTx(const uint256& hash, bool inBlock) bool CBlockPolicyEstimator::_removeTx(const uint256& hash, bool inBlock)
@ -579,11 +574,26 @@ CBlockPolicyEstimator::CBlockPolicyEstimator(const fs::path& estimation_filepath
CBlockPolicyEstimator::~CBlockPolicyEstimator() = default; CBlockPolicyEstimator::~CBlockPolicyEstimator() = default;
void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, bool validFeeEstimate) void CBlockPolicyEstimator::TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /*unused*/)
{
processTransaction(tx);
}
void CBlockPolicyEstimator::TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason /*unused*/, uint64_t /*unused*/)
{
removeTx(tx->GetHash());
}
void CBlockPolicyEstimator::MempoolTransactionsRemovedForBlock(const std::vector<RemovedMempoolTransactionInfo>& txs_removed_for_block, unsigned int nBlockHeight)
{
processBlock(txs_removed_for_block, nBlockHeight);
}
void CBlockPolicyEstimator::processTransaction(const NewMempoolTransactionInfo& tx)
{ {
LOCK(m_cs_fee_estimator); LOCK(m_cs_fee_estimator);
unsigned int txHeight = entry.GetHeight(); const unsigned int txHeight = tx.info.txHeight;
uint256 hash = entry.GetTx().GetHash(); const auto& hash = tx.info.m_tx->GetHash();
if (mapMemPoolTxs.count(hash)) { if (mapMemPoolTxs.count(hash)) {
LogPrint(BCLog::ESTIMATEFEE, "Blockpolicy error mempool tx %s already being tracked\n", LogPrint(BCLog::ESTIMATEFEE, "Blockpolicy error mempool tx %s already being tracked\n",
hash.ToString()); hash.ToString());
@ -597,17 +607,23 @@ void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, boo
// It will be synced next time a block is processed. // It will be synced next time a block is processed.
return; return;
} }
// This transaction should only count for fee estimation if:
// - it's not being re-added during a reorg which bypasses typical mempool fee limits
// - the node is not behind
// - the transaction is not dependent on any other transactions in the mempool
// - it's not part of a package.
const bool validForFeeEstimation = !tx.m_from_disconnected_block && !tx.m_submitted_in_package && tx.m_chainstate_is_current && tx.m_has_no_mempool_parents;
// Only want to be updating estimates when our blockchain is synced, // Only want to be updating estimates when our blockchain is synced,
// otherwise we'll miscalculate how many blocks its taking to get included. // otherwise we'll miscalculate how many blocks its taking to get included.
if (!validFeeEstimate) { if (!validForFeeEstimation) {
untrackedTxs++; untrackedTxs++;
return; return;
} }
trackedTxs++; trackedTxs++;
// Feerates are stored and reported as BTC-per-kb: // Feerates are stored and reported as BTC-per-kb:
CFeeRate feeRate(entry.GetFee(), entry.GetTxSize()); const CFeeRate feeRate(tx.info.m_fee, tx.info.m_virtual_transaction_size);
mapMemPoolTxs[hash].blockHeight = txHeight; mapMemPoolTxs[hash].blockHeight = txHeight;
unsigned int bucketIndex = feeStats->NewTx(txHeight, static_cast<double>(feeRate.GetFeePerK())); unsigned int bucketIndex = feeStats->NewTx(txHeight, static_cast<double>(feeRate.GetFeePerK()));

View file

@ -12,6 +12,7 @@
#include <threadsafety.h> #include <threadsafety.h>
#include <uint256.h> #include <uint256.h>
#include <util/fs.h> #include <util/fs.h>
#include <validationinterface.h>
#include <array> #include <array>
#include <chrono> #include <chrono>
@ -35,9 +36,9 @@ static constexpr std::chrono::hours MAX_FILE_AGE{60};
static constexpr bool DEFAULT_ACCEPT_STALE_FEE_ESTIMATES{false}; static constexpr bool DEFAULT_ACCEPT_STALE_FEE_ESTIMATES{false};
class AutoFile; class AutoFile;
class CTxMemPoolEntry;
class TxConfirmStats; class TxConfirmStats;
struct RemovedMempoolTransactionInfo; struct RemovedMempoolTransactionInfo;
struct NewMempoolTransactionInfo;
/* Identifier for each of the 3 different TxConfirmStats which will track /* Identifier for each of the 3 different TxConfirmStats which will track
* history over different time horizons. */ * history over different time horizons. */
@ -144,7 +145,7 @@ struct FeeCalculation
* a certain number of blocks. Every time a block is added to the best chain, this class records * a certain number of blocks. Every time a block is added to the best chain, this class records
* stats on the transactions included in that block * stats on the transactions included in that block
*/ */
class CBlockPolicyEstimator class CBlockPolicyEstimator : public CValidationInterface
{ {
private: private:
/** Track confirm delays up to 12 blocks for short horizon */ /** Track confirm delays up to 12 blocks for short horizon */
@ -199,7 +200,7 @@ private:
public: public:
/** Create new BlockPolicyEstimator and initialize stats tracking classes with default values */ /** Create new BlockPolicyEstimator and initialize stats tracking classes with default values */
CBlockPolicyEstimator(const fs::path& estimation_filepath, const bool read_stale_estimates); CBlockPolicyEstimator(const fs::path& estimation_filepath, const bool read_stale_estimates);
~CBlockPolicyEstimator(); virtual ~CBlockPolicyEstimator();
/** Process all the transactions that have been included in a block */ /** Process all the transactions that have been included in a block */
void processBlock(const std::vector<RemovedMempoolTransactionInfo>& txs_removed_for_block, void processBlock(const std::vector<RemovedMempoolTransactionInfo>& txs_removed_for_block,
@ -207,11 +208,11 @@ public:
EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator); EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator);
/** Process a transaction accepted to the mempool*/ /** Process a transaction accepted to the mempool*/
void processTransaction(const CTxMemPoolEntry& entry, bool validFeeEstimate) void processTransaction(const NewMempoolTransactionInfo& tx)
EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator); EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator);
/** Remove a transaction from the mempool tracking stats*/ /** Remove a transaction from the mempool tracking stats for non BLOCK removal reasons*/
bool removeTx(uint256 hash, bool inBlock) bool removeTx(uint256 hash)
EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator); EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator);
/** DEPRECATED. Return a feerate estimate */ /** DEPRECATED. Return a feerate estimate */
@ -261,6 +262,15 @@ public:
/** Calculates the age of the file, since last modified */ /** Calculates the age of the file, since last modified */
std::chrono::hours GetFeeEstimatorFileAge(); std::chrono::hours GetFeeEstimatorFileAge();
protected:
/** Overridden from CValidationInterface. */
void TransactionAddedToMempool(const NewMempoolTransactionInfo& tx, uint64_t /*unused*/) override
EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator);
void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason /*unused*/, uint64_t /*unused*/) override
EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator);
void MempoolTransactionsRemovedForBlock(const std::vector<RemovedMempoolTransactionInfo>& txs_removed_for_block, unsigned int nBlockHeight) override
EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator);
private: private:
mutable Mutex m_cs_fee_estimator; mutable Mutex m_cs_fee_estimator;

View file

@ -121,7 +121,6 @@ CTxMemPool MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeConte
mempool_opts.expiry = std::chrono::hours{fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999)}; mempool_opts.expiry = std::chrono::hours{fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(0, 999)};
nBytesPerSigOp = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(1, 999); nBytesPerSigOp = fuzzed_data_provider.ConsumeIntegralInRange<unsigned>(1, 999);
mempool_opts.estimator = nullptr;
mempool_opts.check_ratio = 1; mempool_opts.check_ratio = 1;
mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool(); mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool();

View file

@ -44,9 +44,16 @@ FUZZ_TARGET(policy_estimator, .init = initialize_policy_estimator)
return; return;
} }
const CTransaction tx{*mtx}; const CTransaction tx{*mtx};
block_policy_estimator.processTransaction(ConsumeTxMemPoolEntry(fuzzed_data_provider, tx), fuzzed_data_provider.ConsumeBool()); const CTxMemPoolEntry& entry = ConsumeTxMemPoolEntry(fuzzed_data_provider, tx);
const auto tx_info = NewMempoolTransactionInfo(entry.GetSharedTx(), entry.GetFee(),
entry.GetTxSize(), entry.GetHeight(),
/* m_from_disconnected_block */ false,
/* m_submitted_in_package */ false,
/* m_chainstate_is_current */ true,
/* m_has_no_mempool_parents */ fuzzed_data_provider.ConsumeBool());
block_policy_estimator.processTransaction(tx_info);
if (fuzzed_data_provider.ConsumeBool()) { if (fuzzed_data_provider.ConsumeBool()) {
(void)block_policy_estimator.removeTx(tx.GetHash(), /*inBlock=*/fuzzed_data_provider.ConsumeBool()); (void)block_policy_estimator.removeTx(tx.GetHash());
} }
}, },
[&] { [&] {
@ -69,7 +76,7 @@ FUZZ_TARGET(policy_estimator, .init = initialize_policy_estimator)
block_policy_estimator.processBlock(txs, fuzzed_data_provider.ConsumeIntegral<unsigned int>()); block_policy_estimator.processBlock(txs, fuzzed_data_provider.ConsumeIntegral<unsigned int>());
}, },
[&] { [&] {
(void)block_policy_estimator.removeTx(ConsumeUInt256(fuzzed_data_provider), /*inBlock=*/fuzzed_data_provider.ConsumeBool()); (void)block_policy_estimator.removeTx(ConsumeUInt256(fuzzed_data_provider));
}, },
[&] { [&] {
block_policy_estimator.FlushUnconfirmed(); block_policy_estimator.FlushUnconfirmed();

View file

@ -123,7 +123,6 @@ CTxMemPool MakeMempool(FuzzedDataProvider& fuzzed_data_provider, const NodeConte
CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)}; CTxMemPool::Options mempool_opts{MemPoolOptionsForTest(node)};
// ...override specific options for this specific fuzz suite // ...override specific options for this specific fuzz suite
mempool_opts.estimator = nullptr;
mempool_opts.check_ratio = 1; mempool_opts.check_ratio = 1;
mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool(); mempool_opts.require_standard = fuzzed_data_provider.ConsumeBool();

View file

@ -8,6 +8,7 @@
#include <txmempool.h> #include <txmempool.h>
#include <uint256.h> #include <uint256.h>
#include <util/time.h> #include <util/time.h>
#include <validationinterface.h>
#include <test/util/setup_common.h> #include <test/util/setup_common.h>
@ -19,7 +20,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
{ {
CBlockPolicyEstimator& feeEst = *Assert(m_node.fee_estimator); CBlockPolicyEstimator& feeEst = *Assert(m_node.fee_estimator);
CTxMemPool& mpool = *Assert(m_node.mempool); CTxMemPool& mpool = *Assert(m_node.mempool);
LOCK2(cs_main, mpool.cs); RegisterValidationInterface(&feeEst);
TestMemPoolEntryHelper entry; TestMemPoolEntryHelper entry;
CAmount basefee(2000); CAmount basefee(2000);
CAmount deltaFee(100); CAmount deltaFee(100);
@ -59,8 +60,23 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
for (int j = 0; j < 10; j++) { // For each fee for (int j = 0; j < 10; j++) { // For each fee
for (int k = 0; k < 4; k++) { // add 4 fee txs for (int k = 0; k < 4; k++) { // add 4 fee txs
tx.vin[0].prevout.n = 10000*blocknum+100*j+k; // make transaction unique tx.vin[0].prevout.n = 10000*blocknum+100*j+k; // make transaction unique
{
LOCK2(cs_main, mpool.cs);
mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
// Since TransactionAddedToMempool callbacks are generated in ATMP,
// not addUnchecked, we cheat and create one manually here
const int64_t virtual_size = GetVirtualTransactionSize(*MakeTransactionRef(tx));
const NewMempoolTransactionInfo tx_info{NewMempoolTransactionInfo(MakeTransactionRef(tx),
feeV[j],
virtual_size,
entry.nHeight,
/* m_from_disconnected_block */ false,
/* m_submitted_in_package */ false,
/* m_chainstate_is_current */ true,
/* m_has_no_mempool_parents */ true)};
GetMainSignals().TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
}
uint256 hash = tx.GetHash(); uint256 hash = tx.GetHash();
mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
txHashes[j].push_back(hash); txHashes[j].push_back(hash);
} }
} }
@ -76,10 +92,17 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
txHashes[9-h].pop_back(); txHashes[9-h].pop_back();
} }
} }
mpool.removeForBlock(block, ++blocknum);
{
LOCK(mpool.cs);
mpool.removeForBlock(block, ++blocknum);
}
block.clear(); block.clear();
// Check after just a few txs that combining buckets works as expected // Check after just a few txs that combining buckets works as expected
if (blocknum == 3) { if (blocknum == 3) {
// Wait for fee estimator to catch up
SyncWithValidationInterfaceQueue();
// At this point we should need to combine 3 buckets to get enough data points // At this point we should need to combine 3 buckets to get enough data points
// So estimateFee(1) should fail and estimateFee(2) should return somewhere around // So estimateFee(1) should fail and estimateFee(2) should return somewhere around
// 9*baserate. estimateFee(2) %'s are 100,100,90 = average 97% // 9*baserate. estimateFee(2) %'s are 100,100,90 = average 97%
@ -114,8 +137,13 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
// Mine 50 more blocks with no transactions happening, estimates shouldn't change // Mine 50 more blocks with no transactions happening, estimates shouldn't change
// We haven't decayed the moving average enough so we still have enough data points in every bucket // We haven't decayed the moving average enough so we still have enough data points in every bucket
while (blocknum < 250) while (blocknum < 250) {
LOCK(mpool.cs);
mpool.removeForBlock(block, ++blocknum); mpool.removeForBlock(block, ++blocknum);
}
// Wait for fee estimator to catch up
SyncWithValidationInterfaceQueue();
BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0)); BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
for (int i = 2; i < 10;i++) { for (int i = 2; i < 10;i++) {
@ -130,14 +158,35 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
for (int j = 0; j < 10; j++) { // For each fee multiple for (int j = 0; j < 10; j++) { // For each fee multiple
for (int k = 0; k < 4; k++) { // add 4 fee txs for (int k = 0; k < 4; k++) { // add 4 fee txs
tx.vin[0].prevout.n = 10000*blocknum+100*j+k; tx.vin[0].prevout.n = 10000*blocknum+100*j+k;
{
LOCK2(cs_main, mpool.cs);
mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
// Since TransactionAddedToMempool callbacks are generated in ATMP,
// not addUnchecked, we cheat and create one manually here
const int64_t virtual_size = GetVirtualTransactionSize(*MakeTransactionRef(tx));
const NewMempoolTransactionInfo tx_info{NewMempoolTransactionInfo(MakeTransactionRef(tx),
feeV[j],
virtual_size,
entry.nHeight,
/* m_from_disconnected_block */ false,
/* m_submitted_in_package */ false,
/* m_chainstate_is_current */ true,
/* m_has_no_mempool_parents */ true)};
GetMainSignals().TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
}
uint256 hash = tx.GetHash(); uint256 hash = tx.GetHash();
mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
txHashes[j].push_back(hash); txHashes[j].push_back(hash);
} }
} }
mpool.removeForBlock(block, ++blocknum); {
LOCK(mpool.cs);
mpool.removeForBlock(block, ++blocknum);
}
} }
// Wait for fee estimator to catch up
SyncWithValidationInterfaceQueue();
for (int i = 1; i < 10;i++) { for (int i = 1; i < 10;i++) {
BOOST_CHECK(feeEst.estimateFee(i) == CFeeRate(0) || feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee); BOOST_CHECK(feeEst.estimateFee(i) == CFeeRate(0) || feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
} }
@ -152,8 +201,16 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
txHashes[j].pop_back(); txHashes[j].pop_back();
} }
} }
mpool.removeForBlock(block, 266);
{
LOCK(mpool.cs);
mpool.removeForBlock(block, 266);
}
block.clear(); block.clear();
// Wait for fee estimator to catch up
SyncWithValidationInterfaceQueue();
BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0)); BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
for (int i = 2; i < 10;i++) { for (int i = 2; i < 10;i++) {
BOOST_CHECK(feeEst.estimateFee(i) == CFeeRate(0) || feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee); BOOST_CHECK(feeEst.estimateFee(i) == CFeeRate(0) || feeEst.estimateFee(i).GetFeePerK() > origFeeEst[i-1] - deltaFee);
@ -165,17 +222,39 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
for (int j = 0; j < 10; j++) { // For each fee multiple for (int j = 0; j < 10; j++) { // For each fee multiple
for (int k = 0; k < 4; k++) { // add 4 fee txs for (int k = 0; k < 4; k++) { // add 4 fee txs
tx.vin[0].prevout.n = 10000*blocknum+100*j+k; tx.vin[0].prevout.n = 10000*blocknum+100*j+k;
{
LOCK2(cs_main, mpool.cs);
mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
// Since TransactionAddedToMempool callbacks are generated in ATMP,
// not addUnchecked, we cheat and create one manually here
const int64_t virtual_size = GetVirtualTransactionSize(*MakeTransactionRef(tx));
const NewMempoolTransactionInfo tx_info{NewMempoolTransactionInfo(MakeTransactionRef(tx),
feeV[j],
virtual_size,
entry.nHeight,
/* m_from_disconnected_block */ false,
/* m_submitted_in_package */ false,
/* m_chainstate_is_current */ true,
/* m_has_no_mempool_parents */ true)};
GetMainSignals().TransactionAddedToMempool(tx_info, mpool.GetAndIncrementSequence());
}
uint256 hash = tx.GetHash(); uint256 hash = tx.GetHash();
mpool.addUnchecked(entry.Fee(feeV[j]).Time(Now<NodeSeconds>()).Height(blocknum).FromTx(tx));
CTransactionRef ptx = mpool.get(hash); CTransactionRef ptx = mpool.get(hash);
if (ptx) if (ptx)
block.push_back(ptx); block.push_back(ptx);
} }
} }
mpool.removeForBlock(block, ++blocknum);
{
LOCK(mpool.cs);
mpool.removeForBlock(block, ++blocknum);
}
block.clear(); block.clear();
} }
// Wait for fee estimator to catch up
SyncWithValidationInterfaceQueue();
BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0)); BOOST_CHECK(feeEst.estimateFee(1) == CFeeRate(0));
for (int i = 2; i < 9; i++) { // At 9, the original estimate was already at the bottom (b/c scale = 2) for (int i = 2; i < 9; i++) { // At 9, the original estimate was already at the bottom (b/c scale = 2)
BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() < origFeeEst[i-1] - deltaFee); BOOST_CHECK(feeEst.estimateFee(i).GetFeePerK() < origFeeEst[i-1] - deltaFee);

View file

@ -18,7 +18,6 @@ using node::NodeContext;
CTxMemPool::Options MemPoolOptionsForTest(const NodeContext& node) CTxMemPool::Options MemPoolOptionsForTest(const NodeContext& node)
{ {
CTxMemPool::Options mempool_opts{ CTxMemPool::Options mempool_opts{
.estimator = node.fee_estimator.get(),
// Default to always checking mempool regardless of // Default to always checking mempool regardless of
// chainparams.DefaultConsistencyChecks for tests // chainparams.DefaultConsistencyChecks for tests
.check_ratio = 1, .check_ratio = 1,

View file

@ -12,9 +12,9 @@
#include <consensus/tx_verify.h> #include <consensus/tx_verify.h>
#include <consensus/validation.h> #include <consensus/validation.h>
#include <logging.h> #include <logging.h>
#include <policy/fees.h>
#include <policy/policy.h> #include <policy/policy.h>
#include <policy/settings.h> #include <policy/settings.h>
#include <random.h>
#include <reverse_iterator.h> #include <reverse_iterator.h>
#include <util/check.h> #include <util/check.h>
#include <util/moneystr.h> #include <util/moneystr.h>
@ -402,7 +402,6 @@ void CTxMemPoolEntry::UpdateAncestorState(int32_t modifySize, CAmount modifyFee,
CTxMemPool::CTxMemPool(const Options& opts) CTxMemPool::CTxMemPool(const Options& opts)
: m_check_ratio{opts.check_ratio}, : m_check_ratio{opts.check_ratio},
minerPolicyEstimator{opts.estimator},
m_max_size_bytes{opts.max_size_bytes}, m_max_size_bytes{opts.max_size_bytes},
m_expiry{opts.expiry}, m_expiry{opts.expiry},
m_incremental_relay_feerate{opts.incremental_relay_feerate}, m_incremental_relay_feerate{opts.incremental_relay_feerate},
@ -433,7 +432,7 @@ void CTxMemPool::AddTransactionsUpdated(unsigned int n)
nTransactionsUpdated += n; nTransactionsUpdated += n;
} }
void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAncestors, bool validFeeEstimate) void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAncestors)
{ {
// Add to memory pool without checking anything. // Add to memory pool without checking anything.
// Used by AcceptToMemoryPool(), which DOES do // Used by AcceptToMemoryPool(), which DOES do
@ -477,9 +476,6 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAnces
nTransactionsUpdated++; nTransactionsUpdated++;
totalTxSize += entry.GetTxSize(); totalTxSize += entry.GetTxSize();
m_total_fee += entry.GetFee(); m_total_fee += entry.GetFee();
if (minerPolicyEstimator) {
minerPolicyEstimator->processTransaction(entry, validFeeEstimate);
}
txns_randomized.emplace_back(newit->GetSharedTx()); txns_randomized.emplace_back(newit->GetSharedTx());
newit->idx_randomized = txns_randomized.size() - 1; newit->idx_randomized = txns_randomized.size() - 1;
@ -497,16 +493,12 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason)
// even if not directly reported below. // even if not directly reported below.
uint64_t mempool_sequence = GetAndIncrementSequence(); uint64_t mempool_sequence = GetAndIncrementSequence();
const auto& hash = it->GetTx().GetHash();
if (reason != MemPoolRemovalReason::BLOCK) { if (reason != MemPoolRemovalReason::BLOCK) {
// Notify clients that a transaction has been removed from the mempool // Notify clients that a transaction has been removed from the mempool
// for any reason except being included in a block. Clients interested // for any reason except being included in a block. Clients interested
// in transactions included in blocks can subscribe to the BlockConnected // in transactions included in blocks can subscribe to the BlockConnected
// notification. // notification.
GetMainSignals().TransactionRemovedFromMempool(it->GetSharedTx(), reason, mempool_sequence); GetMainSignals().TransactionRemovedFromMempool(it->GetSharedTx(), reason, mempool_sequence);
if (minerPolicyEstimator) {
minerPolicyEstimator->removeTx(hash, false);
}
} }
TRACE5(mempool, removed, TRACE5(mempool, removed,
it->GetTx().GetHash().data(), it->GetTx().GetHash().data(),
@ -519,7 +511,7 @@ void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason)
for (const CTxIn& txin : it->GetTx().vin) for (const CTxIn& txin : it->GetTx().vin)
mapNextTx.erase(txin.prevout); mapNextTx.erase(txin.prevout);
RemoveUnbroadcastTx(hash, true /* add logging because unchecked */ ); RemoveUnbroadcastTx(it->GetTx().GetHash(), true /* add logging because unchecked */);
if (txns_randomized.size() > 1) { if (txns_randomized.size() > 1) {
// Update idx_randomized of the to-be-moved entry. // Update idx_randomized of the to-be-moved entry.
@ -638,7 +630,7 @@ void CTxMemPool::removeConflicts(const CTransaction &tx)
} }
/** /**
* Called when a block is connected. Removes from mempool and updates the miner fee estimator. * Called when a block is connected. Removes from mempool.
*/ */
void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight) void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigned int nBlockHeight)
{ {
@ -657,10 +649,6 @@ void CTxMemPool::removeForBlock(const std::vector<CTransactionRef>& vtx, unsigne
removeConflicts(*tx); removeConflicts(*tx);
ClearPrioritisation(tx->GetHash()); ClearPrioritisation(tx->GetHash());
} }
// Update policy estimates
if (minerPolicyEstimator) {
minerPolicyEstimator->processBlock(txs_removed_for_block, nBlockHeight);
}
GetMainSignals().MempoolTransactionsRemovedForBlock(txs_removed_for_block, nBlockHeight); GetMainSignals().MempoolTransactionsRemovedForBlock(txs_removed_for_block, nBlockHeight);
lastRollingFeeUpdate = GetTime(); lastRollingFeeUpdate = GetTime();
blockSinceLastRollingFeeBump = true; blockSinceLastRollingFeeBump = true;
@ -1091,10 +1079,10 @@ int CTxMemPool::Expire(std::chrono::seconds time)
return stage.size(); return stage.size();
} }
void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, bool validFeeEstimate) void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry)
{ {
auto ancestors{AssumeCalculateMemPoolAncestors(__func__, entry, Limits::NoLimits())}; auto ancestors{AssumeCalculateMemPoolAncestors(__func__, entry, Limits::NoLimits())};
return addUnchecked(entry, ancestors, validFeeEstimate); return addUnchecked(entry, ancestors);
} }
void CTxMemPool::UpdateChild(txiter entry, txiter child, bool add) void CTxMemPool::UpdateChild(txiter entry, txiter child, bool add)

View file

@ -202,8 +202,6 @@ struct entry_time {};
struct ancestor_score {}; struct ancestor_score {};
struct index_by_wtxid {}; struct index_by_wtxid {};
class CBlockPolicyEstimator;
/** /**
* Information about a mempool transaction. * Information about a mempool transaction.
*/ */
@ -303,7 +301,6 @@ class CTxMemPool
protected: protected:
const int m_check_ratio; //!< Value n means that 1 times in n we check. const int m_check_ratio; //!< Value n means that 1 times in n we check.
std::atomic<unsigned int> nTransactionsUpdated{0}; //!< Used by getblocktemplate to trigger CreateNewBlock() invocation std::atomic<unsigned int> nTransactionsUpdated{0}; //!< Used by getblocktemplate to trigger CreateNewBlock() invocation
CBlockPolicyEstimator* const minerPolicyEstimator;
uint64_t totalTxSize GUARDED_BY(cs){0}; //!< sum of all mempool tx's virtual sizes. Differs from serialized tx size since witness data is discounted. Defined in BIP 141. uint64_t totalTxSize GUARDED_BY(cs){0}; //!< sum of all mempool tx's virtual sizes. Differs from serialized tx size since witness data is discounted. Defined in BIP 141.
CAmount m_total_fee GUARDED_BY(cs){0}; //!< sum of all mempool tx's fees (NOT modified fee) CAmount m_total_fee GUARDED_BY(cs){0}; //!< sum of all mempool tx's fees (NOT modified fee)
@ -472,8 +469,8 @@ public:
// Note that addUnchecked is ONLY called from ATMP outside of tests // Note that addUnchecked is ONLY called from ATMP outside of tests
// and any other callers may break wallet's in-mempool tracking (due to // and any other callers may break wallet's in-mempool tracking (due to
// lack of CValidationInterface::TransactionAddedToMempool callbacks). // lack of CValidationInterface::TransactionAddedToMempool callbacks).
void addUnchecked(const CTxMemPoolEntry& entry, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); void addUnchecked(const CTxMemPoolEntry& entry) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
void addUnchecked(const CTxMemPoolEntry& entry, setEntries& setAncestors, bool validFeeEstimate = true) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main); void addUnchecked(const CTxMemPoolEntry& entry, setEntries& setAncestors) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main);
void removeRecursive(const CTransaction& tx, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs); void removeRecursive(const CTransaction& tx, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs);
/** After reorg, filter the entries that would no longer be valid in the next block, and update /** After reorg, filter the entries that would no longer be valid in the next block, and update

View file

@ -1126,17 +1126,8 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws)
ws.m_replaced_transactions.push_back(it->GetSharedTx()); ws.m_replaced_transactions.push_back(it->GetSharedTx());
} }
m_pool.RemoveStaged(ws.m_all_conflicting, false, MemPoolRemovalReason::REPLACED); m_pool.RemoveStaged(ws.m_all_conflicting, false, MemPoolRemovalReason::REPLACED);
// This transaction should only count for fee estimation if:
// - it's not being re-added during a reorg which bypasses typical mempool fee limits
// - the node is not behind
// - the transaction is not dependent on any other transactions in the mempool
// - it's not part of a package. Since package relay is not currently supported, this
// transaction has not necessarily been accepted to miners' mempools.
bool validForFeeEstimation = !bypass_limits && !args.m_package_submission && IsCurrentForFeeEstimation(m_active_chainstate) && m_pool.HasNoInputsOf(tx);
// Store transaction in memory // Store transaction in memory
m_pool.addUnchecked(*entry, ws.m_ancestors, validForFeeEstimation); m_pool.addUnchecked(*entry, ws.m_ancestors);
// trim mempool and check if tx was trimmed // trim mempool and check if tx was trimmed
// If we are validating a package, don't trim here because we could evict a previous transaction // If we are validating a package, don't trim here because we could evict a previous transaction