Skip to content

Commit b5c608e

Browse files
authored
Fix to use vsize for mempool fee histogram (#225)
* base fix * Refactored. Also fixed bug where vsize was 4x * more refactoring * Added some Txn ser/deser testing * Improved the frequency of mempool fee histogram updates - We now update the mempool fee histogram every 10 seconds, instead of every 30 - Whenever we get a new block, we update it immediately upon the first mempool synch after the block arrives This should improve the txn estimates in Electrum for BTC users. * Dont print Storage::refreshMempoolHistogram elapsed time unless >= 10msec Also do the printing with no locks held, and destruct the swapped-in hist vec with no locks held. * Tweak to mempool bench to populate vsize correctly * Nit
1 parent b3f090d commit b5c608e

File tree

14 files changed

+334
-208
lines changed

14 files changed

+334
-208
lines changed

src/Controller.cpp

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -257,15 +257,27 @@ void Controller::startup()
257257
{
258258
// set up periodic refresh of mempool fee histogram
259259
constexpr const char *feeHistogramTimer = "feeHistogramTimer";
260-
constexpr int feeHistogramTimerInterval = 30 * 1000; // every 30 seconds
260+
constexpr int feeHistogramTimerInterval = 10 * 1000; // every 10 seconds
261261
conns += connect(this, &Controller::upToDate, this, [this] {
262262
callOnTimerSoon(feeHistogramTimerInterval, feeHistogramTimer, [this]{
263263
storage->refreshMempoolHistogram();
264264
return true;
265-
}, false, Qt::TimerType::VeryCoarseTimer);
265+
}, false, Qt::TimerType::CoarseTimer);
266+
});
267+
conns += connect(this, &Controller::synchedMempool, this, [this]{
268+
// If we just synched the mempool after a new block arrived, or for the first time after app start,
269+
// refresh the fee histogram immediately.
270+
if (needFeeHistogramUpdate) {
271+
needFeeHistogramUpdate = false;
272+
storage->refreshMempoolHistogram();
273+
restartTimer(feeHistogramTimer);
274+
}
266275
});
267276
// disable the timer if downloading blocks and restart it later when up-to-date
268-
conns += connect(this, &Controller::synchronizing, this, [this]{ stopTimer(feeHistogramTimer); });
277+
conns += connect(this, &Controller::synchronizing, this, [this]{
278+
stopTimer(feeHistogramTimer);
279+
needFeeHistogramUpdate = true; // indicate that the next time synchedMempool() is emitted, do a fee histogram refresh
280+
});
269281
}
270282

271283
{
@@ -1106,11 +1118,14 @@ void Controller::process(bool beSilentIfUpToDate)
11061118
// ...
11071119
if (bitcoindmgr->hasDSProofRPC())
11081120
sm->state = State::SynchDSPs; // remote bitcoind has dsproof rpc, proceed to synch dsps
1109-
else
1121+
else {
1122+
emit synchedMempool();
11101123
sm->state = State::End; // remote bitcoind lacks dsproof rpc, finish successfully
1124+
}
11111125
AGAIN();
11121126
} else if (sm->state == State::SynchDSPsFinished) {
11131127
// ...
1128+
emit synchedMempool();
11141129
sm->state = State::End;
11151130
AGAIN();
11161131
}

src/Controller.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,11 @@ class Controller : public Mgr, public ThreadObjectMixin, public TimersByNameMixi
8585
}
8686

8787
signals:
88-
/// Emitted whenever bitcoind is detected to be up-to-date, and everything is synched up.
88+
/// Emitted whenever bitcoind is detected to be up-to-date, and everything (except mempool) is synched up.
8989
/// note this is not emitted during regular polling, but only after `synchronizing` was emitted previously.
9090
void upToDate();
91-
/// Emitted whenever we begin synching to bitcoind. After this completes successfully, upToDate will be emitted
92-
/// exactly once.
91+
/// Emitted whenever we begin dl'ing blocks from bitcoind. After this completes successfully, upToDate will be
92+
/// emitted exactly once.
9393
/// This signal may be emitted multiple times if there were errors and we are periodically retrying.
9494
void synchronizing();
9595
/// Emitted whenever we failed to synchronize to bitcoind.
@@ -117,6 +117,10 @@ class Controller : public Mgr, public ThreadObjectMixin, public TimersByNameMixi
117117
/// Emitted whenever we begin downloading blocks, and whenever we end the last DownloadBlocksTask (for whatever reason)
118118
void downloadingBlocks(bool b);
119119

120+
/// Emitted after a successful mempool synch. This is emitted more often than upToDate() (which is only emitted
121+
/// when new blocks arrive); this is emitted every time we successfully poll the mempool for new txns.
122+
void synchedMempool();
123+
120124
protected:
121125
Stats stats() const override; // from StatsMixin
122126
Stats debug(const StatsParams &) const override; // from StatsMixin
@@ -218,6 +222,9 @@ protected slots:
218222
/// for that block, until a new block arrives, then is cleared again.
219223
std::unordered_set<TxHash, HashHasher> mempoolIgnoreTxns;
220224

