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;
        }