public void Idempotent() { // NOTE: This test checks that blocks can be evaluated idempotently. Also it checks // the action results in pre-evaluation step and in evaluation step are equal. const int repeatCount = 2; var signer = new PrivateKey(); var timestamp = DateTimeOffset.UtcNow; var txAddress = signer.ToAddress(); var txs = new[] { Transaction <RandomAction> .Create( nonce : 0, privateKey : signer, genesisHash : null, actions : new[] { new RandomAction(txAddress), }), }; var stateStore = new TrieStateStore(new MemoryKeyValueStore()); HashAlgorithmGetter hashAlgorithmGetter = _ => HashAlgorithmType.Of <SHA256>(); PreEvaluationBlock <RandomAction> noStateRootBlock = MineGenesis( hashAlgorithmGetter: hashAlgorithmGetter, miner: GenesisMiner.PublicKey, timestamp: timestamp, transactions: txs ); Block <RandomAction> stateRootBlock = noStateRootBlock.Evaluate(GenesisMiner, null, stateStore); var actionEvaluator = new ActionEvaluator <RandomAction>( hashAlgorithmGetter: hashAlgorithmGetter, policyBlockAction: null, stateGetter: ActionEvaluator <RandomAction> .NullStateGetter, balanceGetter: ActionEvaluator <RandomAction> .NullBalanceGetter, trieGetter: null); var generatedRandomNumbers = new List <int>(); AssertPreEvaluationBlocksEqual(stateRootBlock, noStateRootBlock); for (int i = 0; i < repeatCount; ++i) { var actionEvaluations = actionEvaluator.Evaluate( noStateRootBlock, StateCompleterSet <RandomAction> .Reject); generatedRandomNumbers.Add( (Integer)actionEvaluations[0].OutputStates.GetState(txAddress)); actionEvaluations = actionEvaluator.Evaluate( stateRootBlock, StateCompleterSet <RandomAction> .Reject); generatedRandomNumbers.Add( (Integer)actionEvaluations[0].OutputStates.GetState(txAddress)); } for (int i = 1; i < generatedRandomNumbers.Count; ++i) { Assert.Equal(generatedRandomNumbers[0], generatedRandomNumbers[i]); } }
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 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); }
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; } } }