3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-07 14:54:15 +00:00

Peer Bloom filter generation -> new PeerFilterProvider interface

This commit is contained in:
Matt Corallo 2013-07-09 19:07:05 +02:00 committed by Mike Hearn
parent 2061d28e52
commit b51485ca23
4 changed files with 118 additions and 44 deletions

View File

@ -0,0 +1,43 @@
/*
* Copyright 2013 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.bitcoin.core;
/**
* An interface which provides the information required to properly filter data downloaded from Peers.
* Note that an implementer is responsible for calling {@link PeerGroup#recalculateFastCatchupAndFilter()} whenever a
* change occurs which effects the data provided via this interface.
*/
public interface PeerFilterProvider {
/**
* Returns the earliest timestamp (seconds since epoch) for which full/bloom-filtered blocks must be downloaded.
* Blocks with timestamps before this time will only have headers downloaded. 0 requires that all blocks be
* downloaded, and thus this should default to {@link System#currentTimeMillis()}/1000.
*/
public long getEarliestKeyCreationTime();
/**
* Gets the number of elements that will be added to a bloom filter returned by
* {@link PeerFilterProvider#getBloomFilter(int, double, long)}
*/
public int getBloomFilterElementCount();
/**
* Gets a bloom filter that contains all the necessary elements for the listener to receive relevant transactions.
* Default value should be an empty bloom filter with the given size, falsePositiveRate, and nTweak.
*/
public BloomFilter getBloomFilter(int size, double falsePositiveRate, long nTweak);
}

View File

