From 13065580243ec4980ae6dc7426feac38f5446390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Dahlquist?= Date: Wed, 29 May 2019 10:13:37 -0300 Subject: [PATCH] No longer accepting orphan block headers --- pom.xml | 2 +- .../bitcoinj/core/BtcAbstractBlockChain.java | 140 +----------------- .../rsk/bitcoinj/core/BtcBlockChainTest.java | 33 +++++ 3 files changed, 41 insertions(+), 134 deletions(-) create mode 100644 src/test/java/co/rsk/bitcoinj/core/BtcBlockChainTest.java diff --git a/pom.xml b/pom.xml index 7747e3d4a..c34e31750 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 co.rsk.bitcoinj - 0.14.4-rsk-7 + 0.14.4-rsk-8 bitcoinj-thin diff --git a/src/main/java/co/rsk/bitcoinj/core/BtcAbstractBlockChain.java b/src/main/java/co/rsk/bitcoinj/core/BtcAbstractBlockChain.java index 0800162c9..b7e34e9e9 100644 --- a/src/main/java/co/rsk/bitcoinj/core/BtcAbstractBlockChain.java +++ b/src/main/java/co/rsk/bitcoinj/core/BtcAbstractBlockChain.java @@ -20,7 +20,6 @@ import co.rsk.bitcoinj.store.BlockStoreException; import co.rsk.bitcoinj.store.BtcBlockStore; import co.rsk.bitcoinj.wallet.Wallet; -import com.google.common.base.Preconditions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -97,29 +96,6 @@ public abstract class BtcAbstractBlockChain { protected final NetworkParameters params; - // Holds a block header and, optionally, a list of tx hashes or block's transactions - class OrphanBlock { - final BtcBlock block; - final FilteredBlock filteredBlock; - final List filteredTxHashes; - final Map filteredTxn; - OrphanBlock(BtcBlock block, @Nullable List filteredTxHashes, @Nullable Map filteredTxn, FilteredBlock filteredBlock) { - 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; - this.filteredBlock = filteredBlock; - } - public Boolean hasFilteredBlock() { - return filteredBlock != null; - } - } - // Holds blocks that we have received but can't plug into the chain yet, eg because they were created whilst we - // were downloading the block chain. - private final LinkedHashMap orphanBlocks = new LinkedHashMap(); - /** False positive estimation uses a double exponential moving average. */ public static final double FP_ESTIMATOR_ALPHA = 0.0001; /** False positive estimation uses a double exponential moving average. */ @@ -216,7 +192,7 @@ public boolean add(FilteredBlock block) throws VerificationException { * of blocks added during the execution of the add process. */ public BlockchainAddResult addBlock(BtcBlock block) throws VerificationException { - return runAddProcces(block, true, null, null, null); + return runAddProcces(block, null, null, null); } /** @@ -224,13 +200,13 @@ public BlockchainAddResult addBlock(BtcBlock block) throws VerificationException * of blocks added during the execution of the add process. */ public BlockchainAddResult addBlock(FilteredBlock block) throws VerificationException { - return runAddProcces(block.getBlockHeader(), true, block.getTransactionHashes(), block.getAssociatedTransactions(), block); + return runAddProcces(block.getBlockHeader(), block.getTransactionHashes(), block.getAssociatedTransactions(), block); } /** * This code was duplicated on add(Block) and in add(FilteredBlock), as the original comment says. The way to handle exceptions should be improved */ - private BlockchainAddResult runAddProcces(BtcBlock block, boolean tryConnecting, + private BlockchainAddResult runAddProcces(BtcBlock block, @Nullable List filteredTxHashList, @Nullable Map filteredTxn, FilteredBlock filteredBlock) throws VerificationException{ try { // The block has a list of hashes of transactions that matched the Bloom filter, and a list of associated @@ -240,7 +216,7 @@ private BlockchainAddResult runAddProcces(BtcBlock block, boolean tryConnecting, // 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, tryConnecting, filteredTxHashList, filteredTxn, filteredBlock); + return add(block, filteredTxHashList, filteredTxn, filteredBlock); } catch (BlockStoreException e) { // TODO: Figure out a better way to propagate this exception to the user. throw new RuntimeException(e); @@ -263,22 +239,17 @@ private BlockchainAddResult runAddProcces(BtcBlock block, boolean tryConnecting, // filteredTxHashList contains all transactions, filteredTxn just a subset - private BlockchainAddResult add(BtcBlock block, boolean tryConnecting, + private BlockchainAddResult add(BtcBlock block, @Nullable List filteredTxHashList, @Nullable Map filteredTxn, FilteredBlock filteredBlock) throws BlockStoreException, VerificationException { // TODO: Use read/write locks to ensure that during chain download properties are still low latency. BlockchainAddResult result = new BlockchainAddResult(); try { - // Quick check for duplicates to avoid an expensive check further down (in findSplit). This can happen a lot - // when connecting orphan transactions due to the dumb brute force algorithm we use. + // Quick check for duplicates to avoid an expensive check further down (in findSplit). if (block.equals(getChainHead().getHeader())) { result.setSuccess(Boolean.TRUE); return result; } - if (tryConnecting && orphanBlocks.containsKey(block.getHash())) { - result.setSuccess(Boolean.FALSE); - return result; - } // If we want to verify transactions (ie we are running with full blocks), verify that block has transactions if (shouldVerifyTransactions() && block.transactions == null) @@ -308,12 +279,7 @@ private BlockchainAddResult add(BtcBlock block, boolean tryConnecting, // Try linking it to a place in the currently known blocks. if (storedPrev == null) { - // We can't find the previous block. Probably we are still in the process of downloading the chain and a - // block was solved whilst we were doing it. We put it to one side and try to connect it later when we - // have more blocks. - checkState(tryConnecting, "bug in tryConnectingOrphans"); log.warn("Block does not connect: {} prev {}", block.getHashAsString(), block.getPrevBlockHash()); - orphanBlocks.put(block.getHash(), new OrphanBlock(block, filteredTxHashList, filteredTxn, filteredBlock)); result.setSuccess(Boolean.FALSE); return result; } else { @@ -321,35 +287,13 @@ private BlockchainAddResult add(BtcBlock block, boolean tryConnecting, params.checkDifficultyTransitions(storedPrev, block, blockStore); connectBlock(block, storedPrev, shouldVerifyTransactions(), filteredTxHashList, filteredTxn); } - - if (tryConnecting) { - List orphans = tryConnectingOrphans(); - for(OrphanBlock ob : orphans) { - result.addConnectedOrphan(ob.block); - if(ob.hasFilteredBlock()) - result.addConnectedFilteredOrphan(ob.filteredBlock); - } - } + result.setSuccess(Boolean.TRUE); return result; } finally { } } - /** - * Returns the hashes of the currently stored orphan blocks and then deletes them from this objects storage. - * Used by Peer when a filter exhaustion event has occurred and thus any orphan blocks that have been downloaded - * might be inaccurate/incomplete. - */ - public Set drainOrphanBlocks() { - try { - Set hashes = new HashSet(orphanBlocks.keySet()); - orphanBlocks.clear(); - return hashes; - } finally { - } - } - // expensiveChecks enables checks that require looking at blocks further back in the chain // than the previous one when connecting (eg median timestamp check) // It could be exposed, but for now we just set it to shouldVerifyTransactions() @@ -529,45 +473,6 @@ protected void setChainHead(StoredBlock chainHead) throws BlockStoreException { } } - /** - * For each block in orphanBlocks, see if we can now fit it on top of the chain and if so, do so. - */ - private List tryConnectingOrphans() throws VerificationException, BlockStoreException { - // For each block in our orphan list, try and fit it onto the head of the chain. If we succeed remove it - // from the list and keep going. If we changed the head of the list at the end of the round try again until - // we can't fit anything else on the top. - // - // This algorithm is kind of crappy, we should do a topo-sort then just connect them in order, but for small - // numbers of orphan blocks it does OK. - List orphansAdded = new ArrayList(); - int blocksConnectedThisRound; - do { - blocksConnectedThisRound = 0; - Iterator iter = orphanBlocks.values().iterator(); - while (iter.hasNext()) { - OrphanBlock orphanBlock = iter.next(); - // Look up the blocks previous. - StoredBlock prev = getStoredBlockInCurrentScope(orphanBlock.block.getPrevBlockHash()); - if (prev == null) { - // This is still an unconnected/orphan block. - log.debug("Orphan block {} is not connectable right now", orphanBlock.block.getHash()); - continue; - } - // Otherwise we can connect it now. - // False here ensures we don't recurse infinitely downwards when connecting huge chains. - log.info("Connected orphan {}", orphanBlock.block.getHash()); - add(orphanBlock.block, false, orphanBlock.filteredTxHashes, orphanBlock.filteredTxn, orphanBlock.filteredBlock); - orphansAdded.add(orphanBlock); - iter.remove(); - blocksConnectedThisRound++; - } - if (blocksConnectedThisRound > 0) { - log.info("Connected {} orphan blocks.", blocksConnectedThisRound); - } - } while (blocksConnectedThisRound > 0); - return orphansAdded; - } - /** * Returns the block at the head of the current best chain. This is the block which represents the greatest * amount of cumulative work done. @@ -578,37 +483,6 @@ public StoredBlock getChainHead() { } } - /** - * An orphan block is one that does not connect to the chain anywhere (ie we can't find its parent, therefore - * it's an orphan). Typically this occurs when we are downloading the chain and didn't reach the head yet, and/or - * if a block is solved whilst we are downloading. It's possible that we see a small amount of orphan blocks which - * chain together, this method tries walking backwards through the known orphan blocks to find the bottom-most. - * - * @return from or one of froms parents, or null if "from" does not identify an orphan block - */ - @Nullable - public BtcBlock getOrphanRoot(Sha256Hash from) { - try { - OrphanBlock cursor = orphanBlocks.get(from); - if (cursor == null) - return null; - OrphanBlock tmp; - while ((tmp = orphanBlocks.get(cursor.block.getPrevBlockHash())) != null) { - cursor = tmp; - } - return cursor.block; - } finally { - } - } - - /** Returns true if the given block is currently in the orphan blocks list. */ - public boolean isOrphan(Sha256Hash block) { - try { - return orphanBlocks.containsKey(block); - } finally { - } - } - /** * Returns an estimate of when the given block will be reached, assuming a perfect 10 minute average for each * block. This is useful for turning transaction lock times into human readable times. Note that a height in diff --git a/src/test/java/co/rsk/bitcoinj/core/BtcBlockChainTest.java b/src/test/java/co/rsk/bitcoinj/core/BtcBlockChainTest.java new file mode 100644 index 000000000..f80408591 --- /dev/null +++ b/src/test/java/co/rsk/bitcoinj/core/BtcBlockChainTest.java @@ -0,0 +1,33 @@ +package co.rsk.bitcoinj.core; + +import co.rsk.bitcoinj.store.BtcBlockStore; +import co.rsk.bitcoinj.store.BtcMemoryBlockStore; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; + +import static co.rsk.bitcoinj.core.TransactionOutputTest.PARAMS; +import static co.rsk.bitcoinj.core.Utils.HEX; +import static org.junit.Assert.assertFalse; + +public class BtcBlockChainTest { + + BtcAbstractBlockChain blockchain; + + @Before + public void setUp() throws Exception { + final BtcBlockStore blockStore = new BtcMemoryBlockStore(PARAMS); + blockchain = new BtcBlockChain(new Context(PARAMS), blockStore); + } + + @Test + public void orphanBlockNotStored() { + BtcBlock block = new BtcBlock( + PARAMS, 2l, Sha256Hash.of(HEX.decode("0011")), + Sha256Hash.ZERO_HASH, PARAMS.genesisBlock.getTime().getTime() + 1, PARAMS.genesisBlock.getDifficultyTarget(), + 0, new ArrayList()); + assertFalse(blockchain.add(block.cloneAsHeader())); + } + +}