private void CheckDifficultyTransitions(StoredBlock storedPrev, StoredBlock storedNext)
{
var prev = storedPrev.Header;
var next = storedNext.Header;
// Is this supposed to be a difficulty transition point?
if ((storedPrev.Height + 1)%_params.Interval != 0)
{
// No ... so check the difficulty didn't actually change.
if (next.DifficultyTarget != prev.DifficultyTarget)
throw new VerificationException("Unexpected change in difficulty at height " + storedPrev.Height +
": " + next.DifficultyTarget.ToString("x") + " vs " +
prev.DifficultyTarget.ToString("x"));
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.
var now = Environment.TickCount;
var cursor = _blockStore.Get(prev.Hash);
for (var i = 0; i < _params.Interval - 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.Header.PrevBlockHash);
}
_log.DebugFormat("Difficulty transition traversal took {0}ms", Environment.TickCount - now);
var blockIntervalAgo = cursor.Header;
var timespan = (int) (prev.TimeSeconds - blockIntervalAgo.TimeSeconds);
// Limit the adjustment step.
if (timespan < _params.TargetTimespan/4)
timespan = _params.TargetTimespan/4;
if (timespan > _params.TargetTimespan*4)
timespan = _params.TargetTimespan*4;
var newDifficulty = Utils.DecodeCompactBits(blockIntervalAgo.DifficultyTarget);
newDifficulty = newDifficulty.Multiply(BigInteger.ValueOf(timespan));
newDifficulty = newDifficulty.Divide(BigInteger.ValueOf(_params.TargetTimespan));
if (newDifficulty.CompareTo(_params.ProofOfWorkLimit) > 0)
{
_log.DebugFormat("Difficulty hit proof of work limit: {0}", newDifficulty.ToString(16));
newDifficulty = _params.ProofOfWorkLimit;
}
var accuracyBytes = (int) (next.DifficultyTarget >> 24) - 3;
var receivedDifficulty = next.GetDifficultyTargetAsInteger();
// The calculated difficulty is to a higher precision than received, so reduce here.
var mask = BigInteger.ValueOf(0xFFFFFF).ShiftLeft(accuracyBytes*8);
newDifficulty = newDifficulty.And(mask);
if (newDifficulty.CompareTo(receivedDifficulty) != 0)
throw new VerificationException("Network provided difficulty bits do not match what was calculated: " +
receivedDifficulty.ToString(16) + " vs " + newDifficulty.ToString(16));
}