/* * Copyright by the original author or authors. * * 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 org.bitcoinj.net; import com.google.common.collect.Lists; import org.bitcoinj.core.BloomFilter; import org.bitcoinj.core.PeerFilterProvider; import com.google.common.collect.ImmutableList; import java.util.LinkedList; // This code is unit tested by the PeerGroup tests. /** * <p>A reusable object that will calculate, given a list of {@link org.bitcoinj.core.PeerFilterProvider}s, a merged * {@link org.bitcoinj.core.BloomFilter} and earliest key time for all of them. * Used by the {@link org.bitcoinj.core.PeerGroup} class internally.</p> * * <p>Thread safety: threading here can be complicated. Each filter provider is given a begin event, which may acquire * a lock (and is guaranteed to receive an end event). This class is mostly thread unsafe and is meant to be used from a * single thread only, PeerGroup ensures this by only accessing it from the dedicated PeerGroup thread. PeerGroup does * not hold any locks whilst this object is used, relying on the single thread to prevent multiple filters being * calculated in parallel, thus a filter provider can do things like make blocking calls into PeerGroup from a separate * thread. However the bloomFilterFPRate property IS thread safe, for convenience.</p> */ public class FilterMerger { // We use a constant tweak to avoid giving up privacy when we regenerate our filter with new keys private final long bloomFilterTweak = (long) (Math.random() * Long.MAX_VALUE); private volatile double vBloomFilterFPRate; private int lastBloomFilterElementCount; private BloomFilter lastFilter; public FilterMerger(double bloomFilterFPRate) { this.vBloomFilterFPRate = bloomFilterFPRate; } public static class Result { public BloomFilter filter; public long earliestKeyTimeSecs; public boolean changed; } public Result calculate(ImmutableList<PeerFilterProvider> providers) { LinkedList<PeerFilterProvider> begunProviders = Lists.newLinkedList(); try { // All providers must be in a consistent, unchanging state because the filter is a merged one that's // large enough for all providers elements: if a provider were to get more elements in the middle of the // calculation, we might assert or calculate the filter wrongly. Most providers use a lock here but // snapshotting required state is also a legitimate strategy. for (PeerFilterProvider provider : providers) { provider.beginBloomFilterCalculation(); begunProviders.add(provider); } Result result = new Result(); result.earliestKeyTimeSecs = Long.MAX_VALUE; int elements = 0; boolean requiresUpdateAll = false; for (PeerFilterProvider p : providers) { result.earliestKeyTimeSecs = Math.min(result.earliestKeyTimeSecs, p.getEarliestKeyCreationTime()); elements += p.getBloomFilterElementCount(); requiresUpdateAll = requiresUpdateAll || p.isRequiringUpdateAllBloomFilter(); } 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.BloomUpdate bloomFlags = requiresUpdateAll ? BloomFilter.BloomUpdate.UPDATE_ALL : BloomFilter.BloomUpdate.UPDATE_P2PUBKEY_ONLY; double fpRate = vBloomFilterFPRate; BloomFilter filter = new BloomFilter(lastBloomFilterElementCount, fpRate, bloomFilterTweak, bloomFlags); for (PeerFilterProvider p : providers) filter.merge(p.getBloomFilter(lastBloomFilterElementCount, fpRate, bloomFilterTweak)); result.changed = !filter.equals(lastFilter); result.filter = lastFilter = filter; } // Now adjust the earliest key time backwards by a week to handle the case of clock drift. This can occur // both in block header timestamps and if the users clock was out of sync when the key was first created // (to within a small amount of tolerance). result.earliestKeyTimeSecs -= 86400 * 7; return result; } finally { for (PeerFilterProvider provider : begunProviders) { provider.endBloomFilterCalculation(); } } } public void setBloomFilterFPRate(double bloomFilterFPRate) { this.vBloomFilterFPRate = bloomFilterFPRate; } public double getBloomFilterFPRate() { return vBloomFilterFPRate; } public BloomFilter getLastFilter() { return lastFilter; } }