Skip to content

Commit d104aa0

Browse files
committed
Merge #17951: Use rolling bloom filter of recent block txs for AlreadyHave() check
a029e18 Use rolling bloom filter of recent block tx's for AlreadyHave() check (Suhas Daftuar) Pull request description: In order to determine whether to download or process a relayed transaction, we first try to check whether we already have the transaction -- either in the mempool, in our filter of recently rejected transactions, in our orphan pool, or already confirmed in a block. Prior to this commit, the heuristic for checking whether a transaction was confirmed in a block is based on whether there's a coin cache entry corresponding to the 0- or 1-index vout of the tx. While that is a quick check, it is very imprecise (eg if those outputs were already spent in another block, we wouldn't detect that the transaction has already been confirmed) -- we can do better by just keeping a rolling bloom filter of the transactions in recent blocks, which will better capture the case of a transaction which has been confirmed and then fully spent. This should reduce the bandwidth that we waste by requesting transactions which will not be accepted to the mempool. To avoid relay problems for transactions which have been included in a recent block but then reorged out of the chain, we clear the bloom filter whenever a block is disconnected. ACKs for top commit: MarcoFalke: re-ACK a029e18 only stylistic and comment fixups 🍴 sipa: utACK a029e18 jonatack: Code review ACK a029e18 also built/ran tests and am running bitcoind with mempool debug logging and custom logging. Looked a bit into CRollingBloomFilter and also the mempool median time past checks mentioned above; I don't have a deep understanding of those areas yet but the concept here and changes LGTM. Tests and other optimisations could be added as a follow-up. In favor of seeing this move forward if no major immediate concerns. Tree-SHA512: 784c9a35bcd3af5db469063ac7d26b4bac430e451e5637a34d8a538c3ffd1433abdd3f06e5584e7a84bfa9e791449e61819397b5a6c7890fa59d78ec3ba507b2
2 parents 3e1bf71 + a029e18 commit d104aa0

File tree

2 files changed

+69
-25
lines changed

2 files changed

+69
-25
lines changed

src/net_processing.cpp

Lines changed: 68 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,14 @@ namespace {
148148
std::unique_ptr<CRollingBloomFilter> recentRejects GUARDED_BY(cs_main);
149149
uint256 hashRecentRejectsChainTip GUARDED_BY(cs_main);
150150

151+
/*
152+
* Filter for transactions that have been recently confirmed.
153+
* We use this to avoid requesting transactions that have already been
154+
* confirnmed.
155+
*/
156+
RecursiveMutex g_cs_recent_confirmed_transactions;
157+
std::unique_ptr<CRollingBloomFilter> g_recent_confirmed_transactions GUARDED_BY(g_cs_recent_confirmed_transactions);
158+
151159
/** Blocks that are in flight, and that are in the queue to be downloaded. */
152160
struct QueuedBlock {
153161
uint256 hash;
@@ -1116,6 +1124,16 @@ PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn, BanMan* banman, CS
11161124
// Initialize global variables that cannot be constructed at startup.
11171125
recentRejects.reset(new CRollingBloomFilter(120000, 0.000001));
11181126

1127+
// Blocks don't typically have more than 4000 transactions, so this should
1128+
// be at least six blocks (~1 hr) worth of transactions that we can store.
1129+
// If the number of transactions appearing in a block goes up, or if we are
1130+
// seeing getdata requests more than an hour after initial announcement, we
1131+
// can increase this number.
1132+
// The false positive rate of 1/1M should come out to less than 1
1133+
// transaction per day that would be inadvertently ignored (which is the
1134+
// same probability that we have in the reject filter).
1135+
g_recent_confirmed_transactions.reset(new CRollingBloomFilter(24000, 0.000001));
1136+
11191137
const Consensus::Params& consensusParams = Params().GetConsensus();
11201138
// Stale tip checking and peer eviction are on two different timers, but we
11211139
// don't want them to get out of sync due to drift in the scheduler, so we
@@ -1129,36 +1147,59 @@ PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn, BanMan* banman, CS
11291147
* Evict orphan txn pool entries (EraseOrphanTx) based on a newly connected
11301148
* block. Also save the time of the last tip update.
11311149
*/
1132-
void PeerLogicValidation::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex, const std::vector<CTransactionRef>& vtxConflicted) {
1133-
LOCK(g_cs_orphans);
1150+
void PeerLogicValidation::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex, const std::vector<CTransactionRef>& vtxConflicted)
1151+
{
1152+
{
1153+
LOCK(g_cs_orphans);
11341154

1135-
std::vector<uint256> vOrphanErase;
1155+
std::vector<uint256> vOrphanErase;
11361156

1137-
for (const CTransactionRef& ptx : pblock->vtx) {
1138-
const CTransaction& tx = *ptx;
1157+
for (const CTransactionRef& ptx : pblock->vtx) {
1158+
const CTransaction& tx = *ptx;
11391159

1140-
// Which orphan pool entries must we evict?
1141-
for (const auto& txin : tx.vin) {
1142-
auto itByPrev = mapOrphanTransactionsByPrev.find(txin.prevout);
1143-
if (itByPrev == mapOrphanTransactionsByPrev.end()) continue;
1144-
for (auto mi = itByPrev->second.begin(); mi != itByPrev->second.end(); ++mi) {
1145-
const CTransaction& orphanTx = *(*mi)->second.tx;
1146-
const uint256& orphanHash = orphanTx.GetHash();
1147-
vOrphanErase.push_back(orphanHash);
1160+
// Which orphan pool entries must we evict?
1161+
for (const auto& txin : tx.vin) {
1162+
auto itByPrev = mapOrphanTransactionsByPrev.find(txin.prevout);
1163+
if (itByPrev == mapOrphanTransactionsByPrev.end()) continue;
1164+
for (auto mi = itByPrev->second.begin(); mi != itByPrev->second.end(); ++mi) {
1165+
const CTransaction& orphanTx = *(*mi)->second.tx;
1166+
const uint256& orphanHash = orphanTx.GetHash();
1167+
vOrphanErase.push_back(orphanHash);
1168+
}
11481169
}
11491170
}
1150-
}
11511171

1152-
// Erase orphan transactions included or precluded by this block
1153-
if (vOrphanErase.size()) {
1154-
int nErased = 0;
1155-
for (const uint256& orphanHash : vOrphanErase) {
1156-
nErased += EraseOrphanTx(orphanHash);
1172+
// Erase orphan transactions included or precluded by this block
1173+
if (vOrphanErase.size()) {
1174+
int nErased = 0;
1175+
for (const uint256& orphanHash : vOrphanErase) {
1176+
nErased += EraseOrphanTx(orphanHash);
1177+
}
1178+
LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx included or conflicted by block\n", nErased);
1179+
}
1180+
1181+
g_last_tip_update = GetTime();
1182+
}
1183+
{
1184+
LOCK(g_cs_recent_confirmed_transactions);
1185+
for (const auto ptx : pblock->vtx) {
1186+
g_recent_confirmed_transactions->insert(ptx->GetHash());
11571187
}
1158-
LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx included or conflicted by block\n", nErased);
11591188
}
1189+
}
11601190

1161-
g_last_tip_update = GetTime();
1191+
void PeerLogicValidation::BlockDisconnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex* pindex)
1192+
{
1193+
// To avoid relay problems with transactions that were previously
1194+
// confirmed, clear our filter of recently confirmed transactions whenever
1195+
// there's a reorg.
1196+
// This means that in a 1-block reorg (where 1 block is disconnected and
1197+
// then another block reconnected), our filter will drop to having only one
1198+
// block's worth of transactions in it, but that should be fine, since
1199+
// presumably the most common case of relaying a confirmed transaction
1200+
// should be just after a new block containing it is found.
1201+
LOCK(g_cs_recent_confirmed_transactions);
1202+
g_recent_confirmed_transactions->reset();
11621203
}
11631204

11641205
// All of the following cache a recent block, and are protected by cs_most_recent_block
@@ -1311,12 +1352,14 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
13111352
LOCK(g_cs_orphans);
13121353
if (mapOrphanTransactions.count(inv.hash)) return true;
13131354
}
1314-
const CCoinsViewCache& coins_cache = ::ChainstateActive().CoinsTip();
1355+
1356+
{
1357+
LOCK(g_cs_recent_confirmed_transactions);
1358+
if (g_recent_confirmed_transactions->contains(inv.hash)) return true;
1359+
}
13151360

13161361
return recentRejects->contains(inv.hash) ||
1317-
mempool.exists(inv.hash) ||
1318-
coins_cache.HaveCoinInCache(COutPoint(inv.hash, 0)) || // Best effort: only try output 0 and 1
1319-
coins_cache.HaveCoinInCache(COutPoint(inv.hash, 1));
1362+
mempool.exists(inv.hash);
13201363
}
13211364
case MSG_BLOCK:
13221365
case MSG_WITNESS_BLOCK:

src/net_processing.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class PeerLogicValidation final : public CValidationInterface, public NetEventsI
3333
* Overridden from CValidationInterface.
3434
*/
3535
void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected, const std::vector<CTransactionRef>& vtxConflicted) override;
36+
void BlockDisconnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex* pindex) override;
3637
/**
3738
* Overridden from CValidationInterface.
3839
*/

0 commit comments

Comments
 (0)