3
0
mirror of https://github.com/Qortal/altcoinj.git synced 2025-02-13 10:45:51 +00:00

Moved block difficulty checks into NetworkParameters.

This commit is contained in:
Ross Nicoll 2015-05-12 20:49:17 +01:00 committed by Mike Hearn
parent 7a3aa74c6e
commit e030f1a1f4
5 changed files with 134 additions and 110 deletions

View File

@ -409,8 +409,9 @@ public abstract class AbstractBlockChain {
orphanBlocks.put(block.getHash(), new OrphanBlock(block, filteredTxHashList, filteredTxn));
return false;
} else {
checkState(lock.isHeldByCurrentThread());
// It connects to somewhere on the chain. Not necessarily the top of the best known chain.
checkDifficultyTransitions(storedPrev, block);
params.checkDifficultyTransitions(storedPrev, block, blockStore);
connectBlock(block, storedPrev, shouldVerifyTransactions(), filteredTxHashList, filteredTxn);
}
@ -825,109 +826,6 @@ public abstract class AbstractBlockChain {
} while (blocksConnectedThisRound > 0);
}
// February 16th 2012
private static final Date testnetDiffDate = new Date(1329264000000L);
/**
* Throws an exception if the block's difficulty is not correct.
*
* @throws VerificationException if the block's difficulty is not correct.
*/
protected void checkDifficultyTransitions(StoredBlock storedPrev, Block nextBlock) throws BlockStoreException, VerificationException {
checkState(lock.isHeldByCurrentThread());
Block prev = storedPrev.getHeader();
// Is this supposed to be a difficulty transition point?
if ((storedPrev.getHeight() + 1) % params.getInterval() != 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) && nextBlock.getTime().after(testnetDiffDate)) {
checkTestnetDifficulty(storedPrev, prev, nextBlock);
return;
}
// No ... so check the difficulty didn't actually change.
if (nextBlock.getDifficultyTarget() != prev.getDifficultyTarget())
throw new VerificationException("Unexpected change in difficulty at height " + storedPrev.getHeight() +
": " + Long.toHexString(nextBlock.getDifficultyTarget()) + " vs " +
Long.toHexString(prev.getDifficultyTarget()));
return;
}
// We need to find a block far back in the chain. It's OK that this is expensive because it only occurs every
// two weeks after the initial block chain download.
long now = System.currentTimeMillis();
StoredBlock cursor = blockStore.get(prev.getHash());
for (int i = 0; i < params.getInterval() - 1; i++) {
if (cursor == null) {
// This should never happen. If it does, it means we are following an incorrect or busted chain.
throw new VerificationException(
"Difficulty transition point but we did not find a way back to the genesis block.");
}
cursor = blockStore.get(cursor.getHeader().getPrevBlockHash());
}
long elapsed = System.currentTimeMillis() - now;
if (elapsed > 50)
log.info("Difficulty transition traversal took {}msec", elapsed);
Block blockIntervalAgo = cursor.getHeader();
int timespan = (int) (prev.getTimeSeconds() - blockIntervalAgo.getTimeSeconds());
// Limit the adjustment step.
final int targetTimespan = params.getTargetTimespan();
if (timespan < targetTimespan / 4)
timespan = targetTimespan / 4;
if (timespan > targetTimespan * 4)
timespan = targetTimespan * 4;
BigInteger newTarget = Utils.decodeCompactBits(prev.getDifficultyTarget());
newTarget = newTarget.multiply(BigInteger.valueOf(timespan));
newTarget = newTarget.divide(BigInteger.valueOf(targetTimespan));
if (newTarget.compareTo(params.getMaxTarget()) > 0) {
log.info("Difficulty hit proof of work limit: {}", newTarget.toString(16));
newTarget = params.getMaxTarget();
}
int accuracyBytes = (int) (nextBlock.getDifficultyTarget() >>> 24) - 3;
long receivedTargetCompact = nextBlock.getDifficultyTarget();
// The calculated difficulty is to a higher precision than received, so reduce here.
BigInteger mask = BigInteger.valueOf(0xFFFFFFL).shiftLeft(accuracyBytes * 8);
newTarget = newTarget.and(mask);
long newTargetCompact = Utils.encodeCompactBits(newTarget);
if (newTargetCompact != receivedTargetCompact)
throw new VerificationException("Network provided difficulty bits do not match what was calculated: " +
newTargetCompact + " vs " + receivedTargetCompact);
}
private void checkTestnetDifficulty(StoredBlock storedPrev, Block prev, Block next) throws VerificationException, BlockStoreException {
checkState(lock.isHeldByCurrentThread());
// 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.getGenesisBlock()) &&
cursor.getHeight() % params.getInterval() != 0 &&
cursor.getHeader().getDifficultyTargetAsInteger().equals(params.getMaxTarget()))
cursor = cursor.getPrev(blockStore);
BigInteger cursorTarget = cursor.getHeader().getDifficultyTargetAsInteger();
BigInteger newTarget = next.getDifficultyTargetAsInteger();
if (!cursorTarget.equals(newTarget))
throw new VerificationException("Testnet block transition that is not allowed: " +
Long.toHexString(cursor.getHeader().getDifficultyTarget()) + " vs " +
Long.toHexString(next.getDifficultyTarget()));
}
}
/**
* Returns true if any connected wallet considers any transaction in the block to be relevant.
*/

