diff --git a/src/Libplanet.Action/AssemblyInfo.cs b/src/Libplanet.Action/AssemblyInfo.cs index 3b64ad1989d..d32db2c362f 100644 --- a/src/Libplanet.Action/AssemblyInfo.cs +++ b/src/Libplanet.Action/AssemblyInfo.cs @@ -1,6 +1,7 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Libplanet.Tests")] +[assembly: InternalsVisibleTo("Libplanet")] [assembly: InternalsVisibleTo("Libplanet.Action.Tests")] [assembly: InternalsVisibleTo("Libplanet.Explorer.Tests")] [assembly: InternalsVisibleTo("Libplanet.Mocks")] +[assembly: InternalsVisibleTo("Libplanet.Tests")] diff --git a/src/Libplanet/Blockchain/StateStoreExtensions.cs b/src/Libplanet/Blockchain/StateStoreExtensions.cs new file mode 100644 index 00000000000..333b3d1fdaf --- /dev/null +++ b/src/Libplanet/Blockchain/StateStoreExtensions.cs @@ -0,0 +1,104 @@ +using System; +using System.Security.Cryptography; +using Bencodex.Types; +using Libplanet.Action.State; +using Libplanet.Common; +using Libplanet.Crypto; +using Libplanet.Store.Trie; +using Libplanet.Types.Blocks; + +namespace Libplanet.Store +{ + /// + /// Convenient extension methods for . + /// + public static class IStateStoreExtensions + { + /// + /// Commits representing a world state directly + /// to and returns its state root hash. + /// The world state created is set to . + /// + /// The to commit to. + /// The data representing a world state to commit. + /// + /// Thrown if given + /// is not in the right format. + /// + /// + /// Every key in must be a of length + /// . + /// + /// + /// Every value in must be a with + /// each key in the being a of length + /// . + /// + /// + /// + public static HashDigest CommitWorld(this IStateStore stateStore, Dictionary data) + { + var stateRoot = stateStore.GetStateRoot(null); + stateRoot = stateRoot.SetMetadata( + new TrieMetadata(BlockMetadata.CurrentProtocolVersion)); + foreach((var key, var value) in data) + { + if (key is not Binary binary) + { + throw new ArgumentException( + $"Given {data} contains a key that is of invalid type: {key.GetType()}", + nameof(data)); + } + + if (binary.ByteArray.Length != Address.Size) + { + throw new ArgumentException( + $"Given {data} contains a key that is of invalid length that is " + + $"not {Address.Size}: {binary.ByteArray.Length}", + nameof(data)); + } + + if (value is not Dictionary dict) + { + throw new ArgumentException( + $"Given {data} contains a value that is of invalid type: {value.GetType()}", + nameof(data)); + } + + stateRoot = stateRoot.Set( + KeyConverters.ToStateKey(new Address(binary.ByteArray)), + new Binary(stateStore.CommitAccount(dict).ByteArray)); + } + + return stateStore.Commit(stateRoot).Hash; + } + + private static HashDigest CommitAccount(this IStateStore stateStore, Dictionary data) + { + var stateRoot = stateStore.GetStateRoot(null); + foreach((var key, var value) in data) + { + if (key is not Binary binary) + { + throw new ArgumentException( + $"Given {data} contains a key that is of invalid type: {key.GetType()}", + nameof(data)); + } + + if (binary.ByteArray.Length != Address.Size) + { + throw new ArgumentException( + $"Given {data} contains a key that is of invalid length that is " + + $"not {Address.Size}: {binary.ByteArray.Length}", + nameof(data)); + } + + stateRoot = stateRoot.Set( + KeyConverters.ToStateKey(new Address(binary.ByteArray)), + value); + } + + return stateStore.Commit(stateRoot).Hash; + } + } +}