Skip to content

Commit 03b1db6

Browse files
author
MarcoFalke
committed
Merge #18766: Disable fee estimation in blocksonly mode (by removing the fee estimates global)
4e28753 feestimator: encapsulate estimation file logic (Antoine Poinsot) e8ea6ad init: don't create a CBlockPolicyEstimator if we don't relay transactions (Antoine Poinsot) 86ff2cf Remove the remaining fee estimation globals (Antoine Poinsot) 03bfeee interface: remove unused estimateSmartFee method from node (Antoine Poinsot) Pull request description: If the `blocksonly` mode is turned on after running with transaction relay enabled for a while, the fee estimation will serve outdated data to both the internal wallet and to external applications that might be feerate-sensitive and make use of `estimatesmartfee` (for example a Lightning Network node). This has already caused issues (for example bitcoin/bitcoin#16840 (C-lightning), or lightningnetwork/lnd#2562 (LND)) and it seems prudent to fail rather than to give inaccurate values. This fixes #16840, and closes #16890 which tried to fix the symptoms (RPC) but not the cause as mentioned by sdaftuar : > If this is a substantial problem, then I would think we should take action to protect our own wallet users as well (rather than hide the results of what our fee estimation would do!). ACKs for top commit: MarcoFalke: re-ACK 4e28753 👋 jnewbery: utACK 4e28753 Tree-SHA512: c869cf03b86d8194002970bbc84662dae76874967949b9be0d9a4511a1eabcb1627c38aca3154da9dcece1a4c49ec02bd4f9fcca2ec310986e07904559e63ba8
2 parents 00f4dcd + 4e28753 commit 03b1db6

File tree

15 files changed

+75
-53
lines changed

15 files changed

+75
-53
lines changed

src/init.cpp

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@
8585
#include <zmq/zmqrpc.h>
8686
#endif
8787

88-
static bool fFeeEstimatesInitialized = false;
8988
static const bool DEFAULT_PROXYRANDOMIZE = true;
9089
static const bool DEFAULT_REST_ENABLE = false;
9190
static const bool DEFAULT_STOPAFTERBLOCKIMPORT = false;
@@ -99,8 +98,6 @@ static const bool DEFAULT_STOPAFTERBLOCKIMPORT = false;
9998
#define MIN_CORE_FILEDESCRIPTORS 150
10099
#endif
101100

102-
static const char* FEE_ESTIMATES_FILENAME="fee_estimates.dat";
103-
104101
static const char* DEFAULT_ASMAP_FILENAME="ip_asn.map";
105102

106103
/**
@@ -236,17 +233,8 @@ void Shutdown(NodeContext& node)
236233
DumpMempool(*node.mempool);
237234
}
238235

239-
if (fFeeEstimatesInitialized)
240-
{
241-
::feeEstimator.FlushUnconfirmed();
242-
fs::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME;
243-
CAutoFile est_fileout(fsbridge::fopen(est_path, "wb"), SER_DISK, CLIENT_VERSION);
244-
if (!est_fileout.IsNull())
245-
::feeEstimator.Write(est_fileout);
246-
else
247-
LogPrintf("%s: Failed to write fee estimates to %s\n", __func__, est_path.string());
248-
fFeeEstimatesInitialized = false;
249-
}
236+
// Drop transactions we were still watching, and record fee estimations.
237+
if (node.fee_estimator) node.fee_estimator->Flush();
250238

251239
// FlushStateToDisk generates a ChainStateFlushed callback, which we should avoid missing
252240
if (node.chainman) {
@@ -304,6 +292,7 @@ void Shutdown(NodeContext& node)
304292
globalVerifyHandle.reset();
305293
ECC_Stop();
306294
node.mempool.reset();
295+
node.fee_estimator.reset();
307296
node.chainman = nullptr;
308297
node.scheduler.reset();
309298

@@ -1384,14 +1373,24 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA
13841373
// is not yet setup and may end up being set up twice if we
13851374
// need to reindex later.
13861375

1376+
// see Step 2: parameter interactions for more information about these
1377+
fListen = args.GetBoolArg("-listen", DEFAULT_LISTEN);
1378+
fDiscover = args.GetBoolArg("-discover", true);
1379+
g_relay_txes = !args.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY);
1380+
13871381
assert(!node.banman);
13881382
node.banman = MakeUnique<BanMan>(GetDataDir() / "banlist.dat", &uiInterface, args.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME));
13891383
assert(!node.connman);
13901384
node.connman = MakeUnique<CConnman>(GetRand(std::numeric_limits<uint64_t>::max()), GetRand(std::numeric_limits<uint64_t>::max()), args.GetBoolArg("-networkactive", true));
13911385

1386+
assert(!node.fee_estimator);
1387+
// Don't initialize fee estimation with old data if we don't relay transactions,
1388+
// as they would never get updated.
1389+
if (g_relay_txes) node.fee_estimator = std::make_unique<CBlockPolicyEstimator>();
1390+
13921391
assert(!node.mempool);
13931392
int check_ratio = std::min<int>(std::max<int>(args.GetArg("-checkmempool", chainparams.DefaultConsistencyChecks() ? 1 : 0), 0), 1000000);
1394-
node.mempool = MakeUnique<CTxMemPool>(&::feeEstimator, check_ratio);
1393+
node.mempool = std::make_unique<CTxMemPool>(node.fee_estimator.get(), check_ratio);
13951394

13961395
assert(!node.chainman);
13971396
node.chainman = &g_chainman;
@@ -1473,11 +1472,6 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA
14731472
}
14741473
}
14751474

1476-
// see Step 2: parameter interactions for more information about these
1477-
fListen = args.GetBoolArg("-listen", DEFAULT_LISTEN);
1478-
fDiscover = args.GetBoolArg("-discover", true);
1479-
g_relay_txes = !args.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY);
1480-
14811475
for (const std::string& strAddr : args.GetArgs("-externalip")) {
14821476
CService addrLocal;
14831477
if (Lookup(strAddr, addrLocal, GetListenPort(), fNameLookup) && addrLocal.IsValid())
@@ -1785,13 +1779,6 @@ bool AppInitMain(const util::Ref& context, NodeContext& node, interfaces::BlockA
17851779
return false;
17861780
}
17871781

1788-
fs::path est_path = GetDataDir() / FEE_ESTIMATES_FILENAME;
1789-
CAutoFile est_filein(fsbridge::fopen(est_path, "rb"), SER_DISK, CLIENT_VERSION);
1790-
// Allowed to fail as this file IS missing on first startup.
1791-
if (!est_filein.IsNull())
1792-
::feeEstimator.Read(est_filein);
1793-
fFeeEstimatesInitialized = true;
1794-
17951782
// ********************************************************* Step 8: start indexers
17961783
if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
17971784
g_txindex = MakeUnique<TxIndex>(nTxIndexCache, false, fReindex);

src/interfaces/node.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,6 @@ class Node
151151
//! Get network active.
152152
virtual bool getNetworkActive() = 0;
153153

154-
//! Estimate smart fee.
155-
virtual CFeeRate estimateSmartFee(int num_blocks, bool conservative, int* returned_target = nullptr) = 0;
156-
157154
//! Get dust relay fee.
158155
virtual CFeeRate getDustRelayFee() = 0;
159156

src/node/context.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <interfaces/chain.h>
99
#include <net.h>
1010
#include <net_processing.h>
11+
#include <policy/fees.h>
1112
#include <scheduler.h>
1213
#include <txmempool.h>
1314

src/node/context.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
class ArgsManager;
1414
class BanMan;
15+
class CBlockPolicyEstimator;
1516
class CConnman;
1617
class CScheduler;
1718
class CTxMemPool;
@@ -36,6 +37,7 @@ class WalletClient;
3637
struct NodeContext {
3738
std::unique_ptr<CConnman> connman;
3839
std::unique_ptr<CTxMemPool> mempool;
40+
std::unique_ptr<CBlockPolicyEstimator> fee_estimator;
3941
std::unique_ptr<PeerManager> peerman;
4042
ChainstateManager* chainman{nullptr}; // Currently a raw pointer because the memory is not managed by this struct
4143
std::unique_ptr<BanMan> banman;

src/node/interfaces.cpp

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -221,15 +221,6 @@ class NodeImpl : public Node
221221
}
222222
}
223223
bool getNetworkActive() override { return m_context->connman && m_context->connman->GetNetworkActive(); }
224-
CFeeRate estimateSmartFee(int num_blocks, bool conservative, int* returned_target = nullptr) override
225-
{
226-
FeeCalculation fee_calc;
227-
CFeeRate result = ::feeEstimator.estimateSmartFee(num_blocks, &fee_calc, conservative);
228-
if (returned_target) {
229-
*returned_target = fee_calc.returnedTarget;
230-
}
231-
return result;
232-
}
233224
CFeeRate getDustRelayFee() override { return ::dustRelayFee; }
234225
UniValue executeRpc(const std::string& command, const UniValue& params, const std::string& uri) override
235226
{
@@ -601,11 +592,13 @@ class ChainImpl : public Chain
601592
}
602593
CFeeRate estimateSmartFee(int num_blocks, bool conservative, FeeCalculation* calc) override
603594
{
604-
return ::feeEstimator.estimateSmartFee(num_blocks, calc, conservative);
595+
if (!m_node.fee_estimator) return {};
596+
return m_node.fee_estimator->estimateSmartFee(num_blocks, calc, conservative);
605597
}
606598
unsigned int estimateMaxBlocks() override
607599
{
608-
return ::feeEstimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
600+
if (!m_node.fee_estimator) return 0;
601+
return m_node.fee_estimator->HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
609602
}
610603
CFeeRate mempoolMinFee() override
611604
{

src/policy/fees.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#include <txmempool.h>
1111
#include <util/system.h>
1212

13+
static const char* FEE_ESTIMATES_FILENAME="fee_estimates.dat";
14+
1315
static constexpr double INF_FEERATE = 1e99;
1416

1517
std::string StringForFeeEstimateHorizon(FeeEstimateHorizon horizon) {
@@ -489,6 +491,7 @@ CBlockPolicyEstimator::CBlockPolicyEstimator()
489491
{
490492
static_assert(MIN_BUCKET_FEERATE > 0, "Min feerate must be nonzero");
491493
size_t bucketIndex = 0;
494+
492495
for (double bucketBoundary = MIN_BUCKET_FEERATE; bucketBoundary <= MAX_BUCKET_FEERATE; bucketBoundary *= FEE_SPACING, bucketIndex++) {
493496
buckets.push_back(bucketBoundary);
494497
bucketMap[bucketBoundary] = bucketIndex;
@@ -500,6 +503,13 @@ CBlockPolicyEstimator::CBlockPolicyEstimator()
500503
feeStats = std::unique_ptr<TxConfirmStats>(new TxConfirmStats(buckets, bucketMap, MED_BLOCK_PERIODS, MED_DECAY, MED_SCALE));
501504
shortStats = std::unique_ptr<TxConfirmStats>(new TxConfirmStats(buckets, bucketMap, SHORT_BLOCK_PERIODS, SHORT_DECAY, SHORT_SCALE));
502505
longStats = std::unique_ptr<TxConfirmStats>(new TxConfirmStats(buckets, bucketMap, LONG_BLOCK_PERIODS, LONG_DECAY, LONG_SCALE));
506+
507+
// If the fee estimation file is present, read recorded estimations
508+
fs::path est_filepath = GetDataDir() / FEE_ESTIMATES_FILENAME;
509+
CAutoFile est_file(fsbridge::fopen(est_filepath, "rb"), SER_DISK, CLIENT_VERSION);
510+
if (est_file.IsNull() || !Read(est_file)) {
511+
LogPrintf("Failed to read fee estimates from %s. Continue anyway.\n", est_filepath.string());
512+
}
503513
}
504514

505515
CBlockPolicyEstimator::~CBlockPolicyEstimator()
@@ -856,6 +866,15 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation
856866
return CFeeRate(llround(median));
857867
}
858868

869+
void CBlockPolicyEstimator::Flush() {
870+
FlushUnconfirmed();
871+
872+
fs::path est_filepath = GetDataDir() / FEE_ESTIMATES_FILENAME;
873+
CAutoFile est_file(fsbridge::fopen(est_filepath, "wb"), SER_DISK, CLIENT_VERSION);
874+
if (est_file.IsNull() || !Write(est_file)) {
875+
LogPrintf("Failed to write fee estimates to %s\n", est_filepath.string());
876+
}
877+
}
859878

860879
bool CBlockPolicyEstimator::Write(CAutoFile& fileout) const
861880
{

src/policy/fees.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,9 @@ class CBlockPolicyEstimator
215215
/** Calculation of highest target that estimates are tracked for */
216216
unsigned int HighestTargetTracked(FeeEstimateHorizon horizon) const;
217217

218+
/** Drop still unconfirmed transactions and record current estimations, if the fee estimation file is present. */
219+
void Flush();
220+
218221
private:
219222
mutable RecursiveMutex m_cs_fee_estimator;
220223

src/rpc/blockchain.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <node/coinstats.h>
1818
#include <node/context.h>
1919
#include <node/utxo_snapshot.h>
20+
#include <policy/fees.h>
2021
#include <policy/feerate.h>
2122
#include <policy/policy.h>
2223
#include <policy/rbf.h>
@@ -81,6 +82,15 @@ ChainstateManager& EnsureChainman(const util::Ref& context)
8182
return *node.chainman;
8283
}
8384