225+
/// Used to update the mempool fee histogram early right after the synchedMempool() signal is emitted
226+
bool needFeeHistogramUpdate = true;
227+
221228
private slots:
222229
/// Stops the zmqHashBlockNotifier; called if we received an empty hashblock endpoint address from BitcoinDMgr or
223230
/// when all connections to bitcoind are lost

src/Controller_SynchMempoolTask.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,9 +477,13 @@ void SynchMempoolTask::doDLNextTx()
477477
return;
478478
}
479479

480-
// save size now -- this is needed later to calculate fees and for everything else.
481-
// note: for btc core with segwit this size is not the same "virtual" size as what bitcoind would report
480+
// Save size now -- this is needed later to calculate fees and for everything else.
482481
tx->sizeBytes = unsigned(expectedLen);
482+
if (isSegWit) {
483+
tx->vsizeBytes = ctx.GetVirtualSize(tx->sizeBytes);
484+
} else {
485+
tx->vsizeBytes = tx->sizeBytes;
486+
}
483487

484488
if (TRACE)
485489
Debug() << "got reply for tx: " << hashHex << " " << txdata.length() << " bytes";

src/Mempool.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ auto Mempool::calcCompactFeeHistogram(unsigned binSizeBytes) const -> FeeHistogr
4646
for (const auto & [txid, tx] : txs) {
4747
if (tx->fee < bitcoin::Amount::zero()) continue; // skip negative fees (coinbase txn, etc)
4848
const auto feeRate = unsigned(tx->fee / bitcoin::Amount::satoshi()) // sats
49-
/ std::max(tx->sizeBytes, 1u); // per byte
50-
histogram[feeRate] += tx->sizeBytes; // accumulate size by feeRate
49+
/ std::max(tx->vsizeBytes, 1u); // per vbyte
50+
histogram[feeRate] += tx->vsizeBytes; // accumulate size by feeRate
5151
}
5252

