public ISourceBlock <ValidatableTx> CalculateUtxo(IChainStateCursor chainStateCursor, Chain chain, ISourceBlock <DecodedBlockTx> blockTxes, CancellationToken cancelToken = default(CancellationToken)) { var chainedHeader = chain.LastBlock; var blockSpentTxes = new BlockSpentTxesBuilder(); var utxoCalculator = new TransformBlock <DecodedBlockTx, ValidatableTx>( blockTx => { var tx = blockTx.Transaction; var txIndex = blockTx.Index; var prevTxOutputs = ImmutableArray.CreateBuilder <PrevTxOutput>(!blockTx.IsCoinbase ? tx.Inputs.Length : 0); //TODO apply real coinbase rule // https://github.com/bitcoin/bitcoin/blob/481d89979457d69da07edd99fba451fd42a47f5c/src/core.h#L219 if (!blockTx.IsCoinbase) { // spend each of the transaction's inputs in the utxo for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++) { var input = tx.Inputs[inputIndex]; var prevTxOutput = this.Spend(chainStateCursor, txIndex, tx, inputIndex, input, chainedHeader, blockSpentTxes); prevTxOutputs.Add(prevTxOutput); } } // there exist two duplicate coinbases in the blockchain, which the design assumes to be impossible // ignore the first occurrences of these duplicates so that they do not need to later be deleted from the utxo, an unsupported operation // no other duplicates will occur again, it is now disallowed var isDupeCoinbase = IsDupeCoinbase(chainedHeader, tx); // add transaction's outputs to utxo, except for the genesis block and the duplicate coinbases if (chainedHeader.Height > 0 && !isDupeCoinbase) { // mint the transaction's outputs in the utxo this.Mint(chainStateCursor, tx, txIndex, chainedHeader); // increase unspent output count chainStateCursor.UnspentOutputCount += tx.Outputs.Length; // increment unspent tx count chainStateCursor.UnspentTxCount++; chainStateCursor.TotalTxCount++; chainStateCursor.TotalInputCount += tx.Inputs.Length; chainStateCursor.TotalOutputCount += tx.Outputs.Length; } return(new ValidatableTx(blockTx, chainedHeader, prevTxOutputs.MoveToImmutable())); }, new ExecutionDataflowBlockOptions { CancellationToken = cancelToken }); blockTxes.LinkTo(utxoCalculator, new DataflowLinkOptions { PropagateCompletion = true }); return(OnCompleteBlock.Create(utxoCalculator, () => { if (!chainStateCursor.TryAddBlockSpentTxes(chainedHeader.Height, blockSpentTxes.ToImmutable())) { throw new ValidationException(chainedHeader.Hash); } }, cancelToken)); }
private ISourceBlock <T> InitMerkleValidator <T>(ChainedHeader chainedHeader, ISourceBlock <T> blockTxes, CancellationToken cancelToken) where T : BlockTx { var merkleStream = new MerkleStream <BlockTxNode>(); var txHashes = new HashSet <UInt256>(); var cve_2012_2459 = false; var merkleValidator = new TransformManyBlock <T, T>( blockTx => { if (cve_2012_2459) { return(new T[0]); } if (txHashes.Add(blockTx.Hash)) { try { merkleStream.AddNode(blockTx); return(new[] { blockTx }); } //TODO catch (InvalidOperationException) { throw CreateMerkleRootException(chainedHeader); } } else { // TODO this needs proper testing, and needs to be made sure this is a safe way to handle the attack // TODO the block should be unmutated before being shared onto the network // CVE-2012-2459 // - if a tx has been repeated, this may be a merkle tree malleability attack against the block // - finish the merkle stream early and verify if the root still matches the block header // // - if the root matches, this is a CVE-2012-2459 attack // - proceed to validate the block normally by ignoring the remaining duplicate transactions // // - if the root does not match // - the block truly did contain duplicate transactions and is invalid merkleStream.FinishPairing(); if (merkleStream.RootNode.Hash == chainedHeader.MerkleRoot) { cve_2012_2459 = true; } else { throw CreateMerkleRootException(chainedHeader); } //TODO throw exception anyway for the sake of the pull tester //return new DecodedBlockTx[0]; //TODO remove the attacked version of the block coreStorage.TryRemoveChainedHeader(chainedHeader.Hash); coreStorage.TryRemoveBlockTransactions(chainedHeader.Hash); //TODO fail the block as missing, not invalid throw new MissingDataException(chainedHeader.Hash); } }, new ExecutionDataflowBlockOptions { CancellationToken = cancelToken }); blockTxes.LinkTo(merkleValidator, new DataflowLinkOptions { PropagateCompletion = true }); return(OnCompleteBlock.Create(merkleValidator, () => { try { merkleStream.FinishPairing(); } //TODO catch (InvalidOperationException) { throw CreateMerkleRootException(chainedHeader); } if (merkleStream.RootNode.Hash != chainedHeader.MerkleRoot) { throw CreateMerkleRootException(chainedHeader); } }, cancelToken)); }