// TODO - the way this operates is specific to the block validation pipeline, this should be more apparent public async Task ApplyChangesAsync() { CheckWriteTransaction(); if (changesApplied) { throw new InvalidOperationException(); } unspentTxes.WorkQueue.Complete(); unspentTxOutputs.WorkQueue.Complete(); await utxoApplier.Completion; await utxoApplier2.Completion; parentCursor.ChainTip = chainTip; parentCursor.UnspentOutputCount = unspentOutputCount; parentCursor.UnspentTxCount = unspentTxCount; parentCursor.TotalTxCount = totalTxCount; parentCursor.TotalInputCount = totalInputCount; parentCursor.TotalOutputCount = totalOutputCount; if (headers.Updated.Count > 0) { throw new InvalidOperationException(); } foreach (var chainedHeader in headers.Added) { if (!parentCursor.TryAddHeader(chainedHeader.Value)) { throw new InvalidOperationException(); } } foreach (var blockHash in headers.Deleted) { if (!parentCursor.TryRemoveHeader(blockHash)) { throw new InvalidOperationException(); } } if (blockSpentTxes.Updated.Count > 0) { throw new InvalidOperationException(); } foreach (var spentTxes in blockSpentTxes.Added) { if (!parentCursor.TryAddBlockSpentTxes(spentTxes.Key, spentTxes.Value)) { throw new InvalidOperationException(); } } foreach (var blockHeight in blockSpentTxes.Deleted) { if (!parentCursor.TryRemoveBlockSpentTxes(blockHeight)) { throw new InvalidOperationException(); } } if (blockUnmintedTxes.Updated.Count > 0) { throw new InvalidOperationException(); } foreach (var unmintedTxes in blockUnmintedTxes.Added) { if (!parentCursor.TryAddBlockUnmintedTxes(unmintedTxes.Key, unmintedTxes.Value)) { throw new InvalidOperationException(); } } foreach (var blockHeight in blockUnmintedTxes.Deleted) { if (!parentCursor.TryRemoveBlockUnmintedTxes(blockHeight)) { throw new InvalidOperationException(); } } changesApplied = true; }
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)); }
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); }