internal ActionEvaluation EvaluateBlockAction( Block <T> block, IReadOnlyList <ActionEvaluation> txActionEvaluations, StateCompleterSet <T> stateCompleters, ITrie?previousBlockStatesTrie ) { if (block == null) { throw new ArgumentNullException(nameof(block)); } if (_blockAction is null) { var message = "To evaluate block action, Policy.BlockAction must not be null."; throw new InvalidOperationException(message); } _logger.Debug( "Evaluating block action in block {blockIndex}: {block}", block?.Index, block); IAccountStateDelta?lastStates = null; if (!(txActionEvaluations is null) && txActionEvaluations.Count > 0) { lastStates = txActionEvaluations[txActionEvaluations.Count - 1].OutputStates; } Address miner = block !.Miner.GetValueOrDefault(); if (lastStates is null) { IValue?GetState(Address a) => _stateGetter(a, block.PreviousHash, stateCompleters.StateCompleter); FungibleAssetValue GetBalance(Address address, Currency currency) => _balanceGetter( address, currency, block.PreviousHash, stateCompleters.FungibleAssetStateCompleter ); lastStates = block.ProtocolVersion > 0 ? new AccountStateDeltaImpl(GetState, GetBalance, miner) : new AccountStateDeltaImplV0(GetState, GetBalance, miner); } return(ActionEvaluation.EvaluateActionsGradually( block.PreEvaluationHash, block.Index, null, lastStates, miner, miner, Array.Empty <byte>(), new[] { _blockAction }.ToImmutableList(), previousBlockStatesTrie: previousBlockStatesTrie, blockAction: true).First()); }
internal void RenderFastForward( bool render, Block <T> oldTip, Block <T> newTip, Block <T> branchpoint, IReadOnlyList <Block <T> > fastForwardPath, StateCompleterSet <T> stateCompleters) { if (render && ActionRenderers.Any()) { _logger.Debug("Rendering actions in new chain."); long count = 0; foreach (Block <T> block in fastForwardPath) { ImmutableList <ActionEvaluation> evaluations = ActionEvaluator.Evaluate(block, stateCompleters).ToImmutableList(); count += RenderActions( evaluations: evaluations, block: block, stateCompleters: stateCompleters); } _logger.Debug( $"{nameof(Swap)}() completed rendering {{Count}} actions.", count); foreach (IActionRenderer <T> renderer in ActionRenderers) { renderer.RenderBlockEnd(oldTip, newTip); } } }
internal void RenderRewind( bool render, Block <T> oldTip, Block <T> newTip, Block <T> branchpoint, IReadOnlyList <Block <T> > rewindPath, StateCompleterSet <T> stateCompleters) { if (render && ActionRenderers.Any()) { // Unrender stale actions. _logger.Debug("Unrendering abandoned actions..."); long count = 0; foreach (Block <T> block in rewindPath) { ImmutableList <ActionEvaluation> evaluations = ActionEvaluator.Evaluate(block, stateCompleters) .ToImmutableList().Reverse(); count += UnrenderActions( evaluations: evaluations, block: block, stateCompleters: stateCompleters); } _logger.Debug( $"{nameof(Swap)}() completed unrendering {{Actions}} actions.", count); } }
internal void RenderSwap( bool render, Block <T> oldTip, Block <T> newTip, Block <T> branchpoint, IReadOnlyList <Block <T> > rewindPath, IReadOnlyList <Block <T> > fastForwardPath, StateCompleterSet <T> stateCompleters) { // If there is no rewind, it is not considered as a reorg. bool reorg = rewindPath.Count > 0; if (render && reorg) { foreach (IRenderer <T> renderer in Renderers) { renderer.RenderReorg( oldTip: oldTip, newTip: newTip, branchpoint: branchpoint); } } RenderRewind( render: render, oldTip: oldTip, newTip: newTip, branchpoint: branchpoint, rewindPath: rewindPath, stateCompleters: stateCompleters); if (render) { foreach (IRenderer <T> renderer in Renderers) { renderer.RenderBlock( oldTip: oldTip, newTip: newTip); } } RenderFastForward( render: render, oldTip: oldTip, newTip: newTip, branchpoint: branchpoint, fastForwardPath: fastForwardPath, stateCompleters: stateCompleters); if (render && reorg) { foreach (IRenderer <T> renderer in Renderers) { renderer.RenderReorgEnd( oldTip: oldTip, newTip: newTip, branchpoint: branchpoint); } } }
internal long RenderActions( IReadOnlyList <ActionEvaluation> evaluations, Block <T> block, StateCompleterSet <T> stateCompleters) { DateTimeOffset startTime = DateTimeOffset.UtcNow; _logger.Debug( "Rendering actions in block #{BlockIndex} {BlockHash}...", block.Index, block.Hash); if (evaluations is null) { evaluations = ActionEvaluator.Evaluate(block, stateCompleters); } long count = 0; foreach (var evaluation in evaluations) { foreach (IActionRenderer <T> renderer in ActionRenderers) { if (evaluation.Exception is null) { renderer.RenderAction( evaluation.Action, evaluation.InputContext.GetUnconsumedContext(), evaluation.OutputStates); } else { renderer.RenderActionError( evaluation.Action, evaluation.InputContext.GetUnconsumedContext(), evaluation.Exception); } count++; } } TimeSpan renderDuration = DateTimeOffset.Now - startTime; _logger .ForContext("Tag", "Metric") .ForContext("Subtag", "BlockRenderDuration") .Debug( "Finished rendering {RenderCount} renders for actions in " + "block #{BlockIndex} {BlockHash} in {DurationMs:F0}ms.", count, block.Index, block.Hash, renderDuration.TotalMilliseconds); return(count); }
/// <summary> /// Recalculates and complements all <i>missing</i> block states including and upto given /// <paramref name="blockHash"/> starting from the last known states in /// <see cref="StateStore"/> before <paramref name="blockHash"/> if the states are missing /// for <paramref name="blockHash"/>. /// </summary> /// <param name="blockHash">The starting point for searching backwards.</param> internal void ComplementLatestBlockStates(BlockHash blockHash) { _logger.Verbose("Complementing latest block states upto {BlockHash}...", blockHash); // Prevent recursive trial to recalculate & complement incomplete block states by // mistake; if the below code works as intended, these state completers must never // be invoked. StateCompleterSet <T> stateCompleters = StateCompleterSet <T> .Reject; Stack <BlockHash> stack = new Stack <BlockHash>(); BlockHash? pointer = blockHash; while (pointer is { } p) { HashDigest <SHA256>?stateRootHash = Store.GetStateRootHash(p); if (stateRootHash is { } h&& StateStore.ContainsStateRoot(h)) { break; }
internal IReadOnlyList <ActionEvaluation> EvaluateActions( Block <T> block, StateCompleterSet <T> stateCompleters ) { AccountStateGetter stateGetter; AccountBalanceGetter balanceGetter; if (block.PreviousHash is null) { stateGetter = _ => null; balanceGetter = (a, c) => new FungibleAssetValue(c); } else { stateGetter = a => _stateGetter( a, block.PreviousHash, stateCompleters.StateCompleter ); balanceGetter = (address, currency) => _balanceGetter( address, currency, block.PreviousHash, stateCompleters.FungibleAssetStateCompleter ); } ITrie?previousBlockStatesTrie = !(_trieGetter is null) && block.PreviousHash is { } h ? _trieGetter(h) : null; ImmutableList <ActionEvaluation> txEvaluations = block .Evaluate( DateTimeOffset.UtcNow, stateGetter, balanceGetter, previousBlockStatesTrie) .ToImmutableList(); return(_blockAction is null ? txEvaluations : txEvaluations.Add( EvaluateBlockAction( block, txEvaluations, stateCompleters, previousBlockStatesTrie))); }
internal long UnrenderActions( IReadOnlyList <ActionEvaluation> evaluations, Block <T> block, StateCompleterSet <T> stateCompleters) { _logger.Debug( "Unrender actions in block #{BlockIndex} {BlockHash}", block?.Index, block?.Hash); if (evaluations is null) { evaluations = ActionEvaluator.Evaluate(block, stateCompleters) .Reverse().ToImmutableList(); } long count = 0; foreach (ActionEvaluation evaluation in evaluations) { foreach (IActionRenderer <T> renderer in ActionRenderers) { if (evaluation.Exception is null) { renderer.UnrenderAction( evaluation.Action, evaluation.InputContext.GetUnconsumedContext(), evaluation.OutputStates); } else { renderer.UnrenderActionError( evaluation.Action, evaluation.InputContext.GetUnconsumedContext(), evaluation.Exception); } } count++; } return(count); }
/// <summary> /// Render actions of the given <paramref name="block"/>. /// </summary> /// <param name="evaluations"><see cref="ActionEvaluation"/>s of the block. If it is /// <c>null</c>, evaluate actions of the <paramref name="block"/> again.</param> /// <param name="block"><see cref="Block{T}"/> to render actions.</param> /// <param name="stateCompleters">The strategy to complement incomplete block states. /// <see cref="StateCompleterSet{T}.Recalculate"/> by default.</param> /// <returns>The number of actions rendered.</returns> internal long RenderActions( IReadOnlyList <ActionEvaluation> evaluations, Block <T> block, StateCompleterSet <T> stateCompleters) { _logger.Debug("Render actions in block {blockIndex}: {block}", block?.Index, block); if (evaluations is null) { evaluations = ActionEvaluator.Evaluate(block, stateCompleters); } long count = 0; foreach (var evaluation in evaluations) { foreach (IActionRenderer <T> renderer in ActionRenderers) { if (evaluation.Exception is null) { renderer.RenderAction( evaluation.Action, evaluation.InputContext.GetUnconsumedContext(), evaluation.OutputStates); } else { renderer.RenderActionError( evaluation.Action, evaluation.InputContext.GetUnconsumedContext(), evaluation.Exception); } count++; } } return(count); }
/// <summary> /// Recalculates and complements all <i>missing</i> block states including and upto given /// <paramref name="blockHash"/> starting from the genesis block. /// </summary> /// <param name="blockHash">The inclusive limit of target hash to terminate complementation. /// </param> /// <remarks> /// <para> /// If a complementation of the entire blockchain is needed, call with the tip hash of the /// <see cref="BlockChain{T}"/>. /// </para> /// <para> /// Unlike <see cref="RecalculateBlockStates"/>, this method skips recalculations if states /// are found for intermediate blocks. This may not be fully secure if states for /// blocks in <see cref="IStateStore"/> are somehow corrupted. /// </para> /// </remarks> internal void ComplementAllBlockStates(BlockHash blockHash) { _logger.Verbose("Complementing all block states upto {BlockHash}...", blockHash); // Prevent recursive trial to recalculate & complement incomplete block states by // mistake; if the below code works as intended, these state completers must never // be invoked. StateCompleterSet <T> stateCompleters = StateCompleterSet <T> .Reject; // Calculates and fills the incomplete states on the fly. foreach (BlockHash hash in BlockHashes) { Block <T> block = this[hash]; if (StateStore.ContainsStateRoot(block.StateRootHash)) { continue; } IReadOnlyList <ActionEvaluation> evaluations = ActionEvaluator.Evaluate( block, stateCompleters); _rwlock.EnterWriteLock(); try { SetStates(block, evaluations); } finally { _rwlock.ExitWriteLock(); } if (blockHash.Equals(hash)) { break; } } }
internal System.Action Swap( BlockChain <T> other, bool render, StateCompleterSet <T>?stateCompleters = null) { if (other is null) { throw new ArgumentNullException(nameof(other)); } // As render/unrender processing requires every step's states from the branchpoint // to the new/stale tip, incomplete states need to be complemented anyway... StateCompleterSet <T> completers = stateCompleters ?? StateCompleterSet <T> .Recalculate; if (Tip.Equals(other.Tip)) { // If it's swapped for a chain with the same tip, it means there is no state change. // Hence render is unnecessary. render = false; } else { _logger.Debug( "The blockchain was reorged from " + "{OldChainId} (#{OldTipIndex} {OldTipHash}) " + "to {NewChainId} (#{NewTipIndex} {NewTipHash}).", Id, Tip.Index, Tip.Hash, other.Id, other.Tip.Index, other.Tip.Hash); } System.Action renderSwap = () => { }; _rwlock.EnterUpgradeableReadLock(); try { // Finds the branch point. Block <T> branchpoint = FindTopCommon(this, other); if (branchpoint is null) { const string msg = "A chain cannot be reorged into a heterogeneous chain with a " + "different genesis."; throw new InvalidGenesisBlockException(Genesis.Hash, other.Genesis.Hash, msg); } _logger.Debug( "The branchpoint is #{BranchpointIndex} {BranchpointHash}.", branchpoint.Index, branchpoint ); Block <T> oldTip = Tip, newTip = other.Tip; ImmutableList <Block <T> > rewindPath = GetRewindPath(this, branchpoint.Hash); ImmutableList <Block <T> > fastForwardPath = GetFastForwardPath(other, branchpoint.Hash); // If there is no rewind, it is not considered as a reorg. bool reorg = rewindPath.Count > 0; _rwlock.EnterWriteLock(); try { IEnumerable <long> LongRange(long start, long count) { for (long i = 0; i < count; i++) { yield return(start + i); } } ImmutableHashSet <Transaction <T> > GetTxsWithRange(BlockChain <T> chain, Block <T> start, Block <T> end) => LongRange(start.Index + 1, end.Index - start.Index) .SelectMany(index => chain[index].Transactions) .ToImmutableHashSet(); // It assumes reorg is small size. If it was big, this may be heavy task. ImmutableHashSet <Transaction <T> > txsToStage = GetTxsWithRange(this, branchpoint, Tip); ImmutableHashSet <Transaction <T> > txsToUnstage = GetTxsWithRange(other, branchpoint, other.Tip); foreach (Transaction <T> tx in txsToStage) { StagePolicy.Stage(this, tx); } Guid obsoleteId = Id; Id = other.Id; Store.SetCanonicalChainId(Id); _blocks = new BlockSet <T>(Policy.GetHashAlgorithm, Store); foreach (Transaction <T> tx in txsToUnstage) { StagePolicy.Unstage(this, tx.Id); } TipChanged?.Invoke(this, (oldTip, newTip)); Store.DeleteChainId(obsoleteId); } finally { _rwlock.ExitWriteLock(); } renderSwap = () => RenderSwap( render: render, oldTip: oldTip, newTip: newTip, branchpoint: branchpoint, rewindPath: rewindPath, fastForwardPath: fastForwardPath, stateCompleters: completers); } finally { _rwlock.ExitUpgradeableReadLock(); } return(renderSwap); }
// FIXME it's very dangerous because replacing Id means // ALL blocks (referenced by MineBlock(), etc.) will be changed. internal System.Action Swap( BlockChain <T> other, bool render, StateCompleterSet <T>?stateCompleters = null) { if (other is null) { throw new ArgumentNullException(nameof(other)); } // As render/unrender processing requires every step's states from the branchpoint // to the new/stale tip, incomplete states need to be complemented anyway... StateCompleterSet <T> completers = stateCompleters ?? StateCompleterSet <T> .Recalculate; if (Tip.Equals(other.Tip)) { // If it's swapped for a chain with the same tip, it means there is no state change. // Hence render is unnecessary. render = false; } else { _logger.Debug( "The blockchain was reorged from " + "{OldChainId} (#{OldTipIndex} {OldTipHash}) " + "to {NewChainId} (#{NewTipIndex} {NewTipHash}).", Id, Tip.Index, Tip.Hash, other.Id, other.Tip.Index, other.Tip.Hash); } System.Action renderSwap = () => { }; _rwlock.EnterUpgradeableReadLock(); try { // Finds the branch point. Block <T> branchpoint = FindTopCommon(this, other); if (branchpoint is null) { const string msg = "A chain cannot be reorged into a heterogeneous chain with a " + "different genesis."; throw new InvalidGenesisBlockException(Genesis.Hash, other.Genesis.Hash, msg); } _logger.Debug( "The branchpoint is #{BranchpointIndex} {BranchpointHash}.", branchpoint.Index, branchpoint ); Block <T> oldTip = Tip, newTip = other.Tip; ImmutableList <Block <T> > rewindPath = GetRewindPath(this, branchpoint.Hash); ImmutableList <Block <T> > fastForwardPath = GetFastForwardPath(other, branchpoint.Hash); // If there is no rewind, it is not considered as a reorg. bool reorg = rewindPath.Count > 0; _rwlock.EnterWriteLock(); try { IEnumerable <Transaction <T> > GetTxsWithRange(BlockChain <T> chain, Block <T> start, Block <T> end) => Enumerable .Range((int)start.Index + 1, (int)(end.Index - start.Index)) .SelectMany(x => { // FIXME: Change the type of IBlockContent<T>.Transactions to // IImmutableSet<Transaction<T>>, and define a distinct property // to Block<T> for this ordering. Block <T> block = chain[x]; return(ActionEvaluator <T> .OrderTxsForEvaluation( block.ProtocolVersion, block.Transactions, block.PreEvaluationHash )); }); // It assumes reorg is small size. If it was big, this may be heavy task. ImmutableHashSet <Transaction <T> > unstagedTxs = GetTxsWithRange(this, branchpoint, Tip).ToImmutableHashSet(); ImmutableHashSet <Transaction <T> > stageTxs = GetTxsWithRange(other, branchpoint, other.Tip).ToImmutableHashSet(); ImmutableHashSet <Transaction <T> > restageTxs = unstagedTxs.Except(stageTxs); foreach (Transaction <T> restageTx in restageTxs) { StagePolicy.Stage(this, restageTx); } Guid obsoleteId = Id; Id = other.Id; Store.SetCanonicalChainId(Id); _blocks = new BlockSet <T>(Policy.GetHashAlgorithm, Store); TipChanged?.Invoke(this, (oldTip, newTip)); Store.DeleteChainId(obsoleteId); } finally { _rwlock.ExitWriteLock(); } renderSwap = () => RenderSwap( render: render, oldTip: oldTip, newTip: newTip, branchpoint: branchpoint, rewindPath: rewindPath, fastForwardPath: fastForwardPath, stateCompleters: completers); } finally { _rwlock.ExitUpgradeableReadLock(); } return(renderSwap); }