CChainState::m_mempool
optional by making it a pointer instead of a reference. This will allow a simplification to assumeutxo semantics (see #15606#pullrequestreview-692965905) and help facilitate the -nomempool
option.
2569@@ -2565,12 +2570,12 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, CBlockIndex
2570 }
2571 }
2572
2573- if (fBlocksDisconnected) {
2574+ if (fBlocksDisconnected && m_mempool) {
2575 // If any blocks were disconnected, disconnectpool may be non empty. Add
2576 // any disconnected transactions back to the mempool.
2577- UpdateMempoolForReorg(*this, m_mempool, disconnectpool, true);
2578+ mempoolHandleReorg(disconnectpool, false);
true
-> false
?
2646@@ -2642,7 +2647,8 @@ bool CChainState::ActivateBestChain(BlockValidationState& state, std::shared_ptr
2647
2648 {
2649 LOCK(cs_main);
2650- LOCK(m_mempool.cs); // Lock transaction pool for at least as long as it takes for connectTrace to be consumed
2651+ // Lock transaction pool for at least as long as it takes for connectTrace to be consumed
2652+ if (m_mempool) LOCK(m_mempool->cs);
{}
won’t prevent the lock from being closed in the same line.
2802@@ -2792,7 +2803,9 @@ bool CChainState::InvalidateBlock(BlockValidationState& state, CBlockIndex* pind
2803 LimitValidationInterfaceQueue();
2804
2805 LOCK(cs_main);
2806- LOCK(m_mempool.cs); // Lock for as long as disconnectpool is in scope to make sure UpdateMempoolForReorg is called after DisconnectTip without unlocking in between
2807+ // Lock for as long as disconnectpool is in scope to make sure UpdateMempoolForReorg is
2808+ // called after DisconnectTip without unlocking in between
2809+ if (m_mempool) LOCK(m_mempool->cs);
4505@@ -4492,6 +4506,41 @@ bool CChainState::ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size)
4506 return ret;
4507 }
4508
4509+void CChainState::mempoolHandleReorg(DisconnectedBlockTransactions& disconnectpool, bool add_to_mempool) {
0void CChainState::mempoolHandleReorg(DisconnectedBlockTransactions& disconnectpool, bool add_to_mempool)
1{
properly format new code?
600@@ -600,7 +601,7 @@ class CChainState
601 //! CChainState instances.
602 BlockManager& m_blockman;
603
604- explicit CChainState(CTxMemPool& mempool, BlockManager& blockman, std::optional<uint256> from_snapshot_blockhash = std::nullopt);
605+ explicit CChainState(CTxMemPool* mempool, BlockManager& blockman, std::optional<uint256> from_snapshot_blockhash = std::nullopt);
Concept ACK. I think you can drop the second commit and add a LOCK_RETURNED
helper method to be able to keep using lock annotations when m_mempool is null:
0RecursiveMutex* MempoolMutex() const LOCK_RETURNED(m_mempool->cs) { return m_mempool ? &m_mempool->cs : nullptr; }
Seems to work for me in commit 8db5953e2d666fb9c25ae8f0df5b4c5c5897cd58
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
Reviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.
Rebased to remove the second commit per Russ’ suggestion, which also addresses most of Marco’s feedback.
Seems to work for me in commit 8db5953
Wow, that was wizardly. Thanks @ryanofsky!
I think std::variant can do this
I’ll look into this tomorrow.
912@@ -907,7 +913,7 @@ class ChainstateManager
913 // constructor
914 //! @param[in] snapshot_blockhash If given, signify that this chainstate
915 //! is based on a snapshot.
916- CChainState& InitializeChainstate(CTxMemPool& mempool, const std::optional<uint256>& snapshot_blockhash = std::nullopt)
917+ CChainState& InitializeChainstate(CTxMemPool* mempool, const std::optional<uint256>& snapshot_blockhash = std::nullopt)
798@@ -798,6 +799,11 @@ class CChainState
799
800 bool LoadBlockIndexDB() EXCLUSIVE_LOCKS_REQUIRED(cs_main);
801
802+ //! Indirection necessary to make lock annotations work with an optional mempool.
803+ RecursiveMutex* MempoolMutex() const LOCK_RETURNED(m_mempool->cs) {
0 RecursiveMutex* MempoolMutex() const LOCK_RETURNED(m_mempool->cs)
1 {
clang-format?
LOCK_RETURNED
1348@@ -1349,7 +1349,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
1349 const int64_t load_block_index_start_time = GetTimeMillis();
1350 try {
1351 LOCK(cs_main);
1352- chainman.InitializeChainstate(*Assert(node.mempool));
1353+ chainman.InitializeChainstate(Assert(node.mempool).get());
In commit “validation: make CChainState::m_mempool optional” (b17bb4c37d58ca49e877983a632bfcaa493be993)
Might be a little more obviously safe to write Assert(node.mempool.get())
instead of Assert(node.mempool).get()
since that version looks like the Assert function might be returning a temporary unique_ptr
that would delete the mempool.
2256@@ -2254,7 +2257,7 @@ static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const C
2257 bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTransactions* disconnectpool)
2258 {
2259 AssertLockHeld(cs_main);
2260- AssertLockHeld(m_mempool.cs);
2261+ if (m_mempool) AssertLockHeld(m_mempool->cs);
In commit “validation: make CChainState::m_mempool optional” (b17bb4c37d58ca49e877983a632bfcaa493be993)
Note: It could be nice to be able to write AssertLockHeld(MempoolMutex()
for consistency with LOCK(MempoolMutex())
and to avoid the if statement. But it’d be beyond scope of this PR since it’d require changing AssertLockHeld
and there are other issues with that function anyway.
4771@@ -4763,7 +4772,7 @@ bool ChainstateManager::ActivateSnapshot(
4772 }
4773
4774 auto snapshot_chainstate = WITH_LOCK(::cs_main, return std::make_unique<CChainState>(
4775- this->ActiveChainstate().m_mempool, m_blockman, base_blockhash));
4776+ nullptr, m_blockman, base_blockhash));
In commit “validation: make CChainState::m_mempool optional” (b17bb4c37d58ca49e877983a632bfcaa493be993)
Note: Naively I’d expect the mempool pointer for the activated snapshot to be non-null, but I guess it could be set later when the snapshot is fully initialized.
(if you retouch)
0 /* mempool */ nullptr, m_blockman, base_blockhash));
Rebased.
Note: Naively I’d expect the mempool pointer for the activated snapshot to be non-null, but I guess it could be set later when the snapshot is fully initialized.
This is a good point. I’d come up with a commit that implements the std::variant
feedback (and am glad to have for learning about that), but @ryanofsky’s point here makes that change inappropriate: with assumeutxo, we may actually want the snapshot chain to have a non-null mempool if we’re in, say, init.cpp and loading a snapshot chainstate that is active but not yet at tip.
I’ve covered all of the other feedback aside from the lock assertion for the reasons Russ mentioned; maybe we can get around to it later.
913@@ -907,7 +914,7 @@ class ChainstateManager
914 // constructor
915 //! @param[in] snapshot_blockhash If given, signify that this chainstate
916 //! is based on a snapshot.
917- CChainState& InitializeChainstate(CTxMemPool& mempool, const std::optional<uint256>& snapshot_blockhash = std::nullopt)
918+ CChainState& InitializeChainstate(CTxMemPool* mempool, const std::optional<uint256>& snapshot_blockhash = std::nullopt)
What do you think about making this std::optional<CTxMemPool, std::optional>?
In this case, snapshot_blockhash can’t be possibly specified without mempool, and that’s what I originally suggested.
2053@@ -2053,7 +2054,7 @@ bool CChainState::FlushStateToDisk(
2054 bool fFlushForPrune = false;
2055 bool fDoFullFlush = false;
2056
2057- CoinsCacheSizeState cache_state = GetCoinsCacheSizeState(&m_mempool);
2058+ CoinsCacheSizeState cache_state = GetCoinsCacheSizeState(m_mempool);
CChainState::GetCoinsCacheSizeState(const CTxMemPool* tx_pool)
, maybe it makes sense to just remove the argument. There’s no need to pass a data member of CChainState
to a member function of CChainState
- it can just use its own m_mempool
.
2290@@ -2288,7 +2291,7 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr
2291 while (disconnectpool->DynamicMemoryUsage() > MAX_DISCONNECTED_TX_POOL_SIZE * 1000) {
2292 // Drop the earliest entry, and remove its children from the mempool.
2293 auto it = disconnectpool->queuedTx.get<insertion_order>().begin();
2294- m_mempool.removeRecursive(**it, MemPoolRemovalReason::REORG);
2295+ if (m_mempool) m_mempool->removeRecursive(**it, MemPoolRemovalReason::REORG);
This if (m_mempool)
condition can be at the level of the if (disconnectpool)
above:
0 return false;
1 }
2
3- if (disconnectpool) {
4+ if (m_mempool && disconnectpool) {
5 // Save transactions to re-add to mempool at end of reorg
6 for (auto it = block.vtx.rbegin(); it != block.vtx.rend(); ++it) {
7 disconnectpool->addTransaction(*it);
8@@ -2291,7 +2291,7 @@ bool CChainState::DisconnectTip(BlockValidationState& state, DisconnectedBlockTr
9 while (disconnectpool->DynamicMemoryUsage() > MAX_DISCONNECTED_TX_POOL_SIZE * 1000) {
10 // Drop the earliest entry, and remove its children from the mempool.
11 auto it = disconnectpool->queuedTx.get<insertion_order>().begin();
12- if (m_mempool) m_mempool->removeRecursive(**it, MemPoolRemovalReason::REORG);
13+ m_mempool->removeRecursive(**it, MemPoolRemovalReason::REORG);
14 disconnectpool->removeEntry(it);
15 }
16 }
That would potentially save some work updating the disconnectpool
data structure, which will never get used.
Even better (but not necessarily part of this PR) would be to only create a DisconnectedBlockTransactions
if m_mempool
is non-null, and then update UpdateMempoolForReorg()
and DisconnectedBlockTransactions()
to take optional DisconnectedBlockTransactions*
. There’s no need to create and maintain a DisconnectedBlockTransactions
if we’re never going to use it.
2205@@ -2205,11 +2206,13 @@ static void AppendWarning(bilingual_str& res, const bilingual_str& warn)
2206 }
2207
2208 /** Check warning conditions and do some notifications on new chain tip set. */
2209-static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const CChainParams& chainParams, CChainState& active_chainstate)
2210+static void UpdateTip(CTxMemPool* mempool, const CBlockIndex* pindexNew, const CChainParams& chainParams, CChainState& active_chainstate)
CChainState
to avoid passing mempool
, chainParams
and active_chainstate
.
2403@@ -2401,7 +2404,7 @@ bool CChainState::ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew
2404 int64_t nTime5 = GetTimeMicros(); nTimeChainState += nTime5 - nTime4;
2405 LogPrint(BCLog::BENCH, " - Writing chainstate: %.2fms [%.2fs (%.2fms/blk)]\n", (nTime5 - nTime4) * MILLI, nTimeChainState * MICRO, nTimeChainState * MILLI / nBlocksTotal);
2406 // Remove conflicting transactions from the mempool.;
2407- m_mempool.removeForBlock(blockConnecting.vtx, pindexNew->nHeight);
2408+ if (m_mempool) m_mempool->removeForBlock(blockConnecting.vtx, pindexNew->nHeight);
2409 disconnectpool.removeForBlock(blockConnecting.vtx);
No need to update disconnectpool
if the mempool doesn’t exist:
0 if (m_mempool) {
1 m_mempool->removeForBlock(blockConnecting.vtx, pindexNew->nHeight);
2 disconnectpool.removeForBlock(blockConnecting.vtx);
3 }
2509@@ -2507,7 +2510,7 @@ bool CChainState::ActivateBestChainStep(BlockValidationState& state, CBlockIndex
2510 if (!DisconnectTip(state, &disconnectpool)) {
2511 // This is likely a fatal error, but keep the mempool consistent,
2512 // just in case. Only remove from the mempool in this case.
2513- UpdateMempoolForReorg(*this, m_mempool, disconnectpool, false);
2514+ if (m_mempool) UpdateMempoolForReorg(*this, *m_mempool, disconnectpool, false);
UpdateMempoolForReorg
to either be a member of CChainState
or take CTxMemPool
as an optional pointer, and then return immediately if mempool is null. That would avoid scattering so many if (m_mempool)
checks across multiple validation functions.
CChainState
since I think that makes sense; can strip off the commit if there are any objections.
341@@ -342,11 +342,15 @@ static bool IsCurrentForFeeEstimation(CChainState& active_chainstate) EXCLUSIVE_
342 * Passing fAddToMempool=false will skip trying to add the transactions back,
343 * and instead just erase from the mempool as needed.
344 */
345-
346-static void UpdateMempoolForReorg(CChainState& active_chainstate, CTxMemPool& mempool, DisconnectedBlockTransactions& disconnectpool, bool fAddToMempool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, mempool.cs)
347+void CChainState::UpdateMempoolForReorg(
348+ CChainState& active_chainstate,
In commit “refactor: move UpdateMempoolForReorg into CChainState” (7cdcd08d665570167b06a93e003369f7fa833cec)
active_chainstate
is always *this
right? Can the parameter be dropped?
2291 }
2292 while (disconnectpool->DynamicMemoryUsage() > MAX_DISCONNECTED_TX_POOL_SIZE * 1000) {
2293 // Drop the earliest entry, and remove its children from the mempool.
2294 auto it = disconnectpool->queuedTx.get<insertion_order>().begin();
2295- m_mempool.removeRecursive(**it, MemPoolRemovalReason::REORG);
2296+ if (m_mempool) m_mempool->removeRecursive(**it, MemPoolRemovalReason::REORG);
if
here is not needed, it’s augmented by the check you added above (if (disconnectpool && m_mempool) {
)?
341@@ -342,11 +342,14 @@ static bool IsCurrentForFeeEstimation(CChainState& active_chainstate) EXCLUSIVE_
342 * Passing fAddToMempool=false will skip trying to add the transactions back,
343 * and instead just erase from the mempool as needed.
344 */
345-
346-static void UpdateMempoolForReorg(CChainState& active_chainstate, CTxMemPool& mempool, DisconnectedBlockTransactions& disconnectpool, bool fAddToMempool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, mempool.cs)
347+void CChainState::UpdateMempoolForReorg(
Mixed feelings about this change. I like params reduction, but I think a reader might get confused: “UpdateMempoolForReorg
? But I don’t have mempool.”
I would slightly prefer changing the name to UpdateMempoolForReorgIfExists
or TryUpdateMempoolForReorg
, but feel free to ignore.
586@@ -587,8 +587,9 @@ class CChainState
587 */
588 mutable std::atomic<bool> m_cached_finished_ibd{false};
589
590- //! mempool that is kept in sync with the chain
591- CTxMemPool& m_mempool;
592+ //! Optional mempool that is kept in sync with the chain.
593+ //! Only the active chainstate has a mempool.
594+ CTxMemPool* m_mempool;
This could be a const pointer:
0 CTxMemPool* const m_mempool;
since it’s set in the initializer list and never modified.
328@@ -329,7 +329,8 @@ static bool IsCurrentForFeeEstimation(CChainState& active_chainstate) EXCLUSIVE_
329 return true;
330 }
331
332-/* Make mempool consistent after a reorg, by re-adding or recursively erasing
333+/**
806+ void UpdateMempoolForReorg(
807+ DisconnectedBlockTransactions& disconnectpool,
808+ bool fAddToMempool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs);
809+
810+ /** Check warning conditions and do some notifications on new chain tip set. */
811+ void UpdateTip(const CBlockIndex* pindexNew, CChainState& active_chainstate)
CChainState& active_chainstate
argument - this is a private method in CChainState
where the active_chainstate
arg is always *this
. You can remove the argument and use the implicit this
inside the function.
817+ * in-mempool descendants of such transactions would be removed).
818+ *
819+ * Passing fAddToMempool=false will skip trying to add the transactions back,
820+ * and instead just erase from the mempool as needed.
821+ */
822+ void TryUpdateMempoolForReorg(
UpdateMempoolForReorg()
). The Try
suggests that this operation may fail or that it’s best-effort. In fact, if a mempool exists then it really will be updated to be internally consistent.
2212@@ -2218,11 +2213,11 @@ static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const C
2213 }
2214
2215 bilingual_str warning_messages;
2216- if (!active_chainstate.IsInitialBlockDownload()) {
2217+ if (!this->IsInitialBlockDownload()) {
this->
to call member functions. this->
is implicitly added before the names: https://en.cppreference.com/w/cpp/language/this.
this->
prefix to be explicit about a member function call; last time I talked about this with others it was uncontroversial.
2231@@ -2237,14 +2232,14 @@ static void UpdateTip(CTxMemPool& mempool, const CBlockIndex* pindexNew, const C
2232 pindexNew->GetBlockHash().ToString(), pindexNew->nHeight, pindexNew->nVersion,
2233 log(pindexNew->nChainWork.getdouble())/log(2.0), (unsigned long)pindexNew->nChainTx,
2234 FormatISO8601DateTime(pindexNew->GetBlockTime()),
2235- GuessVerificationProgress(chainParams.TxData(), pindexNew), active_chainstate.CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), active_chainstate.CoinsTip().GetCacheSize(),
2236+ GuessVerificationProgress(m_params.TxData(), pindexNew), this->CoinsTip().DynamicMemoryUsage() * (1.0 / (1<<20)), this->CoinsTip().GetCacheSize(),
this->
here.
600@@ -600,7 +601,10 @@ class CChainState
601 //! CChainState instances.
602 BlockManager& m_blockman;
603
604- explicit CChainState(CTxMemPool& mempool, BlockManager& blockman, std::optional<uint256> from_snapshot_blockhash = std::nullopt);
605+ explicit CChainState(
606+ CTxMemPool* const mempool,
These new const
keywords on the CChainstate::CChainstate
and InitializeChainstate
arguments are harmless but unnecessary. When you’re passing by value (which is what happens when you pass a pointer), making that argument const doesn’t change the interface, since the argument is copied to a locally scoped parameter inside the function. It’s only when passing by reference that marking arguments const matters.
You can tell that it doesn’t make a difference because the function declaration here has the first arg as CTxMemPool* const
, and the function definition in the .cpp file has CTxMemPool*
(non const pointer). The compiler doesn’t complain because the const-ness of a pass by value arg isn’t part of the signature.
mempool
in the body of the function, right?
this makes it clear that we aren’t going to do any pointer arithmetic on mempool
mempool
inside the function is a copy of the pointer, and so whether or not it’s mutated is not part of the contract with the caller, and so doesn’t need to be in the function declaration. Like I said it’s harmless, but you’ll see that there are almost no examples in the code of pass by value parameters being marked const.
373+ m_mempool->removeForReorg(*this, STANDARD_LOCKTIME_VERIFY_FLAGS);
374 // Re-limit mempool size, in case we added any transactions
375- LimitMempoolSize(mempool, active_chainstate.CoinsTip(), gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000, std::chrono::hours{gArgs.GetArg("-mempoolexpiry", DEFAULT_MEMPOOL_EXPIRY)});
376+ LimitMempoolSize(
377+ *m_mempool,
378+ this->CoinsTip(),
this->
19@@ -20,7 +20,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate)
20 {
21 CTxMemPool mempool;
22 BlockManager blockman{};
23- CChainState chainstate{mempool, blockman};
24+ CChainState chainstate{&mempool, blockman};
tx_pool
from mempool
in this test? It’s not used anywhere so presumably can be removed?
Anyone have any idea what the deal is with libsecp tests failing on ARM here?
This doesn’t look like a spurious failure.
@MarcoFalke would probably be most knowledgeable about the qemu-arm coredump #22415 (comment) https://cirrus-ci.com/task/6754077301276672?logs=ci#L3494, I was curious about that too. It seems like it is happening in a libsecp256k1 test so not related? But maybe there are ways to debug.
(I don’t want to bikeshed the style issues, since they don’t actually matter at all. But just for fun I’ll say I do agree with John in not liking mostly spurious const
. Also would drop Try
prefix or replace it with Maybe
since Try
suggests some kind of failure would be expected. I do think having this->
prefixes is more readable in current code to distinguish method calls from function calls, but in non-legacy code it’d be cleaner to use lowerCaseMethod()
and UpperCaseFunction()
names to avoid the ambiguity.)
(~x[m][shift]) << (64 - (1 << usebits))) == 0
failure is expected: https://github.com/bitcoin-core/secp256k1/issues/610#issuecomment-482803876
Since we now have multiple chainstate objects, only one of them is active at any given
time. An active chainstate has a mempool, but there's no point to others having one.
This change will simplify proposed assumeutxo semantics. See the discussion here:
https://github.com/bitcoin/bitcoin/pull/15606#pullrequestreview-692965905
Co-authored-by: Russell Yanofsky <russ@yanofsky.org>
Allows fewer arguments and simplification of call sites.
Co-authored-by: John Newbery <john@johnnewbery.com>
Pushed a rebase:
TryUpdateMempool...
-> MaybeUpdateMempool...
const
s
Unnecessary argument since we can make use of this->m_mempool
Co-authored-by: John Newbery <john@johnnewbery.com>
Makes sense and saves on arguments.
Co-authored-by: John Newbery <john@johnnewbery.com>
tx_pool
removedreview ACK ceb7b35a39145717e2d9d356fd382bd1f95d2a5a 😌
Signature:
0-----BEGIN PGP SIGNED MESSAGE-----
1Hash: SHA512
2
3review ACK ceb7b35a39145717e2d9d356fd382bd1f95d2a5a 😌
4-----BEGIN PGP SIGNATURE-----
5
6iQGzBAEBCgAdFiEE+rVPoUahrI9sLGYTzit1aX5ppUgFAlwqrYAACgkQzit1aX5p
7pUhhlwwAtTlbDcsq2EyaUfR0ZvA00PHL6azSeUF1jZ0G0i3eTSVzYlATm+ePtd0W
8wsJW4atryMHoqAGZeXTpjt8SZ9XqVdlylswiYefDVTmc9qq9T7ejW4uVtzY/sM45
9D6jWRRSUg9xX8c7zrjRaxbPfdGonDTKxkh4BK3v1B9mm2+YVb5EjMDqszzsJs1ef
103VfWNwhRWmfLI9voCAIfOUzFQQC1dWI7NqS7V6MoTnEIpJouK34hvaA51un7iu38
11ER5rUhmVUTT/VxkwbzlOlu4C654IVgAq4S2YQ0NWSIDgsuqxEe/C2nfUlm/bPl/v
12Khu3jCJz9u26BCLOfkBEpftGUhhFK+djxgitDz/ktzD8taU6Fy2sDPp8vjL8cL3B
13//Y87WELvtdUwDxBtqfxkkh+Yy7029uNm3n4Rh6Gex3dsAAZhWAaCKM/1uO1g2+9
14KLFtb8sjMb57JQwG3Cdqs889JKjkUzeHdM52w9eu2WBQyWUhSAApuYsf1CUasjwS
15I/SNB2Cz
16=pjF5
17-----END PGP SIGNATURE-----
Timestamp of file with hash 74406e84f80bcb1056122ddab2c038117cd73fd1e1a79cd5df1958eacc8b9b20 -