Skip to content

Commit 219b916

Browse files
committed
Merge pull request #6498
a8d0407 Move recentRejects initialization to top of InitBlockIndex (Wladimir J. van der Laan) 0847d9c Keep track of recently rejected transactions (Peter Todd) d741371 Only use randomly created nonces in CRollingBloomFilter. (Pieter Wuille) d2d7ee0 Make CRollingBloomFilter set nTweak for you (Peter Todd) a3d65fe Reuse vector hashing code for uint256 (Pieter Wuille) bbe4108 Add uint256 support to CRollingBloomFilter (Peter Todd)
2 parents 10ac38e + a8d0407 commit 219b916

5 files changed

Lines changed: 109 additions & 23 deletions

File tree

src/bloom.cpp

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "hash.h"
99
#include "script/script.h"
1010
#include "script/standard.h"
11+
#include "random.h"
1112
#include "streams.h"
1213

1314
#include <math.h>
@@ -121,6 +122,12 @@ void CBloomFilter::clear()
121122
isEmpty = true;
122123
}
123124

125+
void CBloomFilter::reset(unsigned int nNewTweak)
126+
{
127+
clear();
128+
nTweak = nNewTweak;
129+
}
130+
124131
bool CBloomFilter::IsWithinSizeConstraints() const
125132
{
126133
return vData.size() <= MAX_BLOOM_FILTER_SIZE && nHashFuncs <= MAX_HASH_FUNCS;
@@ -209,15 +216,17 @@ void CBloomFilter::UpdateEmptyFull()
209216
isEmpty = empty;
210217
}
211218

212-
CRollingBloomFilter::CRollingBloomFilter(unsigned int nElements, double fpRate, unsigned int nTweak) :
213-
b1(nElements * 2, fpRate, nTweak), b2(nElements * 2, fpRate, nTweak)
219+
CRollingBloomFilter::CRollingBloomFilter(unsigned int nElements, double fpRate) :
220+
b1(nElements * 2, fpRate, 0), b2(nElements * 2, fpRate, 0)
214221
{
215222
// Implemented using two bloom filters of 2 * nElements each.
216223
// We fill them up, and clear them, staggered, every nElements
217224
// inserted, so at least one always contains the last nElements
218225
// inserted.
219-
nBloomSize = nElements * 2;
220226
nInsertions = 0;
227+
nBloomSize = nElements * 2;
228+
229+
reset();
221230
}
222231

223232
void CRollingBloomFilter::insert(const std::vector<unsigned char>& vKey)
@@ -234,6 +243,12 @@ void CRollingBloomFilter::insert(const std::vector<unsigned char>& vKey)
234243
}
235244
}
236245

