From c94ea141ab7a32661f58847950dea1eeed48d08e Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Tue, 26 Sep 2023 03:34:41 +0900 Subject: [PATCH] Cherry-pick from migrate/iaccount-to-itrie --- .../Hosting/LibplanetNodeService.cs | 9 +- Libplanet.Headless/ReducedStore.cs | 162 ------------------ .../Commands/MarketCommand.cs | 4 +- .../Commands/ReplayCommand.cs | 14 +- .../Commands/StateCommand.cs | 17 +- NineChronicles.Headless.Executable/Program.cs | 1 + .../Common/MockState.cs | 5 +- .../GraphTypes/GraphQLTestBase.cs | 1 + .../ActionEvaluationPublisher.cs | 96 +++++------ .../Controllers/GraphQLController.cs | 10 +- .../GraphQLServiceExtensions.cs | 3 - .../GraphTypes/StandaloneQuery.cs | 41 ++--- .../GraphTypes/StandaloneSubscription.cs | 28 +-- .../GraphTypes/TransactionHeadlessQuery.cs | 32 ++-- .../HostBuilderExtensions.cs | 2 + .../NineChroniclesNodeService.cs | 4 +- 16 files changed, 114 insertions(+), 315 deletions(-) delete mode 100644 Libplanet.Headless/ReducedStore.cs diff --git a/Libplanet.Headless/Hosting/LibplanetNodeService.cs b/Libplanet.Headless/Hosting/LibplanetNodeService.cs index 76ee633c6..25b10b962 100644 --- a/Libplanet.Headless/Hosting/LibplanetNodeService.cs +++ b/Libplanet.Headless/Hosting/LibplanetNodeService.cs @@ -98,8 +98,7 @@ public LibplanetNodeService( (Store, StateStore) = LoadStore( Properties.StorePath, Properties.StoreType, - Properties.StoreStatesCacheSize, - Properties.NoReduceStore); + Properties.StoreStatesCacheSize); var chainIds = Store.ListChainIds().ToList(); Log.Debug($"Number of chain ids: {chainIds.Count()}"); @@ -303,7 +302,7 @@ public override async Task StopAsync(CancellationToken cancellationToken) } } - protected (IStore, IStateStore) LoadStore(string path, string type, int statesCacheSize, bool noReduceStore = false) + protected (IStore, IStateStore) LoadStore(string path, string type, int statesCacheSize) { IStore store = null; if (type == "rocksdb") @@ -344,10 +343,6 @@ public override async Task StopAsync(CancellationToken cancellationToken) } store ??= new DefaultStore(path, flush: false); - if (!noReduceStore) - { - store = new ReducedStore(store); - } IKeyValueStore stateKeyValueStore = new RocksDBKeyValueStore(Path.Combine(path, "states")); IStateStore stateStore = new TrieStateStore(stateKeyValueStore); diff --git a/Libplanet.Headless/ReducedStore.cs b/Libplanet.Headless/ReducedStore.cs deleted file mode 100644 index 389597d0b..000000000 --- a/Libplanet.Headless/ReducedStore.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using Bencodex.Types; -using Libplanet.Crypto; -using Libplanet.Store; -using Libplanet.Types.Blocks; -using Libplanet.Types.Tx; - -namespace Libplanet.Headless -{ - /// - /// A decorator that reduce space consumption by omitting input calls which - /// are unused by Nine Chronicles. - /// Calls on this will be forwarded to its , except for: - /// - /// - /// - /// - public sealed class ReducedStore : IStore - { - public ReducedStore(IStore internalStore) - { - InternalStore = internalStore; - } - - public IStore InternalStore { get; } - - public long AppendIndex(Guid chainId, BlockHash hash) => - InternalStore.AppendIndex(chainId, hash); - - public bool ContainsBlock(BlockHash blockHash) => - InternalStore.ContainsBlock(blockHash); - - public bool ContainsTransaction(TxId txId) => - InternalStore.ContainsTransaction(txId); - - public long CountBlocks() => - InternalStore.CountBlocks(); - - public long CountIndex(Guid chainId) => - InternalStore.CountIndex(chainId); - - public bool DeleteBlock(BlockHash blockHash) => - InternalStore.DeleteBlock(blockHash); - - public void DeleteChainId(Guid chainId) => - InternalStore.DeleteChainId(chainId); - - public void ForkBlockIndexes( - Guid sourceChainId, - Guid destinationChainId, - BlockHash branchpoint - ) => - InternalStore.ForkBlockIndexes(sourceChainId, destinationChainId, branchpoint); - - public void ForkTxNonces(Guid sourceChainId, Guid destinationChainId) => - InternalStore.ForkTxNonces(sourceChainId, destinationChainId); - - public Block GetBlock(BlockHash blockHash) - => InternalStore.GetBlock(blockHash); - - public BlockDigest? GetBlockDigest(BlockHash blockHash) => - InternalStore.GetBlockDigest(blockHash); - - public long? GetBlockIndex(BlockHash blockHash) => - InternalStore.GetBlockIndex(blockHash); - - public Guid? GetCanonicalChainId() => - InternalStore.GetCanonicalChainId(); - - public Transaction GetTransaction(TxId txid) => - InternalStore.GetTransaction(txid); - - public TxExecution GetTxExecution(BlockHash blockHash, TxId txid) => - InternalStore.GetTxExecution(blockHash, txid); - - public long GetTxNonce(Guid chainId, Address address) => - InternalStore.GetTxNonce(chainId, address); - - public void IncreaseTxNonce(Guid chainId, Address signer, long delta = 1) => - InternalStore.IncreaseTxNonce(chainId, signer, delta); - - public BlockHash? IndexBlockHash(Guid chainId, long index) => - InternalStore.IndexBlockHash(chainId, index); - - public IEnumerable IterateBlockHashes() => - InternalStore.IterateBlockHashes(); - - public IEnumerable IterateIndexes( - Guid chainId, - int offset = 0, - int? limit = null - ) => - InternalStore.IterateIndexes(chainId, offset, limit); - - public IEnumerable ListChainIds() => - InternalStore.ListChainIds(); - - public IEnumerable> ListTxNonces(Guid chainId) => - InternalStore.ListTxNonces(chainId); - - public void PutBlock(Block block) => - InternalStore.PutBlock(block); - - public void PutTransaction(Transaction tx) => - InternalStore.PutTransaction(tx); - - public void PutTxExecution(TxSuccess txSuccess) - { - // Omit TxSuccess.UpdatedStates as it is unused by Nine Chronicles and too big. - TxSuccess reducedTxSuccess = new TxSuccess( - txSuccess.BlockHash, - txSuccess.TxId, - updatedStates: txSuccess.UpdatedStates.ToImmutableDictionary(pair => pair.Key, _ => (IValue)Null.Value), - updatedFungibleAssets: txSuccess.UpdatedFungibleAssets - ); - InternalStore.PutTxExecution(reducedTxSuccess); - } - - public void PutTxExecution(TxFailure txFailure) => - InternalStore.PutTxExecution(txFailure); - - public void SetCanonicalChainId(Guid chainId) => - InternalStore.SetCanonicalChainId(chainId); - - public void PutTxIdBlockHashIndex(TxId txId, BlockHash blockHash) => - InternalStore.PutTxIdBlockHashIndex(txId, blockHash); - - public BlockHash? GetFirstTxIdBlockHashIndex(TxId txId) => - InternalStore.GetFirstTxIdBlockHashIndex(txId); - - public IEnumerable IterateTxIdBlockHashIndex(TxId txId) => - InternalStore.IterateTxIdBlockHashIndex(txId); - - public void DeleteTxIdBlockHashIndex(TxId txId, BlockHash blockHash) => - InternalStore.DeleteTxIdBlockHashIndex(txId, blockHash); - - public void PruneOutdatedChains(bool noopWithoutCanon = false) => - InternalStore.PruneOutdatedChains(noopWithoutCanon); - - public BlockCommit GetChainBlockCommit(Guid chainId) => - InternalStore.GetChainBlockCommit(chainId); - - public void PutChainBlockCommit(Guid chainId, BlockCommit blockCommit) => - InternalStore.PutChainBlockCommit(chainId, blockCommit); - - public BlockCommit GetBlockCommit(BlockHash blockHash) => - InternalStore.GetBlockCommit(blockHash); - - public void PutBlockCommit(BlockCommit blockCommit) => - InternalStore.PutBlockCommit(blockCommit); - - public void DeleteBlockCommit(BlockHash blockHash) => - InternalStore.DeleteBlockCommit(blockHash); - - public IEnumerable GetBlockCommitHashes() => - InternalStore.GetBlockCommitHashes(); - - public void Dispose() => InternalStore.Dispose(); - } -} diff --git a/NineChronicles.Headless.Executable/Commands/MarketCommand.cs b/NineChronicles.Headless.Executable/Commands/MarketCommand.cs index a7ea80af1..8aaa5f649 100644 --- a/NineChronicles.Headless.Executable/Commands/MarketCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/MarketCommand.cs @@ -108,8 +108,8 @@ public void Query( IEnumerable<(Transaction, ActionBase)> actions = block.Transactions .Reverse() .Where(tx => includeFails || - !(chain.GetTxExecution(block.Hash, tx.Id) is { } e) || - e is TxSuccess) + !(chain.GetTxExecution(block.Hash, tx.Id) is { } e) || + !e.Fail) .SelectMany(tx => tx.Actions is { } ca ? ca.Reverse().Select(a => (tx, ToAction(a))) : Enumerable.Empty<(Transaction, ActionBase)>()); diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs index 19ebea278..c332de64c 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs @@ -101,7 +101,7 @@ public int Tx( // Evaluate tx. IAccountState previousBlockStates = blockChain.GetAccountState(previousBlock.Hash); - IAccount previousStates = new Account(previousBlockStates); + IAccount previousStates = AccountStateDelta.Create(previousBlockStates); var actions = tx.Actions.Select(a => ToAction(a)); var actionEvaluations = EvaluateActions( preEvaluationHash: targetBlock.PreEvaluationHash, @@ -271,7 +271,7 @@ public int Blocks( try { var rootHash = blockChain.DetermineBlockStateRootHash(block, - out IReadOnlyList actionEvaluations); + out IReadOnlyList actionEvaluations); if (verbose) { @@ -301,8 +301,9 @@ public int Blocks( outputSw?.WriteLine(msg); var actionEvaluator = GetActionEvaluator(blockChain); - var actionEvaluations = actionEvaluator.Evaluate(block); - LoggingActionEvaluations(actionEvaluations, outputSw); + var actionEvaluations = blockChain.DetermineBlockStateRootHash(block, + out IReadOnlyList failedActionEvaluations); + LoggingActionEvaluations(failedActionEvaluations, outputSw); msg = $"- block #{block.Index} evaluating failed with "; _console.Out.Write(msg); @@ -398,7 +399,8 @@ public int RemoteTx( cacheDirectory ?? Path.Join(Path.GetTempPath(), "ncd-replay-remote-tx-cache")); var previousBlockHash = BlockHash.FromString(previousBlockHashValue); - var previousStates = new Account(blockChainStates.GetAccountState(previousBlockHash)); + var previousStates = + AccountStateDelta.Create(blockChainStates.GetAccountState(previousBlockHash)); var actions = transaction.Actions .Select(ToAction) @@ -558,7 +560,7 @@ private void LoggingAboutIncompleteBlockStatesException( } private void LoggingActionEvaluations( - IReadOnlyList actionEvaluations, + IReadOnlyList actionEvaluations, TextWriter? textWriter) { var count = actionEvaluations.Count; diff --git a/NineChronicles.Headless.Executable/Commands/StateCommand.cs b/NineChronicles.Headless.Executable/Commands/StateCommand.cs index 235e7bbd8..664c4c8f8 100644 --- a/NineChronicles.Headless.Executable/Commands/StateCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/StateCommand.cs @@ -132,31 +132,20 @@ IStateStore stateStore block.Index, block.Hash ); - IReadOnlyList delta; HashDigest stateRootHash = block.Index < 1 ? BlockChain.DetermineGenesisStateRootHash( actionEvaluator, preEvalBlock, - out delta) + out _) : chain.DetermineBlockStateRootHash( preEvalBlock, - out delta); + out _); DateTimeOffset now = DateTimeOffset.Now; if (invalidStateRootHashBlock is null && !stateRootHash.Equals(block.StateRootHash)) { - string blockDump = DumpBencodexToFile( - block.MarshalBlock(), - $"block_{block.Index}_{block.Hash}" - ); - string deltaDump = DumpBencodexToFile( - new Dictionary( - GetTotalDelta(delta, ToStateKey, ToFungibleAssetKey, ToTotalSupplyKey, ValidatorSetKey)), - $"delta_{block.Index}_{block.Hash}" - ); string message = $"Unexpected state root hash for block #{block.Index} {block.Hash}.\n" + - $" Expected: {block.StateRootHash}\n Actual: {stateRootHash}\n" + - $" Block file: {blockDump}\n Evaluated delta file: {deltaDump}\n"; + $" Expected: {block.StateRootHash}\n Actual: {stateRootHash}\n"; if (!bypassStateRootHashCheck) { throw new CommandExitedException(message, 1); diff --git a/NineChronicles.Headless.Executable/Program.cs b/NineChronicles.Headless.Executable/Program.cs index 5b3d9696b..16fec7e05 100644 --- a/NineChronicles.Headless.Executable/Program.cs +++ b/NineChronicles.Headless.Executable/Program.cs @@ -506,6 +506,7 @@ IActionLoader MakeSingleActionLoader() standaloneContext.NineChroniclesNodeService!.ActionRenderer, standaloneContext.NineChroniclesNodeService!.ExceptionRenderer, standaloneContext.NineChroniclesNodeService!.NodeStatusRenderer, + standaloneContext.NineChroniclesNodeService!.BlockChain, IPAddress.Loopback.ToString(), rpcProperties.RpcListenPort, context, diff --git a/NineChronicles.Headless.Tests/Common/MockState.cs b/NineChronicles.Headless.Tests/Common/MockState.cs index 2ca86f1b0..54ac73e76 100644 --- a/NineChronicles.Headless.Tests/Common/MockState.cs +++ b/NineChronicles.Headless.Tests/Common/MockState.cs @@ -86,7 +86,10 @@ private MockState( public ValidatorSet ValidatorSet => _validatorSet; - public ITrie Trie => throw new NotSupportedException(); + public ITrie Trie + { + get => new MerkleTrie(new MemoryKeyValueStore()); + } public IValue? GetState(Address address) => _states.TryGetValue(address, out IValue? value) ? value diff --git a/NineChronicles.Headless.Tests/GraphTypes/GraphQLTestBase.cs b/NineChronicles.Headless.Tests/GraphTypes/GraphQLTestBase.cs index b5533d1fa..02eb0fc12 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/GraphQLTestBase.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/GraphQLTestBase.cs @@ -115,6 +115,7 @@ public GraphQLTestBase(ITestOutputHelper output) ncService.ActionRenderer, ncService.ExceptionRenderer, ncService.NodeStatusRenderer, + ncService.BlockChain, "", 0, new RpcContext(), diff --git a/NineChronicles.Headless/ActionEvaluationPublisher.cs b/NineChronicles.Headless/ActionEvaluationPublisher.cs index 9d07b8d95..bee439ace 100644 --- a/NineChronicles.Headless/ActionEvaluationPublisher.cs +++ b/NineChronicles.Headless/ActionEvaluationPublisher.cs @@ -3,12 +3,14 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; using System.Diagnostics.Metrics; using System.IO; using System.IO.Compression; using System.Linq; using System.Reactive.Concurrency; using System.Reactive.Linq; +using System.Reactive.Subjects; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; @@ -16,8 +18,9 @@ using Bencodex.Types; using Grpc.Core; using Grpc.Net.Client; -using Lib9c.Abstractions; using Lib9c.Renderers; +using Libplanet.Action.State; +using Libplanet.Blockchain; using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Types.Blocks; @@ -26,9 +29,7 @@ using MessagePack; using Microsoft.Extensions.Hosting; using Nekoyume.Action; -using Nekoyume.Model.State; using Nekoyume.Shared.Hubs; -using Sentry; using Serilog; namespace NineChronicles.Headless @@ -42,6 +43,7 @@ public class ActionEvaluationPublisher : BackgroundService private readonly ActionRenderer _actionRenderer; private readonly ExceptionRenderer _exceptionRenderer; private readonly NodeStatusRenderer _nodeStatusRenderer; + private readonly IBlockChainStates _blockChainStates; private readonly ConcurrentDictionary _clients = new ConcurrentDictionary(); private readonly ConcurrentDictionary _clientsByDevice = new ConcurrentDictionary(); @@ -54,6 +56,7 @@ public ActionEvaluationPublisher( ActionRenderer actionRenderer, ExceptionRenderer exceptionRenderer, NodeStatusRenderer nodeStatusRenderer, + IBlockChainStates blockChainStates, string host, int port, RpcContext context, @@ -63,6 +66,7 @@ public ActionEvaluationPublisher( _actionRenderer = actionRenderer; _exceptionRenderer = exceptionRenderer; _nodeStatusRenderer = nodeStatusRenderer; + _blockChainStates = blockChainStates; _host = host; _port = port; _context = context; @@ -100,7 +104,7 @@ public async Task AddClient(Address clientAddress) }; GrpcChannel channel = GrpcChannel.ForAddress($"http://{_host}:{_port}", options); - Client client = await Client.CreateAsync(channel, clientAddress, _context, _sentryTraces); + Client client = await Client.CreateAsync(channel, _blockChainStates, clientAddress, _context, _sentryTraces); if (_clients.TryAdd(clientAddress, client)) { if (clientAddress == default) @@ -220,6 +224,7 @@ private async void RemoveClient(string clientAddressHex) private sealed class Client : IAsyncDisposable { private readonly IActionEvaluationHub _hub; + private readonly IBlockChainStates _blockChainStates; private readonly RpcContext _context; private readonly Address _clientAddress; @@ -227,6 +232,9 @@ private sealed class Client : IAsyncDisposable private IDisposable? _actionEveryRenderSubscribe; private IDisposable? _everyExceptionSubscribe; private IDisposable? _nodeStatusSubscribe; + + private Subject _NCActionRenderSubject { get; } + = new Subject(); public ImmutableHashSet
TargetAddresses { get; set; } @@ -234,11 +242,13 @@ private sealed class Client : IAsyncDisposable private Client( IActionEvaluationHub hub, + IBlockChainStates blockChainStates, Address clientAddress, RpcContext context, ConcurrentDictionary sentryTraces) { _hub = hub; + _blockChainStates = blockChainStates; _clientAddress = clientAddress; _context = context; TargetAddresses = ImmutableHashSet
.Empty; @@ -247,6 +257,7 @@ private Client( public static async Task CreateAsync( GrpcChannel channel, + IBlockChainStates blockChainStates, Address clientAddress, RpcContext context, ConcurrentDictionary sentryTraces) @@ -257,7 +268,7 @@ public static async Task CreateAsync( ); await hub.JoinAsync(clientAddress.ToHex()); - return new Client(hub, clientAddress, context, sentryTraces); + return new Client(hub, blockChainStates, clientAddress, context, sentryTraces); } public void Subscribe( @@ -296,56 +307,22 @@ await _hub.BroadcastRenderBlockAsync( { try { + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); ActionBase? pa = ev.Action is RewardGold ? null : ev.Action; var extra = new Dictionary(); - - var previousStates = ev.PreviousState; - if (pa is IBattleArenaV1 battleArena) + IAccountState output = _blockChainStates.GetAccountState(ev.OutputState); + IAccountState input = _blockChainStates.GetAccountState(ev.PreviousState); + AccountDiff diff = AccountDiff.Create(input, output); + if (!TargetAddresses.Any(diff.StateDiffs.Keys.Append(ev.Signer).Contains)) { - var enemyAvatarAddress = battleArena.EnemyAvatarAddress; - if (previousStates.GetState(enemyAvatarAddress) is { } eAvatar) - { - const string inventoryKey = "inventory"; - previousStates = previousStates.SetState(enemyAvatarAddress, eAvatar); - if (previousStates.GetState(enemyAvatarAddress.Derive(inventoryKey)) is { } inventory) - { - previousStates = previousStates.SetState( - enemyAvatarAddress.Derive(inventoryKey), - inventory); - } - } - - var enemyItemSlotStateAddress = - ItemSlotState.DeriveAddress(battleArena.EnemyAvatarAddress, - Nekoyume.Model.EnumType.BattleType.Arena); - if (previousStates.GetState(enemyItemSlotStateAddress) is { } eItemSlot) - { - previousStates = previousStates.SetState(enemyItemSlotStateAddress, eItemSlot); - } - - var enemyRuneSlotStateAddress = - RuneSlotState.DeriveAddress(battleArena.EnemyAvatarAddress, - Nekoyume.Model.EnumType.BattleType.Arena); - if (previousStates.GetState(enemyRuneSlotStateAddress) is { } eRuneSlot) - { - previousStates = previousStates.SetState(enemyRuneSlotStateAddress, eRuneSlot); - var runeSlot = new RuneSlotState(eRuneSlot as List); - var enemyRuneSlotInfos = runeSlot.GetEquippedRuneSlotInfos(); - var runeAddresses = enemyRuneSlotInfos.Select(info => - RuneState.DeriveAddress(battleArena.EnemyAvatarAddress, info.RuneId)); - foreach (var address in runeAddresses) - { - if (previousStates.GetState(address) is { } rune) - { - previousStates = previousStates.SetState(address, rune); - } - } - } + return; } + var encodeElapsedMilliseconds = stopwatch.ElapsedMilliseconds; - var eval = new NCActionEvaluation(pa, ev.Signer, ev.BlockIndex, ev.OutputState, ev.Exception, previousStates, ev.RandomSeed, extra); + var eval = new NCActionEvaluation(pa, ev.Signer, ev.BlockIndex, ev.OutputState, ev.Exception, ev.PreviousState, ev.RandomSeed, extra); var encoded = MessagePackSerializer.Serialize(eval); var c = new MemoryStream(); await using (var df = new DeflateStream(c, CompressionLevel.Fastest)) @@ -363,6 +340,21 @@ await _hub.BroadcastRenderBlockAsync( ); await _hub.BroadcastRenderAsync(compressed); + stopwatch.Stop(); + + var broadcastElapsedMilliseconds = stopwatch.ElapsedMilliseconds - encodeElapsedMilliseconds; + Log + .ForContext("tag", "Metric") + .ForContext("subtag", "ActionEvaluationPublisherElapse") + .Information( + "[{ClientAddress}], #{BlockIndex}, {Action}," + + " {EncodeElapsedMilliseconds}, {BroadcastElapsedMilliseconds}, {TotalElapsedMilliseconds}", + _clientAddress, + ev.BlockIndex, + ev.Action.GetType(), + encodeElapsedMilliseconds, + broadcastElapsedMilliseconds, + encodeElapsedMilliseconds + broadcastElapsedMilliseconds); } catch (SerializationException se) { @@ -449,14 +441,14 @@ private bool ContainsAddressToBroadcast(ActionEvaluation ev) private bool ContainsAddressToBroadcastLocal(ActionEvaluation ev) { - var updatedAddresses = ev.OutputState.Delta.UpdatedAddresses; - return _context.AddressesToSubscribe.Any(updatedAddresses.Add(ev.Signer).Contains); + int t =ev.RandomSeed; + return true; } private bool ContainsAddressToBroadcastRemoteClient(ActionEvaluation ev) { - var updatedAddresses = ev.OutputState.Delta.UpdatedAddresses; - return TargetAddresses.Any(updatedAddresses.Add(ev.Signer).Contains); + int r = ev.RandomSeed; + return true; } } } diff --git a/NineChronicles.Headless/Controllers/GraphQLController.cs b/NineChronicles.Headless/Controllers/GraphQLController.cs index 501af000f..88dacd21f 100644 --- a/NineChronicles.Headless/Controllers/GraphQLController.cs +++ b/NineChronicles.Headless/Controllers/GraphQLController.cs @@ -18,6 +18,8 @@ using NineChronicles.Headless.Requests; using Serilog; using Lib9c.Renderers; +using Libplanet.Action.State; +using System.Collections.Immutable; namespace NineChronicles.Headless.Controllers { @@ -233,7 +235,13 @@ private void NotifyAction(ActionEvaluation eval) return; } Address address = StandaloneContext.NineChroniclesNodeService.MinerPrivateKey.PublicKey.ToAddress(); - if (eval.OutputState.Delta.UpdatedAddresses.Contains(address) || eval.Signer == address) + var input = StandaloneContext.NineChroniclesNodeService.BlockChain.GetAccountState(eval.PreviousState); + var output = StandaloneContext.NineChroniclesNodeService.BlockChain.GetAccountState(eval.OutputState); + var diff = AccountDiff.Create(input, output); + var updatedAddresses = diff.FungibleAssetValueDiffs + .Select(pair => pair.Key.Item1) + .Concat(diff.StateDiffs.Keys).ToImmutableHashSet(); + if (updatedAddresses.Contains(address) || eval.Signer == address) { if (eval.Signer == address) { diff --git a/NineChronicles.Headless/GraphQLServiceExtensions.cs b/NineChronicles.Headless/GraphQLServiceExtensions.cs index 9820b3d93..cad7d67af 100644 --- a/NineChronicles.Headless/GraphQLServiceExtensions.cs +++ b/NineChronicles.Headless/GraphQLServiceExtensions.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Reflection; using GraphQL.Types; -using Libplanet.Action; using Libplanet.Explorer.GraphTypes; using Libplanet.Explorer.Interfaces; using Libplanet.Explorer.Queries; @@ -36,8 +35,6 @@ public static IServiceCollection AddLibplanetScalarTypes(this IServiceCollection services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); diff --git a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs index a5d9688d5..9a129a488 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs @@ -120,33 +120,28 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi var recipient = context.GetArgument("recipient"); - IEnumerable txs = digest.TxIds - .Select(bytes => new TxId(bytes)) + IEnumerable blockTxs = digest.TxIds + .Select(b => new TxId(b.ToBuilder().ToArray())) .Select(store.GetTransaction); - var pairs = txs - .Where(tx => - tx.Actions!.Count == 1 && - store.GetTxExecution(blockHash, tx.Id) is TxSuccess) - .Select(tx => (tx.Id, ToAction(tx.Actions.First()))) - .Where(pair => - pair.Item2 is ITransferAsset transferAssset && - transferAssset.Amount.Currency.Ticker == "NCG") + var filtered = blockTxs + .Where(tx => tx.Actions.Count == 1) + .Select(tx => (store.GetTxExecution(blockHash, tx.Id), ToAction(tx.Actions[0]))) + .Where(pair => pair.Item1 is { } && pair.Item2 is ITransferAsset) .Select(pair => (pair.Item1, (ITransferAsset)pair.Item2)) - .Where(pair => (!(recipient is { } r) || pair.Item2.Recipient == r)); + .Where(pair => !pair.Item1.Fail && + (!recipient.HasValue || pair.Item2.Recipient == recipient) && + pair.Item2.Amount.Currency.Ticker == "NCG"); + + var histories = filtered.Select(pair => + new TransferNCGHistory( + pair.Item1.BlockHash, + pair.Item1.TxId, + pair.Item2.Sender, + pair.Item2.Recipient, + pair.Item2.Amount, + pair.Item2.Memo)); - TransferNCGHistory ToTransferNCGHistory((TxId TxId, ITransferAsset Transfer) pair) - { - return new TransferNCGHistory( - blockHash, - pair.TxId, - pair.Transfer.Sender, - pair.Transfer.Recipient, - pair.Transfer.Amount, - pair.Transfer.Memo); - } - - var histories = pairs.Select(ToTransferNCGHistory); return histories; }); diff --git a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs index 4fdf34bac..f32eeb1f8 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs @@ -321,29 +321,17 @@ private IObservable SubscribeTx(IResolveFieldContext context) } var txExecution = store.GetTxExecution(blockHash, transaction.Id); var txExecutedBlock = chain[blockHash]; - - return txExecution switch - { - TxSuccess success => new TxResult( - TxStatus.SUCCESS, + return txExecution.Fail + ? new TxResult( + TxStatus.FAILURE, txExecutedBlock.Index, txExecutedBlock.Hash.ToString(), - null, - success.UpdatedStates - .Select(kv => new KeyValuePair( - kv.Key, - kv.Value)) - .ToImmutableDictionary(), - success.UpdatedFungibleAssets), - TxFailure failure => new TxResult( - TxStatus.FAILURE, + txExecution.ExceptionNames) + : new TxResult( + TxStatus.SUCCESS, txExecutedBlock.Index, txExecutedBlock.Hash.ToString(), - failure.ExceptionName, - null, - null), - _ => null - }; + txExecution.ExceptionNames); } private void RenderBlock((Block OldTip, Block NewTip) pair) @@ -465,7 +453,7 @@ private void RenderMonsterCollectionStateSubject(ActionEvaluation eval) var agentState = new AgentState(agentDict); Address deriveAddress = MonsterCollectionState.DeriveAddress(address, agentState.MonsterCollectionRound); var subject = subjects.stateSubject; - if (eval.OutputState.GetState(deriveAddress) is Dictionary state) + if (service.BlockChain.GetAccountState(eval.OutputState).GetState(deriveAddress) is Dictionary state) { subject.OnNext(new MonsterCollectionState(state)); } diff --git a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs index 5be72bb01..a3cdad940 100644 --- a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs +++ b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs @@ -208,41 +208,29 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext) if (!(store.GetFirstTxIdBlockHashIndex(txId) is { } txExecutedBlockHash)) { return blockChain.GetStagedTransactionIds().Contains(txId) - ? new TxResult(TxStatus.STAGING, null, null, null, null, null) - : new TxResult(TxStatus.INVALID, null, null, null, null, null); + ? new TxResult(TxStatus.STAGING, null, null, null) + : new TxResult(TxStatus.INVALID, null, null, null); } try { TxExecution execution = blockChain.GetTxExecution(txExecutedBlockHash, txId); Block txExecutedBlock = blockChain[txExecutedBlockHash]; - return execution switch - { - TxSuccess txSuccess => new TxResult( - TxStatus.SUCCESS, + return execution.Fail + ? new TxResult( + TxStatus.FAILURE, txExecutedBlock.Index, txExecutedBlock.Hash.ToString(), - null, - txSuccess.UpdatedStates - .Select(kv => new KeyValuePair( - kv.Key, - kv.Value)) - .ToImmutableDictionary(), - txSuccess.UpdatedFungibleAssets), - TxFailure txFailure => new TxResult( - TxStatus.FAILURE, + execution.ExceptionNames) + : new TxResult( + TxStatus.SUCCESS, txExecutedBlock.Index, txExecutedBlock.Hash.ToString(), - txFailure.ExceptionName, - null, - null), - _ => throw new NotImplementedException( - $"{nameof(execution)} is not expected concrete class.") - }; + execution.ExceptionNames); } catch (Exception) { - return new TxResult(TxStatus.INVALID, null, null, null, null, null); + return new TxResult(TxStatus.INVALID, null, null, null); } } ); diff --git a/NineChronicles.Headless/HostBuilderExtensions.cs b/NineChronicles.Headless/HostBuilderExtensions.cs index ae5d84326..2fecb3a6e 100644 --- a/NineChronicles.Headless/HostBuilderExtensions.cs +++ b/NineChronicles.Headless/HostBuilderExtensions.cs @@ -37,6 +37,7 @@ StandaloneContext context }; return builder.ConfigureServices(services => { + services.AddOptions(); services.AddHostedService(provider => service); services.AddSingleton(provider => service); services.AddSingleton(provider => service.Swarm); @@ -60,6 +61,7 @@ StandaloneContext context context.NineChroniclesNodeService!.ActionRenderer, context.NineChroniclesNodeService!.ExceptionRenderer, context.NineChroniclesNodeService!.NodeStatusRenderer, + context.NineChroniclesNodeService!.BlockChain, IPAddress.Loopback.ToString(), 0, rpcContext, diff --git a/NineChronicles.Headless/NineChroniclesNodeService.cs b/NineChronicles.Headless/NineChroniclesNodeService.cs index 155068c0e..4f6cd33f3 100644 --- a/NineChronicles.Headless/NineChroniclesNodeService.cs +++ b/NineChronicles.Headless/NineChroniclesNodeService.cs @@ -14,6 +14,7 @@ using Libplanet.Headless.Hosting; using Libplanet.Net; using Libplanet.Store; +using Libplanet.Types.Blocks; using Microsoft.Extensions.Hosting; using Nekoyume.Blockchain; using Nekoyume.Blockchain.Policy; @@ -278,8 +279,7 @@ internal void ConfigureContext(StandaloneContext standaloneContext) standaloneContext.Store = Store; standaloneContext.Swarm = Swarm; standaloneContext.CurrencyFactory = - new CurrencyFactory( - () => standaloneContext.BlockChain.GetAccountState(standaloneContext.BlockChain.Tip.Hash)); + new CurrencyFactory(() => standaloneContext.BlockChain.GetAccountState((BlockHash?)null)); standaloneContext.FungibleAssetValueFactory = new FungibleAssetValueFactory(standaloneContext.CurrencyFactory); BootstrapEnded.WaitAsync().ContinueWith((task) =>