package jelectrum; import java.io.File; import java.util.Set; import java.util.TreeMap; import java.util.Map; import bloomtime.Bloomtime; import java.util.ArrayList; import java.util.Collection; import com.google.protobuf.ByteString; /** * If we used just one "bloomfilter" it would be hard work to check * every time it was wrong. It would also be hard work to make it * large enough to not be wrong. So instead we make layers of bloom filters. * Each layer can be wrong, but probably they won't all. And each layer * is not much work to check. * * So this class is making layers to check. I did number work to * try to set the sizes to make it work well in space and time. * But they might need to be changed. * * It is guessing that there will be 750,000 blocks and 10000 keys * in each block. */ public class BloomLayerCake { public static final int ESTIMATE_KEYS_PER_BLOCK=10000; public static final int MAX_BLOCKS=750000; private File dir; private int max_blocks; private ArrayList<LayerInfo> layers; public BloomLayerCake(File dir) throws Exception { this.dir = dir; max_blocks=MAX_BLOCKS; dir.mkdirs(); layers = new ArrayList<>(); // If any of these values are changed, the entire bloom filter needs to be recreated layers.add(new LayerInfo(0, 10000, 0.03)); layers.add(new LayerInfo(1, 400, 0.03)); layers.add(new LayerInfo(2, 16, 0.03)); layers.add(new LayerInfo(3, 1, 0.03)); } /** * Using this, the string hashes differently on each layer * making multiple layers of false positives less likely * (probably) */ private ByteString getDataForLayer(String addr, int layer) { String s = addr + "_l" + layer; return ByteString.copyFromUtf8(s); } public void addAddresses(int block_height, Collection<String> lst) { for(LayerInfo li : layers) { int slice = li.mapBlockHeightToSlice(block_height); for(String s : lst) { li.bloomtime.accumulateBits(slice, getDataForLayer(s, li.layer_no)); } } } public void flush() { for(LayerInfo li : layers) { li.bloomtime.flushBits(); } } public Set<Integer> getBlockHeightsForAddress(String address) { TreeMap<Integer, Integer> block_ranges=new TreeMap<>(); for(int l=0; l<layers.size(); l++) { ByteString data = getDataForLayer(address, l); if (l == 0) { Set<Integer> slices = layers.get(l).bloomtime.getMatchingSlices(data); for(int slice : slices) { int low = layers.get(l).mapSliceIntoBlockHeightLow(slice); int high = layers.get(l).mapSliceIntoBlockHeightHigh(slice); block_ranges.put(low, high); } } else { TreeMap<Integer, Integer> next_block_ranges=new TreeMap<>(); for(Map.Entry<Integer, Integer> me : block_ranges.entrySet()) { int in_low = me.getKey(); int in_high = me.getValue(); int in_slice_low = layers.get(l).mapBlockHeightToSlice(in_low); int in_slice_high = layers.get(l).mapBlockHeightToSlice(in_high); //System.out.println("At layer " + l + " checking " + in_low + " to " + in_high); Set<Integer> slices = layers.get(l).bloomtime.getMatchingSlices(data, in_slice_low, in_slice_high); for(int slice : slices) { int low = layers.get(l).mapSliceIntoBlockHeightLow(slice); int high = layers.get(l).mapSliceIntoBlockHeightHigh(slice); next_block_ranges.put(low, high); } } block_ranges = next_block_ranges; } } return block_ranges.keySet(); } public class LayerInfo { private int blocks_per_layer; protected Bloomtime bloomtime; protected int layer_no; public LayerInfo(int layer_no, int blocks, double prob) throws Exception { blocks_per_layer = blocks; this.layer_no = layer_no; double estimate_keys = blocks * ESTIMATE_KEYS_PER_BLOCK; int bit_len = (int)Math.round(estimate_keys * Math.log(prob) / Math.log(1 / Math.pow(2, Math.log(2)))); int slices = max_blocks / blocks; if (max_blocks % blocks != 0) slices++; while (slices % 8 != 0) slices++; int hash_count = (int)Math.round(Math.log(2) * bit_len / estimate_keys); bloomtime = new Bloomtime(new File(dir, "layer_" + layer_no), slices, bit_len, hash_count); } public int mapBlockHeightToSlice(int height) { return height / blocks_per_layer; } public int mapSliceIntoBlockHeightHigh(int slice) { return blocks_per_layer * (slice +1); } public int mapSliceIntoBlockHeightLow(int slice) { return blocks_per_layer * slice; } } }