diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs index e5f91af7f..2a5154b23 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs @@ -2,23 +2,14 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; -using System.IO; -using System.Linq; using System.Security.Cryptography; -using Bencodex; -using Bencodex.Types; using Cocona; using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Action; using Libplanet.Action.State; -using Libplanet.Types.Assets; -using Libplanet.Types.Consensus; -using Libplanet.Types.Blocks; using Libplanet.Types.Tx; -using RocksDbSharp; using Serilog; -using Libplanet.Store.Trie; namespace NineChronicles.Headless.Executable.Commands { @@ -89,259 +80,6 @@ public Random(int seed) public int Seed { get; private set; } } - private sealed class LocalCacheBlockChainStates : IBlockChainStates - { - private readonly IBlockChainStates _source; - private readonly RocksDb _rocksDb; - - public LocalCacheBlockChainStates(IBlockChainStates source, string cacheDirectory) - { - _source = source; - var options = new DbOptions().SetCreateIfMissing(); - _rocksDb = RocksDb.Open(options, cacheDirectory); - } - public IWorldState GetWorldState(BlockHash? offset) - => new LocalCacheWorldState( - _source.GetWorldState(offset), - _source.GetAccountState, - _rocksDb); - - public IWorldState GetWorldState(HashDigest? hash) - => new LocalCacheWorldState( - _source.GetWorldState(hash), - _source.GetAccountState, - _rocksDb); - - public IAccountState GetAccountState(HashDigest? hash) - => new LocalCacheAccountState( - _source.GetAccountState(hash), - _rocksDb); - - public IAccountState GetAccountState(BlockHash? offset, Address address) - => new LocalCacheAccountState( - _source.GetAccountState(offset, address), - _rocksDb); - - public IValue? GetState(BlockHash? offset, Address accountAddress, Address address) - => GetAccountState(offset, accountAddress).GetState(address); - - public IValue? GetState(HashDigest? stateRootHash, Address address) - => GetAccountState(stateRootHash).GetState(address); - - public FungibleAssetValue GetBalance(HashDigest? stateRootHash, Address address, Currency currency) - => GetAccountState(stateRootHash).GetBalance(address, currency); - - public FungibleAssetValue GetBalance(BlockHash? offset, Address address, Currency currency) - => GetAccountState(offset, ReservedAddresses.LegacyAccount).GetBalance(address, currency); - - public FungibleAssetValue GetTotalSupply(HashDigest? stateRootHash, Currency currency) - => GetAccountState(stateRootHash).GetTotalSupply(currency); - - public FungibleAssetValue GetTotalSupply(BlockHash? offset, Currency currency) - => GetAccountState(offset, ReservedAddresses.LegacyAccount).GetTotalSupply(currency); - - public ValidatorSet GetValidatorSet(HashDigest? stateRootHash) - => GetAccountState(stateRootHash).GetValidatorSet(); - - public ValidatorSet GetValidatorSet(BlockHash? offset) - => GetAccountState(offset, ReservedAddresses.LegacyAccount).GetValidatorSet(); - } - - private sealed class LocalCacheWorldState : IWorldState - { - private static readonly Codec Codec = new Codec(); - private readonly IWorldState _worldState; - private readonly Func?, IAccountState> _accountStateGetter; - private readonly RocksDb _rocksDb; - - public LocalCacheWorldState( - IWorldState worldState, - Func?, IAccountState> accountStateGetter, - RocksDb rocksDb) - { - _worldState = worldState; - _accountStateGetter = accountStateGetter; - _rocksDb = rocksDb; - } - - public ITrie Trie => _worldState.Trie; - - public bool Legacy => _worldState.Legacy; - - public IAccount GetAccount(Address address) - { - var key = WithStateRootHash(address.ToByteArray()); - try - { - return GetAccount(key); - } - catch (KeyNotFoundException) - { - var account = _worldState.GetAccount(address); - SetAccount(key, account); - return account; - } - } - - public IAccount GetAccount(byte[] key) - { - if (_rocksDb.Get(key) is not { } bytes) - { - throw new KeyNotFoundException(); - } - - return new Account(_accountStateGetter( - new HashDigest(((Binary)Codec.Decode(bytes)).ToImmutableArray()))); - } - - private void SetAccount(byte[] key, IAccount? account) - { - _rocksDb.Put(key, account is null ? new byte[] { 0x78 } : account.Trie.Hash.ToByteArray()); - } - - private byte[] WithStateRootHash(params byte[][] suffixes) - { - if (Trie.Hash is { } stateRootHash) - { - var stream = new MemoryStream(HashDigest.Size + suffixes.Sum(s => s.Length)); - stream.Write(stateRootHash.ToByteArray()); - foreach (var suffix in suffixes) - { - stream.Write(suffix); - } - - return stream.ToArray(); - } - throw new InvalidOperationException(); - } - } - - private sealed class LocalCacheAccountState : IAccountState - { - private static readonly Codec _codec = new Codec(); - private readonly IAccountState _accountState; - private readonly RocksDb _rocksDb; - - public LocalCacheAccountState( - IAccountState accountState, - RocksDb rocksDb) - { - _accountState = accountState; - _rocksDb = rocksDb; - } - - public ITrie Trie => _accountState.Trie; - - public IValue? GetState(Address address) - { - var key = WithStateRootHash(address.ToByteArray()); - try - { - return GetValue(key); - } - catch (KeyNotFoundException) - { - var state = _accountState.GetState(address); - SetValue(key, state); - return state; - } - } - - public IReadOnlyList GetStates(IReadOnlyList
addresses) - { - return addresses.Select(GetState).ToList(); - } - - public FungibleAssetValue GetBalance(Address address, Currency currency) - { - var key = WithStateRootHash(address.ToByteArray(), currency.Hash.ToByteArray()); - try - { - var state = GetValue(key); - if (state is not Integer integer) - { - throw new InvalidOperationException(); - } - - return FungibleAssetValue.FromRawValue(currency, integer); - } - catch (KeyNotFoundException) - { - var fav = _accountState.GetBalance(address, currency); - SetValue(key, (Integer)fav.RawValue); - return fav; - } - } - - public FungibleAssetValue GetTotalSupply(Currency currency) - { - var key = WithStateRootHash(currency.Hash.ToByteArray()); - try - { - var state = GetValue(key); - if (state is not Integer integer) - { - throw new InvalidOperationException(); - } - - return FungibleAssetValue.FromRawValue(currency, integer); - } - catch (KeyNotFoundException) - { - var fav = _accountState.GetTotalSupply(currency); - SetValue(key, (Integer)fav.RawValue); - return fav; - } - } - - public ValidatorSet GetValidatorSet() - { - var key = WithStateRootHash(new byte[] { 0x5f, 0x5f, 0x5f }); - try - { - var state = GetValue(key); - return state is not null ? new ValidatorSet(state) : new ValidatorSet(); - } - catch (KeyNotFoundException) - { - var validatorSet = _accountState.GetValidatorSet(); - SetValue(key, validatorSet.Bencoded); - return validatorSet; - } - } - - private IValue? GetValue(byte[] key) - { - if (_rocksDb.Get(key) is not { } bytes) - { - throw new KeyNotFoundException(); - } - - return bytes[0] == 'x' ? null : _codec.Decode(bytes); - } - - private void SetValue(byte[] key, IValue? value) - { - _rocksDb.Put(key, value is null ? new byte[] { 0x78 } : _codec.Encode(value)); - } - - private byte[] WithStateRootHash(params byte[][] suffixes) - { - if (Trie.Hash is { } stateRootHash) - { - var stream = new MemoryStream(HashDigest.Size + suffixes.Sum(s => s.Length)); - stream.Write(stateRootHash.ToByteArray()); - foreach (var suffix in suffixes) - { - stream.Write(suffix); - } - - return stream.ToArray(); - } - throw new InvalidOperationException(); - } - } - /// /// Almost duplicate https://github.com/planetarium/libplanet/blob/main/Libplanet/Action/ActionEvaluator.cs#L286. /// diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs index 0937a48eb..9e1db0b20 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs @@ -354,12 +354,7 @@ public int RemoteTx( [Option("tx", new[] { 't' }, Description = "The transaction id")] string transactionId, [Option("endpoint", new[] { 'e' }, Description = "GraphQL endpoint to get remote state")] - string endpoint, - [Option( - "cache-directory", - new [] { 'c' }, - Description = "A directory to store states, balances, etc as cache")] - string? cacheDirectory=null) + string endpoint) { var graphQlClient = new GraphQLHttpClient(new Uri(endpoint), new SystemTextJsonSerializer()); var transactionResponse = GetTransactionData(graphQlClient, transactionId); @@ -392,9 +387,7 @@ public int RemoteTx( var miner = new Address(minerValue); var explorerEndpoint = $"{endpoint}/explorer"; - var blockChainStates = new LocalCacheBlockChainStates( - new RemoteBlockChainStates(new Uri(explorerEndpoint)), - cacheDirectory ?? Path.Join(Path.GetTempPath(), "ncd-replay-remote-tx-cache")); + var blockChainStates = new RemoteBlockChainStates(new Uri(explorerEndpoint)); var previousBlockHash = BlockHash.FromString(previousBlockHashValue); var previousStates = new World(blockChainStates.GetWorldState(previousBlockHash));