246+
void CRollingBloomFilter::insert(const uint256& hash)
247+
{
248+
vector<unsigned char> data(hash.begin(), hash.end());
249+
insert(data);
250+
}
251+
237252
bool CRollingBloomFilter::contains(const std::vector<unsigned char>& vKey) const
238253
{
239254
if (nInsertions < nBloomSize / 2) {
@@ -242,9 +257,16 @@ bool CRollingBloomFilter::contains(const std::vector<unsigned char>& vKey) const
242257
return b1.contains(vKey);
243258
}
244259

245-
void CRollingBloomFilter::clear()
260+
bool CRollingBloomFilter::contains(const uint256& hash) const
261+
{
262+
vector<unsigned char> data(hash.begin(), hash.end());
263+
return contains(data);
264+
}
265+
266+
void CRollingBloomFilter::reset()
246267
{
247-
b1.clear();
248-
b2.clear();
268+
unsigned int nNewTweak = GetRand(std::numeric_limits<unsigned int>::max());
269+
b1.reset(nNewTweak);
270+
b2.reset(nNewTweak);
249271
nInsertions = 0;
250272
}

src/bloom.h

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ class CBloomFilter
8989
bool contains(const uint256& hash) const;
9090

9191
void clear();
92+
void reset(unsigned int nNewTweak);
9293

9394
//! True if the size is <= MAX_BLOOM_FILTER_SIZE and the number of hash functions is <= MAX_HASH_FUNCS
9495
//! (catch a filter which was just deserialized which was too big)
@@ -103,20 +104,29 @@ class CBloomFilter
103104

104105
/**
105106
* RollingBloomFilter is a probabilistic "keep track of most recently inserted" set.
106-
* Construct it with the number of items to keep track of, and a false-positive rate.
107+
* Construct it with the number of items to keep track of, and a false-positive
108+
* rate. Unlike CBloomFilter, by default nTweak is set to a cryptographically
109+
* secure random value for you. Similarly rather than clear() the method
110+
* reset() is provided, which also changes nTweak to decrease the impact of
111+
* false-positives.
107112
*
108113
* contains(item) will always return true if item was one of the last N things
109114
* insert()'ed ... but may also return true for items that were not inserted.
110115
*/
111116
class CRollingBloomFilter
112117
{
113118
public:
114-
CRollingBloomFilter(unsigned int nElements, double nFPRate, unsigned int nTweak);
119+
// A random bloom filter calls GetRand() at creation time.
120+
// Don't create global CRollingBloomFilter objects, as they may be
121+
// constructed before the randomizer is properly initialized.
122+
CRollingBloomFilter(unsigned int nElements, double nFPRate);
115123

116124
void insert(const std::vector<unsigned char>& vKey);
125+
void insert(const uint256& hash);
117126
bool contains(const std::vector<unsigned char>& vKey) const;
127+
bool contains(const uint256& hash) const;
118128

119-
void clear();
129+
void reset();
120130

121131
private:
122132
unsigned int nBloomSize;

src/main.cpp

Lines changed: 64 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,29 @@ namespace {
162162
*/
163163
map<uint256, NodeId> mapBlockSource;
164164

165+
/**
166+
* Filter for transactions that were recently rejected by
167+
* AcceptToMemoryPool. These are not rerequested until the chain tip
168+
* changes, at which point the entire filter is reset. Protected by
169+
* cs_main.
170+
*
171+
* Without this filter we'd be re-requesting txs from each of our peers,
172+
* increasing bandwidth consumption considerably. For instance, with 100
173+
* peers, half of which relay a tx we don't accept, that might be a 50x
174+
* bandwidth increase. A flooding attacker attempting to roll-over the
175+
* filter using minimum-sized, 60byte, transactions might manage to send
176+
* 1000/sec if we have fast peers, so we pick 120,000 to give our peers a
177+
* two minute window to send invs to us.
178+
*
179+
* Decreasing the false positive rate is fairly cheap, so we pick one in a
180+
* million to make it highly unlikely for users to have issues with this
181+
* filter.
182+
*
183+
* Memory used: 1.7MB
184+
*/
185+
boost::scoped_ptr<CRollingBloomFilter> recentRejects;
186+
uint256 hashRecentRejectsChainTip;
187+
165188
/** Blocks that are in flight, and that are in the queue to be downloaded. Protected by cs_main. */
166189
struct QueuedBlock {
167190
uint256 hash;
@@ -3248,6 +3271,7 @@ void UnloadBlockIndex()
32483271
setDirtyBlockIndex.clear();
32493272
setDirtyFileInfo.clear();
32503273
mapNodeState.clear();
3274+
recentRejects.reset(NULL);
32513275

32523276
BOOST_FOREACH(BlockMap::value_type& entry, mapBlockIndex) {
32533277
delete entry.second;
@@ -3268,6 +3292,10 @@ bool LoadBlockIndex()
32683292
bool InitBlockIndex() {
32693293
const CChainParams& chainparams = Params();
32703294
LOCK(cs_main);
3295+
3296+
// Initialize global variables that cannot be constructed at startup.
3297+
recentRejects.reset(new CRollingBloomFilter(120000, 0.000001));
3298+
32713299
// Check whether we're already initialized
32723300
if (chainActive.Genesis() != NULL)
32733301
return true;
@@ -3670,10 +3698,21 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
36703698
{
36713699
case MSG_TX:
36723700
{
3673-
bool txInMap = false;
3674-
txInMap = mempool.exists(inv.hash);
3675-
return txInMap || mapOrphanTransactions.count(inv.hash) ||
3676-
pcoinsTip->HaveCoins(inv.hash);
3701+
assert(recentRejects);
3702+
if (chainActive.Tip()->GetBlockHash() != hashRecentRejectsChainTip)
3703+
{
3704+
// If the chain tip has changed previously rejected transactions
3705+
// might be now valid, e.g. due to a nLockTime'd tx becoming valid,
3706+
// or a double-spend. Reset the rejects filter and give those
3707+
// txs a second chance.
3708+
hashRecentRejectsChainTip = chainActive.Tip()->GetBlockHash();
3709+
recentRejects->reset();
3710+
}
3711+
3712+
return recentRejects->contains(inv.hash) ||
3713+
mempool.exists(inv.hash) ||
3714+
mapOrphanTransactions.count(inv.hash) ||
3715+
pcoinsTip->HaveCoins(inv.hash);
36773716
}
36783717
case MSG_BLOCK:
36793718
return mapBlockIndex.count(inv.hash);
@@ -4273,6 +4312,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
42734312
// Probably non-standard or insufficient fee/priority
42744313
LogPrint("mempool", " removed orphan tx %s\n", orphanHash.ToString());
42754314
vEraseQueue.push_back(orphanHash);
4315+
assert(recentRejects);
4316+
recentRejects->insert(orphanHash);
42764317
}
42774318
mempool.check(pcoinsTip);
42784319
}
@@ -4290,11 +4331,24 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
42904331
unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx);
42914332
if (nEvicted > 0)
42924333
LogPrint("mempool", "mapOrphan overflow, removed %u tx\n", nEvicted);
4293-
} else if (pfrom->fWhitelisted) {
4294-
// Always relay transactions received from whitelisted peers, even
4295-
// if they are already in the mempool (allowing the node to function
4296-
// as a gateway for nodes hidden behind it).
4297-
RelayTransaction(tx);
4334+
} else {
4335+
// AcceptToMemoryPool() returned false, possibly because the tx is
4336+
// already in the mempool; if the tx isn't in the mempool that
4337+
// means it was rejected and we shouldn't ask for it again.
4338+
if (!mempool.exists(tx.GetHash())) {
4339+
assert(recentRejects);
4340+
recentRejects->insert(tx.GetHash());
4341+
}
4342+
if (pfrom->fWhitelisted) {
4343+
// Always relay transactions received from whitelisted peers, even
4344+
// if they were rejected from the mempool, allowing the node to
4345+
// function as a gateway for nodes hidden behind it.
4346+
//
4347+
// FIXME: This includes invalid transactions, which means a
4348+
// whitelisted peer could get us banned! We may want to change
4349+
// that.
4350+
RelayTransaction(tx);
4351+
}
42984352
}
42994353
int nDoS = 0;
43004354
if (state.IsInvalid(nDoS))
@@ -4797,7 +4851,7 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
47974851
{
47984852
// Periodically clear addrKnown to allow refresh broadcasts
47994853
if (nLastRebroadcast)
4800-
pnode->addrKnown.clear();
4854+
pnode->addrKnown.reset();
48014855

48024856
// Rebroadcast our address
48034857
AdvertizeLocal(pnode);

src/net.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2060,7 +2060,7 @@ unsigned int SendBufferSize() { return 1000*GetArg("-maxsendbuffer", 1*1000); }
20602060

20612061
CNode::CNode(SOCKET hSocketIn, const CAddress& addrIn, const std::string& addrNameIn, bool fInboundIn) :
20622062
ssSend(SER_NETWORK, INIT_PROTO_VERSION),
2063-
addrKnown(5000, 0.001, insecure_rand()),
2063+
addrKnown(5000, 0.001),
20642064
setInventoryKnown(SendBufferSize() / 1000)
20652065
{
20662066
nServices = 0;

src/test/bloom_tests.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,7 @@ static std::vector<unsigned char> RandomData()
469469
BOOST_AUTO_TEST_CASE(rolling_bloom)
470470
{
471471
// last-100-entry, 1% false positive:
472-
CRollingBloomFilter rb1(100, 0.01, 0);
472+
CRollingBloomFilter rb1(100, 0.01);
473473

474474
// Overfill:
475475
static const int DATASIZE=399;
@@ -500,7 +500,7 @@ BOOST_AUTO_TEST_CASE(rolling_bloom)
500500
BOOST_CHECK(nHits < 175);
501501

502502
BOOST_CHECK(rb1.contains(data[DATASIZE-1]));
503-
rb1.clear();
503+
rb1.reset();
504504
BOOST_CHECK(!rb1.contains(data[DATASIZE-1]));
505505

506506
// Now roll through data, make sure last 100 entries
@@ -527,7 +527,7 @@ BOOST_AUTO_TEST_CASE(rolling_bloom)
527527
BOOST_CHECK(nHits < 100);
528528

529529
// last-1000-entry, 0.01% false positive:
530-
CRollingBloomFilter rb2(1000, 0.001, 0);
530+
CRollingBloomFilter rb2(1000, 0.001);
531531
for (int i = 0; i < DATASIZE; i++) {
532532
rb2.insert(data[i]);
533533
}

0 commit comments

Comments
 (0)