Skip to content

Commit 9a59420

Browse files
petertoddFuzzbawls
authored andcommitted
Keep track of recently rejected transactions
Nodes can have divergent policies on which transactions they will accept and relay. This can cause you to repeatedly request and reject the same tx after its inved to you from various peers which have accepted it. Here we add rolling bloom filter to keep track of such rejections, clearing the filter every time the chain tip changes. Credit goes to Alex Morcos, who created the patch that this code is based on. Original code by Peter Todd. Refactored to not construct the filter at startup time by Pieter Wuille.
1 parent 34ee777 commit 9a59420

File tree

1 file changed

+57
-9
lines changed

1 file changed

+57
-9
lines changed

src/main.cpp

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,29 @@ uint32_t nBlockSequenceId = 1;
177177
*/
178178
std::map<uint256, NodeId> mapBlockSource;
179179

180+
/**
181+
* Filter for transactions that were recently rejected by
182+
* AcceptToMemoryPool. These are not rerequested until the chain tip
183+
* changes, at which point the entire filter is reset. Protected by
184+
* cs_main.
185+
*
186+
* Without this filter we'd be re-requesting txs from each of our peers,
187+
* increasing bandwidth consumption considerably. For instance, with 100
188+
* peers, half of which relay a tx we don't accept, that might be a 50x
189+
* bandwidth increase. A flooding attacker attempting to roll-over the
190+
* filter using minimum-sized, 60byte, transactions might manage to send
191+
* 1000/sec if we have fast peers, so we pick 120,000 to give our peers a
192+
* two minute window to send invs to us.
193+
*
194+
* Decreasing the false positive rate is fairly cheap, so we pick one in a
195+
* million to make it highly unlikely for users to have issues with this
196+
* filter.
197+
*
198+
* Memory used: 1.7MB
199+
*/
200+
boost::scoped_ptr<CRollingBloomFilter> recentRejects;
201+
uint256 hashRecentRejectsChainTip;
202+
180203
/** Blocks that are in flight, and that are in the queue to be downloaded. Protected by cs_main. */
181204
struct QueuedBlock {
182205
uint256 hash;
@@ -4667,6 +4690,7 @@ void UnloadBlockIndex()
46674690
setDirtyBlockIndex.clear();
46684691
setDirtyFileInfo.clear();
46694692
mapNodeState.clear();
4693+
recentRejects.reset(nullptr);
46704694

46714695
for (BlockMap::value_type& entry : mapBlockIndex) {
46724696
delete entry.second;
@@ -4717,6 +4741,9 @@ bool InitBlockIndex()
47174741
}
47184742
}
47194743

4744+
// Initialize global variables that cannot be constructed at startup.
4745+
recentRejects.reset(new CRollingBloomFilter(120000, 0.000001));
4746+
47204747
return true;
47214748
}
47224749

@@ -5006,9 +5033,18 @@ bool static AlreadyHave(const CInv& inv)
50065033
{
50075034
switch (inv.type) {
50085035
case MSG_TX: {
5009-
bool txInMap = false;
5010-
txInMap = mempool.exists(inv.hash);
5011-
return txInMap || mapOrphanTransactions.count(inv.hash) ||
5036+
if (chainActive.Tip()->GetBlockHash() != hashRecentRejectsChainTip) {
5037+
// If the chain tip has changed previously rejected transactions
5038+
// might be now valid, e.g. due to a nLockTime'd tx becoming valid,
5039+
// or a double-spend. Reset the rejects filter and give those
5040+
// txs a second chance.
5041+
hashRecentRejectsChainTip = chainActive.Tip()->GetBlockHash();
5042+
recentRejects->reset();
5043+
}
5044+
5045+
return recentRejects->contains(inv.hash) ||
5046+
mempool.exists(inv.hash) ||
5047+
mapOrphanTransactions.count(inv.hash) ||
50125048
pcoinsTip->HaveCoins(inv.hash);
50135049
}
50145050
case MSG_BLOCK:
@@ -5737,6 +5773,7 @@ bool static ProcessMessage(CNode* pfrom, std::string strCommand, CDataStream& vR
57375773
// Probably non-standard or insufficient fee/priority
57385774
LogPrint(BCLog::MEMPOOL, " removed orphan tx %s\n", orphanHash.ToString());
57395775
vEraseQueue.push_back(orphanHash);
5776+
recentRejects->insert(orphanHash);
57405777
}
57415778
mempool.check(pcoinsTip);
57425779
}
@@ -5760,12 +5797,23 @@ bool static ProcessMessage(CNode* pfrom, std::string strCommand, CDataStream& vR
57605797
unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx);
57615798
if (nEvicted > 0)
57625799
LogPrint(BCLog::MEMPOOL, "mapOrphan overflow, removed %u tx\n", nEvicted);
5763-
} else if (pfrom->fWhitelisted) {
5764-
// Always relay transactions received from whitelisted peers, even
5765-
// if they are already in the mempool (allowing the node to function
5766-
// as a gateway for nodes hidden behind it).
5767-
5768-
RelayTransaction(tx);
5800+
} else {
5801+
// AcceptToMemoryPool() returned false, possibly because the tx is
5802+
// already in the mempool; if the tx isn't in the mempool that
5803+
// means it was rejected and we shouldn't ask for it again.
5804+
if (!mempool.exists(tx.GetHash())) {
5805+
recentRejects->insert(tx.GetHash());
5806+
}
5807+
if (pfrom->fWhitelisted) {
5808+
// Always relay transactions received from whitelisted peers, even
5809+
// if they were rejected from the mempool, allowing the node to
5810+
// function as a gateway for nodes hidden behind it.
5811+
//
5812+
// FIXME: This includes invalid transactions, which means a
5813+
// whitelisted peer could get us banned! We may want to change
5814+
// that.
5815+
RelayTransaction(tx);
5816+
}
57695817
}
57705818

57715819
int nDoS = 0;

0 commit comments

Comments
 (0)