/** * Make a filtered block from the payload. Extension point for alternative * serialization format support. */ @Override public FilteredBlock makeFilteredBlock(byte[] payloadBytes) throws ProtocolException { return new FilteredBlock(params, payloadBytes); }
class OrphanBlock { final Block block; final List<Sha256Hash> filteredTxHashes; final Map<Sha256Hash, Transaction> filteredTxn; OrphanBlock(Block block, @Nullable List<Sha256Hash> filteredTxHashes, @Nullable Map<Sha256Hash, Transaction> filteredTxn) { final boolean filtered = filteredTxHashes != null && filteredTxn != null; Preconditions.checkArgument((block.transactions == null && filtered) || (block.transactions != null && !filtered)); this.block = block; this.filteredTxHashes = filteredTxHashes; this.filteredTxn = filteredTxn; } } // Holds blocks that we have received but can't plug into the chain yet, eg because they were created whilst we
@Override public synchronized void onBlocksDownloaded(Peer peer, Block block, @Nullable FilteredBlock filteredBlock, int blocksLeft) { blocksInLastSecond++; bytesInLastSecond += Block.HEADER_SIZE; List<Transaction> blockTransactions = block.getTransactions(); // This whole area of the type hierarchy is a mess. int txCount = (blockTransactions != null ? countAndMeasureSize(blockTransactions) : 0) + (filteredBlock != null ? countAndMeasureSize(filteredBlock.getAssociatedTransactions().values()) : 0); txnsInLastSecond = txnsInLastSecond + txCount; if (filteredBlock != null) origTxnsInLastSecond += filteredBlock.getTransactionCount(); }
@Test public void deserializeFilteredBlock() throws Exception { // Random real block (000000000000dab0130bbcc991d3d7ae6b81aa6f50a798888dfe62337458dc45) // With one tx FilteredBlock block = new FilteredBlock(PARAMS, HEX.decode("0100000079cda856b143d9db2c1caff01d1aecc8630d30625d10e8b4b8b0000000000000b50cc069d6a3e33e3ff84a5c41d9d3febe7c770fdcc96b2c3ff60abe184f196367291b4d4c86041b8fa45d630100000001b50cc069d6a3e33e3ff84a5c41d9d3febe7c770fdcc96b2c3ff60abe184f19630101")); // Check that the header was properly deserialized assertTrue(block.getBlockHeader().getHash().equals(Sha256Hash.wrap("000000000000dab0130bbcc991d3d7ae6b81aa6f50a798888dfe62337458dc45"))); // Check that the partial merkle tree is correct List<Sha256Hash> txesMatched = block.getTransactionHashes(); assertTrue(txesMatched.size() == 1); assertTrue(txesMatched.contains(Sha256Hash.wrap("63194f18be0af63f2c6bc9dc0f777cbefed3d9415c4af83f3ee3a3d669c00cb5"))); // Check round tripping. assertEquals(block, new FilteredBlock(PARAMS, block.bitcoinSerialize())); }
/** * Creates a new FilteredBlock from the given Block, using this filter to select transactions. Matches can cause the * filter to be updated with the matched element, this ensures that when a filter is applied to a block, spends of * matched transactions are also matched. However it means this filter can be mutated by the operation. The returned * filtered block already has the matched transactions associated with it. */ public synchronized FilteredBlock applyAndUpdate(Block block) { List<Transaction> txns = block.getTransactions(); List<Sha256Hash> txHashes = new ArrayList<>(txns.size()); List<Transaction> matched = Lists.newArrayList(); byte[] bits = new byte[(int) Math.ceil(txns.size() / 8.0)]; for (int i = 0; i < txns.size(); i++) { Transaction tx = txns.get(i); txHashes.add(tx.getHash()); if (applyAndUpdate(tx)) { Utils.setBitLE(bits, i); matched.add(tx); } } PartialMerkleTree pmt = PartialMerkleTree.buildFromLeaves(block.getParams(), bits, txHashes); FilteredBlock filteredBlock = new FilteredBlock(block.getParams(), block.cloneAsHeader(), pmt); for (Transaction transaction : matched) filteredBlock.provideTransaction(transaction); return filteredBlock; }
protected void endFilteredBlock(FilteredBlock m) { if (log.isDebugEnabled()) log.debug("{}: Received broadcast filtered block {}", getAddress(), m.getHash().toString()); if (!vDownloadData) { log.debug("{}: Received block we did not ask for: {}", getAddress(), m.getHash().toString()); return; pendingBlockDownloads.remove(m.getBlockHeader().getHash()); try { try { if (awaitingFreshFilter != null) { log.info("Discarding block {} because we're still waiting for a fresh filter", m.getHash()); awaitingFreshFilter.add(m.getHash()); return; // Chain download process is restarted via a call to setBloomFilter. } else if (checkForFilterExhaustion(m)) { log.info("Bloom filter exhausted whilst processing block {}, discarding", m.getHash()); awaitingFreshFilter = new LinkedList<Sha256Hash>(); awaitingFreshFilter.add(m.getHash()); awaitingFreshFilter.addAll(blockChain.drainOrphanBlocks()); return; // Chain download process is restarted via a call to setBloomFilter. invokeOnBlocksDownloaded(m.getBlockHeader(), m); } else { final Block orphanRoot = checkNotNull(blockChain.getOrphanRoot(m.getHash())); blockChainDownloadLocked(orphanRoot.getHash()); } finally {
FilteredBlock filteredBlock = new FilteredBlock(PARAMS, HEX.decode("0100000006e533fd1ada86391f3f6c343204b0d278d4aaec1c0b20aa27ba0300000000006abbb3eb3d733a9fe18967fd7d4c117e4ccbbac5bec4d910d900b3ae0793e77f54241b4d4c86041b4089cc9b0c000000084c30b63cfcdc2d35e3329421b9805ef0c6565d35381ca857762ea0b3a5a128bbca5065ff9617cbcba45eb23726df6498a9b9cafed4f54cbab9d227b0035ddefbbb15ac1d57d0182aaee61c74743a9c4f785895e563909bafec45c9a2b0ff3181d77706be8b1dcc91112eada86d424e2d0a8907c3488b6e44fda5a74a25cbc7d6bb4fa04245f4ac8a1a571d5537eac24adca1454d65eda446055479af6c6d4dd3c9ab658448c10b6921b7a4ce3021eb22ed6bb6a7fde1e5bcc4b1db6615c6abc5ca042127bfaf9f44ebce29cb29c6df9d05b47f35b2edff4f0064b578ab741fa78276222651209fe1a2c4c0fa1c58510aec8b090dd1eb1f82f9d261b8273b525b02ff1a")); assertTrue(filteredBlock.getHash().equals(block.getHash())); List<Sha256Hash> txHashList = filteredBlock.getTransactionHashes(); assertTrue(txHashList.size() == 4);
@Test public void createFilteredBlock() throws Exception { ECKey key1 = new ECKey(); ECKey key2 = new ECKey(); Transaction tx1 = FakeTxBuilder.createFakeTx(PARAMS, Coin.COIN, key1); Transaction tx2 = FakeTxBuilder.createFakeTx(PARAMS, Coin.FIFTY_COINS, key2.toAddress(PARAMS)); Block block = FakeTxBuilder.makeSolvedTestBlock(PARAMS.getGenesisBlock(), Address.fromBase58(PARAMS, "msg2t2V2sWNd85LccoddtWysBTR8oPnkzW"), tx1, tx2); BloomFilter filter = new BloomFilter(4, 0.1, 1); filter.insert(key1); filter.insert(key2); FilteredBlock filteredBlock = filter.applyAndUpdate(block); assertEquals(4, filteredBlock.getTransactionCount()); // This call triggers verification of the just created data. List<Sha256Hash> txns = filteredBlock.getTransactionHashes(); assertTrue(txns.contains(tx1.getHash())); assertTrue(txns.contains(tx2.getHash())); }
/** * Used by {@link Peer} to decide whether or not to discard this block and any blocks building upon it, in case * the Bloom filter used to request them may be exhausted, that is, not have sufficient keys in the deterministic * sequence within it to reliably find relevant transactions. */ public boolean checkForFilterExhaustion(FilteredBlock block) { keyChainGroupLock.lock(); try { int epoch = keyChainGroup.getCombinedKeyLookaheadEpochs(); for (Transaction tx : block.getAssociatedTransactions().values()) { markKeysAsUsed(tx); } int newEpoch = keyChainGroup.getCombinedKeyLookaheadEpochs(); checkState(newEpoch >= epoch); // If the key lookahead epoch has advanced, there was a call to addKeys and the PeerGroup already has a // pending request to recalculate the filter queued up on another thread. The calling Peer should abandon // block at this point and await a new filter before restarting the download. return newEpoch > epoch; } finally { keyChainGroupLock.unlock(); } }
@Override public boolean add(FilteredBlock block) throws VerificationException, PrunedException { boolean success = super.add(block); if (success) { trackFilteredTransactions(block.getTransactionCount()); } return success; } }
/** * Provide this FilteredBlock with a transaction which is in its Merkle tree. * @return false if the tx is not relevant to this FilteredBlock */ public boolean provideTransaction(Transaction tx) throws VerificationException { Sha256Hash hash = tx.getHash(); if (getTransactionHashes().contains(hash)) { associatedTransactions.put(hash, tx); return true; } return false; }
if (!currentFilteredBlock.provideTransaction(tx)) {
/** * Creates a new FilteredBlock from the given Block, using this filter to select transactions. Matches can cause the * filter to be updated with the matched element, this ensures that when a filter is applied to a block, spends of * matched transactions are also matched. However it means this filter can be mutated by the operation. The returned * filtered block already has the matched transactions associated with it. */ public synchronized FilteredBlock applyAndUpdate(Block block) { List<Transaction> txns = block.getTransactions(); List<Sha256Hash> txHashes = new ArrayList<Sha256Hash>(txns.size()); List<Transaction> matched = Lists.newArrayList(); byte[] bits = new byte[(int) Math.ceil(txns.size() / 8.0)]; for (int i = 0; i < txns.size(); i++) { Transaction tx = txns.get(i); txHashes.add(tx.getHash()); if (applyAndUpdate(tx)) { Utils.setBitLE(bits, i); matched.add(tx); } } PartialMerkleTree pmt = PartialMerkleTree.buildFromLeaves(block.getParams(), bits, txHashes); FilteredBlock filteredBlock = new FilteredBlock(block.getParams(), block.cloneAsHeader(), pmt); for (Transaction transaction : matched) filteredBlock.provideTransaction(transaction); return filteredBlock; }
protected void endFilteredBlock(FilteredBlock m) { if (log.isDebugEnabled()) log.debug("{}: Received broadcast filtered block {}", getAddress(), m.getHash().toString()); if (!vDownloadData) { log.debug("{}: Received block we did not ask for: {}", getAddress(), m.getHash().toString()); return; pendingBlockDownloads.remove(m.getBlockHeader().getHash()); try { try { if (awaitingFreshFilter != null) { log.info("Discarding block {} because we're still waiting for a fresh filter", m.getHash()); awaitingFreshFilter.add(m.getHash()); return; // Chain download process is restarted via a call to setBloomFilter. } else if (checkForFilterExhaustion(m)) { log.info("Bloom filter exhausted whilst processing block {}, discarding", m.getHash()); awaitingFreshFilter = new LinkedList<Sha256Hash>(); awaitingFreshFilter.add(m.getHash()); awaitingFreshFilter.addAll(blockChain.drainOrphanBlocks()); return; // Chain download process is restarted via a call to setBloomFilter. invokeOnBlocksDownloaded(m.getBlockHeader(), m); } else { final Block orphanRoot = checkNotNull(blockChain.getOrphanRoot(m.getHash())); blockChainDownloadLocked(orphanRoot.getHash()); } finally {
/** * Used by {@link Peer} to decide whether or not to discard this block and any blocks building upon it, in case * the Bloom filter used to request them may be exhausted, that is, not have sufficient keys in the deterministic * sequence within it to reliably find relevant transactions. */ public boolean checkForFilterExhaustion(FilteredBlock block) { keyChainGroupLock.lock(); try { int epoch = keyChainGroup.getCombinedKeyLookaheadEpochs(); for (Transaction tx : block.getAssociatedTransactions().values()) { markKeysAsUsed(tx); } int newEpoch = keyChainGroup.getCombinedKeyLookaheadEpochs(); checkState(newEpoch >= epoch); // If the key lookahead epoch has advanced, there was a call to addKeys and the PeerGroup already has a // pending request to recalculate the filter queued up on another thread. The calling Peer should abandon // block at this point and await a new filter before restarting the download. return newEpoch > epoch; } finally { keyChainGroupLock.unlock(); } }
@Override public boolean add(FilteredBlock block) throws VerificationException, PrunedException { boolean success = super.add(block); if (success) { trackFilteredTransactions(block.getTransactionCount()); } return success; } }
/** * Provide this FilteredBlock with a transaction which is in its Merkle tree. * @return false if the tx is not relevant to this FilteredBlock */ public boolean provideTransaction(Transaction tx) throws VerificationException { Sha256Hash hash = tx.getHash(); if (getTransactionHashes().contains(hash)) { associatedTransactions.put(hash, tx); return true; } return false; }
if (!currentFilteredBlock.provideTransaction(tx)) {
class OrphanBlock { final Block block; final List<Sha256Hash> filteredTxHashes; final Map<Sha256Hash, Transaction> filteredTxn; OrphanBlock(Block block, @Nullable List<Sha256Hash> filteredTxHashes, @Nullable Map<Sha256Hash, Transaction> filteredTxn) { final boolean filtered = filteredTxHashes != null && filteredTxn != null; Preconditions.checkArgument((block.transactions == null && filtered) || (block.transactions != null && !filtered)); this.block = block; this.filteredTxHashes = filteredTxHashes; this.filteredTxn = filteredTxn; } } // Holds blocks that we have received but can't plug into the chain yet, eg because they were created whilst we
@Override public synchronized void onBlocksDownloaded(Peer peer, Block block, @Nullable FilteredBlock filteredBlock, int blocksLeft) { blocksInLastSecond++; bytesInLastSecond += Block.HEADER_SIZE; List<Transaction> blockTransactions = block.getTransactions(); // This whole area of the type hierarchy is a mess. int txCount = (blockTransactions != null ? countAndMeasureSize(blockTransactions) : 0) + (filteredBlock != null ? countAndMeasureSize(filteredBlock.getAssociatedTransactions().values()) : 0); txnsInLastSecond = txnsInLastSecond + txCount; if (filteredBlock != null) origTxnsInLastSecond += filteredBlock.getTransactionCount(); }