package com.matthewmitchell.peercoinj.net;
import com.matthewmitchell.peercoinj.core.BloomFilter;
import com.matthewmitchell.peercoinj.core.PeerFilterProvider;
import com.google.common.collect.ImmutableList;
import java.util.LinkedList;
import java.util.concurrent.locks.Lock;
// This code is unit tested by the PeerGroup tests.
/**
* <p>A reusable object that will calculate, given a list of {@link com.matthewmitchell.peercoinj.core.PeerFilterProvider}s, a merged
* {@link com.matthewmitchell.peercoinj.core.BloomFilter} and earliest key time for all of them.
* Used by the {@link com.matthewmitchell.peercoinj.core.PeerGroup} class internally.</p>
*
* <p>Thread safety: this class tracks the element count of the last filter it calculated and so must be synchronised
* externally or used from only one thread. It will acquire a lock on each filter in turn before performing the
* calculation because the providers may be mutated in other threads in parallel, but global consistency is required
* to produce a merged filter.</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 double bloomFilterFPRate;
private int lastBloomFilterElementCount;
private BloomFilter lastFilter;
public FilterMerger(double bloomFilterFPRate) {
this.bloomFilterFPRate = bloomFilterFPRate;
}
public static class Result {
public BloomFilter filter;
public long earliestKeyTimeSecs;
public boolean changed;
}
public Result calculate(ImmutableList<PeerFilterProvider> providers) {
LinkedList<Lock> takenLocks = new LinkedList<Lock>();
try {
// Lock all the providers so they cannot be mutated out from underneath us whilst we're in the process
// of calculating the Bloom filter. 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.
for (PeerFilterProvider provider : providers) {
Lock lock = provider.getLock();
lock.lock();
takenLocks.add(lock);
}
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;
BloomFilter filter = new BloomFilter(lastBloomFilterElementCount, bloomFilterFPRate, bloomFilterTweak, bloomFlags);
for (PeerFilterProvider p : providers)
filter.merge(p.getBloomFilter(lastBloomFilterElementCount, bloomFilterFPRate, 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 (Lock takenLock : takenLocks) {
takenLock.unlock();
}
}
}
public void setBloomFilterFPRate(double bloomFilterFPRate) {
this.bloomFilterFPRate = bloomFilterFPRate;
}
public double getBloomFilterFPRate() {
return bloomFilterFPRate;
}
public BloomFilter getLastFilter() {
return lastFilter;
}
}