private bool Matches(StoredBlock block1, StoredBlock block2) { if (block1 == null || block2 == null) { return false; } return block1.Hash.SequenceEqual(block2.Hash); }
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; }
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; }
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; }
/// <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(); }