public StoredBlock AddBlockContent(byte[] hash, byte[] content) { transactionalResource.Enlist(); StoredBlock updatedBlock = storage.AddBlockContent(hash, content); if (lastChain != null) { //todo: cashing is a little bit ugly List<StoredBlock> chainBlocks = new List<StoredBlock>(lastChain.Length); for (int i = lastChain.Length - 1; i >= 0; i--) { StoredBlock chainBlock = lastChain.GetBlockByOffset(i); if (chainBlock.Hash.SequenceEqual(updatedBlock.Hash)) { chainBlocks.Add(updatedBlock); } else { chainBlocks.Add(chainBlock); } } lastChain = new Subchain(chainBlocks); } return updatedBlock; }
public void TestIsTimestampValid() { List<StoredBlock> blocks = new List<StoredBlock>(); StoredBlock prevBlock = null; for (int i = 0; i < 11; i++) { BlockHeader header = new BlockHeader ( genesisBlockHeader.Version, prevBlock == null ? new byte[32] : prevBlock.Hash, new byte[32], (uint) i, 0x21100000, 0); StoredBlock storedBlock = new StoredBlockBuilder(header).Build(); blocks.Add(storedBlock); prevBlock = storedBlock; } Subchain subchain = new Subchain(blocks); { BlockHeader header = new BlockHeader ( genesisBlockHeader.Version, new byte[32], new byte[32], 5, 0x21100000, 0); StoredBlock storedBlock = new StoredBlockBuilder(header).Build(); Assert.False(BlockHeaderValidator.IsTimeStampValid(storedBlock, subchain)); } { BlockHeader header = new BlockHeader ( genesisBlockHeader.Version, new byte[32], new byte[32], 6, 0x21100000, 0); StoredBlock storedBlock = new StoredBlockBuilder(header).Build(); Assert.True(BlockHeaderValidator.IsTimeStampValid(storedBlock, subchain)); } }
/// <summary> /// Checks that the nBits field of a block follow protocol rules. /// </summary> internal static bool IsNBitsValid(StoredBlock block, Subchain parentChain) { //todo: add test for this method StoredBlock parentBlock = parentChain.GetBlockByOffset(0); BigInteger blockDifficultyTarget = DifficultyUtils.NBitsToTarget(block.Header.NBits); if (block.Height%DifficultyUtils.DifficultyAdjustmentIntervalInBlocks != 0) { BigInteger parentBlockDifficultyTarget = DifficultyUtils.NBitsToTarget(parentBlock.Header.NBits); if (blockDifficultyTarget != parentBlockDifficultyTarget) { return false; } } else { StoredBlock baseBlock = parentChain.GetBlockByHeight(block.Height - DifficultyUtils.DifficultyAdjustmentIntervalInBlocks); // todo: check against non-bugged value and allow some percent-based difference? // Note: There is a known bug here. It known as "off-by-one" or "Time Warp Bug". // Bug Description: http://bitcoin.stackexchange.com/questions/20597/where-exactly-is-the-off-by-one-difficulty-bug // Related Attack Description: https://bitcointalk.org/index.php?topic=43692.msg521772#msg521772 uint timeSpent = parentBlock.Header.Timestamp - baseBlock.Header.Timestamp; BigInteger oldTarget = DifficultyUtils.NBitsToTarget(parentBlock.Header.NBits); var newTarget = DifficultyUtils.CalculateNewTarget(timeSpent, oldTarget); // this rounds newTarget value to a value that would fit into NBits uint newNBits = DifficultyUtils.TargetToNBits(newTarget); newTarget = DifficultyUtils.NBitsToTarget(newNBits); if (blockDifficultyTarget != newTarget) { return false; } } return true; }
/// <summary> /// Clears cached values. /// </summary> private void Reset() { lastChain = null; }
public void UpdateBlock(StoredBlock block) { transactionalResource.Enlist(); storage.UpdateBlock(block); if (lastChain != null) { //todo: cashing is a little bit ugly List<StoredBlock> chainBlocks = new List<StoredBlock>(lastChain.Length); for (int i = lastChain.Length - 1; i >= 0; i--) { StoredBlock chainBlock = lastChain.GetBlockByOffset(i); if (chainBlock.Hash.SequenceEqual(block.Hash)) { chainBlocks.Add(block); } else { chainBlocks.Add(chainBlock); } } lastChain = new Subchain(chainBlocks); } }
public Subchain FindSubchain(byte[] hash, int length) { // enlistment in transaction is not necessary, since cached data would remain valid until modifying method is called if (lastChain == null || (lastChain.Length < length && !lastChain.GetBlockByOffset(lastChain.Length - 1).Header.IsFirst) || !lastChain.GetBlockByOffset(0).Hash.SequenceEqual(hash)) { lastChain = storage.FindSubchain(hash, length); } return lastChain?.Clone(); }
public static bool IsValid(StoredBlock block, Subchain parentChain) { return IsTimeStampValid(block, parentChain) && IsNBitsValid(block, parentChain); }
/// <summary> /// Checks that timestamp of the block is after median time of the last 11 blocks. /// </summary> internal static bool IsTimeStampValid(StoredBlock block, Subchain parentChain) { //todo: use network settings const int medianBlockCount = 11; List<uint> oldTimestamps = new List<uint>(medianBlockCount); for (int i = 0; i < medianBlockCount && i < parentChain.Length; i++) { oldTimestamps.Add(parentChain.GetBlockByOffset(i).Header.Timestamp); } oldTimestamps.Sort(); uint medianTimestamp = oldTimestamps[oldTimestamps.Count/2]; return block.Header.Timestamp > medianTimestamp; }