Skip to content

Commit 2765d6f

Browse files
glozowtheuni
andcommitted
rewrite DisconnectedBlockTransactions as a list + map
And encapsulate underlying data structures to avoid misuse. It's better to use stdlib instead of boost when we can achieve the same thing. Behavior change: the number returned by DynamicMemoryUsage for the same transactions is higher (due to change in usage or more accurate accounting), which effectively decreases the maximum amount of transactions kept for resubmission in a reorg. Co-authored-by: Cory Fields <cory-nospam-@coryfields.com>
1 parent 79ce9f0 commit 2765d6f

File tree

4 files changed

+83
-68
lines changed

4 files changed

+83
-68
lines changed

src/bench/disconnected_transactions.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ static void Reorg(const ReorgTxns& reorg)
8282
disconnectpool.removeForBlock(reorg.connected_txns_2);
8383

8484
// Sanity Check
85-
assert(disconnectpool.queuedTx.size() == BLOCK_VTX_COUNT - reorg.num_shared);
85+
assert(disconnectpool.size() == BLOCK_VTX_COUNT - reorg.num_shared);
8686

8787
disconnectpool.clear();
8888
}

src/txmempool.h

Lines changed: 55 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -850,29 +850,23 @@ class CCoinsViewMemPool : public CCoinsViewBacked
850850
* Instead, store the disconnected transactions (in order!) as we go, remove any
851851
* that are included in blocks in the new chain, and then process the remaining
852852
* still-unconfirmed transactions at the end.
853+
*
854+
* Order of queuedTx:
855+
* The front of the list should be the most recently-confirmed transactions (transactions at the
856+
* end of vtx of blocks closer to the tip). If memory usage grows too large, we trim from the front
857+
* of the list. After trimming, transactions can be re-added to the mempool from the back of the
858+
* list to the front without running into missing inputs.
853859
*/
860+
class DisconnectedBlockTransactions {
861+
private:
862+
/** Cached dynamic memory usage for the CTransactions (memory for the shared pointers is
863+
* included in the container calculations). */
864+
uint64_t cachedInnerUsage = 0;
865+
std::list<CTransactionRef> queuedTx;
866+
using TxList = decltype(queuedTx);
867+
std::unordered_map<uint256, TxList::iterator, SaltedTxidHasher> iters_by_txid;
854868

855-
// multi_index tag names
856-
struct txid_index {};
857-
struct insertion_order {};
858-
859-
struct DisconnectedBlockTransactions {
860-
typedef boost::multi_index_container<
861-
CTransactionRef,
862-
boost::multi_index::indexed_by<
863-
// sorted by txid
864-
boost::multi_index::hashed_unique<
865-
boost::multi_index::tag<txid_index>,
866-
mempoolentry_txid,
867-
SaltedTxidHasher
868-
>,
869-
// sorted by order in the blockchain
870-
boost::multi_index::sequenced<
871-
boost::multi_index::tag<insertion_order>
872-
>
873-
>
874-
> indexed_disconnected_transactions;
875-
869+
public:
876870
// It's almost certainly a logic bug if we don't clear out queuedTx before
877871
// destruction, as we add to it while disconnecting blocks, and then we
878872
// need to re-process remaining transactions to ensure mempool consistency.
@@ -881,59 +875,79 @@ struct DisconnectedBlockTransactions {
881875
// to be refactored such that this assumption is no longer true (for
882876
// instance if there was some other way we cleaned up the mempool after a
883877
// reorg, besides draining this object).
884-
~DisconnectedBlockTransactions() { assert(queuedTx.empty()); }
885-
886-
indexed_disconnected_transactions queuedTx;
887-
uint64_t cachedInnerUsage = 0;
878+
~DisconnectedBlockTransactions() {
879+
assert(queuedTx.empty());
880+
assert(iters_by_txid.empty());
881+
assert(cachedInnerUsage == 0);
882+
}
888883

889-
// Estimate the overhead of queuedTx to be 6 pointers + an allocation, as
890-
// no exact formula for boost::multi_index_contained is implemented.
891884
size_t DynamicMemoryUsage() const {
892-
return memusage::MallocUsage(sizeof(CTransactionRef) + 6 * sizeof(void*)) * queuedTx.size() + cachedInnerUsage;
885+
return cachedInnerUsage + memusage::DynamicUsage(iters_by_txid) + memusage::DynamicUsage(queuedTx);
893886
}
894887

895-
/** Add transactions from the block, iterating through vtx in reverse order. Callers should call
888+
/** Add transactions from the block, iterating through vtx in reverse order. Callers should call
896889
* this function for blocks in descending order by block height.
897890
* We assume that callers never pass multiple transactions with the same txid, otherwise things
898891
* can go very wrong in removeForBlock due to queuedTx containing an item without a
899892
* corresponding entry in iters_by_txid.
900893
*/
901894
void AddTransactionsFromBlock(const std::vector<CTransactionRef>& vtx)
902895
{
896+
iters_by_txid.reserve(iters_by_txid.size() + vtx.size());
903897
for (auto block_it = vtx.rbegin(); block_it != vtx.rend(); ++block_it) {
904-
queuedTx.insert(*block_it);
905-
cachedInnerUsage += RecursiveDynamicUsage(*block_it);
898+
auto it = queuedTx.insert(queuedTx.end(), *block_it);
899+
iters_by_txid.emplace((*block_it)->GetHash(), it);
900+
cachedInnerUsage += RecursiveDynamicUsage(**block_it);
906901
}
907902
}
908903

909-
// Remove entries based on txid_index, and update memory usage.
904+
/** Remove any entries that are in this block. */
910905
void removeForBlock(const std::vector<CTransactionRef>& vtx)
911906
{
912907
// Short-circuit in the common case of a block being added to the tip
913908
if (queuedTx.empty()) {
914909
return;
915910
}
916-
for (auto const &tx : vtx) {
917-
auto it = queuedTx.find(tx->GetHash());
918-
if (it != queuedTx.end()) {
919-
cachedInnerUsage -= RecursiveDynamicUsage(*it);
920-
queuedTx.erase(it);
911+
for (const auto& tx : vtx) {
912+
auto iter = iters_by_txid.find(tx->GetHash());
913+
if (iter != iters_by_txid.end()) {
914+
auto list_iter = iter->second;
915+
iters_by_txid.erase(iter);
916+
cachedInnerUsage -= RecursiveDynamicUsage(**list_iter);
917+
queuedTx.erase(list_iter);
921918
}
922919
}
923920
}
924921

925-
// Remove an entry by insertion_order index, and update memory usage.
926-
void removeEntry(indexed_disconnected_transactions::index<insertion_order>::type::iterator entry)
922+
/** Remove the first entry and update memory usage. */
923+
CTransactionRef take_first()
927924
{
928-
cachedInnerUsage -= RecursiveDynamicUsage(*entry);
929-
queuedTx.get<insertion_order>().erase(entry);
925+
CTransactionRef first_tx;
926+
if (!queuedTx.empty()) {
927+
first_tx = queuedTx.front();
928+
cachedInnerUsage -= RecursiveDynamicUsage(*queuedTx.front());
929+
iters_by_txid.erase(queuedTx.front()->GetHash());
930+
queuedTx.pop_front();
931+
}
932+
return first_tx;
930933
}
931934

935+
size_t size() const { return queuedTx.size(); }
936+
932937
void clear()
933938
{
934939
cachedInnerUsage = 0;
940+
iters_by_txid.clear();
935941
queuedTx.clear();
936942
}
943+
944+
/** Clear all data structures and return the list of transactions. */
945+
std::list<CTransactionRef> take()
946+
{
947+
std::list<CTransactionRef> ret = std::move(queuedTx);
948+
clear();
949+
return ret;
950+
}
937951
};
938952

939953
#endif // BITCOIN_TXMEMPOOL_H

src/validation.cpp

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -295,28 +295,30 @@ void Chainstate::MaybeUpdateMempoolForReorg(
295295
AssertLockHeld(cs_main);
296296
AssertLockHeld(m_mempool->cs);
297297
std::vector<uint256> vHashUpdate;
298-
// disconnectpool's insertion_order index sorts the entries from
299-
// oldest to newest, but the oldest entry will be the last tx from the
300-
// latest mined block that was disconnected.
301-
// Iterate disconnectpool in reverse, so that we add transactions
302-
// back to the mempool starting with the earliest transaction that had
303-
// been previously seen in a block.
304-
auto it = disconnectpool.queuedTx.get<insertion_order>().rbegin();
305-
while (it != disconnectpool.queuedTx.get<insertion_order>().rend()) {
306-
// ignore validation errors in resurrected transactions
307-
if (!fAddToMempool || (*it)->IsCoinBase() ||
308-
AcceptToMemoryPool(*this, *it, GetTime(),
309-
/*bypass_limits=*/true, /*test_accept=*/false).m_result_type !=
310-
MempoolAcceptResult::ResultType::VALID) {
311-
// If the transaction doesn't make it in to the mempool, remove any
312-
// transactions that depend on it (which would now be orphans).
313-
m_mempool->removeRecursive(**it, MemPoolRemovalReason::REORG);
314-
} else if (m_mempool->exists(GenTxid::Txid((*it)->GetHash()))) {
315-
vHashUpdate.push_back((*it)->GetHash());
316-
}
317-
++it;
318-
}
319-
disconnectpool.queuedTx.clear();
298+
{
299+
// disconnectpool is ordered so that the front is the most recently-confirmed
300+
// transaction (the last tx of the block at the tip) in the disconnected chain.
301+
// Iterate disconnectpool in reverse, so that we add transactions
302+
// back to the mempool starting with the earliest transaction that had
303+
// been previously seen in a block.
304+
const auto queuedTx = disconnectpool.take();
305+
auto it = queuedTx.rbegin();
306+
while (it != queuedTx.rend()) {
307+
// ignore validation errors in resurrected transactions
308+
if (!fAddToMempool || (*it)->IsCoinBase() ||
309+
AcceptToMemoryPool(*this, *it, GetTime(),
310+
/*bypass_limits=*/true, /*test_accept=*/false).m_result_type !=
311+
MempoolAcceptResult::ResultType::VALID) {
312+
// If the transaction doesn't make it in to the mempool, remove any
313+
// transactions that depend on it (which would now be orphans).
314+
m_mempool->removeRecursive(**it, MemPoolRemovalReason::REORG);
315+
} else if (m_mempool->exists(GenTxid::Txid((*it)->GetHash()))) {
316+
vHashUpdate.push_back((*it)->GetHash());
317+
}
318+
++it;
319+
}
320+
}
321+
320322
// AcceptToMemoryPool/addUnchecked all assume that new mempool entries have
321323
// no in-mempool children, which is generally not true when adding
322324
// previously-confirmed transactions back to the mempool.
@@ -2724,9 +2726,8 @@ bool Chainstate::DisconnectTip(BlockValidationState& state, DisconnectedBlockTra
27242726
disconnectpool->AddTransactionsFromBlock(block.vtx);
27252727
while (disconnectpool->DynamicMemoryUsage() > MAX_DISCONNECTED_TX_POOL_SIZE * 1000) {
27262728
// Drop the earliest entry, and remove its children from the mempool.
2727-
auto it = disconnectpool->queuedTx.get<insertion_order>().begin();
2728-
m_mempool->removeRecursive(**it, MemPoolRemovalReason::REORG);
2729-
disconnectpool->removeEntry(it);
2729+
auto ptx = disconnectpool->take_first();
2730+
m_mempool->removeRecursive(*ptx, MemPoolRemovalReason::REORG);
27302731
}
27312732
}
27322733

src/validation.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class CBlockTreeDB;
5151
class CTxMemPool;
5252
class ChainstateManager;
5353
struct ChainTxData;
54-
struct DisconnectedBlockTransactions;
54+
class DisconnectedBlockTransactions;
5555
struct PrecomputedTransactionData;
5656
struct LockPoints;
5757
struct AssumeutxoData;

0 commit comments

Comments
 (0)