From 7c03eefefd495a223c3b907ab654bad0da2cd01e Mon Sep 17 00:00:00 2001 From: Matt Corallo Date: Sat, 21 Jul 2012 16:36:39 +0200 Subject: [PATCH] Send peers a bloom filter from our wallet(s). --- .../com/google/bitcoin/core/PeerGroup.java | 32 +++++++++++++++++-- .../google/bitcoin/core/PeerGroupTest.java | 12 +++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/com/google/bitcoin/core/PeerGroup.java b/core/src/main/java/com/google/bitcoin/core/PeerGroup.java index cb610728..36151ebf 100644 --- a/core/src/main/java/com/google/bitcoin/core/PeerGroup.java +++ b/core/src/main/java/com/google/bitcoin/core/PeerGroup.java @@ -129,6 +129,9 @@ public class PeerGroup extends AbstractIdleService { // Visible for testing Peer.PeerLifecycleListener startupListener = new PeerStartupListener(); + // A bloom filter generated from all connected wallets that is given to new peers + private BloomFilter bloomFilter; + /** * Creates a PeerGroup with the given parameters. No chain is provided so this node will report its chain height * as zero to other peers. This constructor is useful if you just want to explore the network but aren't interested @@ -523,20 +526,36 @@ public class PeerGroup extends AbstractIdleService { wallet.addEventListener(new AbstractWalletEventListener() { @Override public void onKeyAdded(ECKey key) { - recalculateFastCatchupTime(); + recalculateFastCatchupAndFilter(); } }); - recalculateFastCatchupTime(); + recalculateFastCatchupAndFilter(); } - private synchronized void recalculateFastCatchupTime() { + private synchronized void recalculateFastCatchupAndFilter() { // Fully verifying mode doesn't use this optimization (it can't as it needs to see all transactions). if (chain != null && chain.shouldVerifyTransactions()) return; long earliestKeyTime = Long.MAX_VALUE; + int elements = 0; for (Wallet w : wallets) { earliestKeyTime = Math.min(earliestKeyTime, w.getEarliestKeyCreationTime()); + elements += w.getBloomFilterElementCount(); } setFastCatchupTimeSecs(earliestKeyTime); + + if (chain == null || !chain.shouldVerifyTransactions()) { + long nTweak = new Random().nextLong(); + BloomFilter filter = new BloomFilter(elements, 0.001, nTweak); + for (Wallet w : wallets) + filter.merge(w.getBloomFilter(elements, 0.001, nTweak)); + bloomFilter = filter; + log.info("Sending all peers an updated Bloom Filter."); + for (Peer peer : peers) + try { + // peers of a low version will simply ignore filterload messages + peer.sendMessage(filter); + } catch (IOException e) { } + } } /** @@ -647,6 +666,13 @@ public class PeerGroup extends AbstractIdleService { protected synchronized void handleNewPeer(final Peer peer) { // Runs on a netty worker thread for every peer that is newly connected. Peer is not locked at this point. log.info("{}: New peer", peer); + // Give the peer a filter that can be used to probabilistically drop transactions that + // aren't relevant to our wallet. We may still receive some false positives, which is + // OK because it helps improve wallet privacy. + try { + if (bloomFilter != null) + peer.sendMessage(bloomFilter); + } catch (IOException e) { } // That was quick...already disconnected // Link the peer to the memory pool so broadcast transactions have their confidence levels updated. peer.setMemoryPool(memoryPool); peer.setDownloadData(false); diff --git a/core/src/test/java/com/google/bitcoin/core/PeerGroupTest.java b/core/src/test/java/com/google/bitcoin/core/PeerGroupTest.java index 6bdb498c..841772dc 100644 --- a/core/src/test/java/com/google/bitcoin/core/PeerGroupTest.java +++ b/core/src/test/java/com/google/bitcoin/core/PeerGroupTest.java @@ -137,8 +137,10 @@ public class PeerGroupTest extends TestWithNetworkConnections { inv.addTransaction(t1); inbound(p1, inv); + assertTrue(outbound(p1) instanceof BloomFilter); assertTrue(outbound(p1) instanceof GetDataMessage); inbound(p2, inv); + assertTrue(outbound(p2) instanceof BloomFilter); assertNull(outbound(p2)); // Only one peer is used to download. inbound(p1, t1); assertNull(outbound(p2)); @@ -181,7 +183,9 @@ public class PeerGroupTest extends TestWithNetworkConnections { // Only peer 1 tries to download it. inbound(p1, inv); + assertTrue(outbound(p1) instanceof BloomFilter); assertTrue(outbound(p1) instanceof GetDataMessage); + assertTrue(outbound(p2) instanceof BloomFilter); assertNull(outbound(p2)); // Peer 1 goes away. closePeer(peerOf(p1)); @@ -209,6 +213,7 @@ public class PeerGroupTest extends TestWithNetworkConnections { // Expect a zero hash getblocks on p1. This is how the process starts. peerGroup.startBlockChainDownload(new AbstractPeerEventListener() { }); + assertTrue(outbound(p1) instanceof BloomFilter); GetBlocksMessage getblocks = (GetBlocksMessage) outbound(p1); assertEquals(Sha256Hash.ZERO_HASH, getblocks.getStopHash()); // We give back an inv with some blocks in it. @@ -223,6 +228,7 @@ public class PeerGroupTest extends TestWithNetworkConnections { inbound(p1, b1); // Now we successfully connect to another peer. There should be no messages sent. FakeChannel p2 = connectPeer(2); + assertTrue(outbound(p2) instanceof BloomFilter); Message message = (Message)outbound(p2); assertNull(message == null ? "" : message.toString(), message); peerGroup.stop(); @@ -250,12 +256,14 @@ public class PeerGroupTest extends TestWithNetworkConnections { // Peer 2 advertises the tx but does not download it. inbound(p2, inv); + assertTrue(outbound(p2) instanceof BloomFilter); assertTrue(outbound(p2) instanceof GetDataMessage); assertEquals(0, tx.getConfidence().numBroadcastPeers()); assertTrue(peerGroup.getMemoryPool().maybeWasSeen(tx.getHash())); assertNull(event[0]); // Peer 1 advertises the tx, we don't do anything as it's already been requested. inbound(p1, inv); + assertTrue(outbound(p1) instanceof BloomFilter); assertNull(outbound(p1)); inbound(p2, tx); assertNull(outbound(p2)); @@ -300,6 +308,7 @@ public class PeerGroupTest extends TestWithNetworkConnections { // Send ourselves a bit of money. Block b1 = TestUtils.makeSolvedTestBlock(params, blockStore, address); inbound(p1, b1); + assertTrue(outbound(p1) instanceof BloomFilter); assertNull(outbound(p1)); assertEquals(Utils.toNanoCoins(50, 0), wallet.getBalance()); @@ -350,10 +359,12 @@ public class PeerGroupTest extends TestWithNetworkConnections { GetDataMessage getdata = new GetDataMessage(params); getdata.addItem(inv1.getItems().get(0)); inbound(p1, getdata); + assertTrue(outbound(p1) instanceof BloomFilter); Transaction t4 = (Transaction) outbound(p1); assertEquals(t3, t4); FakeChannel p3 = connectPeer(3); + assertTrue(outbound(p3) instanceof BloomFilter); assertTrue(outbound(p3) instanceof InventoryMessage); control.verify(); } @@ -394,6 +405,7 @@ public class PeerGroupTest extends TestWithNetworkConnections { VersionMessage versionMessage = new VersionMessage(params, 2); versionMessage.clientVersion = Pong.MIN_PROTOCOL_VERSION; FakeChannel p1 = connectPeer(1, versionMessage); + assertTrue(outbound(p1) instanceof BloomFilter); Ping ping = (Ping) outbound(p1); inbound(p1, new Pong(ping.getNonce())); assertTrue(peerGroup.getConnectedPeers().get(0).getLastPingTime() < Long.MAX_VALUE);