Skip to content

Commit ccaef6c

Browse files
committed
Merge #16908: txmempool: Make entry time type-safe (std::chrono)
faec689 txmempool: Make entry time type-safe (std::chrono) (MarcoFalke) faaa1f0 util: Add count_seconds time helper (MarcoFalke) 1111170 test: mempool entry time is persisted (MarcoFalke) Pull request description: This changes the type of the entry time of txs into the mempool from `int64_t` to `std::chrono::seconds`. The benefits: * Documents the type for developers * Type violations result in compile errors * After compilation, the two are equivalent (at no run time cost) ACKs for top commit: ajtowns: utACK faec689 laanwj: ACK faec689 Tree-SHA512: d958e058755d1a1d54cef536a8b30a11cc502b7df0d6ecf84a0ab1d38bc8105a67668a99cd5087a444f6de2421238111c5fca133cdf8e2e2273cb12cb6957845
2 parents 19ba43a + faec689 commit ccaef6c

File tree

8 files changed

+40
-23
lines changed

8 files changed

+40
-23
lines changed

src/net.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,7 @@ class CNode
761761
// Used for BIP35 mempool sending
762762
bool fSendMempool GUARDED_BY(cs_tx_inventory){false};
763763
// Last time a "MEMPOOL" request was serviced.
764-
std::atomic<int64_t> timeLastMempoolReq{0};
764+
std::atomic<std::chrono::seconds> m_last_mempool_req{std::chrono::seconds{0}};
765765
int64_t nNextInvSend{0};
766766

767767
CCriticalSection cs_feeFilter;

src/net_processing.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1541,11 +1541,11 @@ void static ProcessGetData(CNode* pfrom, const CChainParams& chainparams, CConnm
15411541
if (mi != mapRelay.end()) {
15421542
connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *mi->second));
15431543
push = true;
1544-
} else if (pfrom->m_tx_relay->timeLastMempoolReq) {
1544+
} else if (pfrom->m_tx_relay->m_last_mempool_req.load().count()) {
15451545
auto txinfo = mempool.info(inv.hash);
15461546
// To protect privacy, do not answer getdata using the mempool when
15471547
// that TX couldn't have been INVed in reply to a MEMPOOL request.
1548-
if (txinfo.tx && txinfo.nTime <= pfrom->m_tx_relay->timeLastMempoolReq) {
1548+
if (txinfo.tx && txinfo.m_time <= pfrom->m_tx_relay->m_last_mempool_req.load()) {
15491549
connman->PushMessage(pfrom, msgMaker.Make(nSendFlags, NetMsgType::TX, *txinfo.tx));
15501550
push = true;
15511551
}
@@ -3873,7 +3873,7 @@ bool PeerLogicValidation::SendMessages(CNode* pto)
38733873
vInv.clear();
38743874
}
38753875
}
3876-
pto->m_tx_relay->timeLastMempoolReq = GetTime();
3876+
pto->m_tx_relay->m_last_mempool_req = GetTime<std::chrono::seconds>();
38773877
}
38783878

38793879
// Determine transactions to relay

src/rpc/blockchain.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPool
418418
info.pushKV("weight", (int)e.GetTxWeight());
419419
info.pushKV("fee", ValueFromAmount(e.GetFee()));
420420
info.pushKV("modifiedfee", ValueFromAmount(e.GetModifiedFee()));
421-
info.pushKV("time", e.GetTime());
421+
info.pushKV("time", count_seconds(e.GetTime()));
422422
info.pushKV("height", (int)e.GetHeight());
423423
info.pushKV("descendantcount", e.GetCountWithDescendants());
424424
info.pushKV("descendantsize", e.GetSizeWithDescendants());

src/txmempool.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -917,7 +917,8 @@ void CTxMemPool::RemoveStaged(setEntries &stage, bool updateDescendants, MemPool
917917
}
918918
}
919919