View File

@ -297,12 +297,12 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
}
// All values were already checked for being non-negative (as it is verified in Transaction.verify())
// but we check again here just for defence in depth. Transactions with zero output value are OK.
if (valueOut.signum() < 0 || valueOut.compareTo(this.params.getMaxMoney()) > 0)
if (valueOut.signum() < 0 || valueOut.compareTo(params.getMaxMoney()) > 0)
throw new VerificationException("Transaction output value out of range");
if (isCoinBase) {
coinbaseValue = valueOut;
} else {
if (valueIn.compareTo(valueOut) < 0 || valueIn.compareTo(this.params.getMaxMoney()) > 0)
if (valueIn.compareTo(valueOut) < 0 || valueIn.compareTo(params.getMaxMoney()) > 0)
throw new VerificationException("Transaction input value out of range");
totalFees = totalFees.add(valueIn.subtract(valueOut));
}
@ -314,7 +314,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
listScriptVerificationResults.add(future);
}
}
if (totalFees.compareTo(this.params.getMaxMoney()) > 0 || block.getBlockInflation(height).add(totalFees).compareTo(coinbaseValue) < 0)
if (totalFees.compareTo(params.getMaxMoney()) > 0 || block.getBlockInflation(height).add(totalFees).compareTo(coinbaseValue) < 0)
throw new VerificationException("Transaction fees out of range");
for (Future<VerificationException> future : listScriptVerificationResults) {
VerificationException e;
@ -427,12 +427,12 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
}
// All values were already checked for being non-negative (as it is verified in Transaction.verify())
// but we check again here just for defence in depth. Transactions with zero output value are OK.
if (valueOut.signum() < 0 || valueOut.compareTo(this.params.getMaxMoney()) > 0)
if (valueOut.signum() < 0 || valueOut.compareTo(params.getMaxMoney()) > 0)
throw new VerificationException("Transaction output value out of range");
if (isCoinBase) {
coinbaseValue = valueOut;
} else {
if (valueIn.compareTo(valueOut) < 0 || valueIn.compareTo(this.params.getMaxMoney()) > 0)
if (valueIn.compareTo(valueOut) < 0 || valueIn.compareTo(params.getMaxMoney()) > 0)
throw new VerificationException("Transaction input value out of range");
totalFees = totalFees.add(valueIn.subtract(valueOut));
}
@ -444,7 +444,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain {
listScriptVerificationResults.add(future);
}
}
if (totalFees.compareTo(this.params.getMaxMoney()) > 0 ||
if (totalFees.compareTo(params.getMaxMoney()) > 0 ||
newBlock.getHeader().getBlockInflation(newBlock.getHeight()).add(totalFees).compareTo(coinbaseValue) < 0)
throw new VerificationException("Transaction fees out of range");
txOutChanges = new TransactionOutputChanges(txOutsCreated, txOutsSpent);

View File

@ -18,9 +18,14 @@
package org.bitcoinj.core;
import com.google.common.base.Objects;
import org.bitcoinj.core.Block;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.VerificationException;
import org.bitcoinj.net.discovery.*;
import org.bitcoinj.params.*;
import org.bitcoinj.script.*;
import org.bitcoinj.store.BlockStore;
import org.bitcoinj.store.BlockStoreException;
import org.bitcoinj.store.BlockStore;
import org.bitcoinj.store.BlockStoreException;
@ -245,6 +250,13 @@ public abstract class NetworkParameters implements Serializable {
return spendableCoinbaseDepth;
}
/**
* Throws an exception if the block's difficulty is not correct.
*
* @throws VerificationException if the block's difficulty is not correct.
*/
public abstract void checkDifficultyTransitions(StoredBlock storedPrev, Block next, final BlockStore blockStore) throws VerificationException, BlockStoreException;
/**
* Returns true if the block height is either not a checkpoint, or is a checkpoint and the hash matches.
*/

View File

@ -16,12 +16,19 @@
package org.bitcoinj.params;
import java.math.BigInteger;
import org.bitcoinj.core.Block;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.Utils;
import org.bitcoinj.utils.MonetaryFormat;
import org.bitcoinj.core.VerificationException;
import org.bitcoinj.store.BlockStore;
import org.bitcoinj.store.BlockStoreException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -42,6 +49,69 @@ public abstract class AbstractBitcoinNetParams extends NetworkParameters {
super();
}
@Override
public void checkDifficultyTransitions(final StoredBlock storedPrev, final Block nextBlock,
final BlockStore blockStore) throws VerificationException, BlockStoreException {
Block prev = storedPrev.getHeader();
// Is this supposed to be a difficulty transition point?
if ((storedPrev.getHeight() + 1) % this.getInterval() != 0) {
// No ... so check the difficulty didn't actually change.
if (nextBlock.getDifficultyTarget() != prev.getDifficultyTarget())
throw new VerificationException("Unexpected change in difficulty at height " + storedPrev.getHeight() +
": " + Long.toHexString(nextBlock.getDifficultyTarget()) + " vs " +
Long.toHexString(prev.getDifficultyTarget()));
return;
}
// We need to find a block far back in the chain. It's OK that this is expensive because it only occurs every
// two weeks after the initial block chain download.
long now = System.currentTimeMillis();
StoredBlock cursor = blockStore.get(prev.getHash());
for (int i = 0; i < this.getInterval() - 1; i++) {
if (cursor == null) {
// This should never happen. If it does, it means we are following an incorrect or busted chain.
throw new VerificationException(
"Difficulty transition point but we did not find a way back to the genesis block.");
}
cursor = blockStore.get(cursor.getHeader().getPrevBlockHash());
}
long elapsed = System.currentTimeMillis() - now;
if (elapsed > 50)
log.info("Difficulty transition traversal took {}msec", elapsed);
Block blockIntervalAgo = cursor.getHeader();
int timespan = (int) (prev.getTimeSeconds() - blockIntervalAgo.getTimeSeconds());
// Limit the adjustment step.
final int targetTimespan = this.getTargetTimespan();
if (timespan < targetTimespan / 4)
timespan = targetTimespan / 4;
if (timespan > targetTimespan * 4)
timespan = targetTimespan * 4;
BigInteger newTarget = Utils.decodeCompactBits(prev.getDifficultyTarget());
newTarget = newTarget.multiply(BigInteger.valueOf(timespan));
newTarget = newTarget.divide(BigInteger.valueOf(targetTimespan));
if (newTarget.compareTo(this.getMaxTarget()) > 0) {
log.info("Difficulty hit proof of work limit: {}", newTarget.toString(16));
newTarget = this.getMaxTarget();
}
int accuracyBytes = (int) (nextBlock.getDifficultyTarget() >>> 24) - 3;
long receivedTargetCompact = nextBlock.getDifficultyTarget();
// The calculated difficulty is to a higher precision than received, so reduce here.
BigInteger mask = BigInteger.valueOf(0xFFFFFFL).shiftLeft(accuracyBytes * 8);
newTarget = newTarget.and(mask);
long newTargetCompact = Utils.encodeCompactBits(newTarget);
if (newTargetCompact != receivedTargetCompact)
throw new VerificationException("Network provided difficulty bits do not match what was calculated: " +
newTargetCompact + " vs " + receivedTargetCompact);
}
@Override
public Coin getMaxMoney() {
return MAX_MONEY;

View File

@ -17,7 +17,16 @@
package org.bitcoinj.params;
import java.math.BigInteger;
import java.util.Date;
import org.bitcoinj.core.Block;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Utils;
import org.bitcoinj.core.VerificationException;
import org.bitcoinj.store.BlockStore;
import org.bitcoinj.store.BlockStoreException;
import static com.google.common.base.Preconditions.checkState;
@ -69,4 +78,39 @@ public class TestNet3Params extends AbstractBitcoinNetParams {
public String getPaymentProtocolId() {
return PAYMENT_PROTOCOL_ID_TESTNET;
}
// February 16th 2012
private static final Date testnetDiffDate = new Date(1329264000000L);
@Override
public void checkDifficultyTransitions(final StoredBlock storedPrev, final Block nextBlock,
final BlockStore blockStore) throws VerificationException, BlockStoreException {
if (nextBlock.getTime().after(testnetDiffDate)) {
Block prev = storedPrev.getHeader();
// 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 = nextBlock.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(getGenesisBlock()) &&
cursor.getHeight() % getInterval() != 0 &&
cursor.getHeader().getDifficultyTargetAsInteger().equals(getMaxTarget()))
cursor = cursor.getPrev(blockStore);
BigInteger cursorTarget = cursor.getHeader().getDifficultyTargetAsInteger();
BigInteger newTarget = nextBlock.getDifficultyTargetAsInteger();
if (!cursorTarget.equals(newTarget))
throw new VerificationException("Testnet block transition that is not allowed: " +
Long.toHexString(cursor.getHeader().getDifficultyTarget()) + " vs " +
Long.toHexString(nextBlock.getDifficultyTarget()));
}
} else {
super.checkDifficultyTransitions(storedPrev, nextBlock, blockStore);
}
}
}