diff --git a/core/src/main/java/com/google/bitcoin/core/AbstractBlockChain.java b/core/src/main/java/com/google/bitcoin/core/AbstractBlockChain.java index c1e8d40e..4685846b 100644 --- a/core/src/main/java/com/google/bitcoin/core/AbstractBlockChain.java +++ b/core/src/main/java/com/google/bitcoin/core/AbstractBlockChain.java @@ -124,6 +124,9 @@ public abstract class AbstractBlockChain { // were downloading the block chain. private final LinkedHashMap orphanBlocks = new LinkedHashMap(); + private static final double FP_ESTIMATOR_DECAY = 0.0001; + private double falsePositiveRate; + /** * Constructs a BlockChain connected to the given list of listeners (eg, wallets) and a store. */ @@ -265,7 +268,12 @@ public abstract class AbstractBlockChain { // a false positive, as expected in any Bloom filtering scheme). The filteredTxn list here will usually // only be full of data when we are catching up to the head of the chain and thus haven't witnessed any // of the transactions. - return add(block.getBlockHeader(), true, block.getTransactionHashes(), block.getAssociatedTransactions()); + boolean success = + add(block.getBlockHeader(), true, block.getTransactionHashes(), block.getAssociatedTransactions()); + if (success) { + onFilteredTransactions(block.getTransactionCount()); + } + return success; } catch (BlockStoreException e) { // TODO: Figure out a better way to propagate this exception to the user. throw new RuntimeException(e); @@ -518,7 +526,7 @@ public abstract class AbstractBlockChain { } } - private static void informListenerForNewTransactions(Block block, NewBlockType newBlockType, + private void informListenerForNewTransactions(Block block, NewBlockType newBlockType, @Nullable List filteredTxHashList, @Nullable Map filteredTxn, StoredBlock newStoredBlock, boolean first, @@ -703,7 +711,7 @@ public abstract class AbstractBlockChain { SIDE_CHAIN } - private static void sendTransactionsToListener(StoredBlock block, NewBlockType blockType, + private void sendTransactionsToListener(StoredBlock block, NewBlockType blockType, BlockChainListener listener, int relativityOffset, List transactions, @@ -714,6 +722,8 @@ public abstract class AbstractBlockChain { if (clone) tx = new Transaction(tx.params, tx.bitcoinSerialize()); listener.receiveFromBlock(tx, block, blockType, relativityOffset++); + } else { + onFalsePositive(tx, block, blockType); } } catch (ScriptException e) { // We don't want scripts we don't understand to break the block chain so just note that this tx was @@ -967,4 +977,27 @@ public abstract class AbstractBlockChain { }, Threading.SAME_THREAD); return result; } + + /** + * The upstream server filtered a number of transactions. Update false-positive estimate based + * on this. + */ + public void onFilteredTransactions(int count) { + falsePositiveRate *= Math.pow(1-FP_ESTIMATOR_DECAY, count); + } + + /** An irrelevant transaction was received. Update false-positive estimate. */ + public void onFalsePositive(Transaction tx, StoredBlock block, AbstractBlockChain.NewBlockType blockType) { + falsePositiveRate += FP_ESTIMATOR_DECAY; + log.warn("false positive, current rate = {}", falsePositiveRate); + } + + /** Resets estimates of false positives, used when the filter is sent to the peer. */ + public void resetFalsePositiveEstimate() { + falsePositiveRate = 0; + } + + public double getFalsePositiveRate() { + return 0; + } } diff --git a/core/src/main/java/com/google/bitcoin/core/FilteredBlock.java b/core/src/main/java/com/google/bitcoin/core/FilteredBlock.java index 8ec81724..55d7bfdc 100644 --- a/core/src/main/java/com/google/bitcoin/core/FilteredBlock.java +++ b/core/src/main/java/com/google/bitcoin/core/FilteredBlock.java @@ -110,4 +110,9 @@ public class FilteredBlock extends Message { public Map getAssociatedTransactions() { return Collections.unmodifiableMap(associatedTransactions); } + + /** Number of transactions in this block, before it was filtered */ + public int getTransactionCount() { + return merkleTree.transactionCount; + } } diff --git a/core/src/main/java/com/google/bitcoin/core/PeerFilterProvider.java b/core/src/main/java/com/google/bitcoin/core/PeerFilterProvider.java index 108945ac..abbdfb88 100644 --- a/core/src/main/java/com/google/bitcoin/core/PeerFilterProvider.java +++ b/core/src/main/java/com/google/bitcoin/core/PeerFilterProvider.java @@ -18,7 +18,7 @@ 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 + * Note that an implementer is responsible for calling {@link PeerGroup#recalculateFastCatchupAndFilter(boolean)} whenever a * change occurs which effects the data provided via this interface. */ public interface PeerFilterProvider { @@ -41,5 +41,6 @@ public interface PeerFilterProvider { */ public BloomFilter getBloomFilter(int size, double falsePositiveRate, long nTweak); + /** Whether this filter provider depends on the server updating the filter on all matches */ boolean isRequiringUpdateAllBloomFilter(); } 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 bf308152..e2b1b208 100644 --- a/core/src/main/java/com/google/bitcoin/core/PeerGroup.java +++ b/core/src/main/java/com/google/bitcoin/core/PeerGroup.java @@ -113,17 +113,22 @@ public class PeerGroup extends AbstractIdleService implements TransactionBroadca // This event listener is added to every peer. It's here so when we announce transactions via an "inv", every // peer can fetch them. - private final AbstractPeerEventListener getDataListener = new AbstractPeerEventListener() { + private final AbstractPeerEventListener peerListener = new AbstractPeerEventListener() { @Override public List getData(Peer peer, GetDataMessage m) { return handleGetData(m); } + + @Override + public void onBlocksDownloaded(Peer peer, Block block, int blocksLeft) { + handleBlocksDownloaded(); + } }; private int minBroadcastConnections = 0; private AbstractWalletEventListener walletEventListener = new AbstractWalletEventListener() { private void onChanged() { - recalculateFastCatchupAndFilter(); + recalculateFastCatchupAndFilter(false); } @Override public void onScriptsAdded(Wallet wallet, List