920-
int CTxMemPool::Expire(int64_t time) {
920+
int CTxMemPool::Expire(std::chrono::seconds time)
921+
{
921922
AssertLockHeld(cs);
922923
indexed_transaction_set::index<entry_time>::type::iterator it = mapTx.get<entry_time>().begin();
923924
setEntries toremove;

src/txmempool.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ class CTxMemPoolEntry
102102
const CAmount& GetFee() const { return nFee; }
103103
size_t GetTxSize() const;
104104
size_t GetTxWeight() const { return nTxWeight; }
105-
int64_t GetTime() const { return nTime; }
105+
std::chrono::seconds GetTime() const { return std::chrono::seconds{nTime}; }
106106
unsigned int GetHeight() const { return entryHeight; }
107107
int64_t GetSigOpCost() const { return sigOpCost; }
108108
int64_t GetModifiedFee() const { return nFee + feeDelta; }
@@ -332,7 +332,7 @@ struct TxMempoolInfo
332332
CTransactionRef tx;
333333

334334
/** Time the transaction entered the mempool. */
335-
int64_t nTime;
335+
std::chrono::seconds m_time;
336336

337337
/** Feerate of the transaction. */
338338
CFeeRate feeRate;
@@ -657,7 +657,7 @@ class CTxMemPool
657657
void TrimToSize(size_t sizelimit, std::vector<COutPoint>* pvNoSpendsRemaining = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs);
658658

659659
/** Expire all transaction (and their dependencies) in the mempool older than time. Return the number of removed transactions. */
660-
int Expire(int64_t time) EXCLUSIVE_LOCKS_REQUIRED(cs);
660+
int Expire(std::chrono::seconds time) EXCLUSIVE_LOCKS_REQUIRED(cs);
661661

662662
/**
663663
* Calculate the ancestor and descendant count for the given transaction.

src/util/time.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@
1010
#include <string>
1111
#include <chrono>
1212

13+
/**
14+
* Helper to count the seconds of a duration.
15+
*
16+
* All durations should be using std::chrono and calling this should generally be avoided in code. Though, it is still
17+
* preferred to an inline t.count() to protect against a reliance on the exact type of t.
18+
*/
19+
inline int64_t count_seconds(std::chrono::seconds t) { return t.count(); }
20+
1321
/**
1422
* DEPRECATED
1523
* Use either GetSystemTimeInSeconds (not mockable) or GetTime<T> (mockable)

src/validation.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -314,10 +314,10 @@ bool CheckSequenceLocks(const CTxMemPool& pool, const CTransaction& tx, int flag
314314
// Returns the script flags which should be checked for a given block
315315
static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consensus::Params& chainparams);
316316

317-
static void LimitMempoolSize(CTxMemPool& pool, size_t limit, unsigned long age)
317+
static void LimitMempoolSize(CTxMemPool& pool, size_t limit, std::chrono::seconds age)
318318
EXCLUSIVE_LOCKS_REQUIRED(pool.cs, ::cs_main)
319319
{
320-
int expired = pool.Expire(GetTime() - age);
320+
int expired = pool.Expire(GetTime<std::chrono::seconds>() - age);
321321
if (expired != 0) {
322322
LogPrint(BCLog::MEMPOOL, "Expired %i transactions from the memory pool\n", expired);
323323
}
@@ -389,7 +389,7 @@ static void UpdateMempoolForReorg(DisconnectedBlockTransactions& disconnectpool,
389389
// We also need to remove any now-immature transactions
390390
mempool.removeForReorg(&::ChainstateActive().CoinsTip(), ::ChainActive().Tip()->nHeight + 1, STANDARD_LOCKTIME_VERIFY_FLAGS);
391391
// Re-limit mempool size, in case we added any transactions
392-
LimitMempoolSize(mempool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60);
392+
LimitMempoolSize(mempool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, std::chrono::hours{gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)});
393393
}
394394

395395
// Used to avoid mempool polluting consensus critical paths if CCoinsViewMempool
@@ -1011,7 +1011,7 @@ bool MemPoolAccept::Finalize(ATMPArgs& args, Workspace& ws)
10111011

10121012
// trim mempool and check if tx was trimmed
10131013
if (!bypass_limits) {
1014-
LimitMempoolSize(m_pool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY) * 60 * 60);
1014+
LimitMempoolSize(m_pool, gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, std::chrono::hours{gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)});
10151015
if (!m_pool.exists(hash))
10161016
return state.Invalid(ValidationInvalidReason::TX_MEMPOOL_POLICY, false, REJECT_INSUFFICIENTFEE, "mempool full");
10171017
}
@@ -5041,8 +5041,8 @@ bool DumpMempool(const CTxMemPool& pool)
50415041
file << (uint64_t)vinfo.size();
50425042
for (const auto& i : vinfo) {
50435043
file << *(i.tx);
5044-
file << (int64_t)i.nTime;
5045-
file << (int64_t)i.nFeeDelta;
5044+
file << int64_t{count_seconds(i.m_time)};
5045+
file << int64_t{i.nFeeDelta};
50465046
mapDeltas.erase(i.tx->GetHash());
50475047
}
50485048

test/functional/mempool_persist.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,15 @@
3737
"""
3838
from decimal import Decimal
3939
import os
40+
import time
4041

4142
from test_framework.test_framework import BitcoinTestFramework
42-
from test_framework.util import assert_equal, assert_raises_rpc_error, wait_until
43+
from test_framework.util import (
44+
assert_equal,
45+
assert_greater_than_or_equal,
46+
assert_raises_rpc_error,
47+
wait_until,
48+
)
4349

4450

4551
class MempoolPersistTest(BitcoinTestFramework):
@@ -51,18 +57,13 @@ def skip_test_if_missing_module(self):
5157
self.skip_if_no_wallet()
5258

5359
def run_test(self):
54-
chain_height = self.nodes[0].getblockcount()
55-
assert_equal(chain_height, 200)
56-
57-
self.log.debug("Mine a single block to get out of IBD")
58-
self.nodes[0].generate(1)
59-
self.sync_all()
60-
6160
self.log.debug("Send 5 transactions from node2 (to its own address)")
61+
tx_creation_time_lower = int(time.time())
6262
for i in range(5):
6363
last_txid = self.nodes[2].sendtoaddress(self.nodes[2].getnewaddress(), Decimal("10"))
6464
node2_balance = self.nodes[2].getbalance()
6565
self.sync_all()
66+
tx_creation_time_higher = int(time.time())
6667

6768
self.log.debug("Verify that node0 and node1 have 5 transactions in their mempools")
6869
assert_equal(len(self.nodes[0].getrawmempool()), 5)
@@ -75,6 +76,10 @@ def run_test(self):
7576
fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees']
7677
assert_equal(fees['base'] + Decimal('0.00001000'), fees['modified'])
7778

79+
tx_creation_time = self.nodes[0].getmempoolentry(txid=last_txid)['time']
80+
assert_greater_than_or_equal(tx_creation_time, tx_creation_time_lower)
81+
assert_greater_than_or_equal(tx_creation_time_higher, tx_creation_time)
82+
7883
self.log.debug("Stop-start the nodes. Verify that node0 has the transactions in its mempool and node1 does not. Verify that node2 calculates its balance correctly after loading wallet transactions.")
7984
self.stop_nodes()
8085
# Give this node a head-start, so we can be "extra-sure" that it didn't load anything later
@@ -93,6 +98,9 @@ def run_test(self):
9398
fees = self.nodes[0].getmempoolentry(txid=last_txid)['fees']
9499
assert_equal(fees['base'] + Decimal('0.00001000'), fees['modified'])
95100

101+
self.log.debug('Verify time is loaded correctly')
102+
assert_equal(tx_creation_time, self.nodes[0].getmempoolentry(txid=last_txid)['time'])
103+
96104
# Verify accounting of mempool transactions after restart is correct
97105
self.nodes[2].syncwithvalidationinterfacequeue() # Flush mempool to wallet
98106
assert_equal(node2_balance, self.nodes[2].getbalance())

0 commit comments

Comments
 (0)