diff --git a/CHANGES.md b/CHANGES.md index f9f1ff02a1..2d3e0fceb2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,28 +10,49 @@ To be released. ### Backward-incompatible API changes + - Changed `IActionEvaluator.Evaluate()`'s return type to + `IReadOnlyList` from + `IReadOnlyList`. [[#3445]] + - Changed `BlockChain.DetermineStateRootHash(IActionEvaluator, + IPreEvaluationBlock, out IReadOnlyList)` to + `BlockChain.DetermineStateRootHash(IActionEvaluator, + IPreEvaluationBlock, out IReadOnlyList)`. + [[#3445]] + - Changed `BlockChain.EvaluateGenesis()`'s return type to + `IReadOnlyList` from + `IReadOnlyList`. [[#3445]] + - Changed `BlockChain.EvaluateBlock()`'s return type to + `IReadOnlyList` from + `IReadOnlyList`. [[#3445]] + ### Backward-incompatible network protocol changes ### Backward-incompatible storage format changes ### Added APIs - - (Libplanet.Explorer) Added `TxResult.InputState` of type + - (Libplanet.Explorer) Added `TxResult.InputState` of type `HashDigest?`. [[#3446], [#3447]] - - (Libplanet.Explorer) Added `TxResult.OutputState` of type + - (Libplanet.Explorer) Added `TxResult.OutputState` of type `HashDigest?`. [[#3446], [#3447]] ### Behavioral changes + - `IActionEvaluator.Evaluate()`, `BlockChain.EvaluateGenesis()`, + and `BlockChain.EvaluateBlock()` have a side-effect of storing + data to `IStateStore` when called. [[#3445]] + ### Bug fixes ### Dependencies ### CLI tools +[#3445]: https://github.com/planetarium/libplanet/pull/3445 [#3446]: https://github.com/planetarium/libplanet/issues/3446 [#3447]: https://github.com/planetarium/libplanet/pull/3447 + Version 3.5.0 ------------- diff --git a/Libplanet.Action/ActionEvaluator.cs b/Libplanet.Action/ActionEvaluator.cs index 9c3b602d0a..ffdfbae2e9 100644 --- a/Libplanet.Action/ActionEvaluator.cs +++ b/Libplanet.Action/ActionEvaluator.cs @@ -11,6 +11,7 @@ using Libplanet.Action.State; using Libplanet.Common; using Libplanet.Store; +using Libplanet.Store.Trie; using Libplanet.Types.Blocks; using Libplanet.Types.Tx; using Serilog; @@ -76,7 +77,7 @@ public static int GenerateRandomSeed( /// [Pure] - public IReadOnlyList Evaluate( + public IReadOnlyList Evaluate( IPreEvaluationBlock block, HashDigest? baseStateRootHash) { @@ -95,19 +96,15 @@ public IReadOnlyList Evaluate( EvaluateBlock(block, previousState).ToImmutableList(); var policyBlockAction = _policyBlockActionGetter(block); - if (policyBlockAction is null) - { - return evaluations; - } - else + if (policyBlockAction is { } blockAction) { previousState = evaluations.Count > 0 ? evaluations.Last().OutputState : previousState; - return evaluations.Add( - EvaluatePolicyBlockAction(block, previousState) - ); + evaluations = evaluations.Add(EvaluatePolicyBlockAction(block, previousState)); } + + return ToCommittedEvaluation(block, evaluations, baseStateRootHash); } finally { @@ -479,6 +476,64 @@ internal IAccount PrepareInitialDelta(HashDigest? stateRootHash) return new Account(new AccountState(_stateStore.GetStateRoot(stateRootHash))); } + internal IReadOnlyList + ToCommittedEvaluation( + IPreEvaluationBlock block, + IReadOnlyList evaluations, + HashDigest? baseStateRootHash) + { + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); + + ITrie trie = _stateStore.GetStateRoot(baseStateRootHash); + var committedEvaluations = new List(); + + int setCount = 0; + foreach (var evaluation in evaluations) + { + ITrie nextTrie = trie; + foreach (var kv in evaluation.OutputState.Delta.ToRawDelta()) + { + nextTrie = nextTrie.Set(kv.Key, kv.Value); + setCount++; + } + + nextTrie = _stateStore.Commit(nextTrie); + var committedEvaluation = new CommittedActionEvaluation( + action: evaluation.Action, + inputContext: new CommittedActionContext( + signer: evaluation.InputContext.Signer, + txId: evaluation.InputContext.TxId, + miner: evaluation.InputContext.Miner, + blockIndex: evaluation.InputContext.BlockIndex, + blockProtocolVersion: evaluation.InputContext.BlockProtocolVersion, + rehearsal: evaluation.InputContext.Rehearsal, + previousState: trie.Hash, + randomSeed: evaluation.InputContext.RandomSeed, + blockAction: evaluation.InputContext.BlockAction), + outputState: nextTrie.Hash, + exception: evaluation.Exception); + committedEvaluations.Add(committedEvaluation); + + trie = nextTrie; + } + + _logger + .ForContext("Tag", "Metric") + .ForContext("Subtag", "StateUpdateDuration") + .Information( + "Took {DurationMs} ms to update the states with {Count} key changes " + + "and resulting in state root hash {StateRootHash} for " + + "block #{BlockIndex} pre-evaluation hash {PreEvaluationHash}", + stopwatch.ElapsedMilliseconds, + setCount, + trie.Hash, + block.Index, + block.PreEvaluationHash); + + return committedEvaluations; + } + [Pure] private static IEnumerable OrderTxsForEvaluationV0( IEnumerable txs, diff --git a/Libplanet.Action/IActionEvaluator.cs b/Libplanet.Action/IActionEvaluator.cs index 69be793465..39c802e78a 100644 --- a/Libplanet.Action/IActionEvaluator.cs +++ b/Libplanet.Action/IActionEvaluator.cs @@ -3,6 +3,7 @@ using System.Security.Cryptography; using Libplanet.Action.Loader; using Libplanet.Common; +using Libplanet.Store; using Libplanet.Types.Blocks; namespace Libplanet.Action @@ -24,16 +25,20 @@ public interface IActionEvaluator /// . /// The result of evaluating every related to /// as an of - /// s. + /// s. /// - /// Publicly exposed for benchmarking. - /// First evaluates all s in + /// + /// This has a side-effect of writing data to internally held . + /// + /// + /// First evaluates all s in /// of and appends the /// evaluation of the held by the instance at - /// the end. + /// the end. + /// /// [Pure] - IReadOnlyList Evaluate( + IReadOnlyList Evaluate( IPreEvaluationBlock block, HashDigest? baseStateRootHash); } diff --git a/Libplanet.Tests/Action/ActionEvaluatorTest.cs b/Libplanet.Tests/Action/ActionEvaluatorTest.cs index a4b1dec6a5..ca5f9d2eb0 100644 --- a/Libplanet.Tests/Action/ActionEvaluatorTest.cs +++ b/Libplanet.Tests/Action/ActionEvaluatorTest.cs @@ -86,15 +86,14 @@ public void Idempotent() transactions: txs).Propose(); var actionEvaluator = new ActionEvaluator( _ => null, - new TrieStateStore(new MemoryKeyValueStore()), + stateStore, new SingleActionLoader(typeof(RandomAction))); Block stateRootBlock = noStateRootBlock.Sign( GenesisProposer, BlockChain.DetermineGenesisStateRootHash( actionEvaluator, noStateRootBlock, - out IReadOnlyList evals)); - stateStore.Commit(null, evals.GetRawTotalDelta()); + out IReadOnlyList evals)); var generatedRandomNumbers = new List(); AssertPreEvaluationBlocksEqual(stateRootBlock, noStateRootBlock); @@ -103,10 +102,14 @@ public void Idempotent() { var actionEvaluations = actionEvaluator.Evaluate(noStateRootBlock, null); generatedRandomNumbers.Add( - (Integer)actionEvaluations[0].OutputState.GetState(txAddress)); + (Integer)new AccountState( + stateStore.GetStateRoot(actionEvaluations[0].OutputState)) + .GetState(txAddress)); actionEvaluations = actionEvaluator.Evaluate(stateRootBlock, null); generatedRandomNumbers.Add( - (Integer)actionEvaluations[0].OutputState.GetState(txAddress)); + (Integer)new AccountState( + stateStore.GetStateRoot(actionEvaluations[0].OutputState)) + .GetState(txAddress)); } for (int i = 1; i < generatedRandomNumbers.Count; ++i) @@ -921,10 +924,12 @@ public void TotalUpdatedFungibleAssets() var block = chain.ProposeBlock( GenesisProposer, txs.ToImmutableList(), CreateBlockCommit(chain.Tip)); - var evals = chain.EvaluateBlock(block); + var evals = actionEvaluator.EvaluateBlock( + block, + new Account(chain.GetAccountState(block.PreviousHash))); - // Includes policy block action - Assert.Equal(4, evals.Count); + // Does not include policy block action + Assert.Equal(3, evals.Count()); var latest = evals.Last().OutputState; // Only addresses[0] and addresses[1] succeeded in minting @@ -991,10 +996,11 @@ public void EvaluateActionAndCollectFee() Assert.Null(evaluations.Single().Exception); Assert.Equal( FungibleAssetValue.FromRawValue(foo, 9), - evaluations.Single().OutputState.GetBalance(address, foo)); + chain.GetAccountState(evaluations.Single().OutputState).GetBalance(address, foo)); Assert.Equal( FungibleAssetValue.FromRawValue(foo, 1), - evaluations.Single().OutputState.GetBalance(miner.ToAddress(), foo)); + chain.GetAccountState( + evaluations.Single().OutputState).GetBalance(miner.ToAddress(), foo)); } [Fact] @@ -1060,10 +1066,11 @@ public void EvaluateThrowingExceedGasLimit() evaluations.Single().Exception?.InnerException?.GetType()); Assert.Equal( FungibleAssetValue.FromRawValue(foo, 5), - evaluations.Single().OutputState.GetBalance(address, foo)); + chain.GetAccountState(evaluations.Single().OutputState).GetBalance(address, foo)); Assert.Equal( FungibleAssetValue.FromRawValue(foo, 5), - evaluations.Single().OutputState.GetBalance(miner.ToAddress(), foo)); + chain.GetAccountState( + evaluations.Single().OutputState).GetBalance(miner.ToAddress(), foo)); } [Fact] diff --git a/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs b/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs index a43b61c48d..4ff7204fe4 100644 --- a/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs +++ b/Libplanet.Tests/Blockchain/BlockChainTest.Append.cs @@ -608,8 +608,8 @@ public void CachedActionEvaluationWrittenOnAppend() _blockChain.StageTransaction(txA0); _blockChain.StageTransaction(txA1); Block block = _blockChain.ProposeBlock(miner); - (IReadOnlyList actionEvaluations, _) = - _blockChain.ToCommittedEvaluation(block, _blockChain.EvaluateBlock(block)); + IReadOnlyList actionEvaluations = + _blockChain.EvaluateBlock(block); Assert.Equal(0L, _blockChain.Tip.Index); _blockChain.Append( block, diff --git a/Libplanet.Tests/Blocks/PreEvaluationBlockTest.cs b/Libplanet.Tests/Blocks/PreEvaluationBlockTest.cs index 63dbef5ebd..ccde686aa6 100644 --- a/Libplanet.Tests/Blocks/PreEvaluationBlockTest.cs +++ b/Libplanet.Tests/Blocks/PreEvaluationBlockTest.cs @@ -110,7 +110,7 @@ public void DetermineStateRootHash() actionTypeLoader: new SingleActionLoader(typeof(Arithmetic))); HashDigest genesisStateRootHash = BlockChain.DetermineGenesisStateRootHash( - actionEvaluator, preEvalGenesis, out _); + actionEvaluator, preEvalGenesis, out var _); _output.WriteLine("#0 StateRootHash: {0}", genesisStateRootHash); Block genesis = preEvalGenesis.Sign(_contents.GenesisKey, genesisStateRootHash); diff --git a/Libplanet.Tests/Store/StoreFixture.cs b/Libplanet.Tests/Store/StoreFixture.cs index ea5cfc1b51..6661eb2b7c 100644 --- a/Libplanet.Tests/Store/StoreFixture.cs +++ b/Libplanet.Tests/Store/StoreFixture.cs @@ -112,8 +112,7 @@ protected StoreFixture(IAction blockAction = null) BlockChain.DetermineGenesisStateRootHash( actionEvaluator, preEval, - out IReadOnlyList evals)); - stateStore.Commit(null, evals.GetRawTotalDelta()); + out IReadOnlyList evals)); stateRootHashes[GenesisBlock.Hash] = GenesisBlock.StateRootHash; Block1 = TestUtils.ProposeNextBlock( GenesisBlock, diff --git a/Libplanet/Blockchain/BlockChain.Evaluate.cs b/Libplanet/Blockchain/BlockChain.Evaluate.cs index 648e490b7f..12c857ef77 100644 --- a/Libplanet/Blockchain/BlockChain.Evaluate.cs +++ b/Libplanet/Blockchain/BlockChain.Evaluate.cs @@ -1,13 +1,11 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.Contracts; +using System.Linq; using System.Security.Cryptography; -using Bencodex.Types; using Libplanet.Action; using Libplanet.Action.Loader; -using Libplanet.Action.State; using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Store; @@ -39,13 +37,12 @@ public partial class BlockChain public static HashDigest DetermineGenesisStateRootHash( IActionEvaluator actionEvaluator, IPreEvaluationBlock preEvaluationBlock, - out IReadOnlyList evaluations) + out IReadOnlyList evaluations) { evaluations = EvaluateGenesis(actionEvaluator, preEvaluationBlock); - IImmutableDictionary delta = evaluations.GetRawTotalDelta(); - IStateStore stateStore = new TrieStateStore(new DefaultKeyValueStore(null)); - ITrie trie = stateStore.Commit(stateStore.GetStateRoot(null).Hash, delta); - return trie.Hash; + return evaluations.Count > 0 + ? evaluations.Last().OutputState + : new TrieStateStore(new DefaultKeyValueStore(null)).GetStateRoot(null).Hash; } /// @@ -55,13 +52,13 @@ public static HashDigest DetermineGenesisStateRootHash( /// evaluate the proposed . /// The to /// evaluate. - /// An of s + /// An of s /// resulting from evaluating using /// . /// Thrown if s /// is not zero. [Pure] - public static IReadOnlyList EvaluateGenesis( + public static IReadOnlyList EvaluateGenesis( IActionEvaluator actionEvaluator, IPreEvaluationBlock preEvaluationBlock) { @@ -105,7 +102,7 @@ public HashDigest DetermineBlockStateRootHash( { Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); - var rawEvaluations = EvaluateBlock(block); + evaluations = EvaluateBlock(block); _logger.Debug( "Took {DurationMs} ms to evaluate block #{BlockIndex} " + @@ -113,13 +110,11 @@ public HashDigest DetermineBlockStateRootHash( stopwatch.ElapsedMilliseconds, block.Index, block.PreEvaluationHash, - rawEvaluations.Count); + evaluations.Count); - (var committedEvaluations, var rootHash) = - ToCommittedEvaluation(block, rawEvaluations); - - evaluations = committedEvaluations; - return rootHash; + return evaluations.Count > 0 + ? evaluations.Last().OutputState + : GetAccountState(block.PreviousHash).Trie.Hash; } finally { @@ -131,13 +126,13 @@ public HashDigest DetermineBlockStateRootHash( /// Evaluates the s in given . /// /// The to execute. - /// An of s for - /// given . + /// An of s + /// for given . /// Thrown when given /// contains an action that cannot be loaded with . /// [Pure] - public IReadOnlyList EvaluateBlock(IPreEvaluationBlock block) => + public IReadOnlyList EvaluateBlock(IPreEvaluationBlock block) => ActionEvaluator.Evaluate(block, Store.GetStateRootHash(block.PreviousHash)); /// @@ -167,62 +162,5 @@ internal Block EvaluateAndSign( : throw new ArgumentException( $"Given {nameof(preEvaluationBlock)} must have protocol version " + $"2 or greater: {preEvaluationBlock.ProtocolVersion}"); - - internal (IReadOnlyList, HashDigest) - ToCommittedEvaluation( - IPreEvaluationBlock block, - IReadOnlyList evaluations) - { - Stopwatch stopwatch = new Stopwatch(); - stopwatch.Start(); - - ITrie trie = GetAccountState(block.PreviousHash).Trie; - var committedEvaluations = new List(); - - int setCount = 0; - foreach (var evaluation in evaluations) - { - ITrie nextTrie = trie; - foreach (var kv in evaluation.OutputState.Delta.ToRawDelta()) - { - nextTrie = nextTrie.Set(kv.Key, kv.Value); - setCount++; - } - - nextTrie = StateStore.Commit(nextTrie); - var committedEvaluation = new CommittedActionEvaluation( - action: evaluation.Action, - inputContext: new CommittedActionContext( - signer: evaluation.InputContext.Signer, - txId: evaluation.InputContext.TxId, - miner: evaluation.InputContext.Miner, - blockIndex: evaluation.InputContext.BlockIndex, - blockProtocolVersion: evaluation.InputContext.BlockProtocolVersion, - rehearsal: evaluation.InputContext.Rehearsal, - previousState: trie.Hash, - randomSeed: evaluation.InputContext.RandomSeed, - blockAction: evaluation.InputContext.BlockAction), - outputState: nextTrie.Hash, - exception: evaluation.Exception); - committedEvaluations.Add(committedEvaluation); - - trie = nextTrie; - } - - _logger - .ForContext("Tag", "Metric") - .ForContext("Subtag", "StateUpdateDuration") - .Information( - "Took {DurationMs} ms to update the states with {Count} key changes " + - "and resulting in state root hash {StateRootHash} for " + - "block #{BlockIndex} pre-evaluation hash {PreEvaluationHash}", - stopwatch.ElapsedMilliseconds, - setCount, - trie.Hash, - block.Index, - block.PreEvaluationHash); - - return (committedEvaluations, trie.Hash); - } } } diff --git a/Libplanet/Blockchain/BlockChain.Swap.cs b/Libplanet/Blockchain/BlockChain.Swap.cs index e6bf06c086..1e543ac405 100644 --- a/Libplanet/Blockchain/BlockChain.Swap.cs +++ b/Libplanet/Blockchain/BlockChain.Swap.cs @@ -1,7 +1,6 @@ #nullable disable using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Security.Cryptography; @@ -175,12 +174,11 @@ internal void RenderFastForward( Block block = Store.GetBlock(hash); HashDigest? baseStateRootHash = Store.GetStateRootHash(block.PreviousHash); - ImmutableList evaluations = - ActionEvaluator.Evaluate(block, baseStateRootHash).ToImmutableList(); - (var committedEvaluations, _) = ToCommittedEvaluation(block, evaluations); + IReadOnlyList evaluations = + ActionEvaluator.Evaluate(block, baseStateRootHash); RenderActions( - evaluations: committedEvaluations, + evaluations: evaluations, block: block); } diff --git a/Libplanet/Blockchain/BlockChain.cs b/Libplanet/Blockchain/BlockChain.cs index 76591f0883..1989564468 100644 --- a/Libplanet/Blockchain/BlockChain.cs +++ b/Libplanet/Blockchain/BlockChain.cs @@ -384,7 +384,7 @@ public static BlockChain Create( var computedStateRootHash = DetermineGenesisStateRootHash( actionEvaluator, preEval, - out IReadOnlyList evals); + out var _); if (!genesisBlock.StateRootHash.Equals(computedStateRootHash)) { throw new InvalidBlockStateRootHashException( @@ -414,9 +414,6 @@ public static BlockChain Create( store.SetCanonicalChainId(id); - var delta = evals.GetRawTotalDelta(); - stateStore.Commit(null, delta); - blockChainStates ??= new BlockChainStates(store, stateStore); return new BlockChain(