From cf3028076769da48081ee040a2600cb724056f19 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Sat, 7 Apr 2012 23:28:18 +0200 Subject: [PATCH] Catch up with new testnet rules. Resolves issue 164. --- .../com/google/bitcoin/core/BlockChain.java | 46 +++++++++++++++++-- .../bitcoin/core/NetworkParameters.java | 27 ++++------- .../java/com/google/bitcoin/core/Peer.java | 2 +- .../google/bitcoin/core/BlockChainTest.java | 1 - 4 files changed, 52 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/com/google/bitcoin/core/BlockChain.java b/core/src/main/java/com/google/bitcoin/core/BlockChain.java index 36a77899..54bb2aad 100644 --- a/core/src/main/java/com/google/bitcoin/core/BlockChain.java +++ b/core/src/main/java/com/google/bitcoin/core/BlockChain.java @@ -198,7 +198,7 @@ public class BlockChain { // 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 tryConnectingUnconnected"); - log.warn("Block does not connect: {}", block.getHashAsString()); + log.warn("Block does not connect: {} prev {}", block.getHashAsString(), block.getPrevBlockHash()); unconnectedBlocks.add(block); return false; } else { @@ -207,8 +207,7 @@ public class BlockChain { // Create a new StoredBlock from this block. It will throw away the transaction data so when block goes // out of scope we will reclaim the used memory. StoredBlock newStoredBlock = storedPrev.build(block); - if (params.checkBlockDifficulty) - checkDifficultyTransitions(storedPrev, newStoredBlock); + checkDifficultyTransitions(storedPrev, newStoredBlock); blockStore.put(newStoredBlock); connectBlock(newStoredBlock, storedPrev, block.transactions); } @@ -406,6 +405,9 @@ public class BlockChain { } while (blocksConnectedThisRound > 0); } + // February 16th 2012 + private static Date testnetDiffDate = new Date(1329264000000L); + /** * Throws an exception if the blocks difficulty is not correct. */ @@ -413,8 +415,18 @@ public class BlockChain { throws BlockStoreException, VerificationException { Block prev = storedPrev.getHeader(); Block next = storedNext.getHeader(); + // Is this supposed to be a difficulty transition point? if ((storedPrev.getHeight() + 1) % params.interval != 0) { + + // TODO: Refactor this hack after 0.5 is released and we stop supporting deserialization compatibility. + // This should be a method of the NetworkParameters, which should in turn be using singletons and a subclass + // for each network type. Then each network can define its own difficulty transition rules. + if (params.getId().equals(NetworkParameters.ID_TESTNET) && next.getTime().after(testnetDiffDate)) { + checkTestnetDifficulty(storedPrev, prev, next); + return; + } + // No ... so check the difficulty didn't actually change. if (next.getDifficultyTarget() != prev.getDifficultyTarget()) throw new VerificationException("Unexpected change in difficulty at height " + storedPrev.getHeight() + @@ -435,7 +447,7 @@ public class BlockChain { } cursor = blockStore.get(cursor.getHeader().getPrevBlockHash()); } - log.debug("Difficulty transition traversal took {}msec", System.currentTimeMillis() - now); + log.info("Difficulty transition traversal took {}msec", System.currentTimeMillis() - now); Block blockIntervalAgo = cursor.getHeader(); int timespan = (int) (prev.getTimeSeconds() - blockIntervalAgo.getTimeSeconds()); @@ -450,7 +462,7 @@ public class BlockChain { newDifficulty = newDifficulty.divide(BigInteger.valueOf(params.targetTimespan)); if (newDifficulty.compareTo(params.proofOfWorkLimit) > 0) { - log.debug("Difficulty hit proof of work limit: {}", newDifficulty.toString(16)); + log.info("Difficulty hit proof of work limit: {}", newDifficulty.toString(16)); newDifficulty = params.proofOfWorkLimit; } @@ -466,6 +478,30 @@ public class BlockChain { receivedDifficulty.toString(16) + " vs " + newDifficulty.toString(16)); } + private void checkTestnetDifficulty(StoredBlock storedPrev, Block prev, Block next) throws VerificationException, BlockStoreException { + // After 15th February 2012 the rules on the testnet change to avoid people running up the difficulty + // and then leaving, making it too hard to mine a block. On non-difficulty transition points, easy + // blocks are allowed if there has been a span of 20 minutes without one. + final long timeDelta = next.getTimeSeconds() - prev.getTimeSeconds(); + // There is an integer underflow bug in bitcoin-qt that means mindiff blocks are accepted when time + // goes backwards. + if (timeDelta >= 0 && timeDelta <= NetworkParameters.TARGET_SPACING * 2) { + // Walk backwards until we find a block that doesn't have the easiest proof of work, then check + // that difficulty is equal to that one. + StoredBlock cursor = storedPrev; + while (!cursor.getHeader().equals(params.genesisBlock) && + cursor.getHeight() % params.interval != 0 && + cursor.getHeader().getDifficultyTargetAsInteger().equals(params.proofOfWorkLimit)) + cursor = cursor.getPrev(blockStore); + BigInteger cursorDifficulty = cursor.getHeader().getDifficultyTargetAsInteger(); + BigInteger newDifficulty = next.getDifficultyTargetAsInteger(); + if (!cursorDifficulty.equals(newDifficulty)) + throw new VerificationException("Testnet block transition that is not allowed: " + + Long.toHexString(cursor.getHeader().getDifficultyTarget()) + " vs " + + Long.toHexString(next.getDifficultyTarget())); + } + } + /** * For the transactions in the given block, update the txToWalletMap such that each wallet maps to a list of * transactions for which it is relevant. diff --git a/core/src/main/java/com/google/bitcoin/core/NetworkParameters.java b/core/src/main/java/com/google/bitcoin/core/NetworkParameters.java index 00c3a01c..d58f4043 100644 --- a/core/src/main/java/com/google/bitcoin/core/NetworkParameters.java +++ b/core/src/main/java/com/google/bitcoin/core/NetworkParameters.java @@ -24,10 +24,12 @@ import java.math.BigInteger; import static com.google.common.base.Preconditions.checkState; +// TODO: Refactor this after we stop supporting serialization compatibility to use subclasses and singletons. + /** - * NetworkParameters contains the data needed for working with an instantiation of a BitCoin chain. + * NetworkParameters contains the data needed for working with an instantiation of a Bitcoin chain.

* - * Currently there are only two, the production chain and the test chain. But in future as BitCoin + * Currently there are only two, the production chain and the test chain. But in future as Bitcoin * evolves there may be more. You can create your own as long as they don't conflict. */ public class NetworkParameters implements Serializable { @@ -86,7 +88,7 @@ public class NetworkParameters implements Serializable { /** * How much time in seconds is supposed to pass between "interval" blocks. If the actual elapsed time is * significantly different from this value, the network difficulty formula will produce a different value. Both - * test and production BitCoin networks use 2 weeks (1209600 seconds). + * test and production Bitcoin networks use 2 weeks (1209600 seconds). */ public int targetTimespan; /** @@ -94,11 +96,6 @@ public class NetworkParameters implements Serializable { * signatures using it. */ public byte[] alertSigningKey; - /** - * Whether to check block difficulty on this network. The rules for testnet have changed and become - * quite complicated, so don't bother verifying them. It's only a useful security check on the main network. - */ - public boolean checkBlockDifficulty; /** * See getId(). This may be null for old deserialized wallets. In that case we derive it heuristically @@ -136,15 +133,14 @@ public class NetworkParameters implements Serializable { return genesisBlock; } - static private final int TARGET_TIMESPAN = 14 * 24 * 60 * 60; // 2 weeks per difficulty cycle, on average. - static private final int TARGET_SPACING = 10 * 60; // 10 minutes per block. - static private final int INTERVAL = TARGET_TIMESPAN / TARGET_SPACING; + public static final int TARGET_TIMESPAN = 14 * 24 * 60 * 60; // 2 weeks per difficulty cycle, on average. + public static final int TARGET_SPACING = 10 * 60; // 10 minutes per block. + public static final int INTERVAL = TARGET_TIMESPAN / TARGET_SPACING; /** Sets up the given NetworkParameters with testnet values. */ private static NetworkParameters createTestNet(NetworkParameters n) { // Genesis hash is 0000000224b1593e3ff16a0e3b61285bbc393a39f78c8aa48c456142671f7110 - // The proof of work limit has to start with 00, as otherwise the value will be interpreted as negative. - n.proofOfWorkLimit = new BigInteger("0000000fffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16); + n.proofOfWorkLimit = Utils.decodeCompactBits(0x1d0fffffL); n.packetMagic = 0xfabfb5daL; n.port = 18333; n.addressHeader = 111; @@ -158,7 +154,6 @@ public class NetworkParameters implements Serializable { n.genesisBlock.setDifficultyTarget(0x1d07fff8L); n.genesisBlock.setNonce(384568319); n.id = ID_TESTNET; - n.checkBlockDifficulty = false; String genesisHash = n.genesisBlock.getHashAsString(); checkState(genesisHash.equals("00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"), genesisHash); @@ -174,7 +169,7 @@ public class NetworkParameters implements Serializable { /** The primary BitCoin chain created by Satoshi. */ public static NetworkParameters prodNet() { NetworkParameters n = new NetworkParameters(); - n.proofOfWorkLimit = new BigInteger("00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16); + n.proofOfWorkLimit = Utils.decodeCompactBits(0x1d00ffffL); n.port = 8333; n.packetMagic = 0xf9beb4d9L; n.addressHeader = 0; @@ -188,7 +183,6 @@ public class NetworkParameters implements Serializable { n.genesisBlock.setTime(1231006505L); n.genesisBlock.setNonce(2083236893); n.id = ID_PRODNET; - n.checkBlockDifficulty = true; String genesisHash = n.genesisBlock.getHashAsString(); checkState(genesisHash.equals("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"), genesisHash); @@ -204,7 +198,6 @@ public class NetworkParameters implements Serializable { n.genesisBlock.setDifficultyTarget(Block.EASIEST_DIFFICULTY_TARGET); n.interval = 10; n.targetTimespan = 200000000; // 6 years. Just a very big number. - n.checkBlockDifficulty = true; n.id = "com.google.bitcoin.unittest"; return n; } diff --git a/core/src/main/java/com/google/bitcoin/core/Peer.java b/core/src/main/java/com/google/bitcoin/core/Peer.java index cca932b0..31a4cb29 100644 --- a/core/src/main/java/com/google/bitcoin/core/Peer.java +++ b/core/src/main/java/com/google/bitcoin/core/Peer.java @@ -315,7 +315,7 @@ public class Peer { } private void processBlock(Block m) throws IOException { - log.info("Received broadcast block {}", m.getHashAsString()); + log.debug("Received broadcast block {}", m.getHashAsString()); try { // Was this block requested by getBlock()? synchronized (pendingGetBlockFutures) { diff --git a/core/src/test/java/com/google/bitcoin/core/BlockChainTest.java b/core/src/test/java/com/google/bitcoin/core/BlockChainTest.java index 4f714ce4..02eef294 100644 --- a/core/src/test/java/com/google/bitcoin/core/BlockChainTest.java +++ b/core/src/test/java/com/google/bitcoin/core/BlockChainTest.java @@ -64,7 +64,6 @@ public class BlockChainTest { coinbaseTo = wallet.keychain.get(0).toAddress(unitTestParams); - testNet.checkBlockDifficulty = true; } @Test