85+
CBlockPolicyEstimator& EnsureFeeEstimator(const util::Ref& context)
86+
{
87+
NodeContext& node = EnsureNodeContext(context);
88+
if (!node.fee_estimator) {
89+
throw JSONRPCError(RPC_INTERNAL_ERROR, "Fee estimation disabled");
90+
}
91+
return *node.fee_estimator;
92+
}
93+
8494
/* Calculate the difficulty for a given block index.
8595
*/
8696
double GetDifficulty(const CBlockIndex* blockindex)

src/rpc/blockchain.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ extern RecursiveMutex cs_main;
1515

1616
class CBlock;
1717
class CBlockIndex;
18+
class CBlockPolicyEstimator;
1819
class CTxMemPool;
1920
class ChainstateManager;
2021
class UniValue;
@@ -54,5 +55,6 @@ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES],
5455
NodeContext& EnsureNodeContext(const util::Ref& context);
5556
CTxMemPool& EnsureMemPool(const util::Ref& context);
5657
ChainstateManager& EnsureChainman(const util::Ref& context);
58+
CBlockPolicyEstimator& EnsureFeeEstimator(const util::Ref& context);
5759

5860
#endif

src/rpc/mining.cpp

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,7 +1059,10 @@ static RPCHelpMan estimatesmartfee()
10591059
{
10601060
RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VSTR});
10611061
RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
1062-
unsigned int max_target = ::feeEstimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
1062+
1063+
CBlockPolicyEstimator& fee_estimator = EnsureFeeEstimator(request.context);
1064+
1065+
unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
10631066
unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
10641067
bool conservative = true;
10651068
if (!request.params[1].isNull()) {
@@ -1073,7 +1076,7 @@ static RPCHelpMan estimatesmartfee()
10731076
UniValue result(UniValue::VOBJ);
10741077
UniValue errors(UniValue::VARR);
10751078
FeeCalculation feeCalc;
1076-
CFeeRate feeRate = ::feeEstimator.estimateSmartFee(conf_target, &feeCalc, conservative);
1079+
CFeeRate feeRate = fee_estimator.estimateSmartFee(conf_target, &feeCalc, conservative);
10771080
if (feeRate != CFeeRate(0)) {
10781081
result.pushKV("feerate", ValueFromAmount(feeRate.GetFeePerK()));
10791082
} else {
@@ -1144,7 +1147,10 @@ static RPCHelpMan estimaterawfee()
11441147
{
11451148
RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM}, true);
11461149
RPCTypeCheckArgument(request.params[0], UniValue::VNUM);
1147-
unsigned int max_target = ::feeEstimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
1150+
1151+
CBlockPolicyEstimator& fee_estimator = EnsureFeeEstimator(request.context);
1152+
1153+
unsigned int max_target = fee_estimator.HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE);
11481154
unsigned int conf_target = ParseConfirmTarget(request.params[0], max_target);
11491155
double threshold = 0.95;
11501156
if (!request.params[1].isNull()) {
@@ -1161,9 +1167,9 @@ static RPCHelpMan estimaterawfee()
11611167
EstimationResult buckets;
11621168

11631169
// Only output results for horizons which track the target
1164-
if (conf_target > ::feeEstimator.HighestTargetTracked(horizon)) continue;
1170+
if (conf_target > fee_estimator.HighestTargetTracked(horizon)) continue;
11651171

1166-
feeRate = ::feeEstimator.estimateRawFee(conf_target, threshold, horizon, &buckets);
1172+
feeRate = fee_estimator.estimateRawFee(conf_target, threshold, horizon, &buckets);
11671173
UniValue horizon_result(UniValue::VOBJ);
11681174
UniValue errors(UniValue::VARR);
11691175
UniValue passbucket(UniValue::VOBJ);

0 commit comments

Comments
 (0)