private bool Matches(StoredBlock block1, StoredBlock block2)
 {
     if (block1 == null || block2 == null)
     {
         return false;
     }
     return block1.Hash.SequenceEqual(block2.Hash);
 }
예제 #2
0
 public void Append(StoredBlock block)
 {
     //todo: add XMLDOC
     StoredBlock lastBlock = blocks.Last();
     if (!lastBlock.Hash.SequenceEqual(block.Header.PrevBlock))
     {
         throw new ArgumentException($"Added block should reference last block in the {nameof(Subchain)}.", nameof(block));
     }
     blocks.Add(block);
 }
 /// <summary>
 /// Returns an updated state, with replaced blocks.
 /// </summary>
 /// <param name="oldBlock">The block to replace.</param>
 /// <param name="newBlock">The replacing block.</param>
 /// <returns> If replacement affects state, then a new state instance is returned; otherwise, current instance is returned.</returns>
 public BlockchainState Update(StoredBlock oldBlock, StoredBlock newBlock)
 {
     if (Contains(oldBlock))
     {
         return new BlockchainState(
             Matches(BestHeader, oldBlock) ? newBlock : BestHeader,
             Matches(BestChain, oldBlock) ? newBlock : BestChain);
     }
     return this;
 }
예제 #4
0
        public static UnspentOutput Create(StoredBlock block, Tx transaction, int outputNumber)
        {
            TxOut output = transaction.Outputs[outputNumber];

            return new UnspentOutput(
                block.Height,
                CryptoUtils.DoubleSha256(BitcoinStreamWriter.GetBytes(transaction.Write)),
                outputNumber,
                output.Value,
                output.PubkeyScript);
        }
        public void AddBlock(StoredBlock block)
        {
            transactionalResource.Enlist();

            storage.AddBlock(block);

            //todo: add tests
            if (lastChain != null)
            {
                lastChain.Append(block);
                lastChain.Truncate(CachedChainMinLength, CachedChainMaxLength);
            }
        }
        /// <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;
        }
        private StoredBlock SetIsInBestHeaderChain(StoredBlock block, bool val)
        {
            StoredBlockBuilder builder = new StoredBlockBuilder(block);
            builder.IsInBestHeaderChain = val;
            StoredBlock newBlock = builder.Build();

            currentState = currentState.Update(block, newBlock);

            return newBlock;
        }
        private UnspentOutputsUpdate PrepareUnspentOutputsUpdate(StoredBlock block, BlockMessage blockMessage)
        {
            ScriptParser parser = new ScriptParser();

            ulong inputsSum = GetBlockReward(block);
            ulong outputsSum = 0;

            UnspentOutputsUpdate update = new UnspentOutputsUpdate(storage);

            for (int transactionNumber = 0; transactionNumber < blockMessage.Transactions.Length; transactionNumber++)
            {
                Tx transaction = blockMessage.Transactions[transactionNumber];

                ulong transactionInputsSum = 0;
                ulong transactionOutputsSum = 0;

                byte[] transactionHash = CryptoUtils.DoubleSha256(BitcoinStreamWriter.GetBytes(transaction.Write));

                List<UnspentOutput> unspentOutputs = update.FindUnspentOutputs(transactionHash);
                if (unspentOutputs.Any())
                {
                    //todo: use network settings
                    if (block.Height == 91842 || block.Height == 91880)
                    {
                        // this blocks are exceptions from BIP-30
                        foreach (UnspentOutput unspentOutput in unspentOutputs)
                        {
                            update.Spend(unspentOutput.TransactionHash, unspentOutput.OutputNumber, block);
                        }
                    }
                    else
                    {
                        throw new BitcoinProtocolViolationException(
                            $"The transaction '{BitConverter.ToString(transactionHash)}'" +
                            $" in block '{BitConverter.ToString(block.Hash)}'" +
                            $" has same hash as an existing unspent transaction (see BIP-30).");
                    }
                }

                //todo: check transaction hash against genesis block transaction hash
                if (transactionNumber != 0)
                {
                    foreach (TxIn input in transaction.Inputs)
                    {
                        UnspentOutput output = update.FindUnspentOutput(input.PreviousOutput.Hash, input.PreviousOutput.Index);
                        if (output == null)
                        {
                            throw new BitcoinProtocolViolationException(
                                $"The input of the transaction '{BitConverter.ToString(transactionHash)}'" +
                                $" in block '{BitConverter.ToString(block.Hash)}'" +
                                $" has been already spent or did not exist.");
                        }
                        //todo: check for overflow
                        transactionInputsSum += output.Sum;

                        List<ScriptCommand> inputCommands;
                        if (!parser.TryParse(input.SignatureScript, out inputCommands))
                        {
                            throw new BitcoinProtocolViolationException(
                                $"The transaction '{BitConverter.ToString(transactionHash)}'" +
                                $" in block '{BitConverter.ToString(block.Hash)}'" +
                                $" has an invalid signature script.");
                        }
                        if (inputCommands.Any(c => !IsValidSignatureCommand(c.Code)))
                        {
                            throw new BitcoinProtocolViolationException(
                                $"The transaction '{BitConverter.ToString(transactionHash)}'" +
                                $" in block '{BitConverter.ToString(block.Hash)}'" +
                                $" has forbidden commands in the signature script.");
                        }

                        //todo: check signature for the output
                        update.Spend(output.TransactionHash, output.OutputNumber, block);
                    }
                }

                for (int outputNumber = 0; outputNumber < transaction.Outputs.Length; outputNumber++)
                {
                    TxOut output = transaction.Outputs[outputNumber];
                    //todo: check for overflow
                    transactionOutputsSum += output.Value;

                    List<ScriptCommand> commands;
                    if (!parser.TryParse(output.PubkeyScript, out commands))
                    {
                        //todo: how Bitcoin Core works in this scenario?
                        throw new BitcoinProtocolViolationException(
                            $"The output of transaction '{BitConverter.ToString(transactionHash)}'" +
                            $" in block '{BitConverter.ToString(block.Hash)}'" +
                            $" has an invalid pubkey script script.");
                    }

                    UnspentOutput unspentOutput = UnspentOutput.Create(block, transaction, outputNumber);
                    update.Add(unspentOutput);
                }

                if (transactionNumber != 0 && transactionInputsSum < transactionOutputsSum)
                {
                    // for coinbase transaction output sum is checked later as part of total block inputs, outputs and reward sums equation
                    throw new BitcoinProtocolViolationException(
                        $"The sum of the inputs in the transaction '{BitConverter.ToString(transactionHash)}'" +
                        $" in block '{BitConverter.ToString(block.Hash)}'" +
                        $" is less than the sum of the outputs.");
                }

                //todo: check for overflow
                inputsSum += transactionInputsSum;
                //todo: check for overflow
                outputsSum += transactionOutputsSum;
            }

            if (inputsSum != outputsSum)
            {
                throw new BitcoinProtocolViolationException(
                    $"The sum of the inputs and the reward" +
                    $" in the block '{BitConverter.ToString(block.Hash)}'" +
                    $" does not match the sum of the outputs.");
            }

            return update;
        }
 private bool IsBetterHeaderThan(StoredBlock block1, StoredBlock block2)
 {
     //todo: are there any other conditions?
     if (block1.TotalWork > block2.TotalWork)
     {
         return true;
     }
     if (block1.TotalWork < block2.TotalWork)
     {
         return false;
     }
     return block1.Header.Timestamp < block2.Header.Timestamp;
 }
 private ulong GetBlockReward(StoredBlock block)
 {
     //todo: use network settings
     ulong reward = 5000000000;
     int height = block.Height;
     //todo: use network settings
     while (height >= 210000)
     {
         height -= 210000;
         reward /= 2;
     }
     return reward;
 }
        private StoredBlock AddHeader(StoredBlock block)
        {
            StoredBlock storedBlock = storage.FindBlockByHash(block.Hash);
            if (storedBlock != null)
            {
                return storedBlock;
            }

            Subchain parentChain = storage.FindSubchain(block.Header.PrevBlock, Blockchain.AnalyzedSubchainLength);
            if (parentChain == null)
            {
                return block;
            }

            StoredBlock parentBlock = parentChain.GetBlockByOffset(0);

            block = block.AppendAfter(parentBlock);

            if (!BlockHeaderValidator.IsValid(block, parentChain))
            {
                throw new BitcoinProtocolViolationException("An invalid header was received.");
            }

            storage.AddBlock(block);

            UpdateBestHeadersChain(block);

            return block;
        }
