Browse Source

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.
0.13
Peter Todd 9 years ago
parent
commit
0847d9cb5f
No known key found for this signature in database
GPG Key ID: 2481403DA5F091FB
  1. 62
      src/main.cpp

62
src/main.cpp

@ -162,6 +162,29 @@ namespace {
*/ */
map<uint256, NodeId> mapBlockSource; map<uint256, NodeId> mapBlockSource;
/**
* Filter for transactions that were recently rejected by
* AcceptToMemoryPool. These are not rerequested until the chain tip
* changes, at which point the entire filter is reset. Protected by
* cs_main.
*
* Without this filter we'd be re-requesting txs from each of our peers,
* increasing bandwidth consumption considerably. For instance, with 100
* peers, half of which relay a tx we don't accept, that might be a 50x
* bandwidth increase. A flooding attacker attempting to roll-over the
* filter using minimum-sized, 60byte, transactions might manage to send
* 1000/sec if we have fast peers, so we pick 120,000 to give our peers a
* two minute window to send invs to us.
*
* Decreasing the false positive rate is fairly cheap, so we pick one in a
* million to make it highly unlikely for users to have issues with this
* filter.
*
* Memory used: 1.7MB
*/
boost::scoped_ptr<CRollingBloomFilter> recentRejects;
uint256 hashRecentRejectsChainTip;
/** Blocks that are in flight, and that are in the queue to be downloaded. Protected by cs_main. */ /** Blocks that are in flight, and that are in the queue to be downloaded. Protected by cs_main. */
struct QueuedBlock { struct QueuedBlock {
uint256 hash; uint256 hash;
@ -3267,6 +3290,7 @@ void UnloadBlockIndex()
setDirtyBlockIndex.clear(); setDirtyBlockIndex.clear();
setDirtyFileInfo.clear(); setDirtyFileInfo.clear();
mapNodeState.clear(); mapNodeState.clear();
recentRejects.reset(NULL);
BOOST_FOREACH(BlockMap::value_type& entry, mapBlockIndex) { BOOST_FOREACH(BlockMap::value_type& entry, mapBlockIndex) {
delete entry.second; delete entry.second;
@ -3320,6 +3344,9 @@ bool InitBlockIndex() {
} }
} }
// Initialize global variables that cannot be constructed at startup.
recentRejects.reset(new CRollingBloomFilter(120000, 0.000001));
return true; return true;
} }
@ -3689,9 +3716,19 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
{ {
case MSG_TX: case MSG_TX:
{ {
bool txInMap = false; if (chainActive.Tip()->GetBlockHash() != hashRecentRejectsChainTip)
txInMap = mempool.exists(inv.hash); {
return txInMap || mapOrphanTransactions.count(inv.hash) || // If the chain tip has changed previously rejected transactions
// might be now valid, e.g. due to a nLockTime'd tx becoming valid,
// or a double-spend. Reset the rejects filter and give those
// txs a second chance.
hashRecentRejectsChainTip = chainActive.Tip()->GetBlockHash();
recentRejects->reset();
}
return recentRejects->contains(inv.hash) ||
mempool.exists(inv.hash) ||
mapOrphanTransactions.count(inv.hash) ||
pcoinsTip->HaveCoins(inv.hash); pcoinsTip->HaveCoins(inv.hash);
} }
case MSG_BLOCK: case MSG_BLOCK:
@ -4292,6 +4329,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
// Probably non-standard or insufficient fee/priority // Probably non-standard or insufficient fee/priority
LogPrint("mempool", " removed orphan tx %s\n", orphanHash.ToString()); LogPrint("mempool", " removed orphan tx %s\n", orphanHash.ToString());
vEraseQueue.push_back(orphanHash); vEraseQueue.push_back(orphanHash);
recentRejects->insert(orphanHash);
} }
mempool.check(pcoinsTip); mempool.check(pcoinsTip);
} }
@ -4309,12 +4347,24 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx); unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx);
if (nEvicted > 0) if (nEvicted > 0)
LogPrint("mempool", "mapOrphan overflow, removed %u tx\n", nEvicted); LogPrint("mempool", "mapOrphan overflow, removed %u tx\n", nEvicted);
} else if (pfrom->fWhitelisted) { } else {
// AcceptToMemoryPool() returned false, possibly because the tx is
// already in the mempool; if the tx isn't in the mempool that
// means it was rejected and we shouldn't ask for it again.
if (!mempool.exists(tx.GetHash())) {
recentRejects->insert(tx.GetHash());
}
if (pfrom->fWhitelisted) {
// Always relay transactions received from whitelisted peers, even // Always relay transactions received from whitelisted peers, even
// if they are already in the mempool (allowing the node to function // if they were rejected from the mempool, allowing the node to
// as a gateway for nodes hidden behind it). // function as a gateway for nodes hidden behind it.
//
// FIXME: This includes invalid transactions, which means a
// whitelisted peer could get us banned! We may want to change
// that.
RelayTransaction(tx); RelayTransaction(tx);
} }
}
int nDoS = 0; int nDoS = 0;
if (state.IsInvalid(nDoS)) if (state.IsInvalid(nDoS))
{ {

Loading…
Cancel
Save