From 822000cf82ce78954209df0bcf56b90c0f42e9b4 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 6 Mar 2017 17:11:33 -0500 Subject: Add pblock to connectTrace at the end of ConnectTip, not start This makes ConnectTip responsible for the ConnectTrace instead of splitting the logic between ActivateBestChainStep and ConnectTip --- src/validation.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'src/validation.cpp') diff --git a/src/validation.cpp b/src/validation.cpp index 239893dc0b..ff38f60813 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2219,24 +2219,23 @@ struct ConnectTrace { * Connect a new block to chainActive. pblock is either NULL or a pointer to a CBlock * corresponding to pindexNew, to bypass loading it again from disk. * - * The block is always added to connectTrace (either after loading from disk or by copying - * pblock) - if that is not intended, care must be taken to remove the last entry in - * blocksConnected in case of failure. + * The block is added to connectTrace if connection succeeds. */ bool static ConnectTip(CValidationState& state, const CChainParams& chainparams, CBlockIndex* pindexNew, const std::shared_ptr& pblock, ConnectTrace& connectTrace) { assert(pindexNew->pprev == chainActive.Tip()); // Read block from disk. int64_t nTime1 = GetTimeMicros(); + std::shared_ptr pthisBlock; if (!pblock) { std::shared_ptr pblockNew = std::make_shared(); - connectTrace.blocksConnected.emplace_back(pindexNew, pblockNew); if (!ReadBlockFromDisk(*pblockNew, pindexNew, chainparams.GetConsensus())) return AbortNode(state, "Failed to read block"); + pthisBlock = pblockNew; } else { - connectTrace.blocksConnected.emplace_back(pindexNew, pblock); + pthisBlock = pblock; } - const CBlock& blockConnecting = *connectTrace.blocksConnected.back().second; + const CBlock& blockConnecting = *pthisBlock; // Apply the block atomically to the chain state. int64_t nTime2 = GetTimeMicros(); nTimeReadFromDisk += nTime2 - nTime1; int64_t nTime3; @@ -2270,6 +2269,8 @@ bool static ConnectTip(CValidationState& state, const CChainParams& chainparams, int64_t nTime6 = GetTimeMicros(); nTimePostConnect += nTime6 - nTime5; nTimeTotal += nTime6 - nTime1; LogPrint(BCLog::BENCH, " - Connect postprocess: %.2fms [%.2fs]\n", (nTime6 - nTime5) * 0.001, nTimePostConnect * 0.000001); LogPrint(BCLog::BENCH, "- Connect block: %.2fms [%.2fs]\n", (nTime6 - nTime1) * 0.001, nTimeTotal * 0.000001); + + connectTrace.blocksConnected.emplace_back(pindexNew, std::move(pthisBlock)); return true; } @@ -2388,8 +2389,6 @@ static bool ActivateBestChainStep(CValidationState& state, const CChainParams& c state = CValidationState(); fInvalidFound = true; fContinue = false; - // If we didn't actually connect the block, don't notify listeners about it - connectTrace.blocksConnected.pop_back(); break; } else { // A system error occurred (disk space, database error, ...). -- cgit v1.2.3 From 29e6e231c88904d0e17187b116db5a958d952bcf Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 6 Mar 2017 17:14:53 -0500 Subject: Make ConnectTrace::blocksConnected private, hide behind accessors --- src/validation.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'src/validation.cpp') diff --git a/src/validation.cpp b/src/validation.cpp index ff38f60813..f2c90e028b 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2212,7 +2212,17 @@ static int64_t nTimePostConnect = 0; * part of a single ActivateBestChainStep call. */ struct ConnectTrace { +private: std::vector > > blocksConnected; + +public: + void BlockConnected(CBlockIndex* pindex, std::shared_ptr pblock) { + blocksConnected.emplace_back(pindex, std::move(pblock)); + } + + std::vector > >& GetBlocksConnected() { + return blocksConnected; + } }; /** @@ -2270,7 +2280,7 @@ bool static ConnectTip(CValidationState& state, const CChainParams& chainparams, LogPrint(BCLog::BENCH, " - Connect postprocess: %.2fms [%.2fs]\n", (nTime6 - nTime5) * 0.001, nTimePostConnect * 0.000001); LogPrint(BCLog::BENCH, "- Connect block: %.2fms [%.2fs]\n", (nTime6 - nTime1) * 0.001, nTimeTotal * 0.000001); - connectTrace.blocksConnected.emplace_back(pindexNew, std::move(pthisBlock)); + connectTrace.BlockConnected(pindexNew, std::move(pthisBlock)); return true; } @@ -2499,7 +2509,7 @@ bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, } // MemPoolConflictRemovalTracker destroyed and conflict evictions are notified // Transactions in the connected block are notified - for (const auto& pair : connectTrace.blocksConnected) { + for (const auto& pair : connectTrace.GetBlocksConnected()) { assert(pair.second); const CBlock& block = *(pair.second); for (unsigned int i = 0; i < block.vtx.size(); i++) -- cgit v1.2.3 From d3167ba9bbefc2e5b7062f81c481547f21c5e44b Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 6 Mar 2017 17:19:22 -0500 Subject: Handle conflicted transactions directly in ConnectTrace --- src/validation.cpp | 84 +++++++++++++++++++++++++----------------------------- 1 file changed, 39 insertions(+), 45 deletions(-) (limited to 'src/validation.cpp') diff --git a/src/validation.cpp b/src/validation.cpp index f2c90e028b..d91afbb714 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -154,39 +154,6 @@ namespace { std::set setDirtyFileInfo; } // anon namespace -/* Use this class to start tracking transactions that are removed from the - * mempool and pass all those transactions through SyncTransaction when the - * object goes out of scope. This is currently only used to call SyncTransaction - * on conflicts removed from the mempool during block connection. Applied in - * ActivateBestChain around ActivateBestStep which in turn calls: - * ConnectTip->removeForBlock->removeConflicts - */ -class MemPoolConflictRemovalTracker -{ -private: - std::vector conflictedTxs; - CTxMemPool &pool; - -public: - MemPoolConflictRemovalTracker(CTxMemPool &_pool) : pool(_pool) { - pool.NotifyEntryRemoved.connect(boost::bind(&MemPoolConflictRemovalTracker::NotifyEntryRemoved, this, _1, _2)); - } - - void NotifyEntryRemoved(CTransactionRef txRemoved, MemPoolRemovalReason reason) { - if (reason == MemPoolRemovalReason::CONFLICT) { - conflictedTxs.push_back(txRemoved); - } - } - - ~MemPoolConflictRemovalTracker() { - pool.NotifyEntryRemoved.disconnect(boost::bind(&MemPoolConflictRemovalTracker::NotifyEntryRemoved, this, _1, _2)); - for (const auto& tx : conflictedTxs) { - GetMainSignals().SyncTransaction(*tx, NULL, CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK); - } - conflictedTxs.clear(); - } -}; - CBlockIndex* FindForkInGlobalIndex(const CChain& chain, const CBlockLocator& locator) { // Find the first block the caller has in the main chain @@ -2210,12 +2177,26 @@ static int64_t nTimePostConnect = 0; /** * Used to track blocks whose transactions were applied to the UTXO state as a * part of a single ActivateBestChainStep call. + * + * This class also tracks transactions that are removed from the mempool as + * conflicts and can be used to pass all those transactions through + * SyncTransaction. */ -struct ConnectTrace { +class ConnectTrace { private: std::vector > > blocksConnected; + std::vector conflictedTxs; + CTxMemPool &pool; public: + ConnectTrace(CTxMemPool &_pool) : pool(_pool) { + pool.NotifyEntryRemoved.connect(boost::bind(&ConnectTrace::NotifyEntryRemoved, this, _1, _2)); + } + + ~ConnectTrace() { + pool.NotifyEntryRemoved.disconnect(boost::bind(&ConnectTrace::NotifyEntryRemoved, this, _1, _2)); + } + void BlockConnected(CBlockIndex* pindex, std::shared_ptr pblock) { blocksConnected.emplace_back(pindex, std::move(pblock)); } @@ -2223,6 +2204,19 @@ public: std::vector > >& GetBlocksConnected() { return blocksConnected; } + + void NotifyEntryRemoved(CTransactionRef txRemoved, MemPoolRemovalReason reason) { + if (reason == MemPoolRemovalReason::CONFLICT) { + conflictedTxs.push_back(txRemoved); + } + } + + void CallSyncTransactionOnConflictedTransactions() { + for (const auto& tx : conflictedTxs) { + GetMainSignals().SyncTransaction(*tx, NULL, CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK); + } + conflictedTxs.clear(); + } }; /** @@ -2470,18 +2464,11 @@ bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, break; const CBlockIndex *pindexFork; - ConnectTrace connectTrace; bool fInitialDownload; { LOCK(cs_main); - { // TODO: Temporarily ensure that mempool removals are notified before - // connected transactions. This shouldn't matter, but the abandoned - // state of transactions in our wallet is currently cleared when we - // receive another notification and there is a race condition where - // notification of a connected conflict might cause an outside process - // to abandon a transaction and then have it inadvertently cleared by - // the notification that the conflicted transaction was evicted. - MemPoolConflictRemovalTracker mrt(mempool); + ConnectTrace connectTrace(mempool); // Destructed before cs_main is unlocked + CBlockIndex *pindexOldTip = chainActive.Tip(); if (pindexMostWork == NULL) { pindexMostWork = FindMostWorkChain(); @@ -2505,8 +2492,15 @@ bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, fInitialDownload = IsInitialBlockDownload(); // throw all transactions though the signal-interface - - } // MemPoolConflictRemovalTracker destroyed and conflict evictions are notified + connectTrace.CallSyncTransactionOnConflictedTransactions(); + + // TODO: Temporarily ensure that mempool removals are notified before + // connected transactions. This shouldn't matter, but the abandoned + // state of transactions in our wallet is currently cleared when we + // receive another notification and there is a race condition where + // notification of a connected conflict might cause an outside process + // to abandon a transaction and then have it inadvertently cleared by + // the notification that the conflicted transaction was evicted. // Transactions in the connected block are notified for (const auto& pair : connectTrace.GetBlocksConnected()) { -- cgit v1.2.3 From a1476877ce7b0614a93b7ba48ebbe71075c0f27c Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Mon, 6 Mar 2017 17:22:50 -0500 Subject: Keep conflictedTxs in ConnectTrace per-block --- src/validation.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'src/validation.cpp') diff --git a/src/validation.cpp b/src/validation.cpp index d91afbb714..cd9d941939 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2179,17 +2179,17 @@ static int64_t nTimePostConnect = 0; * part of a single ActivateBestChainStep call. * * This class also tracks transactions that are removed from the mempool as - * conflicts and can be used to pass all those transactions through - * SyncTransaction. + * conflicts (per block) and can be used to pass all those transactions + * through SyncTransaction. */ class ConnectTrace { private: std::vector > > blocksConnected; - std::vector conflictedTxs; + std::vector > conflictedTxs; CTxMemPool &pool; public: - ConnectTrace(CTxMemPool &_pool) : pool(_pool) { + ConnectTrace(CTxMemPool &_pool) : conflictedTxs(1), pool(_pool) { pool.NotifyEntryRemoved.connect(boost::bind(&ConnectTrace::NotifyEntryRemoved, this, _1, _2)); } @@ -2199,6 +2199,7 @@ public: void BlockConnected(CBlockIndex* pindex, std::shared_ptr pblock) { blocksConnected.emplace_back(pindex, std::move(pblock)); + conflictedTxs.emplace_back(); } std::vector > >& GetBlocksConnected() { @@ -2207,15 +2208,18 @@ public: void NotifyEntryRemoved(CTransactionRef txRemoved, MemPoolRemovalReason reason) { if (reason == MemPoolRemovalReason::CONFLICT) { - conflictedTxs.push_back(txRemoved); + conflictedTxs.back().push_back(txRemoved); } } void CallSyncTransactionOnConflictedTransactions() { - for (const auto& tx : conflictedTxs) { - GetMainSignals().SyncTransaction(*tx, NULL, CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK); + for (const auto& txRemovedForBlock : conflictedTxs) { + for (const auto& tx : txRemovedForBlock) { + GetMainSignals().SyncTransaction(*tx, NULL, CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK); + } } conflictedTxs.clear(); + conflictedTxs.emplace_back(); } }; -- cgit v1.2.3 From f4043349106067de66a3312ed3485149bdb71247 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Tue, 7 Mar 2017 14:43:35 -0500 Subject: Handle SyncTransaction in ActivateBestChain instead of ConnectTrace This makes a later change to move it all into one per-block callback simpler. --- src/validation.cpp | 74 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 48 insertions(+), 26 deletions(-) (limited to 'src/validation.cpp') diff --git a/src/validation.cpp b/src/validation.cpp index cd9d941939..36b0b4ed58 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2174,6 +2174,12 @@ static int64_t nTimeFlush = 0; static int64_t nTimeChainState = 0; static int64_t nTimePostConnect = 0; +struct PerBlockConnectTrace { + CBlockIndex* pindex = NULL; + std::shared_ptr pblock; + std::shared_ptr> conflictedTxs; + PerBlockConnectTrace() : conflictedTxs(std::make_shared>()) {} +}; /** * Used to track blocks whose transactions were applied to the UTXO state as a * part of a single ActivateBestChainStep call. @@ -2181,15 +2187,22 @@ static int64_t nTimePostConnect = 0; * This class also tracks transactions that are removed from the mempool as * conflicts (per block) and can be used to pass all those transactions * through SyncTransaction. + * + * This class assumes (and asserts) that the conflicted transactions for a given + * block are added via mempool callbacks prior to the BlockConnected() associated + * with those transactions. If any transactions are marked conflicted, it is + * assumed that an associated block will always be added. + * + * This class is single-use, once you call GetBlocksConnected() you have to throw + * it away and make a new one. */ class ConnectTrace { private: - std::vector > > blocksConnected; - std::vector > conflictedTxs; + std::vector blocksConnected; CTxMemPool &pool; public: - ConnectTrace(CTxMemPool &_pool) : conflictedTxs(1), pool(_pool) { + ConnectTrace(CTxMemPool &_pool) : blocksConnected(1), pool(_pool) { pool.NotifyEntryRemoved.connect(boost::bind(&ConnectTrace::NotifyEntryRemoved, this, _1, _2)); } @@ -2198,29 +2211,32 @@ public: } void BlockConnected(CBlockIndex* pindex, std::shared_ptr pblock) { - blocksConnected.emplace_back(pindex, std::move(pblock)); - conflictedTxs.emplace_back(); - } - - std::vector > >& GetBlocksConnected() { + assert(!blocksConnected.back().pindex); + assert(pindex); + assert(pblock); + blocksConnected.back().pindex = pindex; + blocksConnected.back().pblock = std::move(pblock); + blocksConnected.emplace_back(); + } + + std::vector& GetBlocksConnected() { + // We always keep one extra block at the end of our list because + // blocks are added after all the conflicted transactions have + // been filled in. Thus, the last entry should always be an empty + // one waiting for the transactions from the next block. We pop + // the last entry here to make sure the list we return is sane. + assert(!blocksConnected.back().pindex); + assert(blocksConnected.back().conflictedTxs->empty()); + blocksConnected.pop_back(); return blocksConnected; } void NotifyEntryRemoved(CTransactionRef txRemoved, MemPoolRemovalReason reason) { + assert(!blocksConnected.back().pindex); if (reason == MemPoolRemovalReason::CONFLICT) { - conflictedTxs.back().push_back(txRemoved); + blocksConnected.back().conflictedTxs->emplace_back(std::move(txRemoved)); } } - - void CallSyncTransactionOnConflictedTransactions() { - for (const auto& txRemovedForBlock : conflictedTxs) { - for (const auto& tx : txRemovedForBlock) { - GetMainSignals().SyncTransaction(*tx, NULL, CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK); - } - } - conflictedTxs.clear(); - conflictedTxs.emplace_back(); - } }; /** @@ -2495,9 +2511,6 @@ bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, pindexFork = chainActive.FindFork(pindexOldTip); fInitialDownload = IsInitialBlockDownload(); - // throw all transactions though the signal-interface - connectTrace.CallSyncTransactionOnConflictedTransactions(); - // TODO: Temporarily ensure that mempool removals are notified before // connected transactions. This shouldn't matter, but the abandoned // state of transactions in our wallet is currently cleared when we @@ -2506,12 +2519,21 @@ bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, // to abandon a transaction and then have it inadvertently cleared by // the notification that the conflicted transaction was evicted. + // throw all transactions though the signal-interface + auto blocksConnected = connectTrace.GetBlocksConnected(); + for (const PerBlockConnectTrace& trace : blocksConnected) { + assert(trace.conflictedTxs); + for (const auto& tx : *trace.conflictedTxs) { + GetMainSignals().SyncTransaction(*tx, NULL, CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK); + } + } + // Transactions in the connected block are notified - for (const auto& pair : connectTrace.GetBlocksConnected()) { - assert(pair.second); - const CBlock& block = *(pair.second); + for (const PerBlockConnectTrace& trace : blocksConnected) { + assert(trace.pblock && trace.pindex); + const CBlock& block = *(trace.pblock); for (unsigned int i = 0; i < block.vtx.size(); i++) - GetMainSignals().SyncTransaction(*block.vtx[i], pair.first, i); + GetMainSignals().SyncTransaction(*block.vtx[i], trace.pindex, i); } } // When we reach this point, we switched to a new tip (stored in pindexNewTip). -- cgit v1.2.3 From 461e49fee2935b1eb4d4ea7bae3023e655c0a6d8 Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Wed, 29 Mar 2017 21:12:42 -0400 Subject: SyncTransaction->TxAddedToMempool/BlockConnected/Disconnected This simplifies fixing the wallet-returns-stale-info issue as we can now hold cs_wallet across an entire block instead of only per-tx (though we only actually do so in the next commit). This change also removes the NOT_IN_BLOCK constant in favor of only passing the CBlockIndex* parameter to SyncTransactions when a new block is being connected, instead of also when a block is being disconnected. This change adds a parameter to BlockConnectedDisconnected which lists the transactions which were removed from mempool due to confliction as a result of this operation. While its somewhat of a shame to make block-validation-logic generate a list of mempool changes to be included in its generated callbacks, fixing this isnt too hard. Further in this change-set, CValidationInterface starts listening to mempool directly, placing it in the middle and giving it a bit of logic to know how to route notifications from block-validation, mempool, etc (though not listening for conflicted-removals yet). --- src/validation.cpp | 33 ++++++--------------------------- 1 file changed, 6 insertions(+), 27 deletions(-) (limited to 'src/validation.cpp') diff --git a/src/validation.cpp b/src/validation.cpp index 36b0b4ed58..c32cdf9dfc 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -949,7 +949,7 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C } } - GetMainSignals().SyncTransaction(tx, NULL, CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK); + GetMainSignals().TransactionAddedToMempool(ptx); return true; } @@ -2120,7 +2120,8 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara CBlockIndex *pindexDelete = chainActive.Tip(); assert(pindexDelete); // Read block from disk. - CBlock block; + std::shared_ptr pblock = std::make_shared(); + CBlock& block = *pblock; if (!ReadBlockFromDisk(block, pindexDelete, chainparams.GetConsensus())) return AbortNode(state, "Failed to read block"); // Apply the block atomically to the chain state. @@ -2162,9 +2163,7 @@ bool static DisconnectTip(CValidationState& state, const CChainParams& chainpara UpdateTip(pindexDelete->pprev, chainparams); // Let wallets know transactions went from 1-confirmed to // 0-confirmed or conflicted: - for (const auto& tx : block.vtx) { - GetMainSignals().SyncTransaction(*tx, pindexDelete->pprev, CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK); - } + GetMainSignals().BlockDisconnected(pblock); return true; } @@ -2511,29 +2510,9 @@ bool ActivateBestChain(CValidationState &state, const CChainParams& chainparams, pindexFork = chainActive.FindFork(pindexOldTip); fInitialDownload = IsInitialBlockDownload(); - // TODO: Temporarily ensure that mempool removals are notified before - // connected transactions. This shouldn't matter, but the abandoned - // state of transactions in our wallet is currently cleared when we - // receive another notification and there is a race condition where - // notification of a connected conflict might cause an outside process - // to abandon a transaction and then have it inadvertently cleared by - // the notification that the conflicted transaction was evicted. - - // throw all transactions though the signal-interface - auto blocksConnected = connectTrace.GetBlocksConnected(); - for (const PerBlockConnectTrace& trace : blocksConnected) { - assert(trace.conflictedTxs); - for (const auto& tx : *trace.conflictedTxs) { - GetMainSignals().SyncTransaction(*tx, NULL, CMainSignals::SYNC_TRANSACTION_NOT_IN_BLOCK); - } - } - - // Transactions in the connected block are notified - for (const PerBlockConnectTrace& trace : blocksConnected) { + for (const PerBlockConnectTrace& trace : connectTrace.GetBlocksConnected()) { assert(trace.pblock && trace.pindex); - const CBlock& block = *(trace.pblock); - for (unsigned int i = 0; i < block.vtx.size(); i++) - GetMainSignals().SyncTransaction(*block.vtx[i], trace.pindex, i); + GetMainSignals().BlockConnected(trace.pblock, trace.pindex, *trace.conflictedTxs); } } // When we reach this point, we switched to a new tip (stored in pindexNewTip). -- cgit v1.2.3