public static async Task ValidateBlockAsync(ICoreStorage coreStorage, ICoreRules rules, Chain newChain, ISourceBlock <ValidatableTx> validatableTxes, CancellationToken cancelToken = default(CancellationToken)) { // tally transactions object finalTally = null; var txTallier = new TransformBlock <ValidatableTx, ValidatableTx>( validatableTx => { var runningTally = finalTally; rules.TallyTransaction(newChain, validatableTx, ref runningTally); finalTally = runningTally; return(validatableTx); }); validatableTxes.LinkTo(txTallier, new DataflowLinkOptions { PropagateCompletion = true }); // validate transactions var txValidator = InitTxValidator(rules, newChain, cancelToken); // begin feeding the tx validator txTallier.LinkTo(txValidator, new DataflowLinkOptions { PropagateCompletion = true }); // validate scripts var scriptValidator = InitScriptValidator(rules, newChain, cancelToken); // begin feeding the script validator txValidator.LinkTo(scriptValidator, new DataflowLinkOptions { PropagateCompletion = true }); //TODO await PipelineCompletion.Create( new Task[] { }, new IDataflowBlock[] { validatableTxes, txTallier, txValidator, scriptValidator }); // validate overall block rules.PostValidateBlock(newChain, finalTally); }
public async Task AddBlockAsync(ChainedHeader chainedHeader, IEnumerable <BlockTx> blockTxes, CancellationToken cancelToken = default(CancellationToken)) { var stopwatch = Stopwatch.StartNew(); var txCount = 0; var inputCount = 0; var newChain = chain.Value.ToBuilder().AddBlock(chainedHeader).ToImmutable(); // pre-validate block before doing any work rules.PreValidateBlock(newChain); using (var chainState = ToImmutable()) using (var handle = storageManager.OpenDeferredChainStateCursor(chainState)) { var chainStateCursor = handle.Item; //TODO note - the utxo updates could be applied either while validation is ongoing, //TODO or after it is complete, begin transaction here to apply immediately chainStateCursor.BeginTransaction(); // verify storage chain tip matches this chain state builder's chain tip CheckChainTip(chainStateCursor); // begin reading and decoding block txes into the buffer var blockTxesBuffer = new BufferBlock <BlockTx>(); var sendBlockTxes = blockTxesBuffer.SendAndCompleteAsync(blockTxes, cancelToken); var decodedTxes = new TransformBlock <BlockTx, DecodedBlockTx>(blockTx => blockTx.Decode(), new ExecutionDataflowBlockOptions { CancellationToken = cancelToken }); // feed block txes through the merkle validator var merkleBlockTxes = InitMerkleValidator(chainedHeader, blockTxesBuffer, cancelToken); merkleBlockTxes.LinkTo(decodedTxes, new DataflowLinkOptions { PropagateCompletion = true }); // track tx/input stats var countBlockTxes = new TransformBlock <DecodedBlockTx, DecodedBlockTx>( blockTx => { txCount++; inputCount += blockTx.Transaction.Inputs.Length; if (!blockTx.IsCoinbase) { stats.txRateMeasure.Tick(); stats.inputRateMeasure.Tick(blockTx.Transaction.Inputs.Length); } return(blockTx); }, new ExecutionDataflowBlockOptions { CancellationToken = cancelToken }); decodedTxes.LinkTo(countBlockTxes, new DataflowLinkOptions { PropagateCompletion = true }); // warm-up utxo entries for block txes var warmedBlockTxes = UtxoLookAhead.LookAhead(countBlockTxes, chainStateCursor, cancelToken); // begin calculating the utxo updates var validatableTxes = utxoBuilder.CalculateUtxo(chainStateCursor, newChain, warmedBlockTxes, cancelToken); // begin validating the block var blockValidator = BlockValidator.ValidateBlockAsync(coreStorage, rules, newChain, validatableTxes, cancelToken); // prepare to finish applying chain state changes once utxo calculation has completed var applyChainState = validatableTxes.Completion.ContinueWith(_ => { if (validatableTxes.Completion.Status == TaskStatus.RanToCompletion) { // finish applying the utxo changes, do not yet commit chainStateCursor.ChainTip = chainedHeader; chainStateCursor.TryAddHeader(chainedHeader); return(chainStateCursor.ApplyChangesAsync()); } else { return(Task.CompletedTask); } }).Unwrap(); var timingTasks = new List <Task>(); // time block txes read timingTasks.Add( blockTxesBuffer.Completion.ContinueWith(_ => { lock (stopwatch) stats.txesReadDurationMeasure.Tick(stopwatch.Elapsed); })); // time block txes decode timingTasks.Add( decodedTxes.Completion.ContinueWith(_ => { lock (stopwatch) stats.txesDecodeDurationMeasure.Tick(stopwatch.Elapsed); })); // time utxo look-ahead timingTasks.Add( warmedBlockTxes.Completion.ContinueWith(_ => { lock (stopwatch) stats.lookAheadDurationMeasure.Tick(stopwatch.Elapsed); })); // time utxo calculation timingTasks.Add( validatableTxes.Completion.ContinueWith(_ => { lock (stopwatch) stats.calculateUtxoDurationMeasure.Tick(stopwatch.Elapsed); })); // time utxo application timingTasks.Add( applyChainState.ContinueWith(_ => { lock (stopwatch) stats.applyUtxoDurationMeasure.Tick(stopwatch.Elapsed); })); // time block validation timingTasks.Add( blockValidator.ContinueWith(_ => { lock (stopwatch) stats.validateDurationMeasure.Tick(stopwatch.Elapsed); })); // wait for all tasks to complete, any exceptions that ocurred will be thrown var pipelineCompletion = PipelineCompletion.Create( new[] { sendBlockTxes, applyChainState, blockValidator } .Concat(timingTasks).ToArray(), new IDataflowBlock[] { blockTxesBuffer, merkleBlockTxes, countBlockTxes, warmedBlockTxes, validatableTxes } .Concat(chainStateCursor.DataFlowBlocks).ToArray()); await pipelineCompletion; var totalTxCount = chainStateCursor.TotalTxCount; var totalInputCount = chainStateCursor.TotalInputCount; var unspentTxCount = chainStateCursor.UnspentTxCount; var unspentOutputCount = chainStateCursor.UnspentOutputCount; // only commit the utxo changes once block validation has completed await commitLock.WaitAsync(); try { await chainStateCursor.CommitTransactionAsync(); chain = new Lazy <Chain>(() => newChain).Force(); } finally { commitLock.Release(); } stats.commitUtxoDurationMeasure.Tick(stopwatch.Elapsed); // update total count stats stats.Height = chain.Value.Height; stats.TotalTxCount = totalTxCount; stats.TotalInputCount = totalInputCount; stats.UnspentTxCount = unspentTxCount; stats.UnspentOutputCount = unspentOutputCount; } stats.blockRateMeasure.Tick(); stats.txesPerBlockMeasure.Tick(txCount); stats.inputsPerBlockMeasure.Tick(inputCount); stats.addBlockDurationMeasure.Tick(stopwatch.Elapsed); LogBlockchainProgress(); }