/// <summary> /// Returns the difficulty target as a 256 bit value that can be compared to a SHA-256 hash. Inside a block the /// target is represented using a compact form. If this form decodes to a value that is out of bounds, /// an exception is thrown. /// </summary> /// <exception cref="VerificationException"/> public BigInteger GetDifficultyTargetAsInteger() { var target = Utils.DecodeCompactBits(_difficultyTarget); if (target.CompareTo(BigInteger.Zero) <= 0 || target.CompareTo(Params.ProofOfWorkLimit) > 0) { throw new VerificationException("Difficulty target is bad: " + target); } return(target); }
/// <summary> /// Throws an exception if the blocks difficulty is not correct. /// </summary> /// <exception cref="BlockStoreException"/> /// <exception cref="VerificationException"/> 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)); } }