Skip to content

Commit

Permalink
Removed local cache for replays
Browse files Browse the repository at this point in the history
  • Loading branch information
greymistcube committed Jan 9, 2024
1 parent 9f2d351 commit 0cabfa8
Show file tree
Hide file tree
Showing 2 changed files with 2 additions and 271 deletions.
262 changes: 0 additions & 262 deletions NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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<SHA256>? hash)
=> new LocalCacheWorldState(
_source.GetWorldState(hash),
_source.GetAccountState,
_rocksDb);

public IAccountState GetAccountState(HashDigest<SHA256>? 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<SHA256>? stateRootHash, Address address)
=> GetAccountState(stateRootHash).GetState(address);

public FungibleAssetValue GetBalance(HashDigest<SHA256>? 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<SHA256>? stateRootHash, Currency currency)
=> GetAccountState(stateRootHash).GetTotalSupply(currency);

public FungibleAssetValue GetTotalSupply(BlockHash? offset, Currency currency)
=> GetAccountState(offset, ReservedAddresses.LegacyAccount).GetTotalSupply(currency);

public ValidatorSet GetValidatorSet(HashDigest<SHA256>? 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<HashDigest<SHA256>?, IAccountState> _accountStateGetter;
private readonly RocksDb _rocksDb;

public LocalCacheWorldState(
IWorldState worldState,
Func<HashDigest<SHA256>?, 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<SHA256>(((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<SHA256>.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<IValue?> GetStates(IReadOnlyList<Address> 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<SHA256>.Size + suffixes.Sum(s => s.Length));
stream.Write(stateRootHash.ToByteArray());
foreach (var suffix in suffixes)
{
stream.Write(suffix);
}

return stream.ToArray();
}
throw new InvalidOperationException();
}
}

/// <summary>
/// Almost duplicate https://github.com/planetarium/libplanet/blob/main/Libplanet/Action/ActionEvaluator.cs#L286.
/// </summary>
Expand Down
11 changes: 2 additions & 9 deletions NineChronicles.Headless.Executable/Commands/ReplayCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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));
Expand Down

0 comments on commit 0cabfa8

Please sign in to comment.