@ -36,6 +36,7 @@ import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.IOException;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
@ -113,6 +114,7 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca
private final AbstractBlockChain chain;
@GuardedBy("lock") private long fastCatchupTimeSecs;
private final CopyOnWriteArrayList<Wallet> wallets;
private final CopyOnWriteArrayList<PeerFilterProvider> peerFilterProviders;
// This event listener is added to every peer. It's here so when we announce transactions via an "inv", every
// peer can fetch them.
@ -126,15 +128,12 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca
private ClientBootstrap bootstrap;
private int minBroadcastConnections = 0;
private AbstractWalletEventListener walletEventListener = new AbstractWalletEventListener() {
@Override
public void onKeysAdded(Wallet wallet, List<ECKey> keys) {
lock.lock();
try {
recalculateFastCatchupAndFilter();
} finally {
lock.unlock();
}
private void onChanged() {
recalculateFastCatchupAndFilter();
}
@Override public void onKeysAdded(Wallet wallet, List<ECKey> keys) { onChanged(); }
@Override public void onCoinsReceived(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { onChanged(); }
@Override public void onCoinsSent(Wallet wallet, Transaction tx, BigInteger prevBalance, BigInteger newBalance) { onChanged(); }
};
private class PeerStartupListener implements Peer.PeerLifecycleListener {
@ -205,6 +204,7 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca
this.chain = chain;
this.fastCatchupTimeSecs = params.getGenesisBlock().getTimeSeconds();
this.wallets = new CopyOnWriteArrayList<Wallet>();
this.peerFilterProviders = new CopyOnWriteArrayList<PeerFilterProvider>();
// This default sentinel value will be overridden by one of two actions:
// - adding a peer discovery source sets it to the default
@ -402,7 +402,7 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca
// Note that the default here means that no tx invs will be received if no wallet is ever added
lock.lock();
try {
ver.relayTxesBeforeFilter = chain != null && chain.shouldVerifyTransactions() && wallets.size() > 0;
ver.relayTxesBeforeFilter = chain != null && chain.shouldVerifyTransactions() && peerFilterProviders.size() > 0;
} finally {
lock.unlock();
}
@ -613,13 +613,34 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca
Preconditions.checkNotNull(wallet);
Preconditions.checkState(!wallets.contains(wallet));
wallets.add(wallet);
announcePendingWalletTransactions(Collections.singletonList(wallet), peers);
wallet.addEventListener(walletEventListener); // TODO: Run this in the current peer thread.
addPeerFilterProvider(wallet);
} finally {
lock.unlock();
}
}
/**
* <p>Link the given PeerFilterProvider to this PeerGroup. DO NOT use this for Wallets, use
* {@link PeerGroup#addWallet(Wallet)} instead.</p>
*
* <p>Note that this should be done before chain download commences because if you add a listener with keys earlier
* than the current chain head, the relevant parts of the chain won't be redownloaded for you.</p>
*/
public void addPeerFilterProvider(PeerFilterProvider provider) {
lock.lock();
try {
Preconditions.checkNotNull(provider);
Preconditions.checkState(!peerFilterProviders.contains(provider));
peerFilterProviders.add(provider);
// Don't bother downloading block bodies before the oldest keys in all our wallets. Make sure we recalculate
// if a key is added. Of course, by then we may have downloaded the chain already. Ideally adding keys would
// automatically rewind the block chain and redownload the blocks to find transactions relevant to those keys,
// all transparently and in the background. But we are a long way from that yet.
wallet.addEventListener(walletEventListener); // TODO: Run this in the current peer thread.
recalculateFastCatchupAndFilter();
updateVersionMessageRelayTxesBeforeFilter(getVersionMessage());
} finally {
@ -632,40 +653,51 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca
*/
public void removeWallet(Wallet wallet) {
wallets.remove(checkNotNull(wallet));
peerFilterProviders.remove(wallet);
wallet.removeEventListener(walletEventListener);
}
private void recalculateFastCatchupAndFilter() {
checkState(lock.isLocked());
// 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();
}
/**
* Recalculates the bloom filter given to peers as well as the timestamp after which full blocks are downloaded
* (instead of only headers).
*/
public void recalculateFastCatchupAndFilter() {
lock.lock();
try {
// 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 (PeerFilterProvider p : peerFilterProviders) {
earliestKeyTime = Math.min(earliestKeyTime, p.getEarliestKeyCreationTime());
elements += p.getBloomFilterElementCount();
}
if (elements > 0) {
// We stair-step our element count so that we avoid creating a filter with different parameters
// as much as possible as that results in a loss of privacy.
// The constant 100 here is somewhat arbitrary, but makes sense for small to medium wallets -
// it will likely mean we never need to create a filter with different parameters.
lastBloomFilterElementCount = elements > lastBloomFilterElementCount ? elements + 100 : lastBloomFilterElementCount;
BloomFilter filter = new BloomFilter(lastBloomFilterElementCount, bloomFilterFPRate, bloomFilterTweak);
for (Wallet w : wallets)
filter.merge(w.getBloomFilter(lastBloomFilterElementCount, bloomFilterFPRate, bloomFilterTweak));
bloomFilter = filter;
for (Peer peer : peers)
try {
peer.setBloomFilter(filter);
} catch (IOException e) {
throw new RuntimeException(e);
if (elements > 0) {
// We stair-step our element count so that we avoid creating a filter with different parameters
// as much as possible as that results in a loss of privacy.
// The constant 100 here is somewhat arbitrary, but makes sense for small to medium wallets -
// it will likely mean we never need to create a filter with different parameters.
lastBloomFilterElementCount = elements > lastBloomFilterElementCount ? elements + 100 : lastBloomFilterElementCount;
BloomFilter filter = new BloomFilter(lastBloomFilterElementCount, bloomFilterFPRate, bloomFilterTweak);
for (PeerFilterProvider p : peerFilterProviders)
filter.merge(p.getBloomFilter(lastBloomFilterElementCount, bloomFilterFPRate, bloomFilterTweak));
if (!filter.equals(bloomFilter)) {
bloomFilter = filter;
for (Peer peer : peers)
try {
peer.setBloomFilter(filter);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// Do this last so that bloomFilter is already set when it gets called.
setFastCatchupTimeSecs(earliestKeyTime);
} finally {
lock.unlock();
}
// Do this last so that bloomFilter is already set when it gets called.
setFastCatchupTimeSecs(earliestKeyTime);
}
/**

View File

@ -79,7 +79,7 @@ import static com.google.common.base.Preconditions.*;
* {@link Wallet#autosaveToFile(java.io.File, long, java.util.concurrent.TimeUnit, com.google.bitcoin.core.Wallet.AutosaveEventListener)}
* for more information about this.</p>
*/
public class Wallet implements Serializable, BlockChainListener {
public class Wallet implements Serializable, BlockChainListener, PeerFilterProvider {
private static final Logger log = LoggerFactory.getLogger(Wallet.class);
private static final long serialVersionUID = 2L;
@ -2527,6 +2527,7 @@ public class Wallet implements Serializable, BlockChainListener {
*
* If there are no keys in the wallet, the current time is returned.
*/
@Override
public long getEarliestKeyCreationTime() {
lock.lock();
try {
@ -2876,9 +2877,7 @@ public class Wallet implements Serializable, BlockChainListener {
return description;
}
/**
* Gets the number of elements that will be added to a bloom filter returned by getBloomFilter
*/
@Override
public int getBloomFilterElementCount() {
int size = getKeychainSize() * 2;
for (Transaction tx : getTransactions(false)) {
@ -2912,6 +2911,7 @@ public class Wallet implements Serializable, BlockChainListener {
*
* See the docs for {@link BloomFilter(int, double)} for a brief explanation of anonymity when using bloom filters.
*/
@Override
public BloomFilter getBloomFilter(int size, double falsePositiveRate, long nTweak) {
BloomFilter filter = new BloomFilter(size, falsePositiveRate, nTweak);
lock.lock();

View File

@ -318,8 +318,7 @@ public class PeerGroupTest extends TestWithPeerGroup {
peerGroup.addWallet(wallet);
// Transaction announced to the first peer.
InventoryMessage inv1 = (InventoryMessage) outbound(p1);
assertTrue(outbound(p1) instanceof BloomFilter); // Filter is recalculated.
assertTrue(outbound(p1) instanceof MemoryPoolMessage);
// Filter is still the same as it was, so it is not rebroadcast
assertEquals(t3.getHash(), inv1.getItems().get(0).hash);
// Peer asks for the transaction, and get it.
GetDataMessage getdata = new GetDataMessage(params);