5353
// Now, compact the bins
@@ -881,7 +881,7 @@ namespace {
881881
std::pair<MPData, bool> loadMempoolDat(const QString &fname) {
882882
// Below code taken from BCHN validation.cpp, but adopted to also support segwit
883883
FILE *pfile = std::fopen(fname.toUtf8().constData(), "rb");
884-
bitcoin::CAutoFile file(pfile, bitcoin::SER_DISK, bitcoin::PROTOCOL_VERSION | bitcoin::SERIALIZE_TRANSACTION_USE_WITNESS);
884+
bitcoin::CAutoFile file(pfile, bitcoin::SER_DISK, bitcoin::PROTOCOL_VERSION | bitcoin::SERIALIZE_TRANSACTION_USE_WITNESS | bitcoin::SERIALIZE_TRANSACTION_USE_CASHTOKENS);
885885
if (file.IsNull())
886886
throw Exception(QString("Failed to open mempool file '%1'").arg(fname));
887887

@@ -912,7 +912,8 @@ namespace {
912912

913913
auto rtx = std::make_shared<Mempool::Tx>();
914914
rtx->hash = BTC::Hash2ByteArrayRev(ctx->GetHashRef());
915-
dataTotal += rtx->sizeBytes = ctx->GetTotalSize(true);
915+
dataTotal += rtx->sizeBytes = ctx->GetTotalSize(isSegWit, false);
916+
rtx->vsizeBytes = isSegWit ? ctx->GetVirtualSize(rtx->sizeBytes) : rtx->sizeBytes;
916917
ret.emplace(std::piecewise_construct,
917918
std::forward_as_tuple(rtx->hash),
918919
std::forward_as_tuple(std::move(rtx), std::move(ctx)));

src/Mempool.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ struct Mempool
4848
TxHash hash; ///< in reverse bitcoind order (ready for hex encode), fixed value.
4949

5050
bitcoin::Amount fee{bitcoin::Amount::zero()}; ///< we calculate this fee ourselves since in the past I noticed we get a funny value sometimes that's off by 1 or 2 sats -- which I suspect is due limitations of doubles, perhaps?
51-
unsigned sizeBytes = 0;
51+
unsigned sizeBytes = 0; // the actual serialized size
52+
unsigned vsizeBytes = 0; // for segwit coins, the virtual size, for non-segwit, identical to sizeBytes
5253
bool hasUnconfirmedParentTx = false; ///< If true, this tx depends on another tx in the mempool. This is not always fixed (confirmedInBlock may change this)
5354

5455
/// These are all the txos in this tx. Once set-up, this doesn't change (unlike IOInfo.utxo).

src/Mixins.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ void TimersByNameMixin::callOnTimerSoon(int ms, const QString &name, const std::
105105
{
106106
if (auto it = _timerMap.find(name); it != _timerMap.end()) {
107107
if (force) {
108-
it->get()->stop(); // immediately stop timer
108+
it.value()->stop(); // immediately stop timer
109109
it = _timerMap.erase(it); // shared_ptr refs will go away immediately, which ends up calling deleteLater on timer
110110
callOnTimerSoon(ms, name, func, false, ttype); // immediately call self recursively once to re-enqueue timer
111111
}
@@ -164,6 +164,15 @@ QVariantMap TimersByNameMixin::activeTimerMapForStats() const
164164
return ret;
165165
}
166166

167+
bool TimersByNameMixin::restartTimer(const QString &name)
168+
{
169+
if (auto it = _timerMap.find(name); it != _timerMap.end()) {
170+
it.value()->start(); // restart from "now"
171+
return true;
172+
}
173+
return false;
174+
}
175+
167176
/// --- StatsMixin
168177
StatsMixin::~StatsMixin() {}
169178

src/Mixins.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ class TimersByNameMixin : public virtual QObjectMixin
172172
/// Stops all extant timers immediately. Note that this implicitly uses deleteLater to delete the timer objects.
173173
/// Returns the number of timers that were stopped.
174174
int stopAllTimers();
175+
176+
/// Stops the named timer, and immediately restarts it with the same interval and oneshot status, effectively
177+
/// re-offsetting the next time the timer will run as offset from *now*.
178+
bool restartTimer(const QString &name);
175179
};
176180

177181

src/Servers.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2707,14 +2707,21 @@ void AdminServer::rpc_getinfo(Client *c, const RPC::BatchId batchId, const RPC::
27072707
auto [mempool, lock] = storage->mempool();
27082708
mp["txs"] = qulonglong(mempool.txs.size());
27092709
mp["addresses"] = qulonglong(mempool.hashXTxs.size());
2710-
std::size_t sizeTotal = 0;
2710+
std::size_t sizeTotal = 0, vsizeTotal = 0;
27112711
std::int64_t feeTotal = 0;
2712-
std::for_each(mempool.txs.begin(), mempool.txs.end(), [&sizeTotal, &feeTotal](const auto &pair){
2713-
sizeTotal += pair.second->sizeBytes;
2714-
feeTotal += pair.second->fee / bitcoin::Amount::satoshi();
2712+
std::for_each(mempool.txs.begin(), mempool.txs.end(), [&sizeTotal, &vsizeTotal, &feeTotal](const auto &pair){
2713+
const auto & [_, tx] = pair;
2714+
sizeTotal += tx->sizeBytes;
2715+
vsizeTotal += tx->vsizeBytes;
2716+
feeTotal += tx->fee / bitcoin::Amount::satoshi();
27152717
});
27162718
mp["size_bytes"] = qulonglong(sizeTotal);
27172719
mp["avg_fee_sats_B"] = sizeTotal ? long(std::round(double(feeTotal) / double(sizeTotal) * 100.0)) / 100.0 : 0.0;
2720+
if (sizeTotal != vsizeTotal) {
2721+
// add these elements to the info map if BTC, LTC, etc
2722+
mp["size_vbytes"] = qulonglong(vsizeTotal);
2723+
mp["avg_fee_sats_vB"] = vsizeTotal ? long(std::round(double(feeTotal) / double(vsizeTotal) * 100.0)) / 100.0 : 0.0;
2724+
}
27182725
res["mempool"] = mp;
27192726
}
27202727
{ // utxoset

src/Storage.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3991,16 +3991,19 @@ auto Storage::mutableMempool() -> std::pair<Mempool &, ExclusiveLockGuard>
39913991

39923992
void Storage::refreshMempoolHistogram()
39933993
{
3994+
Tic t0;
39943995
Mempool::FeeHistogramVec hist;
3996+
// shared lock
39953997
{
3996-
// shared lock
39973998
auto [mempool, lock] = this->mempool();
3998-
auto histTmp = mempool.calcCompactFeeHistogram();
3999-
hist.swap(histTmp);
3999+
hist = mempool.calcCompactFeeHistogram();
40004000
}
40014001
// lock exclusively to do the final swap
4002-
ExclusiveLockGuard g(p->mempoolLock);
4003-
p->mempoolFeeHistogram.swap(hist);
4002+
{
4003+
ExclusiveLockGuard g(p->mempoolLock);
4004+
p->mempoolFeeHistogram.swap(hist);
4005+
}
4006+
if (t0.msec() >= 10) DebugM("Storage::refreshMempoolHistogram took ", t0.msecStr(), " msec");
40044007
}
40054008

40064009
auto Storage::mempoolHistogram() const -> Mempool::FeeHistogramVec

src/bitcoin/interpreter.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1504,8 +1504,7 @@ uint256 GetOutputsHash(const CTransaction &txTo) {
15041504

15051505
} // namespace
15061506

1507-
PrecomputedTransactionData::PrecomputedTransactionData(
1508-
const CTransaction &txTo) {
1507+
PrecomputedTransactionData::PrecomputedTransactionData(const CTransaction &txTo) {
15091508
hashPrevouts = GetPrevoutHash(txTo);
15101509
hashSequence = GetSequenceHash(txTo);
15111510
hashOutputs = GetOutputsHash(txTo);

0 commit comments

Comments
 (0)