Skip to content

Commit

Permalink
Merge pull request #3776 from greymistcube/refactor/world-migration
Browse files Browse the repository at this point in the history
♻️ Refactor world migration
  • Loading branch information
greymistcube authored May 3, 2024
2 parents 0e5d1cf + 4ead92e commit 5bcd797
Show file tree
Hide file tree
Showing 5 changed files with 185 additions and 132 deletions.
122 changes: 3 additions & 119 deletions Libplanet.Action/ActionEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -110,8 +108,8 @@ public IReadOnlyList<ICommittedActionEvaluation> 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<ActionEvaluation> evaluations =
EvaluateBlock(block, previousState).ToImmutableList();
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -506,12 +496,6 @@ internal ActionEvaluation EvaluatePolicyBlockAction(
logger: _logger).Single();
}

internal IWorld PrepareInitialDelta(HashDigest<SHA256>? stateRootHash)
{
return new World(
new WorldBaseState(_stateStore.GetStateRoot(stateRootHash), _stateStore));
}

internal IReadOnlyList<ICommittedActionEvaluation>
ToCommittedEvaluation(
IPreEvaluationBlock block,
Expand Down Expand Up @@ -549,106 +533,6 @@ internal IReadOnlyList<ICommittedActionEvaluation>
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<ITransaction> OrderTxsForEvaluationV0(
IEnumerable<ITransaction> txs,
Expand Down
168 changes: 168 additions & 0 deletions Libplanet.Action/State/IStateStoreExtensions.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Retrieves the <see cref="IWorld"/> associated with
/// given <paramref name="stateRootHash"/>.
/// </summary>
/// <param name="stateStore">The <see cref="IStateStore"/> to retrieve
/// an <see cref="IWorld"/> from.</param>
/// <param name="stateRootHash">The state root hash of the <see cref="IWorld"/>
/// to retrieve.</param>
/// <returns>The <see cref="IWorld"/> associated with
/// given <paramref name="stateRootHash"/>.</returns>
internal static IWorld GetWorld(
this IStateStore stateStore,
HashDigest<SHA256>? stateRootHash)
{
return new World(
new WorldBaseState(stateStore.GetStateRoot(stateRootHash), stateStore));
}

/// <summary>
/// Commits given <paramref name="world"/> to given <paramref name="stateStore"/>.
/// </summary>
/// <param name="stateStore">The <see cref="IStateStore"/> to commit
/// <paramref name="world"/> to.</param>
/// <param name="world">The <see cref="IWorld"/> to commit.</param>
/// <returns>The committed <see cref="IWorld"/>.</returns>
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));
}
}

/// <summary>
/// Migrates given <paramref name="world"/> to <paramref name="targetVersion"/>.
/// </summary>
/// <param name="stateStore">The <see cref="IStateStore"/> to commit
/// the migrated <see cref="IWorld"/>.</param>
/// <param name="world">The <see cref="IWorld"/> to migrate.</param>
/// <param name="targetVersion">The target <see cref="IWorld"/> version
/// to migrate to.</param>
/// <returns>The migrated <see cref="IWorld"/> of <paramref name="world"/> with
/// <see cref="IWorld.Version"/> equal to:
/// <list type="bullet">
/// <item><description>
/// zero if <paramref name="targetVersion"/> is less than
/// <see cref="BlockMetadata.WorldStateProtocolVersion"/>.
/// </description></item>
/// <item><description>
/// <paramref name="targetVersion"/> if <paramref name="targetVersion"/> is
/// greater than or equal to <see cref="BlockMetadata.WorldStateProtocolVersion"/>.
/// </description></item>
/// </list>
/// </returns>
/// <exception cref="ApplicationException">Thrown when <paramref name="targetVersion"/> is
/// lower than the <see cref="IWorld.Version"/> of <paramref name="world"/>.</exception>
/// <remarks>
/// Migrated <see cref="IWorld"/> is automatically committed before returning.
/// </remarks>
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;
}
}
}
6 changes: 3 additions & 3 deletions Libplanet.Tests/Action/ActionEvaluatorTest.Migration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
Loading

0 comments on commit 5bcd797

Please sign in to comment.