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:
parent
7a3aa74c6e
commit
e030f1a1f4
@ -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.
|
||||
*/
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user