diff --git a/Libplanet.Action/ActionEvaluator.cs b/Libplanet.Action/ActionEvaluator.cs index 1dd8d9d20a2..b2f494a9ee4 100644 --- a/Libplanet.Action/ActionEvaluator.cs +++ b/Libplanet.Action/ActionEvaluator.cs @@ -10,13 +10,11 @@ using Libplanet.Action.Loader; using Libplanet.Action.State; using Libplanet.Common; -using Libplanet.Crypto; using Libplanet.Store; using Libplanet.Store.Trie; using Libplanet.Types.Blocks; using Libplanet.Types.Tx; using Serilog; -using static Libplanet.Action.State.KeyConverters; namespace Libplanet.Action { @@ -110,8 +108,8 @@ public IReadOnlyList Evaluate( stopwatch.Start(); try { - IWorld previousState = PrepareInitialDelta(baseStateRootHash); - previousState = MigrateWorld(previousState, block.ProtocolVersion); + IWorld previousState = _stateStore.GetWorld(baseStateRootHash); + previousState = _stateStore.MigrateWorld(previousState, block.ProtocolVersion); ImmutableList evaluations = EvaluateBlock(block, previousState).ToImmutableList(); @@ -333,15 +331,7 @@ IActionContext CreateActionContext(IWorld newPrevState) state = feeCollector.Refund(state); state = feeCollector.Reward(state); - - if (state.Legacy) - { - state = CommitLegacyWorld(state, stateStore); - } - else - { - state = CommitWorld(state, stateStore); - } + state = stateStore.CommitWorld(state); if (!state.Trie.Recorded) { @@ -506,12 +496,6 @@ internal ActionEvaluation EvaluatePolicyBlockAction( logger: _logger).Single(); } - internal IWorld PrepareInitialDelta(HashDigest? stateRootHash) - { - return new World( - new WorldBaseState(_stateStore.GetStateRoot(stateRootHash), _stateStore)); - } - internal IReadOnlyList ToCommittedEvaluation( IPreEvaluationBlock block, @@ -549,106 +533,6 @@ internal IReadOnlyList return committedEvaluations; } - internal IWorld MigrateWorld(IWorld originalWorld, int targetVersion) - { - if (originalWorld.Version > targetVersion) - { - throw new ApplicationException( - $"Given {nameof(originalWorld)} with version {originalWorld.Version} " + - $"cannot be migrated to a lower version {targetVersion}."); - } - - IWorld world = originalWorld; - - // Migrate up to BlockMetadata.WorldStateProtocolVersion - // if conditions are met. - if (targetVersion >= BlockMetadata.WorldStateProtocolVersion && - world.Version < BlockMetadata.WorldStateProtocolVersion) - { - var worldTrie = _stateStore.GetStateRoot(null); - worldTrie = worldTrie.SetMetadata( - new TrieMetadata(BlockMetadata.WorldStateProtocolVersion)); - worldTrie = worldTrie.Set( - ToStateKey(ReservedAddresses.LegacyAccount), - new Binary(world.Trie.Hash.ByteArray)); - worldTrie = _stateStore.Commit(worldTrie); - world = new World(new WorldBaseState(worldTrie, _stateStore)); - } - - // Migrate up to BlockMetadata.ValidatorSetAccountProtocolVersion - // if conditions are met. - if (targetVersion >= BlockMetadata.ValidatorSetAccountProtocolVersion && - world.Version < BlockMetadata.ValidatorSetAccountProtocolVersion) - { - var worldTrie = world.Trie; - worldTrie = worldTrie.SetMetadata( - new TrieMetadata(BlockMetadata.ValidatorSetAccountProtocolVersion)); - worldTrie = _stateStore.Commit(worldTrie); - world = new World(new WorldBaseState(worldTrie, _stateStore)); - - var legacyAccountTrie = - world.GetAccount(ReservedAddresses.LegacyAccount).Trie; - IValue? rawValidatorSet = legacyAccountTrie.Get(ValidatorSetKey); - - // Move encoded validator set only if it already exists. - if (rawValidatorSet is { } rawValue) - { - legacyAccountTrie = legacyAccountTrie.Remove(ValidatorSetKey); - legacyAccountTrie = _stateStore.Commit(legacyAccountTrie); - - var validatorSetAccountTrie = - world.GetAccount(ReservedAddresses.ValidatorSetAccount).Trie; - validatorSetAccountTrie = validatorSetAccountTrie.Set( - ToStateKey(ValidatorSetAccount.ValidatorSetAddress), - rawValue); - validatorSetAccountTrie = _stateStore.Commit(validatorSetAccountTrie); - - worldTrie = worldTrie.Set( - ToStateKey(ReservedAddresses.LegacyAccount), - new Binary(legacyAccountTrie.Hash.ByteArray)); - worldTrie = worldTrie.Set( - ToStateKey(ReservedAddresses.ValidatorSetAccount), - new Binary(validatorSetAccountTrie.Hash.ByteArray)); - worldTrie = _stateStore.Commit(worldTrie); - world = new World(new WorldBaseState(worldTrie, _stateStore)); - } - } - - // Migrate up to target version if conditions are met. - if (targetVersion >= BlockMetadata.WorldStateProtocolVersion && - world.Version < targetVersion) - { - var worldTrie = world.Trie; - worldTrie = worldTrie.SetMetadata(new TrieMetadata(targetVersion)); - worldTrie = _stateStore.Commit(worldTrie); - world = new World(new WorldBaseState(worldTrie, _stateStore)); - } - - return world; - } - - private static IWorld CommitLegacyWorld(IWorld prevWorld, IStateStore stateStore) - { - return new World( - new WorldBaseState( - stateStore.Commit(prevWorld.GetAccount(ReservedAddresses.LegacyAccount).Trie), - stateStore)); - } - - private static IWorld CommitWorld(IWorld prevWorld, IStateStore stateStore) - { - var worldTrie = prevWorld.Trie; - foreach (var account in prevWorld.Delta.Accounts) - { - var accountTrie = stateStore.Commit(account.Value.Trie); - worldTrie = worldTrie.Set( - ToStateKey(account.Key), new Binary(accountTrie.Hash.ByteArray)); - } - - return new World( - new WorldBaseState(stateStore.Commit(worldTrie), stateStore)); - } - [Pure] private static IEnumerable OrderTxsForEvaluationV0( IEnumerable txs, diff --git a/Libplanet.Action/State/IStateStoreExtensions.cs b/Libplanet.Action/State/IStateStoreExtensions.cs new file mode 100644 index 00000000000..99463205349 --- /dev/null +++ b/Libplanet.Action/State/IStateStoreExtensions.cs @@ -0,0 +1,168 @@ +using System; +using System.Security.Cryptography; +using Bencodex.Types; +using Libplanet.Common; +using Libplanet.Store; +using Libplanet.Store.Trie; +using Libplanet.Types.Blocks; + +namespace Libplanet.Action.State +{ + internal static class IStateStoreExtensions + { + /// + /// Retrieves the associated with + /// given . + /// + /// The to retrieve + /// an from. + /// The state root hash of the + /// to retrieve. + /// The associated with + /// given . + internal static IWorld GetWorld( + this IStateStore stateStore, + HashDigest? stateRootHash) + { + return new World( + new WorldBaseState(stateStore.GetStateRoot(stateRootHash), stateStore)); + } + + /// + /// Commits given to given . + /// + /// The to commit + /// to. + /// The to commit. + /// The committed . + internal static IWorld CommitWorld(this IStateStore stateStore, IWorld world) + { + if (world.Version >= BlockMetadata.WorldStateProtocolVersion) + { + var worldTrie = world.Trie; + foreach (var account in world.Delta.Accounts) + { + var accountTrie = stateStore.Commit(account.Value.Trie); + worldTrie = worldTrie.Set( + KeyConverters.ToStateKey(account.Key), + new Binary(accountTrie.Hash.ByteArray)); + } + + return new World( + new WorldBaseState(stateStore.Commit(worldTrie), stateStore)); + } + else + { + return new World( + new WorldBaseState( + stateStore.Commit(world.GetAccount(ReservedAddresses.LegacyAccount).Trie), + stateStore)); + } + } + + /// + /// Migrates given to . + /// + /// The to commit + /// the migrated . + /// The to migrate. + /// The target version + /// to migrate to. + /// The migrated of with + /// equal to: + /// + /// + /// zero if is less than + /// . + /// + /// + /// if is + /// greater than or equal to . + /// + /// + /// + /// Thrown when is + /// lower than the of . + /// + /// Migrated is automatically committed before returning. + /// + internal static IWorld MigrateWorld( + this IStateStore stateStore, + IWorld world, + int targetVersion) + { + if (world.Version > targetVersion) + { + throw new ApplicationException( + $"Given {nameof(world)} with version {world.Version} " + + $"cannot be migrated to a lower version {targetVersion}."); + } + + // Migrate up to BlockMetadata.WorldStateProtocolVersion + // if conditions are met. + if (targetVersion >= BlockMetadata.WorldStateProtocolVersion && + world.Version < BlockMetadata.WorldStateProtocolVersion) + { + var worldTrie = stateStore.GetStateRoot(null); + worldTrie = worldTrie.SetMetadata( + new TrieMetadata(BlockMetadata.WorldStateProtocolVersion)); + worldTrie = worldTrie.Set( + KeyConverters.ToStateKey(ReservedAddresses.LegacyAccount), + new Binary(world.Trie.Hash.ByteArray)); + worldTrie = stateStore.Commit(worldTrie); + world = new World(new WorldBaseState(worldTrie, stateStore)); + } + + // Migrate up to BlockMetadata.ValidatorSetAccountProtocolVersion + // if conditions are met. + if (targetVersion >= BlockMetadata.ValidatorSetAccountProtocolVersion && + world.Version < BlockMetadata.ValidatorSetAccountProtocolVersion) + { + var worldTrie = world.Trie; + worldTrie = worldTrie.SetMetadata( + new TrieMetadata(BlockMetadata.ValidatorSetAccountProtocolVersion)); + worldTrie = stateStore.Commit(worldTrie); + world = new World(new WorldBaseState(worldTrie, stateStore)); + + var legacyAccountTrie = + world.GetAccount(ReservedAddresses.LegacyAccount).Trie; + IValue? rawValidatorSet = legacyAccountTrie.Get(KeyConverters.ValidatorSetKey); + + // Move encoded validator set only if it already exists. + if (rawValidatorSet is { } rawValue) + { + legacyAccountTrie = legacyAccountTrie.Remove(KeyConverters.ValidatorSetKey); + legacyAccountTrie = stateStore.Commit(legacyAccountTrie); + + var validatorSetAccountTrie = + world.GetAccount(ReservedAddresses.ValidatorSetAccount).Trie; + validatorSetAccountTrie = validatorSetAccountTrie.Set( + KeyConverters.ToStateKey(ValidatorSetAccount.ValidatorSetAddress), + rawValue); + validatorSetAccountTrie = stateStore.Commit(validatorSetAccountTrie); + + worldTrie = worldTrie.Set( + KeyConverters.ToStateKey(ReservedAddresses.LegacyAccount), + new Binary(legacyAccountTrie.Hash.ByteArray)); + worldTrie = worldTrie.Set( + KeyConverters.ToStateKey(ReservedAddresses.ValidatorSetAccount), + new Binary(validatorSetAccountTrie.Hash.ByteArray)); + worldTrie = stateStore.Commit(worldTrie); + world = new World(new WorldBaseState(worldTrie, stateStore)); + } + } + + // Migrate up to target version if conditions are met. + if (targetVersion >= BlockMetadata.WorldStateProtocolVersion && + world.Version < targetVersion) + { + var worldTrie = world.Trie; + worldTrie = worldTrie.SetMetadata(new TrieMetadata(targetVersion)); + worldTrie = stateStore.Commit(worldTrie); + world = new World(new WorldBaseState(worldTrie, stateStore)); + } + + return world; + } + } +} diff --git a/Libplanet.Tests/Action/ActionEvaluatorTest.Migration.cs b/Libplanet.Tests/Action/ActionEvaluatorTest.Migration.cs index 00fc8423028..af91fc8e4e6 100644 --- a/Libplanet.Tests/Action/ActionEvaluatorTest.Migration.cs +++ b/Libplanet.Tests/Action/ActionEvaluatorTest.Migration.cs @@ -44,7 +44,7 @@ public void MigrateWorldWithValidatorSet() trie0 = stateStore.Commit(trie0); var world0 = new World(new WorldBaseState(trie0, stateStore)); - var world4 = actionEvaluator.MigrateWorld( + var world4 = stateStore.MigrateWorld( world0, BlockMetadata.PBFTProtocolVersion); Assert.True(world4.Trie.Recorded); Assert.Equal(0, world4.Version); @@ -60,7 +60,7 @@ public void MigrateWorldWithValidatorSet() validatorSet, world4.GetValidatorSet()); - var world5 = actionEvaluator.MigrateWorld( + var world5 = stateStore.MigrateWorld( world0, BlockMetadata.WorldStateProtocolVersion); Assert.True(world5.Trie.Recorded); Assert.Equal(5, world5.Version); @@ -78,7 +78,7 @@ public void MigrateWorldWithValidatorSet() .Get(KeyConverters.ValidatorSetKey)); Assert.Equal(validatorSet, world5.GetValidatorSet()); - var world6 = actionEvaluator.MigrateWorld( + var world6 = stateStore.MigrateWorld( world0, BlockMetadata.ValidatorSetAccountProtocolVersion); Assert.True(world6.Trie.Recorded); Assert.Equal(6, world6.Version); diff --git a/Libplanet.Tests/Action/ActionEvaluatorTest.cs b/Libplanet.Tests/Action/ActionEvaluatorTest.cs index cde2791bcdf..caa424ddd64 100644 --- a/Libplanet.Tests/Action/ActionEvaluatorTest.cs +++ b/Libplanet.Tests/Action/ActionEvaluatorTest.cs @@ -259,7 +259,7 @@ public void EvaluateWithCriticalException() txHash: BlockContent.DeriveTxHash(txs), lastCommit: null), transactions: txs).Propose(); - IWorld previousState = actionEvaluator.PrepareInitialDelta(genesis.StateRootHash); + IWorld previousState = stateStore.GetWorld(genesis.StateRootHash); Assert.Throws( () => actionEvaluator.EvaluateTx( @@ -366,7 +366,7 @@ DumbAction MakeAction(Address address, char identifier, Address? transferTo = nu genesis, GenesisProposer, block1Txs); - IWorld previousState = actionEvaluator.PrepareInitialDelta(genesis.StateRootHash); + IWorld previousState = stateStore.GetWorld(genesis.StateRootHash); var evals = actionEvaluator.EvaluateBlock( block1, previousState).ToImmutableArray(); @@ -395,7 +395,7 @@ DumbAction MakeAction(Address address, char identifier, Address? transferTo = nu Assert.Equal(block1.Index, eval.InputContext.BlockIndex); } - previousState = actionEvaluator.PrepareInitialDelta(genesis.StateRootHash); + previousState = stateStore.GetWorld(genesis.StateRootHash); ActionEvaluation[] evals1 = actionEvaluator.EvaluateBlock(block1, previousState).ToArray(); var output1 = new WorldBaseState(evals1.Last().OutputState.Trie, stateStore); @@ -572,7 +572,7 @@ public void EvaluateTx() stateStore: stateStore, actionTypeLoader: new SingleActionLoader(typeof(DumbAction))); - IWorld previousState = actionEvaluator.PrepareInitialDelta(initTrie.Hash); + IWorld previousState = stateStore.GetWorld(initTrie.Hash); var evaluations = actionEvaluator.EvaluateTx( block: block, tx: tx, @@ -638,7 +638,7 @@ prevEval is null .GetBalance(a, currency).RawValue)); } - previousState = actionEvaluator.PrepareInitialDelta(initTrie.Hash); + previousState = stateStore.GetWorld(initTrie.Hash); IWorld delta = actionEvaluator.EvaluateTx( block: block, tx: tx, @@ -660,9 +660,10 @@ public void EvaluateTxResultThrowingException() DateTimeOffset.UtcNow); var txs = new Transaction[] { tx }; var hash = new BlockHash(GetRandomBytes(BlockHash.Size)); + IStateStore stateStore = new TrieStateStore(new MemoryKeyValueStore()); var actionEvaluator = new ActionEvaluator( policyBlockActionGetter: _ => null, - stateStore: new TrieStateStore(new MemoryKeyValueStore()), + stateStore: stateStore, actionTypeLoader: new SingleActionLoader(typeof(ThrowException)) ); var block = new BlockContent( @@ -674,7 +675,7 @@ public void EvaluateTxResultThrowingException() txHash: BlockContent.DeriveTxHash(txs), lastCommit: CreateBlockCommit(hash, 122, 0)), transactions: txs).Propose(); - IWorld previousState = actionEvaluator.PrepareInitialDelta(null); + IWorld previousState = stateStore.GetWorld(null); var nextState = actionEvaluator.EvaluateTx( block: block, tx: tx, @@ -825,7 +826,7 @@ public void EvaluatePolicyBlockAction() var block = chain.ProposeBlock( GenesisProposer, txs.ToImmutableList(), CreateBlockCommit(chain.Tip)); - IWorld previousState = actionEvaluator.PrepareInitialDelta(null); + IWorld previousState = _storeFx.StateStore.GetWorld(null); var evaluation = actionEvaluator.EvaluatePolicyBlockAction(genesis, previousState); Assert.Equal(chain.Policy.BlockAction, evaluation.Action); @@ -848,7 +849,7 @@ public void EvaluatePolicyBlockAction() Assert.Equal(block.Transactions, evaluation.InputContext.Txs); chain.Append(block, CreateBlockCommit(block), render: true); - previousState = actionEvaluator.PrepareInitialDelta(genesis.StateRootHash); + previousState = _storeFx.StateStore.GetWorld(genesis.StateRootHash); var txEvaluations = actionEvaluator.EvaluateBlock( block, previousState).ToList(); diff --git a/Libplanet.Tests/Blockchain/BlockChainTest.cs b/Libplanet.Tests/Blockchain/BlockChainTest.cs index c60355205dc..407b47d231e 100644 --- a/Libplanet.Tests/Blockchain/BlockChainTest.cs +++ b/Libplanet.Tests/Blockchain/BlockChainTest.cs @@ -1798,7 +1798,7 @@ void BuildIndex(Guid id, Block block) // Build a store with incomplete states Block b = chain.Genesis; - IWorld previousState = actionEvaluator.PrepareInitialDelta(null); + IWorld previousState = stateStore.GetWorld(null); const int accountsCount = 5; Address[] addresses = Enumerable.Repeat(null, accountsCount) .Select(_ => new PrivateKey().Address)