예제 #12
0
 public StoredBlockBuilder(StoredBlock block)
 {
     Header = block.Header;
     Hash = block.Hash;
     Height = block.Height;
     TotalWork = block.TotalWork;
     HasContent = block.HasContent;
     IsInBestHeaderChain = block.IsInBestHeaderChain;
     IsInBestBlockChain = block.IsInBestBlockChain;
 }
 private bool Contains(StoredBlock block)
 {
     return Matches(BestHeader, block) || Matches(BestChain, block);
 }
 public static bool IsValid(StoredBlock block, Subchain parentChain)
 {
     return IsTimeStampValid(block, parentChain) && IsNBitsValid(block, parentChain);
 }
 public static bool IsValid(StoredBlock storedBlock, BlockMessage block)
 {
     //todo: add other validations, recheck each field constraints
     return IsValidCoinbaseTransaction(block);
 }
 public BlockchainState SetBestHeader(StoredBlock newBestHeader)
 {
     return new BlockchainState(newBestHeader, BestChain);
 }
 //todo: make this class internal and move related generic storage tests to TestUtils or make Blockchain.State public
 public BlockchainState(StoredBlock bestHeader, StoredBlock bestChain)
 {
     BestHeader = bestHeader;
     BestChain = bestChain;
 }
        private void UpdateBestHeadersChain(StoredBlock block)
        {
            if (!IsBetterHeaderThan(block, currentState.BestHeader))
            {
                return;
            }

            StoredBlock newHead = block;
            StoredBlock oldHead = currentState.BestHeader;

            //todo: this code should be wrapped in transaction, init should be called on failure
            while (!newHead.Hash.SequenceEqual(oldHead.Hash))
            {
                bool markNewHead = newHead.Height >= oldHead.Height;
                bool markOldHead = oldHead.Height >= newHead.Height;

                if (markNewHead)
                {
                    newHead = SetIsInBestHeaderChain(newHead, true);
                    storage.UpdateBlock(newHead);
                    newHead = storage.FindBlockByHash(newHead.Header.PrevBlock);
                }

                if (markOldHead)
                {
                    oldHead = SetIsInBestHeaderChain(oldHead, false);
                    storage.UpdateBlock(oldHead);
                    oldHead = storage.FindBlockByHash(oldHead.Header.PrevBlock);
                }
            }

            currentState = currentState.SetBestHeader(block);
        }
        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);
            }
        }
        /// <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;
        }
예제 #21
0
        /// <summary>
        /// Returns a copy of this block with the <see cref="Height"/> and <see cref="TotalWork"/> properties based on the given parent block.
        /// </summary>
        /// <param name="parentBlock">The parent block.</param>
        public StoredBlock AppendAfter(StoredBlock parentBlock)
        {
            double blockWork = DifficultyUtils.DifficultyTargetToWork(DifficultyUtils.NBitsToTarget(Header.NBits));

            StoredBlockBuilder builder = new StoredBlockBuilder(this);
            builder.Height = parentBlock.Height + 1;
            builder.TotalWork = parentBlock.TotalWork + blockWork;

            return builder.Build();
        }