TestNet3Params: fix difficulty transition check

The difficulty transition check didn't properly account for the
"20 minute rule" specific to testnet: if the time delta between
a block and its previous block is more than 20 minutes, the block's
difficulty target MUST reset to the minimum difficulty.

Before this fix, it did not check the difficulty target at all in
this case. So it wrongly assumed the difficulty target can be changed
to any value.
This commit is contained in:
Andreas Schildbach 2025-03-09 21:02:17 +01:00
parent 1090649211
commit e58df21bab

View file

@ -110,9 +110,12 @@ public class TestNet3Params extends BitcoinNetworkParams {
// and then leaving, making it too hard to mine a block. On non-difficulty transition points, easy // 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. // blocks are allowed if there has been a span of 20 minutes without one.
final long timeDelta = nextBlock.time().getEpochSecond() - prev.time().getEpochSecond(); final long timeDelta = nextBlock.time().getEpochSecond() - prev.time().getEpochSecond();
// There is an integer underflow bug in bitcoin-qt that means mindiff blocks are accepted when time BigInteger expectedTarget;
// goes backwards. if (timeDelta < 0 && nextBlock.getDifficultyTargetAsInteger().equals(getMaxTarget())) {
if (timeDelta >= 0 && timeDelta <= NetworkParameters.TARGET_SPACING * 2) { // There is an integer underflow bug in bitcoin-qt that means mindiff blocks are accepted when time
// goes backwards.
return;
} else if (timeDelta <= NetworkParameters.TARGET_SPACING * 2) {
// Walk backwards until we find a block that doesn't have the easiest proof of work, then check // 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. // that difficulty is equal to that one.
StoredBlock cursor = storedPrev; StoredBlock cursor = storedPrev;
@ -120,13 +123,16 @@ public class TestNet3Params extends BitcoinNetworkParams {
cursor.getHeight() % getInterval() != 0 && cursor.getHeight() % getInterval() != 0 &&
cursor.getHeader().getDifficultyTargetAsInteger().equals(getMaxTarget())) cursor.getHeader().getDifficultyTargetAsInteger().equals(getMaxTarget()))
cursor = cursor.getPrev(blockStore); cursor = cursor.getPrev(blockStore);
BigInteger cursorTarget = cursor.getHeader().getDifficultyTargetAsInteger(); expectedTarget = cursor.getHeader().getDifficultyTargetAsInteger();
BigInteger newTarget = nextBlock.getDifficultyTargetAsInteger(); } else {
if (!cursorTarget.equals(newTarget)) // 20 minute exception
throw new VerificationException("Testnet block transition that is not allowed: " + expectedTarget = getMaxTarget();
Long.toHexString(cursor.getHeader().getDifficultyTarget()) + " vs " +
Long.toHexString(nextBlock.getDifficultyTarget()));
} }
BigInteger newTarget = nextBlock.getDifficultyTargetAsInteger();
if (!newTarget.equals(expectedTarget))
throw new VerificationException("Testnet block transition that is not allowed: " +
Long.toHexString(ByteUtils.encodeCompactBits(expectedTarget)) + " vs " +
Long.toHexString(nextBlock.getDifficultyTarget()));
} else { } else {
super.checkDifficultyTransitions(storedPrev, nextBlock, blockStore); super.checkDifficultyTransitions(storedPrev, nextBlock, blockStore);
} }