From 4e2b32ea9582c49ddf2d460c0b48bd4072cb9e12 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Wed, 4 Oct 2023 09:55:12 +0900 Subject: [PATCH 01/54] Bump lib9c to f162e5d3 --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 228977705..f162e5d3c 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 228977705598b58e9c589cfcaf4169faaf3a9294 +Subproject commit f162e5d3cd2bf5ef41b4dd47e841534959ef88cf From 69e2e1f10168a9fdafd4aa29cd2e4fe598bfc55d Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Tue, 26 Sep 2023 03:34:41 +0900 Subject: [PATCH 02/54] 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 | 1 + .../NineChroniclesNodeService.cs | 4 +- 16 files changed, 113 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 9fb831a5a..e5a49fb97 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 c7cab8112..cab6730e5 100644 --- a/NineChronicles.Headless.Executable/Program.cs +++ b/NineChronicles.Headless.Executable/Program.cs @@ -480,6 +480,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 21a8bd643..325e59e26 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; @@ -28,9 +31,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using Nekoyume.Action; -using Nekoyume.Model.State; using Nekoyume.Shared.Hubs; -using Sentry; using Serilog; namespace NineChronicles.Headless @@ -44,6 +45,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(); private readonly ConcurrentDictionary _clientsByDevice = new(); @@ -59,6 +61,7 @@ public ActionEvaluationPublisher( ActionRenderer actionRenderer, ExceptionRenderer exceptionRenderer, NodeStatusRenderer nodeStatusRenderer, + IBlockChainStates blockChainStates, string host, int port, RpcContext context, @@ -68,6 +71,7 @@ public ActionEvaluationPublisher( _actionRenderer = actionRenderer; _exceptionRenderer = exceptionRenderer; _nodeStatusRenderer = nodeStatusRenderer; + _blockChainStates = blockChainStates; _host = host; _port = port; _context = context; @@ -120,7 +124,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) @@ -370,6 +374,7 @@ private void DFS(string node, HashSet ips, HashSet ids, Concurre private sealed class Client : IAsyncDisposable { private readonly IActionEvaluationHub _hub; + private readonly IBlockChainStates _blockChainStates; private readonly RpcContext _context; private readonly Address _clientAddress; @@ -377,6 +382,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; } @@ -384,11 +392,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; @@ -397,6 +407,7 @@ private Client( public static async Task CreateAsync( GrpcChannel channel, + IBlockChainStates blockChainStates, Address clientAddress, RpcContext context, ConcurrentDictionary sentryTraces) @@ -407,7 +418,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( @@ -446,56 +457,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)) @@ -513,6 +490,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) { @@ -599,14 +591,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 7ff4b2054..fb5738403 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 65afd87f9..25e37cc22 100644 --- a/NineChronicles.Headless/HostBuilderExtensions.cs +++ b/NineChronicles.Headless/HostBuilderExtensions.cs @@ -33,6 +33,7 @@ NineChroniclesNodeService service { return builder.ConfigureServices(services => { + services.AddOptions(); services.AddHostedService(provider => service); services.AddSingleton(provider => service); services.AddSingleton(provider => service.Swarm); diff --git a/NineChronicles.Headless/NineChroniclesNodeService.cs b/NineChronicles.Headless/NineChroniclesNodeService.cs index 9e075af5b..8c3c0de61 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; @@ -277,8 +278,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) => From f42c9e205c15df9ac6b6ad91bce34f9269fff856 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Tue, 26 Sep 2023 16:51:29 +0900 Subject: [PATCH 03/54] Fix ReplayCommand --- .../Commands/ReplayCommand.Privates.cs | 17 +++++++++-------- .../Commands/ReplayCommand.cs | 5 ++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs index 9581815f3..f04346ddb 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs @@ -70,11 +70,6 @@ public ActionContext( public bool BlockAction => TxId is null; - public void PutLog(string log) - { - // NOTE: Not implemented yet. See also Lib9c.Tests.Action.ActionContext.PutLog(). - } - public void UseGas(long gas) { } @@ -145,8 +140,14 @@ public ValidatorSet GetValidatorSet(BlockHash? offset) public IAccountState GetAccountState(BlockHash? offset) { - return new LocalCacheAccountState(_rocksDb, _source.GetAccountState, offset); + return new LocalCacheAccountState( + _rocksDb, + _source.GetAccountState, + offset); } + + public IAccountState GetAccountState(HashDigest? hash) + => throw new NotImplementedException(); } private sealed class LocalCacheAccountState : IAccountState @@ -158,11 +159,11 @@ private sealed class LocalCacheAccountState : IAccountState public LocalCacheAccountState( RocksDb rocksDb, - Func sourceAccountStateGetter, + Func sourceAccountStateGetterWithBlockHash, BlockHash? offset) { _rocksDb = rocksDb; - _sourceAccountStateGetter = sourceAccountStateGetter; + _sourceAccountStateGetter = sourceAccountStateGetterWithBlockHash; _offset = offset; } diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs index e5a49fb97..4af166134 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 = AccountStateDelta.Create(previousBlockStates); + IAccount previousStates = new Account(previousBlockStates); var actions = tx.Actions.Select(a => ToAction(a)); var actionEvaluations = EvaluateActions( preEvaluationHash: targetBlock.PreEvaluationHash, @@ -399,8 +399,7 @@ public int RemoteTx( cacheDirectory ?? Path.Join(Path.GetTempPath(), "ncd-replay-remote-tx-cache")); var previousBlockHash = BlockHash.FromString(previousBlockHashValue); - var previousStates = - AccountStateDelta.Create(blockChainStates.GetAccountState(previousBlockHash)); + var previousStates = new Account(blockChainStates.GetAccountState(previousBlockHash)); var actions = transaction.Actions .Select(ToAction) From ef56a7e0f2ece9168d3531b3545c92f0282f338d Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Wed, 4 Oct 2023 10:19:59 +0900 Subject: [PATCH 04/54] Cherry-pick fix --- NineChronicles.Headless.Executable/Program.cs | 1 + NineChronicles.Headless.Tests/GraphQLStartupTest.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/NineChronicles.Headless.Executable/Program.cs b/NineChronicles.Headless.Executable/Program.cs index cab6730e5..7db80836c 100644 --- a/NineChronicles.Headless.Executable/Program.cs +++ b/NineChronicles.Headless.Executable/Program.cs @@ -510,6 +510,7 @@ IActionLoader MakeSingleActionLoader() standaloneContext.NineChroniclesNodeService!.ActionRenderer, standaloneContext.NineChroniclesNodeService!.ExceptionRenderer, standaloneContext.NineChroniclesNodeService!.NodeStatusRenderer, + standaloneContext.NineChroniclesNodeService!.BlockChain, IPAddress.Loopback.ToString(), 0, context, diff --git a/NineChronicles.Headless.Tests/GraphQLStartupTest.cs b/NineChronicles.Headless.Tests/GraphQLStartupTest.cs index 2568550be..f582243e0 100644 --- a/NineChronicles.Headless.Tests/GraphQLStartupTest.cs +++ b/NineChronicles.Headless.Tests/GraphQLStartupTest.cs @@ -23,6 +23,7 @@ public GraphQLStartupTest() new ActionRenderer(), new ExceptionRenderer(), new NodeStatusRenderer(), + standaloneContext!.BlockChain, "", 0, new RpcContext(), From ada301d0d5cedd045b9c95de787e68e95bfa50b5 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Wed, 4 Oct 2023 10:29:26 +0900 Subject: [PATCH 05/54] Linting --- NineChronicles.Headless/ActionEvaluationPublisher.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/NineChronicles.Headless/ActionEvaluationPublisher.cs b/NineChronicles.Headless/ActionEvaluationPublisher.cs index 325e59e26..88f8bf8ac 100644 --- a/NineChronicles.Headless/ActionEvaluationPublisher.cs +++ b/NineChronicles.Headless/ActionEvaluationPublisher.cs @@ -382,7 +382,7 @@ private sealed class Client : IAsyncDisposable private IDisposable? _actionEveryRenderSubscribe; private IDisposable? _everyExceptionSubscribe; private IDisposable? _nodeStatusSubscribe; - + private Subject _NCActionRenderSubject { get; } = new Subject(); @@ -458,7 +458,7 @@ await _hub.BroadcastRenderBlockAsync( try { Stopwatch stopwatch = new Stopwatch(); - stopwatch.Start(); + stopwatch.Start(); ActionBase? pa = ev.Action is RewardGold ? null : ev.Action; @@ -470,7 +470,7 @@ await _hub.BroadcastRenderBlockAsync( { return; } - var encodeElapsedMilliseconds = stopwatch.ElapsedMilliseconds; + var encodeElapsedMilliseconds = stopwatch.ElapsedMilliseconds; var eval = new NCActionEvaluation(pa, ev.Signer, ev.BlockIndex, ev.OutputState, ev.Exception, ev.PreviousState, ev.RandomSeed, extra); var encoded = MessagePackSerializer.Serialize(eval); @@ -491,7 +491,7 @@ await _hub.BroadcastRenderBlockAsync( await _hub.BroadcastRenderAsync(compressed); stopwatch.Stop(); - + var broadcastElapsedMilliseconds = stopwatch.ElapsedMilliseconds - encodeElapsedMilliseconds; Log .ForContext("tag", "Metric") @@ -504,7 +504,7 @@ await _hub.BroadcastRenderBlockAsync( ev.Action.GetType(), encodeElapsedMilliseconds, broadcastElapsedMilliseconds, - encodeElapsedMilliseconds + broadcastElapsedMilliseconds); + encodeElapsedMilliseconds + broadcastElapsedMilliseconds); } catch (SerializationException se) { @@ -591,7 +591,7 @@ private bool ContainsAddressToBroadcast(ActionEvaluation ev) private bool ContainsAddressToBroadcastLocal(ActionEvaluation ev) { - int t =ev.RandomSeed; + int t = ev.RandomSeed; return true; } From c3fbb4b29fa1cc78ae3afd8825915101aff15fc0 Mon Sep 17 00:00:00 2001 From: Suho Lee Date: Wed, 4 Oct 2023 15:46:39 +0900 Subject: [PATCH 06/54] fix: some mistake --- .../ActionEvaluationPublisher.cs | 20 ------------------- .../NineChroniclesNodeService.cs | 2 +- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/NineChronicles.Headless/ActionEvaluationPublisher.cs b/NineChronicles.Headless/ActionEvaluationPublisher.cs index 88f8bf8ac..96f293c2b 100644 --- a/NineChronicles.Headless/ActionEvaluationPublisher.cs +++ b/NineChronicles.Headless/ActionEvaluationPublisher.cs @@ -449,7 +449,6 @@ await _hub.BroadcastRenderBlockAsync( ); _actionEveryRenderSubscribe = actionRenderer.EveryRender() - .Where(ContainsAddressToBroadcast) .SubscribeOn(NewThreadScheduler.Default) .ObserveOn(NewThreadScheduler.Default) .Subscribe( @@ -581,25 +580,6 @@ public async ValueTask DisposeAsync() _nodeStatusSubscribe?.Dispose(); await _hub.DisposeAsync(); } - - private bool ContainsAddressToBroadcast(ActionEvaluation ev) - { - return _context.RpcRemoteSever - ? ContainsAddressToBroadcastRemoteClient(ev) - : ContainsAddressToBroadcastLocal(ev); - } - - private bool ContainsAddressToBroadcastLocal(ActionEvaluation ev) - { - int t = ev.RandomSeed; - return true; - } - - private bool ContainsAddressToBroadcastRemoteClient(ActionEvaluation ev) - { - int r = ev.RandomSeed; - return true; - } } } } diff --git a/NineChronicles.Headless/NineChroniclesNodeService.cs b/NineChronicles.Headless/NineChroniclesNodeService.cs index 8c3c0de61..b265edf87 100644 --- a/NineChronicles.Headless/NineChroniclesNodeService.cs +++ b/NineChronicles.Headless/NineChroniclesNodeService.cs @@ -278,7 +278,7 @@ internal void ConfigureContext(StandaloneContext standaloneContext) standaloneContext.Store = Store; standaloneContext.Swarm = Swarm; standaloneContext.CurrencyFactory = - new CurrencyFactory(() => standaloneContext.BlockChain.GetAccountState((BlockHash?)null)); + new CurrencyFactory(() => standaloneContext.BlockChain.GetAccountState(standaloneContext.BlockChain.Tip.Hash)); standaloneContext.FungibleAssetValueFactory = new FungibleAssetValueFactory(standaloneContext.CurrencyFactory); BootstrapEnded.WaitAsync().ContinueWith((task) => From f1a39193e5751a61a560606ca713e860c1843cd5 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Wed, 4 Oct 2023 16:16:54 +0900 Subject: [PATCH 07/54] Implement suggestions --- .../Commands/ReplayCommand.Privates.cs | 2 +- NineChronicles.Headless.Tests/Common/MockState.cs | 5 +---- NineChronicles.Headless/GraphTypes/StandaloneQuery.cs | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs index f04346ddb..9395bad78 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs @@ -147,7 +147,7 @@ public IAccountState GetAccountState(BlockHash? offset) } public IAccountState GetAccountState(HashDigest? hash) - => throw new NotImplementedException(); + => _source.GetAccountState(hash); } private sealed class LocalCacheAccountState : IAccountState diff --git a/NineChronicles.Headless.Tests/Common/MockState.cs b/NineChronicles.Headless.Tests/Common/MockState.cs index 54ac73e76..2ca86f1b0 100644 --- a/NineChronicles.Headless.Tests/Common/MockState.cs +++ b/NineChronicles.Headless.Tests/Common/MockState.cs @@ -86,10 +86,7 @@ private MockState( public ValidatorSet ValidatorSet => _validatorSet; - public ITrie Trie - { - get => new MerkleTrie(new MemoryKeyValueStore()); - } + public ITrie Trie => throw new NotSupportedException(); public IValue? GetState(Address address) => _states.TryGetValue(address, out IValue? value) ? value diff --git a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs index 9a129a488..ae7394372 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs @@ -121,7 +121,7 @@ public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration confi var recipient = context.GetArgument("recipient"); IEnumerable blockTxs = digest.TxIds - .Select(b => new TxId(b.ToBuilder().ToArray())) + .Select(bytes => new TxId(bytes)) .Select(store.GetTransaction); var filtered = blockTxs From ce0cfd77a6fd31557b37648f60ab8556a875c103 Mon Sep 17 00:00:00 2001 From: area363 Date: Wed, 4 Oct 2023 17:38:21 +0900 Subject: [PATCH 08/54] remove state query management --- .../HttpMultiAccountManagementMiddleware.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/NineChronicles.Headless/Middleware/HttpMultiAccountManagementMiddleware.cs b/NineChronicles.Headless/Middleware/HttpMultiAccountManagementMiddleware.cs index 977c8dfb1..05b9ac826 100644 --- a/NineChronicles.Headless/Middleware/HttpMultiAccountManagementMiddleware.cs +++ b/NineChronicles.Headless/Middleware/HttpMultiAccountManagementMiddleware.cs @@ -63,22 +63,6 @@ public async Task InvokeAsync(HttpContext context) context.Request.Body.Seek(0, SeekOrigin.Begin); if (_options.Value.EnableManaging) { - if (body.Contains("agent(address:\\\"") || body.Contains("agent(address: \\\"")) - { - try - { - var agent = new Address(body.Split("\\\"")[1].Split("0x")[1]); - UpdateIpSignerList(remoteIp, agent); - } - catch (Exception ex) - { - _logger.Error( - "[GRAPHQL-MULTI-ACCOUNT-MANAGER] Error message: {message} Stacktrace: {stackTrace}", - ex.Message, - ex.StackTrace); - } - } - if (body.Contains("stageTransaction")) { try From 0731b92cf11082c504390437b904a6b89902e2f9 Mon Sep 17 00:00:00 2001 From: area363 Date: Wed, 4 Oct 2023 17:38:45 +0900 Subject: [PATCH 09/54] update account management configuration --- NineChronicles.Headless.Executable/appsettings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NineChronicles.Headless.Executable/appsettings.json b/NineChronicles.Headless.Executable/appsettings.json index 87b56cfff..aef98715b 100644 --- a/NineChronicles.Headless.Executable/appsettings.json +++ b/NineChronicles.Headless.Executable/appsettings.json @@ -125,8 +125,8 @@ }, "MultiAccountManaging": { "EnableManaging": false, - "ManagementTimeMinutes": 10, - "TxIntervalMinutes": 10, + "ManagementTimeMinutes": 60, + "TxIntervalMinutes": 60, "ThresholdCount": 29 } } From efcab46e158671859190bd87a5cd5b2aae708b47 Mon Sep 17 00:00:00 2001 From: area363 Date: Wed, 4 Oct 2023 22:13:56 +0900 Subject: [PATCH 10/54] merge conditions --- .../HttpMultiAccountManagementMiddleware.cs | 122 +++++++++--------- 1 file changed, 59 insertions(+), 63 deletions(-) diff --git a/NineChronicles.Headless/Middleware/HttpMultiAccountManagementMiddleware.cs b/NineChronicles.Headless/Middleware/HttpMultiAccountManagementMiddleware.cs index 05b9ac826..5f5498e7d 100644 --- a/NineChronicles.Headless/Middleware/HttpMultiAccountManagementMiddleware.cs +++ b/NineChronicles.Headless/Middleware/HttpMultiAccountManagementMiddleware.cs @@ -61,92 +61,88 @@ public async Task InvokeAsync(HttpContext context) var remoteIp = context.Connection.RemoteIpAddress!.ToString(); var body = await new StreamReader(context.Request.Body).ReadToEndAsync(); context.Request.Body.Seek(0, SeekOrigin.Begin); - if (_options.Value.EnableManaging) + if (_options.Value.EnableManaging && body.Contains("stageTransaction")) { - if (body.Contains("stageTransaction")) + try { - try - { - var pattern = "64313.*6565"; - var txPayload = Regex.Match(body, pattern).ToString(); - byte[] bytes = ByteUtil.ParseHex(txPayload); - Transaction tx = Transaction.Deserialize(bytes); - var agent = tx.Signer; - var action = NCActionUtils.ToAction(tx.Actions.Actions.First()); + var pattern = "64313.*6565"; + var txPayload = Regex.Match(body, pattern).ToString(); + byte[] bytes = ByteUtil.ParseHex(txPayload); + Transaction tx = Transaction.Deserialize(bytes); + var agent = tx.Signer; + var action = NCActionUtils.ToAction(tx.Actions.Actions.First()); - // Only monitoring actions not used in the launcher - if (action is not Stake - and not ClaimStakeReward - and not TransferAsset) + // Only monitoring actions not used in the launcher + if (action is not Stake + and not ClaimStakeReward + and not TransferAsset) + { + if (_ipSignerList.ContainsKey(remoteIp)) { - if (_ipSignerList.ContainsKey(remoteIp)) + if (_ipSignerList[remoteIp].Count > _options.Value.ThresholdCount) { - if (_ipSignerList[remoteIp].Count > _options.Value.ThresholdCount) - { - _logger.Information( - "[GRAPHQL-MULTI-ACCOUNT-MANAGER] IP: {IP} List Count: {Count}, AgentAddresses: {Agent}", - remoteIp, - _ipSignerList[remoteIp].Count, - _ipSignerList[remoteIp]); + _logger.Information( + "[GRAPHQL-MULTI-ACCOUNT-MANAGER] IP: {IP} List Count: {Count}, AgentAddresses: {Agent}", + remoteIp, + _ipSignerList[remoteIp].Count, + _ipSignerList[remoteIp]); - if (!MultiAccountManagementList.ContainsKey(agent)) + if (!MultiAccountManagementList.ContainsKey(agent)) + { + if (!MultiAccountTxIntervalTracker.ContainsKey(agent)) { - if (!MultiAccountTxIntervalTracker.ContainsKey(agent)) - { - _logger.Information($"[GRAPHQL-MULTI-ACCOUNT-MANAGER] Adding agent {agent} to the agent tracker."); - MultiAccountTxIntervalTracker.Add(agent, DateTimeOffset.Now); - } - else - { - if ((DateTimeOffset.Now - MultiAccountTxIntervalTracker[agent]).Minutes >= _options.Value.TxIntervalMinutes) - { - _logger.Information($"[GRAPHQL-MULTI-ACCOUNT-MANAGER] Resetting Agent {agent}'s time because it has been more than {_options.Value.TxIntervalMinutes} minutes since the last transaction."); - MultiAccountTxIntervalTracker[agent] = DateTimeOffset.Now; - } - else - { - _logger.Information($"[GRAPHQL-MULTI-ACCOUNT-MANAGER] Managing Agent {agent} for {_options.Value.ManagementTimeMinutes} minutes due to {_ipSignerList[remoteIp].Count} associated accounts."); - ManageMultiAccount(agent); - MultiAccountTxIntervalTracker[agent] = DateTimeOffset.Now; - await CancelRequestAsync(context); - return; - } - } + _logger.Information($"[GRAPHQL-MULTI-ACCOUNT-MANAGER] Adding agent {agent} to the agent tracker."); + MultiAccountTxIntervalTracker.Add(agent, DateTimeOffset.Now); } else { - var currentManagedTime = (DateTimeOffset.Now - MultiAccountManagementList[agent]).Minutes; - if (currentManagedTime > _options.Value.ManagementTimeMinutes) + if ((DateTimeOffset.Now - MultiAccountTxIntervalTracker[agent]).Minutes >= _options.Value.TxIntervalMinutes) { - _logger.Information($"[GRAPHQL-MULTI-ACCOUNT-MANAGER] Restoring Agent {agent} after {_options.Value.ManagementTimeMinutes} minutes."); - RestoreMultiAccount(agent); - MultiAccountTxIntervalTracker[agent] = DateTimeOffset.Now.AddMinutes(-_options.Value.TxIntervalMinutes); - _logger.Information($"[GRAPHQL-MULTI-ACCOUNT-MANAGER] Current time: {DateTimeOffset.Now} Added time: {DateTimeOffset.Now.AddMinutes(-_options.Value.TxIntervalMinutes)}."); + _logger.Information($"[GRAPHQL-MULTI-ACCOUNT-MANAGER] Resetting Agent {agent}'s time because it has been more than {_options.Value.TxIntervalMinutes} minutes since the last transaction."); + MultiAccountTxIntervalTracker[agent] = DateTimeOffset.Now; } else { - _logger.Information($"[GRAPHQL-MULTI-ACCOUNT-MANAGER] Agent {agent} is in managed status for the next {_options.Value.ManagementTimeMinutes - currentManagedTime} minutes."); + _logger.Information($"[GRAPHQL-MULTI-ACCOUNT-MANAGER] Managing Agent {agent} for {_options.Value.ManagementTimeMinutes} minutes due to {_ipSignerList[remoteIp].Count} associated accounts."); + ManageMultiAccount(agent); + MultiAccountTxIntervalTracker[agent] = DateTimeOffset.Now; await CancelRequestAsync(context); return; } } } - } - else - { - UpdateIpSignerList(remoteIp, agent); + else + { + var currentManagedTime = (DateTimeOffset.Now - MultiAccountManagementList[agent]).Minutes; + if (currentManagedTime > _options.Value.ManagementTimeMinutes) + { + _logger.Information($"[GRAPHQL-MULTI-ACCOUNT-MANAGER] Restoring Agent {agent} after {_options.Value.ManagementTimeMinutes} minutes."); + RestoreMultiAccount(agent); + MultiAccountTxIntervalTracker[agent] = DateTimeOffset.Now.AddMinutes(-_options.Value.TxIntervalMinutes); + _logger.Information($"[GRAPHQL-MULTI-ACCOUNT-MANAGER] Current time: {DateTimeOffset.Now} Added time: {DateTimeOffset.Now.AddMinutes(-_options.Value.TxIntervalMinutes)}."); + } + else + { + _logger.Information($"[GRAPHQL-MULTI-ACCOUNT-MANAGER] Agent {agent} is in managed status for the next {_options.Value.ManagementTimeMinutes - currentManagedTime} minutes."); + await CancelRequestAsync(context); + return; + } + } } } - - } - catch (Exception ex) - { - _logger.Error( - "[GRAPHQL-MULTI-ACCOUNT-MANAGER] Error message: {message} Stacktrace: {stackTrace}", - ex.Message, - ex.StackTrace); + else + { + UpdateIpSignerList(remoteIp, agent); + } } } + catch (Exception ex) + { + _logger.Error( + "[GRAPHQL-MULTI-ACCOUNT-MANAGER] Error message: {message} Stacktrace: {stackTrace}", + ex.Message, + ex.StackTrace); + } } } From f46ed434066b98330154f7e84dfca7874608a52e Mon Sep 17 00:00:00 2001 From: Suho Lee Date: Wed, 4 Oct 2023 20:25:48 +0900 Subject: [PATCH 11/54] bump: NineChronicles.RPC.Shared --- NineChronicles.RPC.Shared | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NineChronicles.RPC.Shared b/NineChronicles.RPC.Shared index 2dcbbb19a..cff68421f 160000 --- a/NineChronicles.RPC.Shared +++ b/NineChronicles.RPC.Shared @@ -1 +1 @@ -Subproject commit 2dcbbb19a0c90f3f41de03506802bbd527c0aaba +Subproject commit cff68421fabb098f3d0bfd20fdef55755db309e4 From 38c3cb591c14dff4fb68d0f8c6a2877ea1e439b4 Mon Sep 17 00:00:00 2001 From: Suho Lee Date: Wed, 4 Oct 2023 20:31:16 +0900 Subject: [PATCH 12/54] feat: implement `IBlockChainService` --- NineChronicles.Headless/BlockChainService.cs | 71 +++++++++++++++++++- 1 file changed, 69 insertions(+), 2 deletions(-) diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs index c28425588..71c94bdb4 100644 --- a/NineChronicles.Headless/BlockChainService.cs +++ b/NineChronicles.Headless/BlockChainService.cs @@ -4,10 +4,13 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Security.Cryptography; using System.Threading.Tasks; using Bencodex; using Bencodex.Types; +using Libplanet.Action.State; using Libplanet.Blockchain; +using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Headless.Hosting; using Libplanet.Net; @@ -116,12 +119,21 @@ public UnaryResult GetState(byte[] addressBytes, byte[] blockHashBytes) { var address = new Address(addressBytes); var hash = new BlockHash(blockHashBytes); - IValue state = _blockChain.GetStates(new[] { address }, hash)[0]; + IValue state = _blockChain.GetAccountState(hash).GetState(address); // FIXME: Null과 null 구분해서 반환해야 할 듯 byte[] encoded = _codec.Encode(state ?? Null.Value); return new UnaryResult(encoded); } + public UnaryResult GetStateBySrh(byte[] addressBytes, byte[] stateRootHashBytes) + { + var stateRootHash = new HashDigest(stateRootHashBytes); + var address = new Address(addressBytes); + IValue state = _blockChain.GetAccountState(stateRootHash).GetState(address); + byte[] encoded = _codec.Encode(state ?? Null.Value); + return new UnaryResult(encoded); + } + public async UnaryResult> GetAvatarStates(IEnumerable addressBytesList, byte[] blockHashBytes) { var hash = new BlockHash(blockHashBytes); @@ -140,6 +152,26 @@ public async UnaryResult> GetAvatarStates(IEnumerable return result.ToDictionary(kv => kv.Key, kv => kv.Value); } + public async UnaryResult> GetAvatarStatesBySrh( + IEnumerable addressBytesList, + byte[] stateRootHashBytes) + { + var stateRootHash = new HashDigest(stateRootHashBytes); + var accountState = _blockChain.GetAccountState(stateRootHash); + var result = new ConcurrentDictionary(); + var addresses = addressBytesList.Select(a => new Address(a)).ToList(); + var rawAvatarStates = accountState.GetRawAvatarStates(addresses); + var taskList = rawAvatarStates + .Select(pair => Task.Run(() => + { + result.TryAdd(pair.Key.ToByteArray(), _codec.Encode(pair.Value)); + })) + .ToList(); + + await Task.WhenAll(taskList); + return result.ToDictionary(kv => kv.Key, kv => kv.Value); + } + public UnaryResult> GetStateBulk(IEnumerable addressBytesList, byte[] blockHashBytes) { var hash = new BlockHash(blockHashBytes); @@ -154,13 +186,48 @@ public UnaryResult> GetStateBulk(IEnumerable return new UnaryResult>(result); } + public UnaryResult> GetStateBulkBySrh( + IEnumerable addressBytesList, + byte[] stateRootHashBytes) + { + var stateRootHash = new HashDigest(stateRootHashBytes); + var result = new Dictionary(); + Address[] addresses = addressBytesList.Select(b => new Address(b)).ToArray(); + IReadOnlyList values = _blockChain.GetAccountState(stateRootHash).GetStates(addresses); + for (int i = 0; i < addresses.Length; i++) + { + result.TryAdd(addresses[i].ToByteArray(), _codec.Encode(values[i] ?? Null.Value)); + } + + return new UnaryResult>(result); + } + public UnaryResult GetBalance(byte[] addressBytes, byte[] currencyBytes, byte[] blockHashBytes) { var address = new Address(addressBytes); var serializedCurrency = (Bencodex.Types.Dictionary)_codec.Decode(currencyBytes); Currency currency = CurrencyExtensions.Deserialize(serializedCurrency); var hash = new BlockHash(blockHashBytes); - FungibleAssetValue balance = _blockChain.GetBalance(address, currency, hash); + FungibleAssetValue balance = _blockChain.GetAccountState(hash).GetBalance(address, currency); + byte[] encoded = _codec.Encode( + new Bencodex.Types.List( + new IValue[] + { + balance.Currency.Serialize(), + (Integer) balance.RawValue, + } + ) + ); + return new UnaryResult(encoded); + } + + public UnaryResult GetBalanceBySrh(byte[] addressBytes, byte[] currencyBytes, byte[] stateRootHashBytes) + { + var address = new Address(addressBytes); + var stateRootHash = new HashDigest(stateRootHashBytes); + var serializedCurrency = (Bencodex.Types.Dictionary)_codec.Decode(currencyBytes); + Currency currency = CurrencyExtensions.Deserialize(serializedCurrency); + FungibleAssetValue balance = _blockChain.GetAccountState(stateRootHash).GetBalance(address, currency); byte[] encoded = _codec.Encode( new Bencodex.Types.List( new IValue[] From 50b1518a840eaf8ec8f9b20413051db65ea9986a Mon Sep 17 00:00:00 2001 From: moreal Date: Thu, 5 Oct 2023 14:17:43 +0900 Subject: [PATCH 13/54] Support `NineChronicles.Headless.GraphTypes.TransactionType.SerializedPayload` field --- NineChronicles.Headless/GraphTypes/TransactionType.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/NineChronicles.Headless/GraphTypes/TransactionType.cs b/NineChronicles.Headless/GraphTypes/TransactionType.cs index 3a539f735..d56d4391a 100644 --- a/NineChronicles.Headless/GraphTypes/TransactionType.cs +++ b/NineChronicles.Headless/GraphTypes/TransactionType.cs @@ -1,3 +1,4 @@ +using System; using GraphQL.Types; using Libplanet.Explorer.GraphTypes; using Libplanet.Types.Tx; @@ -48,6 +49,15 @@ public TransactionType() description: "A list of actions in this transaction.", resolve: context => context.Source.Actions ); + + Field>( + name: "SerializedPayload", + description: "A serialized tx payload in base64 string.", + resolve: x => + { + byte[] bytes = x.Source.Serialize(); + return Convert.ToBase64String(bytes); + }); } } } From 1d7cc1221e1399ef3f52dcc24c7d2c18b17babac Mon Sep 17 00:00:00 2001 From: moreal Date: Thu, 5 Oct 2023 14:22:55 +0900 Subject: [PATCH 14/54] Support regex expression in `subscription.tx` with `actionType` argument --- NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs index fb5738403..8b249aebc 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs @@ -15,6 +15,7 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using System.Reactive.Disposables; +using System.Text.RegularExpressions; using Bencodex.Types; using Libplanet.Crypto; using Libplanet.Types.Assets; @@ -145,7 +146,7 @@ public StandaloneSubscription(StandaloneContext standaloneContext) Arguments = new QueryArguments( new QueryArgument> { - Description = "A type of action in transaction.", + Description = "A regular expression to filter transactions based on action type.", Name = "actionType", } ), @@ -304,7 +305,7 @@ private IObservable SubscribeTx(IResolveFieldContext context) return false; } - return typeId == actionType; + return Regex.IsMatch(typeId, actionType); })) .Select(transaction => new Tx { From f2524080fa2a9d1903837a4705240860cfdfedac Mon Sep 17 00:00:00 2001 From: Yang Chun Ung Date: Wed, 11 Oct 2023 16:46:21 +0900 Subject: [PATCH 15/54] Bump lib9c --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 538afeade..0bf659679 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 538afeade5d409a97d709d58b7a521b3e1d76943 +Subproject commit 0bf659679f55122846691ed4fec4cfcb76d81cda From 22c777d816f682b63586f3d70085860e20aac0bb Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Thu, 5 Oct 2023 19:13:00 +0900 Subject: [PATCH 16/54] Bump lib9c to fa91d8eb --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 545ab2893..fa91d8eba 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 545ab2893a0a1d86825d86399dd00b6261c2e2f2 +Subproject commit fa91d8ebab9430779781e4506e8af09c1b75c612 From dd52797b601e6e913e3ef5c3c00c1173fac60065 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Thu, 5 Oct 2023 19:44:03 +0900 Subject: [PATCH 17/54] Bump lib9c to ce1cb9b7 --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index fa91d8eba..ce1cb9b73 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit fa91d8ebab9430779781e4506e8af09c1b75c612 +Subproject commit ce1cb9b737d1cb3e56dabda2faa59ca2a4965e00 From 714f051a75ea6e2832369dc54ecf25c1732e4385 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Tue, 3 Oct 2023 23:04:48 +0900 Subject: [PATCH 18/54] Accommodate API changes for IActionContext --- .../ForkableActionEvaluatorTest.cs | 4 ++-- .../Commands/ReplayCommand.Privates.cs | 20 ++++--------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs b/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs index cd5714f01..f9a8a5888 100644 --- a/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs +++ b/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs @@ -79,7 +79,7 @@ public IReadOnlyList Evaluate(IPreEvaluationBlock block) 0, false, new AccountStateDelta(), - new Random(0), + 0, null, false), new AccountStateDelta(), @@ -106,7 +106,7 @@ public IReadOnlyList Evaluate(IPreEvaluationBlock block) 0, false, new AccountStateDelta(), - new Random(0), + 0, null, false), new AccountStateDelta(), diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs index 9395bad78..861b678c0 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs @@ -29,8 +29,6 @@ public partial class ReplayCommand : CoconaLiteConsoleAppBase /// private sealed class ActionContext : IActionContext { - private readonly int _randomSeed; - public ActionContext( Address signer, TxId? txid, @@ -48,8 +46,7 @@ public ActionContext( BlockProtocolVersion = blockProtocolVersion; Rehearsal = rehearsal; PreviousState = previousState; - Random = new Random(randomSeed); - _randomSeed = randomSeed; + RandomSeed = randomSeed; } public Address Signer { get; } @@ -66,7 +63,7 @@ public ActionContext( public IAccount PreviousState { get; } - public IRandom Random { get; } + public int RandomSeed { get; } public bool BlockAction => TxId is null; @@ -74,20 +71,11 @@ public void UseGas(long gas) { } - public IActionContext GetUnconsumedContext() => - new ActionContext( - Signer, - TxId, - Miner, - BlockIndex, - BlockProtocolVersion, - PreviousState, - _randomSeed, - Rehearsal); - public long GasUsed() => 0; public long GasLimit() => 0; + + public IRandom GetRandom() => new Random(RandomSeed); } private sealed class Random : System.Random, IRandom From f5d1cd24d7aabce35eea57bfc6568974e07bf3e8 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Thu, 5 Oct 2023 14:29:27 +0900 Subject: [PATCH 19/54] Accommodate API changes --- .../ForkableActionEvaluatorTest.cs | 14 +++++++------- .../ForkableActionEvaluator.cs | 7 +++++-- .../Hosting/LibplanetNodeServiceTest.cs | 5 +++-- Libplanet.Headless/Hosting/LibplanetNodeService.cs | 2 +- .../Commands/AccountCommandTest.cs | 2 +- .../Commands/ChainCommandTest.cs | 10 +++++----- .../Store/StoreExtensionsTest.cs | 2 +- .../Commands/ChainCommand.cs | 2 +- .../Commands/ReplayCommand.cs | 8 ++++---- .../Commands/StateCommand.cs | 5 +++-- NineChronicles.Headless.Tests/GraphQLTestUtils.cs | 4 ++-- .../GraphTypes/GraphQLTestBase.cs | 2 +- .../GraphTypes/StandaloneMutationTest.cs | 2 +- .../GraphTypes/StandaloneQueryTest.cs | 12 ++++++------ .../GraphTypes/StandaloneSubscriptionTest.cs | 2 +- .../GraphTypes/TransactionHeadlessQueryTest.cs | 2 +- 16 files changed, 43 insertions(+), 38 deletions(-) diff --git a/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs b/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs index f9a8a5888..a28045b56 100644 --- a/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs +++ b/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs @@ -24,11 +24,11 @@ public void ForkEvaluation() ((101L, long.MaxValue), new PostActionEvaluator()), }); - Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(0))).Action); - Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(99))).Action); - Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(100))).Action); - Assert.Equal((Text)"POST", Assert.Single(evaluator.Evaluate(new MockBlock(101))).Action); - Assert.Equal((Text)"POST", Assert.Single(evaluator.Evaluate(new MockBlock(long.MaxValue))).Action); + Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(0), null)).Action); + Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(99), null)).Action); + Assert.Equal((Text)"PRE", Assert.Single(evaluator.Evaluate(new MockBlock(100), null)).Action); + Assert.Equal((Text)"POST", Assert.Single(evaluator.Evaluate(new MockBlock(101), null)).Action); + Assert.Equal((Text)"POST", Assert.Single(evaluator.Evaluate(new MockBlock(long.MaxValue), null)).Action); } [Fact] @@ -64,7 +64,7 @@ public void CheckPairs() class PostActionEvaluator : IActionEvaluator { public IActionLoader ActionLoader => throw new NotSupportedException(); - public IReadOnlyList Evaluate(IPreEvaluationBlock block) + public IReadOnlyList Evaluate(IPreEvaluationBlock block, HashDigest? baseStateroothash) { return new IActionEvaluation[] { @@ -91,7 +91,7 @@ public IReadOnlyList Evaluate(IPreEvaluationBlock block) class PreActionEvaluator : IActionEvaluator { public IActionLoader ActionLoader => throw new NotSupportedException(); - public IReadOnlyList Evaluate(IPreEvaluationBlock block) + public IReadOnlyList Evaluate(IPreEvaluationBlock block, HashDigest? baseStateRootHash) { return new IActionEvaluation[] { diff --git a/Libplanet.Extensions.ForkableActionEvaluator/ForkableActionEvaluator.cs b/Libplanet.Extensions.ForkableActionEvaluator/ForkableActionEvaluator.cs index 13c60a5ea..1e5f47214 100644 --- a/Libplanet.Extensions.ForkableActionEvaluator/ForkableActionEvaluator.cs +++ b/Libplanet.Extensions.ForkableActionEvaluator/ForkableActionEvaluator.cs @@ -1,5 +1,7 @@ +using System.Security.Cryptography; using Libplanet.Action; using Libplanet.Action.Loader; +using Libplanet.Common; using Libplanet.Types.Blocks; namespace Libplanet.Extensions.ForkableActionEvaluator; @@ -15,9 +17,10 @@ public ForkableActionEvaluator(IEnumerable<((long StartIndex, long EndIndex) Ran public IActionLoader ActionLoader => throw new NotSupportedException(); - public IReadOnlyList Evaluate(IPreEvaluationBlock block) + public IReadOnlyList Evaluate( + IPreEvaluationBlock block, HashDigest? baseStateRootHash) { var actionEvaluator = _router.GetEvaluator(block.Index); - return actionEvaluator.Evaluate(block); + return actionEvaluator.Evaluate(block, baseStateRootHash); } } diff --git a/Libplanet.Headless.Tests/Hosting/LibplanetNodeServiceTest.cs b/Libplanet.Headless.Tests/Hosting/LibplanetNodeServiceTest.cs index 938e5e305..7ccd5cd94 100644 --- a/Libplanet.Headless.Tests/Hosting/LibplanetNodeServiceTest.cs +++ b/Libplanet.Headless.Tests/Hosting/LibplanetNodeServiceTest.cs @@ -25,13 +25,14 @@ public void Constructor() { var policy = new BlockPolicy(); var stagePolicy = new VolatileStagePolicy(); + var stateStore = new TrieStateStore(new MemoryKeyValueStore()); var blockChainStates = new BlockChainStates( new MemoryStore(), - new TrieStateStore(new MemoryKeyValueStore())); + stateStore); var actionLoader = new SingleActionLoader(typeof(DummyAction)); var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - blockChainStates, + stateStore, actionLoader); var genesisBlock = BlockChain.ProposeGenesisBlock(actionEvaluator); var service = new LibplanetNodeService( diff --git a/Libplanet.Headless/Hosting/LibplanetNodeService.cs b/Libplanet.Headless/Hosting/LibplanetNodeService.cs index 25b10b962..2b99920d1 100644 --- a/Libplanet.Headless/Hosting/LibplanetNodeService.cs +++ b/Libplanet.Headless/Hosting/LibplanetNodeService.cs @@ -124,7 +124,7 @@ IActionEvaluator BuildActionEvaluator(IActionEvaluatorConfiguration actionEvalua new Uri(remoteActionEvaluatorConfiguration.StateServiceEndpoint), blockChainStates), DefaultActionEvaluatorConfiguration _ => new ActionEvaluator( _ => blockPolicy.BlockAction, - blockChainStates: blockChainStates, + stateStore: StateStore, actionTypeLoader: actionLoader ), ForkableActionEvaluatorConfiguration forkableActionEvaluatorConfiguration => new diff --git a/NineChronicles.Headless.Executable.Tests/Commands/AccountCommandTest.cs b/NineChronicles.Headless.Executable.Tests/Commands/AccountCommandTest.cs index 08d7a2759..a190c5cdc 100644 --- a/NineChronicles.Headless.Executable.Tests/Commands/AccountCommandTest.cs +++ b/NineChronicles.Headless.Executable.Tests/Commands/AccountCommandTest.cs @@ -49,7 +49,7 @@ public void Balance(StoreType storeType) IBlockPolicy blockPolicy = new BlockPolicySource().GetPolicy(); ActionEvaluator actionEvaluator = new ActionEvaluator( _ => blockPolicy.BlockAction, - new BlockChainStates(store, stateStore), + stateStore, new NCActionLoader()); BlockChain chain = BlockChain.Create( blockPolicy, diff --git a/NineChronicles.Headless.Executable.Tests/Commands/ChainCommandTest.cs b/NineChronicles.Headless.Executable.Tests/Commands/ChainCommandTest.cs index dc21068eb..36f7fbbd9 100644 --- a/NineChronicles.Headless.Executable.Tests/Commands/ChainCommandTest.cs +++ b/NineChronicles.Headless.Executable.Tests/Commands/ChainCommandTest.cs @@ -59,7 +59,7 @@ public void Tip(StoreType storeType) { var actionEvaluator = new ActionEvaluator( _ => new BlockPolicy().BlockAction, - new BlockChainStates(new MemoryStore(), new TrieStateStore(new MemoryKeyValueStore())), + new TrieStateStore(new MemoryKeyValueStore()), new NCActionLoader()); Block genesisBlock = BlockChain.ProposeGenesisBlock(actionEvaluator); IStore store = storeType.CreateStore(_storePath); @@ -93,7 +93,7 @@ public void Inspect(StoreType storeType) IBlockPolicy blockPolicy = new BlockPolicySource().GetTestPolicy(); ActionEvaluator actionEvaluator = new ActionEvaluator( _ => blockPolicy.BlockAction, - new BlockChainStates(store, stateStore), + stateStore, new NCActionLoader()); Block genesisBlock = BlockChain.ProposeGenesisBlock( actionEvaluator, @@ -154,7 +154,7 @@ public void Truncate(StoreType storeType) IBlockPolicy blockPolicy = new BlockPolicySource().GetTestPolicy(); ActionEvaluator actionEvaluator = new ActionEvaluator( _ => blockPolicy.BlockAction, - new BlockChainStates(store, stateStore), + stateStore, new NCActionLoader()); Block genesisBlock = BlockChain.ProposeGenesisBlock( actionEvaluator, @@ -233,7 +233,7 @@ public void PruneState(StoreType storeType) IBlockPolicy blockPolicy = new BlockPolicySource().GetPolicy(); ActionEvaluator actionEvaluator = new ActionEvaluator( _ => blockPolicy.BlockAction, - new BlockChainStates(store, stateStore), + stateStore, new NCActionLoader()); BlockChain chain = BlockChain.Create( blockPolicy, @@ -275,7 +275,7 @@ public void Snapshot(StoreType storeType) IBlockPolicy blockPolicy = new BlockPolicySource().GetPolicy(); ActionEvaluator actionEvaluator = new ActionEvaluator( _ => blockPolicy.BlockAction, - new BlockChainStates(store, stateStore), + stateStore, new NCActionLoader()); BlockChain chain = BlockChain.Create( blockPolicy, diff --git a/NineChronicles.Headless.Executable.Tests/Store/StoreExtensionsTest.cs b/NineChronicles.Headless.Executable.Tests/Store/StoreExtensionsTest.cs index 4626eab45..d5d77b991 100644 --- a/NineChronicles.Headless.Executable.Tests/Store/StoreExtensionsTest.cs +++ b/NineChronicles.Headless.Executable.Tests/Store/StoreExtensionsTest.cs @@ -30,7 +30,7 @@ public void GetGenesisBlock(StoreType storeType) IStore store = storeType.CreateStore(_storePath); IActionEvaluator actionEvaluator = new ActionEvaluator( _ => new BlockPolicy().BlockAction, - new BlockChainStates(new MemoryStore(), new TrieStateStore(new MemoryKeyValueStore())), + new TrieStateStore(new MemoryKeyValueStore()), new NCActionLoader()); Block genesisBlock = BlockChain.ProposeGenesisBlock(actionEvaluator); Guid chainId = Guid.NewGuid(); diff --git a/NineChronicles.Headless.Executable/Commands/ChainCommand.cs b/NineChronicles.Headless.Executable/Commands/ChainCommand.cs index 22ebe8425..c5333094b 100644 --- a/NineChronicles.Headless.Executable/Commands/ChainCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/ChainCommand.cs @@ -131,7 +131,7 @@ public void Inspect( var blockChainStates = new BlockChainStates(store, stateStore); var actionEvaluator = new ActionEvaluator( _ => blockPolicy.BlockAction, - blockChainStates, + stateStore, new NCActionLoader()); BlockChain chain = new BlockChain( blockPolicy, diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs index 4af166134..9467301f8 100644 --- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs @@ -300,7 +300,7 @@ public int Blocks( _console.Out.WriteLine(msg); outputSw?.WriteLine(msg); - var actionEvaluator = GetActionEvaluator(blockChain); + var actionEvaluator = GetActionEvaluator(stateStore); var actionEvaluations = blockChain.DetermineBlockStateRootHash(block, out IReadOnlyList failedActionEvaluations); LoggingActionEvaluations(failedActionEvaluations, outputSw); @@ -483,7 +483,7 @@ private static (FileStream? fs, StreamWriter? sw) GetOutputFileStream( var blockChainStates = new BlockChainStates(store, stateStore); var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - blockChainStates, + stateStore, new NCActionLoader()); return ( store, @@ -524,13 +524,13 @@ private Transaction LoadTx(string txPath) return TxMarshaler.UnmarshalTransaction(txDict); } - private ActionEvaluator GetActionEvaluator(BlockChain blockChain) + private ActionEvaluator GetActionEvaluator(IStateStore stateStore) { var policy = new BlockPolicySource().GetPolicy(); IActionLoader actionLoader = new NCActionLoader(); return new ActionEvaluator( _ => policy.BlockAction, - blockChainStates: blockChain, + stateStore: stateStore, actionTypeLoader: actionLoader); } diff --git a/NineChronicles.Headless.Executable/Commands/StateCommand.cs b/NineChronicles.Headless.Executable/Commands/StateCommand.cs index 664c4c8f8..b88506bb6 100644 --- a/NineChronicles.Headless.Executable/Commands/StateCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/StateCommand.cs @@ -104,12 +104,13 @@ IStateStore stateStore (int)(top.Index - bottom.Index + 1L) ); + var sStore = new TrieStateStore(new Libplanet.Store.Trie.MemoryKeyValueStore()); var blockChainStates = new BlockChainStates( new MemoryStore(), - new TrieStateStore(new Libplanet.Store.Trie.MemoryKeyValueStore())); + sStore); var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - blockChainStates, + sStore, new NCActionLoader()); foreach (BlockHash blockHash in blockHashes) diff --git a/NineChronicles.Headless.Tests/GraphQLTestUtils.cs b/NineChronicles.Headless.Tests/GraphQLTestUtils.cs index ce2ad8c6b..67e8eb60a 100644 --- a/NineChronicles.Headless.Tests/GraphQLTestUtils.cs +++ b/NineChronicles.Headless.Tests/GraphQLTestUtils.cs @@ -82,7 +82,7 @@ public static StandaloneContext CreateStandaloneContext() var policy = new BlockPolicy(); var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - new BlockChainStates(store, stateStore), + stateStore, new NCActionLoader()); var genesisBlock = BlockChain.ProposeGenesisBlock(actionEvaluator); var blockchain = BlockChain.Create( @@ -113,7 +113,7 @@ PrivateKey minerPrivateKey var policy = new BlockPolicy(); var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - new BlockChainStates(store, stateStore), + stateStore, new NCActionLoader()); var genesisBlock = BlockChain.ProposeGenesisBlock( actionEvaluator, diff --git a/NineChronicles.Headless.Tests/GraphTypes/GraphQLTestBase.cs b/NineChronicles.Headless.Tests/GraphTypes/GraphQLTestBase.cs index 02eb0fc12..4643f7e9c 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/GraphQLTestBase.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/GraphQLTestBase.cs @@ -58,7 +58,7 @@ public GraphQLTestBase(ITestOutputHelper output) var blockAction = new RewardGold(); var actionEvaluator = new ActionEvaluator( _ => blockAction, - new BlockChainStates(new MemoryStore(), new TrieStateStore(new MemoryKeyValueStore())), + new TrieStateStore(new MemoryKeyValueStore()), new NCActionLoader()); var genesisBlock = BlockChain.ProposeGenesisBlock( actionEvaluator, diff --git a/NineChronicles.Headless.Tests/GraphTypes/StandaloneMutationTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StandaloneMutationTest.cs index 3f9d1325d..14e1e7f49 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StandaloneMutationTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StandaloneMutationTest.cs @@ -1007,7 +1007,7 @@ private Block MakeGenesisBlock( { var actionEvaluator = new ActionEvaluator( _ => ServiceBuilder.BlockPolicy.BlockAction, - new BlockChainStates(new MemoryStore(), new TrieStateStore(new MemoryKeyValueStore())), + new TrieStateStore(new MemoryKeyValueStore()), new NCActionLoader()); return BlockChain.ProposeGenesisBlock( actionEvaluator, diff --git a/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs index 62dad6f21..a7847392f 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StandaloneQueryTest.cs @@ -118,7 +118,7 @@ public async Task NodeStatus() var apv = AppProtocolVersion.Sign(apvPrivateKey, 0); var actionEvaluator = new ActionEvaluator( _ => null, - new BlockChainStates(new MemoryStore(), new TrieStateStore(new MemoryKeyValueStore())), + new TrieStateStore(new MemoryKeyValueStore()), new NCActionLoader()); var genesisBlock = BlockChain.ProposeGenesisBlock(actionEvaluator); @@ -446,7 +446,7 @@ public async Task ActivationStatus(bool existsActivatedAccounts) }.ToList()); var actionEvaluator = new ActionEvaluator( _ => null, - new BlockChainStates(new MemoryStore(), new TrieStateStore(new MemoryKeyValueStore())), + new TrieStateStore(new MemoryKeyValueStore()), new NCActionLoader()); Block genesis = BlockChain.ProposeGenesisBlock( @@ -840,7 +840,7 @@ public async Task ActivationKeyNonce(bool trim) }; var actionEvaluator = new ActionEvaluator( _ => null, - new BlockChainStates(new MemoryStore(), new TrieStateStore(new MemoryKeyValueStore())), + new TrieStateStore(new MemoryKeyValueStore()), new NCActionLoader()); Block genesis = BlockChain.ProposeGenesisBlock( @@ -925,7 +925,7 @@ public async Task ActivationKeyNonce_Throw_ExecutionError(string code, string ms var actionEvaluator = new ActionEvaluator( _ => null, - new BlockChainStates(new MemoryStore(), new TrieStateStore(new MemoryKeyValueStore())), + new TrieStateStore(new MemoryKeyValueStore()), new NCActionLoader()); Block genesis = BlockChain.ProposeGenesisBlock( @@ -1006,7 +1006,7 @@ public async Task Balance() var actionEvaluator = new ActionEvaluator( _ => null, - new BlockChainStates(new MemoryStore(), new TrieStateStore(new MemoryKeyValueStore())), + new TrieStateStore(new MemoryKeyValueStore()), new NCActionLoader()); Block genesis = BlockChain.ProposeGenesisBlock( @@ -1111,7 +1111,7 @@ private NineChroniclesNodeService MakeNineChroniclesNodeService(PrivateKey priva }.ToList()); var actionEvaluator = new ActionEvaluator( _ => blockPolicy.BlockAction, - new BlockChainStates(new MemoryStore(), new TrieStateStore(new MemoryKeyValueStore())), + new TrieStateStore(new MemoryKeyValueStore()), new NCActionLoader()); Block genesis = BlockChain.ProposeGenesisBlock( diff --git a/NineChronicles.Headless.Tests/GraphTypes/StandaloneSubscriptionTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StandaloneSubscriptionTest.cs index 27b8cec53..419ec81f7 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StandaloneSubscriptionTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StandaloneSubscriptionTest.cs @@ -136,7 +136,7 @@ public async Task SubscribePreloadProgress() var apv = AppProtocolVersion.Sign(apvPrivateKey, 0); var actionEvaluator = new ActionEvaluator( _ => null, - new BlockChainStates(new MemoryStore(), new TrieStateStore(new MemoryKeyValueStore())), + new TrieStateStore(new MemoryKeyValueStore()), new SingleActionLoader(typeof(EmptyAction))); var genesisBlock = BlockChain.ProposeGenesisBlock( actionEvaluator, diff --git a/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs b/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs index 5716a9479..a3bd08316 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/TransactionHeadlessQueryTest.cs @@ -46,7 +46,7 @@ public TransactionHeadlessQueryTest() IBlockPolicy policy = NineChroniclesNodeService.GetTestBlockPolicy(); var actionEvaluator = new ActionEvaluator( _ => policy.BlockAction, - new BlockChainStates(_store, _stateStore), + _stateStore, new NCActionLoader()); Block genesisBlock = BlockChain.ProposeGenesisBlock( actionEvaluator, From e8227e4d2d26db28ce670fd864cf3676e63e6fc9 Mon Sep 17 00:00:00 2001 From: Atralupus Date: Fri, 6 Oct 2023 03:08:19 +0900 Subject: [PATCH 20/54] Add redis, sqlite access control service Apply AccessControlService --- Lib9c | 2 +- .../Configuration.cs | 2 + NineChronicles.Headless.Executable/Program.cs | 8 +++- .../NineChronicles.Headless.csproj | 2 + .../NineChroniclesNodeService.cs | 21 ++++++++-- .../Properties/AccessControlServiceOptions.cs | 20 +++++++++ .../NineChroniclesNodeServiceProperties.cs | 6 ++- .../Services/AccessControlServiceFactory.cs | 34 +++++++++++++++ .../Services/RedisAccessControlService.cs | 22 ++++++++++ .../Services/SQLiteAccessControlService.cs | 41 +++++++++++++++++++ NineChronicles.RPC.Shared | 2 +- 11 files changed, 152 insertions(+), 8 deletions(-) create mode 100644 NineChronicles.Headless/Properties/AccessControlServiceOptions.cs create mode 100644 NineChronicles.Headless/Services/AccessControlServiceFactory.cs create mode 100644 NineChronicles.Headless/Services/RedisAccessControlService.cs create mode 100644 NineChronicles.Headless/Services/SQLiteAccessControlService.cs diff --git a/Lib9c b/Lib9c index 0bf659679..a1d99f34e 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 0bf659679f55122846691ed4fec4cfcb76d81cda +Subproject commit a1d99f34e0739bdac08e2f9bdec5b6e274971f3b diff --git a/NineChronicles.Headless.Executable/Configuration.cs b/NineChronicles.Headless.Executable/Configuration.cs index edba40765..28b2d40cc 100644 --- a/NineChronicles.Headless.Executable/Configuration.cs +++ b/NineChronicles.Headless.Executable/Configuration.cs @@ -89,6 +89,8 @@ public class Configuration public StateServiceManagerServiceOptions? StateServiceManagerService { get; set; } + public AccessControlServiceOptions? AccessControlService { get; set; } + public void Overwrite( string? appProtocolVersionString, string[]? trustedAppProtocolVersionSignerStrings, diff --git a/NineChronicles.Headless.Executable/Program.cs b/NineChronicles.Headless.Executable/Program.cs index 7db80836c..1cb67a931 100644 --- a/NineChronicles.Headless.Executable/Program.cs +++ b/NineChronicles.Headless.Executable/Program.cs @@ -205,7 +205,11 @@ public async Task Run( Description = "Absolute path of \"appsettings.json\" file to provide headless configurations.")] string? configPath = "appsettings.json", [Option(Description = "Sentry DSN")] - string? sentryDsn = "", + string? sentryDsn = "", + [Option(Description = "AccessControlService Type")] + string? acsType = null, + [Option(Description = "AccessControlService ConnectionString")] + string? acsConnectionString = null, [Option(Description = "Trace sample rate for sentry")] double? sentryTraceSampleRate = null, [Ignore] CancellationToken? cancellationToken = null @@ -436,7 +440,7 @@ IActionLoader MakeSingleActionLoader() : new PrivateKey(ByteUtil.ParseHex(headlessConfig.MinerPrivateKeyString)); TimeSpan minerBlockInterval = TimeSpan.FromMilliseconds(headlessConfig.MinerBlockIntervalMilliseconds); var nineChroniclesProperties = - new NineChroniclesNodeServiceProperties(actionLoader, headlessConfig.StateServiceManagerService) + new NineChroniclesNodeServiceProperties(actionLoader, headlessConfig.StateServiceManagerService, headlessConfig.AccessControlService) { MinerPrivateKey = minerPrivateKey, Libplanet = properties, diff --git a/NineChronicles.Headless/NineChronicles.Headless.csproj b/NineChronicles.Headless/NineChronicles.Headless.csproj index 27814de54..6bd8f6ce0 100644 --- a/NineChronicles.Headless/NineChronicles.Headless.csproj +++ b/NineChronicles.Headless/NineChronicles.Headless.csproj @@ -39,7 +39,9 @@ + + diff --git a/NineChronicles.Headless/NineChroniclesNodeService.cs b/NineChronicles.Headless/NineChroniclesNodeService.cs index b265edf87..181f14e6e 100644 --- a/NineChronicles.Headless/NineChroniclesNodeService.cs +++ b/NineChronicles.Headless/NineChroniclesNodeService.cs @@ -20,6 +20,7 @@ using Nekoyume.Blockchain.Policy; using NineChronicles.Headless.Properties; using NineChronicles.Headless.Utils; +using NineChronicles.Headless.Services; using NineChronicles.RPC.Shared.Exceptions; using Nito.AsyncEx; using Serilog; @@ -78,14 +79,27 @@ public NineChroniclesNodeService( bool ignorePreloadFailure = false, bool strictRendering = false, TimeSpan txLifeTime = default, - int txQuotaPerSigner = 10 + int txQuotaPerSigner = 10, + AccessControlServiceOptions? acsOptions = null ) { MinerPrivateKey = minerPrivateKey; Properties = properties; LogEventLevel logLevel = LogEventLevel.Debug; - IStagePolicy stagePolicy = new NCStagePolicy(txLifeTime, txQuotaPerSigner); + + IAccessControlService? accessControlService = null; + + if (acsOptions != null) + { + accessControlService = AccessControlServiceFactory.Create( + acsOptions.GetStorageType(), + acsOptions.AccessControlServiceConnectionString + ); + } + + IStagePolicy stagePolicy = new NCStagePolicy( + txLifeTime, txQuotaPerSigner, accessControlService); BlockRenderer = new BlockRenderer(); ActionRenderer = new ActionRenderer(); @@ -201,7 +215,8 @@ StandaloneContext context ignorePreloadFailure: properties.IgnorePreloadFailure, strictRendering: properties.StrictRender, txLifeTime: properties.TxLifeTime, - txQuotaPerSigner: properties.TxQuotaPerSigner + txQuotaPerSigner: properties.TxQuotaPerSigner, + acsOptions: properties.AccessControlServiceOptions ); service.ConfigureContext(context); var meter = new Meter("NineChronicles"); diff --git a/NineChronicles.Headless/Properties/AccessControlServiceOptions.cs b/NineChronicles.Headless/Properties/AccessControlServiceOptions.cs new file mode 100644 index 000000000..41fcddd27 --- /dev/null +++ b/NineChronicles.Headless/Properties/AccessControlServiceOptions.cs @@ -0,0 +1,20 @@ +using System; +using System.ComponentModel.DataAnnotations; +using NineChronicles.Headless.Services; + +namespace NineChronicles.Headless.Properties +{ + public class AccessControlServiceOptions + { + [Required] + public string AccessControlServiceType { get; set; } = null!; + + [Required] + public string AccessControlServiceConnectionString { get; set; } = null!; + + public AccessControlServiceFactory.StorageType GetStorageType() + { + return Enum.Parse(AccessControlServiceType, true); + } + } +} diff --git a/NineChronicles.Headless/Properties/NineChroniclesNodeServiceProperties.cs b/NineChronicles.Headless/Properties/NineChroniclesNodeServiceProperties.cs index 38a47a8a4..b69914881 100644 --- a/NineChronicles.Headless/Properties/NineChroniclesNodeServiceProperties.cs +++ b/NineChronicles.Headless/Properties/NineChroniclesNodeServiceProperties.cs @@ -12,10 +12,12 @@ namespace NineChronicles.Headless.Properties { public class NineChroniclesNodeServiceProperties { - public NineChroniclesNodeServiceProperties(IActionLoader actionLoader, StateServiceManagerServiceOptions? stateServiceManagerServiceOptions) + public NineChroniclesNodeServiceProperties( + IActionLoader actionLoader, StateServiceManagerServiceOptions? stateServiceManagerServiceOptions, AccessControlServiceOptions? accessControlServiceOptions) { ActionLoader = actionLoader; StateServiceManagerService = stateServiceManagerServiceOptions; + AccessControlServiceOptions = accessControlServiceOptions; } /// @@ -54,6 +56,8 @@ public NineChroniclesNodeServiceProperties(IActionLoader actionLoader, StateServ public StateServiceManagerServiceOptions? StateServiceManagerService { get; } + public AccessControlServiceOptions? AccessControlServiceOptions { get; } + public static LibplanetNodeServiceProperties GenerateLibplanetNodeServiceProperties( string? appProtocolVersionToken = null, diff --git a/NineChronicles.Headless/Services/AccessControlServiceFactory.cs b/NineChronicles.Headless/Services/AccessControlServiceFactory.cs new file mode 100644 index 000000000..0ff8e476a --- /dev/null +++ b/NineChronicles.Headless/Services/AccessControlServiceFactory.cs @@ -0,0 +1,34 @@ +using System; +using Nekoyume.Blockchain; + +namespace NineChronicles.Headless.Services +{ + public static class AccessControlServiceFactory + { + public enum StorageType + { + /// + /// Use Redis + /// + Redis, + + /// + /// Use SQLite + /// + SQLite + } + + public static IAccessControlService Create( + StorageType storageType, + string connectionString + ) + { + return storageType switch + { + StorageType.Redis => new RedisAccessControlService(connectionString), + StorageType.SQLite => new SQLiteAccessControlService(connectionString), + _ => throw new ArgumentOutOfRangeException(nameof(storageType), storageType, null) + }; + } + } +} diff --git a/NineChronicles.Headless/Services/RedisAccessControlService.cs b/NineChronicles.Headless/Services/RedisAccessControlService.cs new file mode 100644 index 000000000..7729cee34 --- /dev/null +++ b/NineChronicles.Headless/Services/RedisAccessControlService.cs @@ -0,0 +1,22 @@ +using StackExchange.Redis; +using Libplanet.Crypto; +using Nekoyume.Blockchain; + +namespace NineChronicles.Headless.Services +{ + public class RedisAccessControlService : IAccessControlService + { + private IDatabase _db; + + public RedisAccessControlService(string storageUri) + { + var redis = ConnectionMultiplexer.Connect(storageUri); + _db = redis.GetDatabase(); + } + + public bool IsAccessDenied(Address address) + { + return _db.KeyExists(address.ToString()); + } + } +} diff --git a/NineChronicles.Headless/Services/SQLiteAccessControlService.cs b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs new file mode 100644 index 000000000..62df4c795 --- /dev/null +++ b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs @@ -0,0 +1,41 @@ +using Microsoft.Data.Sqlite; +using Libplanet.Crypto; +using Nekoyume.Blockchain; + +namespace NineChronicles.Headless.Services +{ + public class SQLiteAccessControlService : IAccessControlService + { + private const string CreateTableSql = + "CREATE TABLE IF NOT EXISTS blocklist (address VARCHAR(42))"; + private const string CheckAccessSql = + "SELECT EXISTS(SELECT 1 FROM blocklist WHERE address=@Address)"; + + private readonly string _connectionString; + + public SQLiteAccessControlService(string connectionString) + { + _connectionString = connectionString; + using var connection = new SqliteConnection(_connectionString); + connection.Open(); + + using var command = connection.CreateCommand(); + command.CommandText = CreateTableSql; + command.ExecuteNonQuery(); + } + + public bool IsAccessDenied(Address address) + { + using var connection = new SqliteConnection(_connectionString); + connection.Open(); + + using var command = connection.CreateCommand(); + command.CommandText = CheckAccessSql; + command.Parameters.AddWithValue("@Address", address.ToString()); + + var result = command.ExecuteScalar(); + + return result is not null && (long)result == 1; + } + } +} diff --git a/NineChronicles.RPC.Shared b/NineChronicles.RPC.Shared index 2dcbbb19a..cff68421f 160000 --- a/NineChronicles.RPC.Shared +++ b/NineChronicles.RPC.Shared @@ -1 +1 @@ -Subproject commit 2dcbbb19a0c90f3f41de03506802bbd527c0aaba +Subproject commit cff68421fabb098f3d0bfd20fdef55755db309e4 From b62f4dd427e1ff3404bd3d3c0614f3146e3a7899 Mon Sep 17 00:00:00 2001 From: Atralupus Date: Fri, 6 Oct 2023 17:06:30 +0900 Subject: [PATCH 21/54] feat: Introduce AccessControlCenter project Port settings fix lint Add Dockerfiles Remove arguments --- Dockerfile.ACC | 37 ++++++++ Dockerfile.ACC.amd64 | 37 ++++++++ Dockerfile.ACC.arm64v8 | 37 ++++++++ .../IMutableAccessControlService.cs | 13 +++ .../MutableAccessControlServiceFactory.cs | 33 +++++++ .../MutableRedisAccessControlService.cs | 36 +++++++ .../MutableSqliteAccessControlService.cs | 61 ++++++++++++ .../AcsService.cs | 95 +++++++++++++++++++ .../Configuration.cs | 11 +++ .../AccessControlServiceController.cs | 48 ++++++++++ ...nicles.Headless.AccessControlCenter.csproj | 32 +++++++ .../Program.cs | 32 +++++++ .../appsettings.json | 5 + NineChronicles.Headless.Executable.sln | 20 ++++ NineChronicles.Headless.Executable/Program.cs | 6 +- .../GraphQLStartupTest.cs | 2 +- .../GraphTypes/StandaloneSubscriptionTest.cs | 1 - .../Properties/AccessControlServiceOptions.cs | 2 +- .../Services/RedisAccessControlService.cs | 2 +- .../Services/SQLiteAccessControlService.cs | 2 +- 20 files changed, 502 insertions(+), 10 deletions(-) create mode 100644 Dockerfile.ACC create mode 100644 Dockerfile.ACC.amd64 create mode 100644 Dockerfile.ACC.arm64v8 create mode 100644 NineChronicles.Headless.AccessControlCenter/AccessControlService/IMutableAccessControlService.cs create mode 100644 NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableAccessControlServiceFactory.cs create mode 100644 NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs create mode 100644 NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs create mode 100644 NineChronicles.Headless.AccessControlCenter/AcsService.cs create mode 100644 NineChronicles.Headless.AccessControlCenter/Configuration.cs create mode 100644 NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs create mode 100644 NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj create mode 100644 NineChronicles.Headless.AccessControlCenter/Program.cs create mode 100644 NineChronicles.Headless.AccessControlCenter/appsettings.json diff --git a/Dockerfile.ACC b/Dockerfile.ACC new file mode 100644 index 000000000..40861ddc3 --- /dev/null +++ b/Dockerfile.ACC @@ -0,0 +1,37 @@ +# Use the SDK image to build the app +FROM mcr.microsoft.com/dotnet/sdk:6.0-jammy AS build-env +WORKDIR /app +ARG COMMIT + +# Copy csproj and restore as distinct layers +COPY ./Lib9c/Lib9c/Lib9c.csproj ./Lib9c/ +COPY ./NineChronicles.Headless/NineChronicles.Headless.csproj ./NineChronicles.Headless/ +COPY ./NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj ./NineChronicles.Headless.AccessControlCenter/ +RUN dotnet restore Lib9c +RUN dotnet restore NineChronicles.Headless +RUN dotnet restore NineChronicles.Headless.AccessControlCenter + +# Copy everything else and build +COPY . ./ +RUN dotnet publish NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj \ + -c Release \ + -r linux-x64 \ + -o out \ + --self-contained \ + --version-suffix $COMMIT + +# Build runtime image +FROM mcr.microsoft.com/dotnet/aspnet:6.0 +WORKDIR /app +RUN apt-get update && apt-get install -y libc6-dev +COPY --from=build-env /app/out . + +# Install native deps & utilities for production +RUN apt-get update \ + && apt-get install -y --allow-unauthenticated \ + libc6-dev jq curl \ + && rm -rf /var/lib/apt/lists/* + +VOLUME /data + +ENTRYPOINT ["dotnet", "NineChronicles.Headless.AccessControlCenter.dll"] diff --git a/Dockerfile.ACC.amd64 b/Dockerfile.ACC.amd64 new file mode 100644 index 000000000..eeb7814d7 --- /dev/null +++ b/Dockerfile.ACC.amd64 @@ -0,0 +1,37 @@ +# Use the SDK image to build the app +FROM mcr.microsoft.com/dotnet/sdk:6.0-jammy AS build-env +WORKDIR /app +ARG COMMIT + +# Copy csproj and restore as distinct layers +COPY ./Lib9c/Lib9c/Lib9c.csproj ./Lib9c/ +COPY ./NineChronicles.Headless/NineChronicles.Headless.csproj ./NineChronicles.Headless/ +COPY ./NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj ./NineChronicles.Headless.AccessControlCenter/ +RUN dotnet restore Lib9c +RUN dotnet restore NineChronicles.Headless +RUN dotnet restore NineChronicles.Headless.AccessControlCenter + +# Copy everything else and build +COPY . ./ +RUN dotnet publish NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj \ + -c Release \ + -r linux-x64 \ + -o out \ + --self-contained \ + --version-suffix $COMMIT + +# Build runtime image +FROM --platform=linux/amd64 mcr.microsoft.com/dotnet/aspnet:6.0-bullseye-slim +WORKDIR /app +RUN apt-get update && apt-get install -y libc6-dev +COPY --from=build-env /app/out . + +# Install native deps & utilities for production +RUN apt-get update \ + && apt-get install -y --allow-unauthenticated \ + libc6-dev jq curl \ + && rm -rf /var/lib/apt/lists/* + +VOLUME /data + +ENTRYPOINT ["dotnet", "NineChronicles.Headless.AccessControlCenter.dll"] diff --git a/Dockerfile.ACC.arm64v8 b/Dockerfile.ACC.arm64v8 new file mode 100644 index 000000000..510a04d02 --- /dev/null +++ b/Dockerfile.ACC.arm64v8 @@ -0,0 +1,37 @@ +# Use the SDK image to build the app +FROM mcr.microsoft.com/dotnet/sdk:6.0-jammy AS build-env +WORKDIR /app +ARG COMMIT + +# Copy csproj and restore as distinct layers +COPY ./Lib9c/Lib9c/Lib9c.csproj ./Lib9c/ +COPY ./NineChronicles.Headless/NineChronicles.Headless.csproj ./NineChronicles.Headless/ +COPY ./NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj ./NineChronicles.Headless.AccessControlCenter/ +RUN dotnet restore Lib9c +RUN dotnet restore NineChronicles.Headless +RUN dotnet restore NineChronicles.Headless.AccessControlCenter + +# Copy everything else and build +COPY . ./ +RUN dotnet publish NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj \ + -c Release \ + -r linux-arm64 \ + -o out \ + --self-contained \ + --version-suffix $COMMIT + +# Build runtime image +FROM --platform=linux/arm64 mcr.microsoft.com/dotnet/aspnet:6.0-bullseye-slim-arm64v8 +WORKDIR /app +RUN apt-get update && apt-get install -y libc6-dev +COPY --from=build-env /app/out . + +# Install native deps & utilities for production +RUN apt-get update \ + && apt-get install -y --allow-unauthenticated \ + libc6-dev jq curl \ + && rm -rf /var/lib/apt/lists/* + +VOLUME /data + +ENTRYPOINT ["dotnet", "NineChronicles.Headless.AccessControlCenter.dll"] diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/IMutableAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/IMutableAccessControlService.cs new file mode 100644 index 000000000..aa8207174 --- /dev/null +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/IMutableAccessControlService.cs @@ -0,0 +1,13 @@ +using Libplanet.Crypto; +using System.Collections.Generic; +using Nekoyume.Blockchain; + +namespace NineChronicles.Headless.AccessControlCenter.AccessControlService +{ + public interface IMutableAccessControlService : IAccessControlService + { + void DenyAccess(Address address); + void AllowAccess(Address address); + List
ListBlockedAddresses(int offset, int limit); + } +} diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableAccessControlServiceFactory.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableAccessControlServiceFactory.cs new file mode 100644 index 000000000..f9f98ac68 --- /dev/null +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableAccessControlServiceFactory.cs @@ -0,0 +1,33 @@ +using System; + +namespace NineChronicles.Headless.AccessControlCenter.AccessControlService +{ + public static class MutableAccessControlServiceFactory + { + public enum StorageType + { + /// + /// Use Redis + /// + Redis, + + /// + /// Use SQLite + /// + SQLite + } + + public static IMutableAccessControlService Create( + StorageType storageType, + string connectionString + ) + { + return storageType switch + { + StorageType.Redis => new MutableRedisAccessControlService(connectionString), + StorageType.SQLite => new MutableSqliteAccessControlService(connectionString), + _ => throw new ArgumentOutOfRangeException(nameof(storageType), storageType, null) + }; + } + } +} diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs new file mode 100644 index 000000000..eec152cd3 --- /dev/null +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Linq; +using Libplanet.Crypto; +using NineChronicles.Headless.Services; + +namespace NineChronicles.Headless.AccessControlCenter.AccessControlService +{ + public class MutableRedisAccessControlService : RedisAccessControlService, IMutableAccessControlService + { + + public MutableRedisAccessControlService(string storageUri) : base(storageUri) + { + } + + public void DenyAccess(Address address) + { + _db.StringSet(address.ToString(), "denied"); + } + + public void AllowAccess(Address address) + { + _db.KeyDelete(address.ToString()); + } + + public List
ListBlockedAddresses(int offset, int limit) + { + var server = _db.Multiplexer.GetServer(_db.Multiplexer.GetEndPoints().First()); + var keys = server + .Keys() + .Select(k => new Address(k.ToString())) + .ToList(); + + return keys.Skip(offset).Take(limit).ToList(); + } + } +} diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs new file mode 100644 index 000000000..1d9455118 --- /dev/null +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs @@ -0,0 +1,61 @@ +using System.Collections.Generic; +using Microsoft.Data.Sqlite; +using Libplanet.Crypto; +using NineChronicles.Headless.Services; + +namespace NineChronicles.Headless.AccessControlCenter.AccessControlService +{ + public class MutableSqliteAccessControlService : SQLiteAccessControlService, IMutableAccessControlService + { + private const string DenyAccessSql = + "INSERT OR IGNORE INTO blocklist (address) VALUES (@Address)"; + private const string AllowAccessSql = "DELETE FROM blocklist WHERE address=@Address"; + + public MutableSqliteAccessControlService(string connectionString) : base(connectionString) + { + } + + public void DenyAccess(Address address) + { + using var connection = new SqliteConnection(_connectionString); + connection.Open(); + + using var command = connection.CreateCommand(); + command.CommandText = DenyAccessSql; + command.Parameters.AddWithValue("@Address", address.ToString()); + command.ExecuteNonQuery(); + } + + public void AllowAccess(Address address) + { + using var connection = new SqliteConnection(_connectionString); + connection.Open(); + + using var command = connection.CreateCommand(); + command.CommandText = AllowAccessSql; + command.Parameters.AddWithValue("@Address", address.ToString()); + command.ExecuteNonQuery(); + } + + public List
ListBlockedAddresses(int offset, int limit) + { + var blockedAddresses = new List
(); + + using var connection = new SqliteConnection(_connectionString); + connection.Open(); + + using var command = connection.CreateCommand(); + command.CommandText = $"SELECT address FROM blocklist LIMIT @Limit OFFSET @Offset"; + command.Parameters.AddWithValue("@Limit", limit); + command.Parameters.AddWithValue("@Offset", offset); + + using var reader = command.ExecuteReader(); + while (reader.Read()) + { + blockedAddresses.Add(new Address(reader.GetString(0))); + } + + return blockedAddresses; + } + } +} diff --git a/NineChronicles.Headless.AccessControlCenter/AcsService.cs b/NineChronicles.Headless.AccessControlCenter/AcsService.cs new file mode 100644 index 000000000..04cea4ae2 --- /dev/null +++ b/NineChronicles.Headless.AccessControlCenter/AcsService.cs @@ -0,0 +1,95 @@ +using System; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using NineChronicles.Headless.AccessControlCenter.AccessControlService; + +namespace NineChronicles.Headless.AccessControlCenter +{ + public class AcsService + { + private readonly string _acsType; + private readonly string _acsConnectionString; + + public AcsService(string acsType, string acsConnectionString) + { + _acsType = acsType; + _acsConnectionString = acsConnectionString; + } + + public IHostBuilder Configure(IHostBuilder hostBuilder, int port) + { + return hostBuilder.ConfigureWebHostDefaults(builder => + { + builder.UseStartup( + x => + new RestApiStartup( + x.Configuration, + _acsType, + _acsConnectionString + ) + ); + builder.ConfigureKestrel(options => + { + options.ListenAnyIP( + port, + listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http1AndHttp2; + } + ); + }); + }); + } + + internal class RestApiStartup + { + private readonly string _acsType; + private readonly string _acsConnectionString; + + public RestApiStartup( + IConfiguration configuration, + string acsType, + string acsConnectionString + ) + { + Configuration = configuration; + _acsType = acsType; + _acsConnectionString = acsConnectionString; + } + + public IConfiguration Configuration { get; } + + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + + var accessControlService = MutableAccessControlServiceFactory.Create( + Enum.Parse(_acsType, true), + _acsConnectionString + ); + + services.AddSingleton(accessControlService); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } + } +} diff --git a/NineChronicles.Headless.AccessControlCenter/Configuration.cs b/NineChronicles.Headless.AccessControlCenter/Configuration.cs new file mode 100644 index 000000000..22ca923df --- /dev/null +++ b/NineChronicles.Headless.AccessControlCenter/Configuration.cs @@ -0,0 +1,11 @@ +namespace NineChronicles.Headless.AccessControlCenter +{ + public class Configuration + { + public int Port { get; set; } + + public string AccessControlServiceType { get; set; } = null!; + + public string AccessControlServiceConnectionString { get; set; } = null!; + } +} diff --git a/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs new file mode 100644 index 000000000..685dca35e --- /dev/null +++ b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using NineChronicles.Headless.AccessControlCenter.AccessControlService; +using System.Linq; +using Libplanet.Crypto; + +namespace NineChronicles.Headless.AccessControlCenter.Controllers +{ + [ApiController] + public class AccessControlServiceController : ControllerBase + { + private readonly IMutableAccessControlService _accessControlService; + + public AccessControlServiceController(IMutableAccessControlService accessControlService) + { + _accessControlService = accessControlService; + } + + [HttpGet("entries/{address}")] + public ActionResult IsAccessDenied(string address) + { + return _accessControlService.IsAccessDenied(new Address(address)); + } + + [HttpPost("entries/{address}/deny")] + public ActionResult DenyAccess(string address) + { + _accessControlService.DenyAccess(new Address(address)); + return Ok(); + } + + [HttpPost("entries/{address}/allow")] + public ActionResult AllowAccess(string address) + { + _accessControlService.AllowAccess(new Address(address)); + return Ok(); + } + + [HttpGet("entries")] + public ActionResult> ListBlockedAddresses(int offset, int limit) + { + return _accessControlService + .ListBlockedAddresses(offset, limit) + .Select(a => a.ToString()) + .ToList(); + } + } +} diff --git a/NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj b/NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj new file mode 100644 index 000000000..74be55c3b --- /dev/null +++ b/NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj @@ -0,0 +1,32 @@ + + + net6 + true + ..\NineChronicles.Headless.Common.ruleset + enable + Debug;Release;DevEx + AnyCPU + true + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/NineChronicles.Headless.AccessControlCenter/Program.cs b/NineChronicles.Headless.AccessControlCenter/Program.cs new file mode 100644 index 000000000..a48cd2312 --- /dev/null +++ b/NineChronicles.Headless.AccessControlCenter/Program.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace NineChronicles.Headless.AccessControlCenter +{ + public static class Program + { + public static void Main(string[] args) + { + // Get configuration + string configPath = + Environment.GetEnvironmentVariable("ACS_CONFIG_FILE") ?? "appsettings.json"; + + var configurationBuilder = new ConfigurationBuilder() + .AddJsonFile(configPath) + .AddEnvironmentVariables("ACS_"); + IConfiguration config = configurationBuilder.Build(); + + var acsConfig = new Configuration(); + config.Bind(acsConfig); + + var service = new AcsService( + acsConfig.AccessControlServiceType, + acsConfig.AccessControlServiceConnectionString + ); + var hostBuilder = service.Configure(Host.CreateDefaultBuilder(), acsConfig.Port); + var host = hostBuilder.Build(); + host.Run(); + } + } +} diff --git a/NineChronicles.Headless.AccessControlCenter/appsettings.json b/NineChronicles.Headless.AccessControlCenter/appsettings.json new file mode 100644 index 000000000..fbf1f5da2 --- /dev/null +++ b/NineChronicles.Headless.AccessControlCenter/appsettings.json @@ -0,0 +1,5 @@ +{ + "Port": "31259", + "AccessControlServiceType": "redis", + "AccessControlServiceConnectionString": "localhost:6379" +} diff --git a/NineChronicles.Headless.Executable.sln b/NineChronicles.Headless.Executable.sln index 03b81380f..b14931789 100644 --- a/NineChronicles.Headless.Executable.sln +++ b/NineChronicles.Headless.Executable.sln @@ -78,6 +78,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Extensions.Remote EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Extensions.RemoteBlockChainStates", "Lib9c\.Libplanet.Extensions.RemoteBlockChainStates\Libplanet.Extensions.RemoteBlockChainStates.csproj", "{8F9E5505-C157-4DF3-A419-FF0108731397}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NineChronicles.Headless.AccessControlCenter", "NineChronicles.Headless.AccessControlCenter\NineChronicles.Headless.AccessControlCenter.csproj", "{162C0F4B-A1D9-4132-BC34-31F1247BC26B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -703,6 +705,24 @@ Global {8F9E5505-C157-4DF3-A419-FF0108731397}.Release|x64.Build.0 = Release|Any CPU {8F9E5505-C157-4DF3-A419-FF0108731397}.Release|x86.ActiveCfg = Release|Any CPU {8F9E5505-C157-4DF3-A419-FF0108731397}.Release|x86.Build.0 = Release|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Debug|x64.ActiveCfg = Debug|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Debug|x64.Build.0 = Debug|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Debug|x86.ActiveCfg = Debug|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Debug|x86.Build.0 = Debug|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.DevEx|Any CPU.ActiveCfg = Debug|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.DevEx|Any CPU.Build.0 = Debug|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.DevEx|x64.ActiveCfg = Debug|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.DevEx|x64.Build.0 = Debug|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.DevEx|x86.ActiveCfg = Debug|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.DevEx|x86.Build.0 = Debug|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Release|Any CPU.Build.0 = Release|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Release|x64.ActiveCfg = Release|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Release|x64.Build.0 = Release|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Release|x86.ActiveCfg = Release|Any CPU + {162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/NineChronicles.Headless.Executable/Program.cs b/NineChronicles.Headless.Executable/Program.cs index 1cb67a931..731fb0380 100644 --- a/NineChronicles.Headless.Executable/Program.cs +++ b/NineChronicles.Headless.Executable/Program.cs @@ -205,11 +205,7 @@ public async Task Run( Description = "Absolute path of \"appsettings.json\" file to provide headless configurations.")] string? configPath = "appsettings.json", [Option(Description = "Sentry DSN")] - string? sentryDsn = "", - [Option(Description = "AccessControlService Type")] - string? acsType = null, - [Option(Description = "AccessControlService ConnectionString")] - string? acsConnectionString = null, + string? sentryDsn = "", [Option(Description = "Trace sample rate for sentry")] double? sentryTraceSampleRate = null, [Ignore] CancellationToken? cancellationToken = null diff --git a/NineChronicles.Headless.Tests/GraphQLStartupTest.cs b/NineChronicles.Headless.Tests/GraphQLStartupTest.cs index f582243e0..c64ea79d0 100644 --- a/NineChronicles.Headless.Tests/GraphQLStartupTest.cs +++ b/NineChronicles.Headless.Tests/GraphQLStartupTest.cs @@ -4,8 +4,8 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; -using static NineChronicles.Headless.Tests.GraphQLTestUtils; using Xunit; +using static NineChronicles.Headless.Tests.GraphQLTestUtils; namespace NineChronicles.Headless.Tests { diff --git a/NineChronicles.Headless.Tests/GraphTypes/StandaloneSubscriptionTest.cs b/NineChronicles.Headless.Tests/GraphTypes/StandaloneSubscriptionTest.cs index 419ec81f7..c441f7d8b 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/StandaloneSubscriptionTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/StandaloneSubscriptionTest.cs @@ -54,7 +54,6 @@ public async Task SubscribeTipChangedEvent() BlockChain.Append(block, GenerateBlockCommit(block.Index, block.Hash, GenesisValidators)); // var data = (Dictionary)((ExecutionNode) result.Data!).ToValue()!; - Assert.Equal(index, BlockChain.Tip.Index); await Task.Delay(TimeSpan.FromSeconds(1)); diff --git a/NineChronicles.Headless/Properties/AccessControlServiceOptions.cs b/NineChronicles.Headless/Properties/AccessControlServiceOptions.cs index 41fcddd27..3cb185f2d 100644 --- a/NineChronicles.Headless/Properties/AccessControlServiceOptions.cs +++ b/NineChronicles.Headless/Properties/AccessControlServiceOptions.cs @@ -8,7 +8,7 @@ public class AccessControlServiceOptions { [Required] public string AccessControlServiceType { get; set; } = null!; - + [Required] public string AccessControlServiceConnectionString { get; set; } = null!; diff --git a/NineChronicles.Headless/Services/RedisAccessControlService.cs b/NineChronicles.Headless/Services/RedisAccessControlService.cs index 7729cee34..b1dfb5e74 100644 --- a/NineChronicles.Headless/Services/RedisAccessControlService.cs +++ b/NineChronicles.Headless/Services/RedisAccessControlService.cs @@ -6,7 +6,7 @@ namespace NineChronicles.Headless.Services { public class RedisAccessControlService : IAccessControlService { - private IDatabase _db; + protected IDatabase _db; public RedisAccessControlService(string storageUri) { diff --git a/NineChronicles.Headless/Services/SQLiteAccessControlService.cs b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs index 62df4c795..0a9c1e456 100644 --- a/NineChronicles.Headless/Services/SQLiteAccessControlService.cs +++ b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs @@ -11,7 +11,7 @@ public class SQLiteAccessControlService : IAccessControlService private const string CheckAccessSql = "SELECT EXISTS(SELECT 1 FROM blocklist WHERE address=@Address)"; - private readonly string _connectionString; + protected readonly string _connectionString; public SQLiteAccessControlService(string connectionString) { From 9b19e86cbf72de8d744d36a21a5e7e070d99fd49 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Mon, 16 Oct 2023 17:30:44 +0900 Subject: [PATCH 22/54] Bump lib9c to aa95dcff --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 0bf659679..aa95dcffd 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 0bf659679f55122846691ed4fec4cfcb76d81cda +Subproject commit aa95dcffd5f6dac64da413aa1b67bbf6a61b0be9 From ea34b9caaeff30e88e8b94a425947e10226f0bf4 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Wed, 11 Oct 2023 11:04:18 +0900 Subject: [PATCH 23/54] Accommodate API changes --- .../ForkableActionEvaluatorTest.cs | 33 +++++++------------ .../ForkableActionEvaluator.cs | 2 +- .../Hosting/LibplanetNodeService.cs | 2 +- .../Commands/ChainCommandTest.cs | 10 ++++++ .../Commands/ChainCommand.cs | 4 +-- .../GraphQLServiceExtensions.cs | 1 + .../GraphTypes/StandaloneSubscription.cs | 12 +++---- .../GraphTypes/TransactionHeadlessQuery.cs | 24 ++++++-------- 8 files changed, 41 insertions(+), 47 deletions(-) diff --git a/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs b/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs index a28045b56..3e40b03c0 100644 --- a/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs +++ b/Libplanet.Extensions.ForkableActionEvaluator.Tests/ForkableActionEvaluatorTest.cs @@ -5,11 +5,8 @@ using Libplanet.Types.Blocks; using Libplanet.Common; using Libplanet.Crypto; -using Libplanet.Extensions.ActionEvaluatorCommonComponents; using Libplanet.Types.Tx; -using ActionEvaluation = Libplanet.Extensions.ActionEvaluatorCommonComponents.ActionEvaluation; using ArgumentOutOfRangeException = System.ArgumentOutOfRangeException; -using Random = Libplanet.Extensions.ActionEvaluatorCommonComponents.Random; namespace Libplanet.Extensions.ForkableActionEvaluator.Tests; @@ -64,26 +61,23 @@ public void CheckPairs() class PostActionEvaluator : IActionEvaluator { public IActionLoader ActionLoader => throw new NotSupportedException(); - public IReadOnlyList Evaluate(IPreEvaluationBlock block, HashDigest? baseStateroothash) + public IReadOnlyList Evaluate(IPreEvaluationBlock block, HashDigest? baseStateroothash) { - return new IActionEvaluation[] + return new ICommittedActionEvaluation[] { - new ActionEvaluation( + new CommittedActionEvaluation( (Text)"POST", - new ActionContext( - null, + new CommittedActionContext( default, null, default, 0, 0, false, - new AccountStateDelta(), + default, 0, - null, false), - new AccountStateDelta(), - null) + default) }; } } @@ -91,26 +85,23 @@ public IReadOnlyList Evaluate(IPreEvaluationBlock block, Hash class PreActionEvaluator : IActionEvaluator { public IActionLoader ActionLoader => throw new NotSupportedException(); - public IReadOnlyList Evaluate(IPreEvaluationBlock block, HashDigest? baseStateRootHash) + public IReadOnlyList Evaluate(IPreEvaluationBlock block, HashDigest? baseStateRootHash) { - return new IActionEvaluation[] + return new ICommittedActionEvaluation[] { - new ActionEvaluation( + new CommittedActionEvaluation( (Text)"PRE", - new ActionContext( - null, + new CommittedActionContext( default, null, default, 0, 0, false, - new AccountStateDelta(), + default, 0, - null, false), - new AccountStateDelta(), - null) + default) }; } } diff --git a/Libplanet.Extensions.ForkableActionEvaluator/ForkableActionEvaluator.cs b/Libplanet.Extensions.ForkableActionEvaluator/ForkableActionEvaluator.cs index 1e5f47214..651794876 100644 --- a/Libplanet.Extensions.ForkableActionEvaluator/ForkableActionEvaluator.cs +++ b/Libplanet.Extensions.ForkableActionEvaluator/ForkableActionEvaluator.cs @@ -17,7 +17,7 @@ public ForkableActionEvaluator(IEnumerable<((long StartIndex, long EndIndex) Ran public IActionLoader ActionLoader => throw new NotSupportedException(); - public IReadOnlyList Evaluate( + public IReadOnlyList Evaluate( IPreEvaluationBlock block, HashDigest? baseStateRootHash) { var actionEvaluator = _router.GetEvaluator(block.Index); diff --git a/Libplanet.Headless/Hosting/LibplanetNodeService.cs b/Libplanet.Headless/Hosting/LibplanetNodeService.cs index 2b99920d1..cc5a3d55a 100644 --- a/Libplanet.Headless/Hosting/LibplanetNodeService.cs +++ b/Libplanet.Headless/Hosting/LibplanetNodeService.cs @@ -121,7 +121,7 @@ IActionEvaluator BuildActionEvaluator(IActionEvaluatorConfiguration actionEvalua return actionEvaluatorConfiguration switch { RemoteActionEvaluatorConfiguration remoteActionEvaluatorConfiguration => new RemoteActionEvaluator( - new Uri(remoteActionEvaluatorConfiguration.StateServiceEndpoint), blockChainStates), + new Uri(remoteActionEvaluatorConfiguration.StateServiceEndpoint)), DefaultActionEvaluatorConfiguration _ => new ActionEvaluator( _ => blockPolicy.BlockAction, stateStore: StateStore, diff --git a/NineChronicles.Headless.Executable.Tests/Commands/ChainCommandTest.cs b/NineChronicles.Headless.Executable.Tests/Commands/ChainCommandTest.cs index 36f7fbbd9..a14f5c529 100644 --- a/NineChronicles.Headless.Executable.Tests/Commands/ChainCommandTest.cs +++ b/NineChronicles.Headless.Executable.Tests/Commands/ChainCommandTest.cs @@ -242,7 +242,16 @@ public void PruneState(StoreType storeType) stateStore, genesisBlock, actionEvaluator); + + // Additional pruning is now required since in-between commits are made + store.Dispose(); + stateStore.Dispose(); + _command.PruneStates(storeType, _storePath); + store = storeType.CreateStore(_storePath); + stateKeyValueStore = new RocksDBKeyValueStore(statesPath); + stateStore = new TrieStateStore(stateKeyValueStore); int prevStatesCount = stateKeyValueStore.ListKeys().Count(); + stateKeyValueStore.Set( new KeyBytes("alpha"), ByteUtil.ParseHex("00")); @@ -250,6 +259,7 @@ public void PruneState(StoreType storeType) new KeyBytes("beta"), ByteUtil.ParseHex("00")); Assert.Equal(prevStatesCount + 2, stateKeyValueStore.ListKeys().Count()); + store.Dispose(); stateStore.Dispose(); _command.PruneStates(storeType, _storePath); diff --git a/NineChronicles.Headless.Executable/Commands/ChainCommand.cs b/NineChronicles.Headless.Executable/Commands/ChainCommand.cs index c5333094b..11184493a 100644 --- a/NineChronicles.Headless.Executable/Commands/ChainCommand.cs +++ b/NineChronicles.Headless.Executable/Commands/ChainCommand.cs @@ -284,7 +284,7 @@ public void Truncate( } snapshotTipHash = hash; - } while (!stateStore.ContainsStateRoot(store.GetBlock(snapshotTipHash).StateRootHash)); + } while (!stateStore.GetStateRoot(store.GetBlock(snapshotTipHash).StateRootHash).Recorded); var forkedId = Guid.NewGuid(); @@ -484,7 +484,7 @@ public void Snapshot( } snapshotTipHash = hash; - } while (!stateStore.ContainsStateRoot(store.GetBlock(snapshotTipHash).StateRootHash)); + } while (!stateStore.GetStateRoot(store.GetBlock(snapshotTipHash).StateRootHash).Recorded); var forkedId = Guid.NewGuid(); diff --git a/NineChronicles.Headless/GraphQLServiceExtensions.cs b/NineChronicles.Headless/GraphQLServiceExtensions.cs index cad7d67af..4679ef4e7 100644 --- a/NineChronicles.Headless/GraphQLServiceExtensions.cs +++ b/NineChronicles.Headless/GraphQLServiceExtensions.cs @@ -45,6 +45,7 @@ public static IServiceCollection AddLibplanetScalarTypes(this IServiceCollection services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); return services; } diff --git a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs index 8b249aebc..bf3c025b7 100644 --- a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs +++ b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs @@ -322,16 +322,12 @@ private IObservable SubscribeTx(IResolveFieldContext context) } var txExecution = store.GetTxExecution(blockHash, transaction.Id); var txExecutedBlock = chain[blockHash]; - return txExecution.Fail - ? new TxResult( - TxStatus.FAILURE, - txExecutedBlock.Index, - txExecutedBlock.Hash.ToString(), - txExecution.ExceptionNames) - : new TxResult( - TxStatus.SUCCESS, + return new TxResult( + txExecution.Fail ? TxStatus.FAILURE : TxStatus.SUCCESS, txExecutedBlock.Index, txExecutedBlock.Hash.ToString(), + txExecution.InputState, + txExecution.OutputState, txExecution.ExceptionNames); } diff --git a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs index a3cdad940..86f227fe0 100644 --- a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs +++ b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs @@ -208,29 +208,25 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext) if (!(store.GetFirstTxIdBlockHashIndex(txId) is { } txExecutedBlockHash)) { return blockChain.GetStagedTransactionIds().Contains(txId) - ? new TxResult(TxStatus.STAGING, null, null, null) - : new TxResult(TxStatus.INVALID, null, null, null); + ? new TxResult(TxStatus.STAGING, null, null, null, null, null) + : new TxResult(TxStatus.INVALID, null, null, null, null, null); } try { TxExecution execution = blockChain.GetTxExecution(txExecutedBlockHash, txId); Block txExecutedBlock = blockChain[txExecutedBlockHash]; - return execution.Fail - ? new TxResult( - TxStatus.FAILURE, - txExecutedBlock.Index, - txExecutedBlock.Hash.ToString(), - execution.ExceptionNames) - : new TxResult( - TxStatus.SUCCESS, - txExecutedBlock.Index, - txExecutedBlock.Hash.ToString(), - execution.ExceptionNames); + return new TxResult( + execution.Fail ? TxStatus.FAILURE : TxStatus.SUCCESS, + txExecutedBlock.Index, + txExecutedBlock.Hash.ToString(), + execution.InputState, + execution.OutputState, + execution.ExceptionNames); } catch (Exception) { - return new TxResult(TxStatus.INVALID, null, null, null); + return new TxResult(TxStatus.INVALID, null, null, null, null, null); } } ); From 47e440222ba59fe5b1acf2be9c7520604113b441 Mon Sep 17 00:00:00 2001 From: Atralupus Date: Mon, 16 Oct 2023 19:12:53 +0900 Subject: [PATCH 24/54] Configuration DI --- .../AcsService.cs | 39 ++++++------------- .../Program.cs | 9 ++--- 2 files changed, 15 insertions(+), 33 deletions(-) diff --git a/NineChronicles.Headless.AccessControlCenter/AcsService.cs b/NineChronicles.Headless.AccessControlCenter/AcsService.cs index 04cea4ae2..596d54fad 100644 --- a/NineChronicles.Headless.AccessControlCenter/AcsService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AcsService.cs @@ -11,27 +11,18 @@ namespace NineChronicles.Headless.AccessControlCenter { public class AcsService { - private readonly string _acsType; - private readonly string _acsConnectionString; - - public AcsService(string acsType, string acsConnectionString) + public AcsService(Configuration configuration) { - _acsType = acsType; - _acsConnectionString = acsConnectionString; + Configuration = configuration; } + public Configuration Configuration { get; } + public IHostBuilder Configure(IHostBuilder hostBuilder, int port) { return hostBuilder.ConfigureWebHostDefaults(builder => { - builder.UseStartup( - x => - new RestApiStartup( - x.Configuration, - _acsType, - _acsConnectionString - ) - ); + builder.UseStartup(x => new RestApiStartup(Configuration)); builder.ConfigureKestrel(options => { options.ListenAnyIP( @@ -47,29 +38,23 @@ public IHostBuilder Configure(IHostBuilder hostBuilder, int port) internal class RestApiStartup { - private readonly string _acsType; - private readonly string _acsConnectionString; - - public RestApiStartup( - IConfiguration configuration, - string acsType, - string acsConnectionString - ) + public RestApiStartup(Configuration configuration) { Configuration = configuration; - _acsType = acsType; - _acsConnectionString = acsConnectionString; } - public IConfiguration Configuration { get; } + public Configuration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddControllers(); var accessControlService = MutableAccessControlServiceFactory.Create( - Enum.Parse(_acsType, true), - _acsConnectionString + Enum.Parse( + Configuration.AccessControlServiceType, + true + ), + Configuration.AccessControlServiceConnectionString ); services.AddSingleton(accessControlService); diff --git a/NineChronicles.Headless.AccessControlCenter/Program.cs b/NineChronicles.Headless.AccessControlCenter/Program.cs index a48cd2312..ee262e673 100644 --- a/NineChronicles.Headless.AccessControlCenter/Program.cs +++ b/NineChronicles.Headless.AccessControlCenter/Program.cs @@ -10,20 +10,17 @@ public static void Main(string[] args) { // Get configuration string configPath = - Environment.GetEnvironmentVariable("ACS_CONFIG_FILE") ?? "appsettings.json"; + Environment.GetEnvironmentVariable("ACC_CONFIG_FILE") ?? "appsettings.json"; var configurationBuilder = new ConfigurationBuilder() .AddJsonFile(configPath) - .AddEnvironmentVariables("ACS_"); + .AddEnvironmentVariables("ACC_"); IConfiguration config = configurationBuilder.Build(); var acsConfig = new Configuration(); config.Bind(acsConfig); - var service = new AcsService( - acsConfig.AccessControlServiceType, - acsConfig.AccessControlServiceConnectionString - ); + var service = new AcsService(acsConfig); var hostBuilder = service.Configure(Host.CreateDefaultBuilder(), acsConfig.Port); var host = hostBuilder.Build(); host.Run(); From a30840f92b3c522250a5ad66f3956328af43a329 Mon Sep 17 00:00:00 2001 From: Atralupus Date: Tue, 17 Oct 2023 00:29:30 +0900 Subject: [PATCH 25/54] Remove unnecessary evaluation --- .../MutableRedisAccessControlService.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs index eec152cd3..89c614a0a 100644 --- a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs @@ -7,7 +7,6 @@ namespace NineChronicles.Headless.AccessControlCenter.AccessControlService { public class MutableRedisAccessControlService : RedisAccessControlService, IMutableAccessControlService { - public MutableRedisAccessControlService(string storageUri) : base(storageUri) { } @@ -25,12 +24,12 @@ public void AllowAccess(Address address) public List
ListBlockedAddresses(int offset, int limit) { var server = _db.Multiplexer.GetServer(_db.Multiplexer.GetEndPoints().First()); - var keys = server + return server .Keys() .Select(k => new Address(k.ToString())) + .Skip(offset) + .Take(limit) .ToList(); - - return keys.Skip(offset).Take(limit).ToList(); } } } From 2cce89c940f4c200e61a2c2f5133a317afe38d30 Mon Sep 17 00:00:00 2001 From: Atralupus Date: Tue, 17 Oct 2023 00:31:29 +0900 Subject: [PATCH 26/54] bump lib9c --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index a1d99f34e..aa95dcffd 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit a1d99f34e0739bdac08e2f9bdec5b6e274971f3b +Subproject commit aa95dcffd5f6dac64da413aa1b67bbf6a61b0be9 From 18e4e26458246a738905f3fb4adaa5e91dafda97 Mon Sep 17 00:00:00 2001 From: Atralupus Date: Tue, 17 Oct 2023 12:59:02 +0900 Subject: [PATCH 27/54] Implement swagger page fix lint fix lint fix lint --- .github/workflows/push_docker_image.yml | 2 + .../{AcsService.cs => AccService.cs} | 40 ++++++++++++++++--- .../MutableRedisAccessControlService.cs | 31 ++++++++++---- .../MutableSqliteAccessControlService.cs | 6 +++ .../AccessControlServiceController.cs | 16 ++++++-- ...nicles.Headless.AccessControlCenter.csproj | 1 + .../Program.cs | 2 +- 7 files changed, 79 insertions(+), 19 deletions(-) rename NineChronicles.Headless.AccessControlCenter/{AcsService.cs => AccService.cs} (58%) diff --git a/.github/workflows/push_docker_image.yml b/.github/workflows/push_docker_image.yml index 90f4bfd83..d603c9d2b 100644 --- a/.github/workflows/push_docker_image.yml +++ b/.github/workflows/push_docker_image.yml @@ -29,6 +29,8 @@ jobs: docker: - repo: planetariumhq/ninechronicles-headless dockerfile: Dockerfile + - repo: planetariumhq/access-control-center + dockerfile: Dockerfile.ACC if: github.ref_type == 'branch' runs-on: ubuntu-latest steps: diff --git a/NineChronicles.Headless.AccessControlCenter/AcsService.cs b/NineChronicles.Headless.AccessControlCenter/AccService.cs similarity index 58% rename from NineChronicles.Headless.AccessControlCenter/AcsService.cs rename to NineChronicles.Headless.AccessControlCenter/AccService.cs index 596d54fad..167beac3b 100644 --- a/NineChronicles.Headless.AccessControlCenter/AcsService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AccService.cs @@ -2,16 +2,16 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Core; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.OpenApi.Models; using NineChronicles.Headless.AccessControlCenter.AccessControlService; namespace NineChronicles.Headless.AccessControlCenter { - public class AcsService + public class AccService { - public AcsService(Configuration configuration) + public AccService(Configuration configuration) { Configuration = configuration; } @@ -49,6 +49,33 @@ public void ConfigureServices(IServiceCollection services) { services.AddControllers(); + services.AddSwaggerGen(c => + { + c.SwaggerDoc( + "v1", + new OpenApiInfo { Title = "Access Control Center API", Version = "v1" } + ); + c.DocInclusionPredicate( + (docName, apiDesc) => + { + var controllerType = + apiDesc.ActionDescriptor + as Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor; + if (controllerType != null) + { + var assemblyName = controllerType.ControllerTypeInfo.Assembly + .GetName() + .Name; + var namespaceName = controllerType.ControllerTypeInfo.Namespace; + return namespaceName?.StartsWith( + "NineChronicles.Headless.AccessControlCenter" + ) ?? false; + } + return false; + } + ); + }); + var accessControlService = MutableAccessControlServiceFactory.Create( Enum.Parse( Configuration.AccessControlServiceType, @@ -62,10 +89,11 @@ public void ConfigureServices(IServiceCollection services) public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - if (env.IsDevelopment()) + app.UseSwagger(); + app.UseSwaggerUI(c => { - app.UseDeveloperExceptionPage(); - } + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Access Control Center API V1"); + }); app.UseRouting(); app.UseAuthorization(); diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs index 89c614a0a..c608edccc 100644 --- a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs @@ -1,13 +1,18 @@ +using System; using System.Collections.Generic; using System.Linq; using Libplanet.Crypto; using NineChronicles.Headless.Services; +using StackExchange.Redis; namespace NineChronicles.Headless.AccessControlCenter.AccessControlService { - public class MutableRedisAccessControlService : RedisAccessControlService, IMutableAccessControlService + public class MutableRedisAccessControlService + : RedisAccessControlService, + IMutableAccessControlService { - public MutableRedisAccessControlService(string storageUri) : base(storageUri) + public MutableRedisAccessControlService(string storageUri) + : base(storageUri) { } @@ -23,13 +28,23 @@ public void AllowAccess(Address address) public List
ListBlockedAddresses(int offset, int limit) { + if (limit > 30) + { + throw new ArgumentException("Limit cannot exceed 30.", nameof(limit)); + } + var server = _db.Multiplexer.GetServer(_db.Multiplexer.GetEndPoints().First()); - return server - .Keys() - .Select(k => new Address(k.ToString())) - .Skip(offset) - .Take(limit) - .ToList(); + + var result = (RedisResult[]?) + server.Execute("SCAN", offset.ToString(), "COUNT", limit.ToString()); + if (result != null) + { + long newCursor = long.Parse((string)result[0]!); + RedisKey[] keys = (RedisKey[])result[1]!; + + return keys.Select(k => new Address(k.ToString())).ToList(); + } + return new List
(); } } } diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs index 1d9455118..1f30b8848 100644 --- a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Microsoft.Data.Sqlite; using Libplanet.Crypto; @@ -39,6 +40,11 @@ public void AllowAccess(Address address) public List
ListBlockedAddresses(int offset, int limit) { + if (limit > 30) + { + throw new ArgumentException("Limit cannot exceed 30.", nameof(limit)); + } + var blockedAddresses = new List
(); using var connection = new SqliteConnection(_connectionString); diff --git a/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs index 685dca35e..b9150600c 100644 --- a/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs +++ b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using NineChronicles.Headless.AccessControlCenter.AccessControlService; @@ -39,10 +40,17 @@ public ActionResult AllowAccess(string address) [HttpGet("entries")] public ActionResult> ListBlockedAddresses(int offset, int limit) { - return _accessControlService - .ListBlockedAddresses(offset, limit) - .Select(a => a.ToString()) - .ToList(); + try + { + return _accessControlService + .ListBlockedAddresses(offset, limit) + .Select(a => a.ToString()) + .ToList(); + } + catch (ArgumentException ex) + { + return BadRequest(ex.Message); + } } } } diff --git a/NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj b/NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj index 74be55c3b..1ca96b941 100644 --- a/NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj +++ b/NineChronicles.Headless.AccessControlCenter/NineChronicles.Headless.AccessControlCenter.csproj @@ -22,6 +22,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/NineChronicles.Headless.AccessControlCenter/Program.cs b/NineChronicles.Headless.AccessControlCenter/Program.cs index ee262e673..c506b85dc 100644 --- a/NineChronicles.Headless.AccessControlCenter/Program.cs +++ b/NineChronicles.Headless.AccessControlCenter/Program.cs @@ -20,7 +20,7 @@ public static void Main(string[] args) var acsConfig = new Configuration(); config.Bind(acsConfig); - var service = new AcsService(acsConfig); + var service = new AccService(acsConfig); var hostBuilder = service.Configure(Host.CreateDefaultBuilder(), acsConfig.Port); var host = hostBuilder.Build(); host.Run(); From b7cae9941c2f6193a695d21b49fb5b2198b4c91c Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Wed, 18 Oct 2023 10:21:42 +0900 Subject: [PATCH 28/54] Fix updated addresses for every render --- NineChronicles.Headless/ActionEvaluationPublisher.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/NineChronicles.Headless/ActionEvaluationPublisher.cs b/NineChronicles.Headless/ActionEvaluationPublisher.cs index 96f293c2b..623da1aa9 100644 --- a/NineChronicles.Headless/ActionEvaluationPublisher.cs +++ b/NineChronicles.Headless/ActionEvaluationPublisher.cs @@ -465,10 +465,15 @@ await _hub.BroadcastRenderBlockAsync( 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 updatedAddresses = diff.StateDiffs.Keys + .Union(diff.FungibleAssetValueDiffs.Select(kv => kv.Key.Item1)) + .Append(ev.Signer) + .ToHashSet(); + if (!TargetAddresses.Any(updatedAddresses.Contains)) { return; } + var encodeElapsedMilliseconds = stopwatch.ElapsedMilliseconds; var eval = new NCActionEvaluation(pa, ev.Signer, ev.BlockIndex, ev.OutputState, ev.Exception, ev.PreviousState, ev.RandomSeed, extra); From 7cf75af01011071ba5d86307af89a375b23f8702 Mon Sep 17 00:00:00 2001 From: Atralupus Date: Thu, 19 Oct 2023 10:43:34 +0900 Subject: [PATCH 29/54] Change limit validation --- .../MutableRedisAccessControlService.cs | 5 ----- .../MutableSqliteAccessControlService.cs | 5 ----- .../AccessControlServiceController.cs | 22 ++++++++++++------- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs index c608edccc..b3858de26 100644 --- a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs @@ -28,11 +28,6 @@ public void AllowAccess(Address address) public List
ListBlockedAddresses(int offset, int limit) { - if (limit > 30) - { - throw new ArgumentException("Limit cannot exceed 30.", nameof(limit)); - } - var server = _db.Multiplexer.GetServer(_db.Multiplexer.GetEndPoints().First()); var result = (RedisResult[]?) diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs index 1f30b8848..2c10ed525 100644 --- a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs @@ -40,11 +40,6 @@ public void AllowAccess(Address address) public List
ListBlockedAddresses(int offset, int limit) { - if (limit > 30) - { - throw new ArgumentException("Limit cannot exceed 30.", nameof(limit)); - } - var blockedAddresses = new List
(); using var connection = new SqliteConnection(_connectionString); diff --git a/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs index b9150600c..65f3b6d59 100644 --- a/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs +++ b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using NineChronicles.Headless.AccessControlCenter.AccessControlService; @@ -40,17 +39,24 @@ public ActionResult AllowAccess(string address) [HttpGet("entries")] public ActionResult> ListBlockedAddresses(int offset, int limit) { - try + var maxLimit = 10; + if (_accessControlService is MutableRedisAccessControlService) { - return _accessControlService - .ListBlockedAddresses(offset, limit) - .Select(a => a.ToString()) - .ToList(); + maxLimit = 10; } - catch (ArgumentException ex) + else if (_accessControlService is MutableSqliteAccessControlService) { - return BadRequest(ex.Message); + maxLimit = 100; } + if (limit > maxLimit) + { + return BadRequest($"The limit cannot exceed {maxLimit}."); + } + + return _accessControlService + .ListBlockedAddresses(offset, limit) + .Select(a => a.ToString()) + .ToList(); } } } From 9c0662445e08a23e722962026737be4d90b12931 Mon Sep 17 00:00:00 2001 From: Atralupus Date: Tue, 17 Oct 2023 13:32:30 +0900 Subject: [PATCH 30/54] Add access denied debug log renaming --- .../Services/RedisAccessControlService.cs | 9 ++++++++- .../Services/SQLiteAccessControlService.cs | 12 ++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/NineChronicles.Headless/Services/RedisAccessControlService.cs b/NineChronicles.Headless/Services/RedisAccessControlService.cs index b1dfb5e74..8e0765746 100644 --- a/NineChronicles.Headless/Services/RedisAccessControlService.cs +++ b/NineChronicles.Headless/Services/RedisAccessControlService.cs @@ -1,6 +1,7 @@ using StackExchange.Redis; using Libplanet.Crypto; using Nekoyume.Blockchain; +using Serilog; namespace NineChronicles.Headless.Services { @@ -16,7 +17,13 @@ public RedisAccessControlService(string storageUri) public bool IsAccessDenied(Address address) { - return _db.KeyExists(address.ToString()); + var result = _db.KeyExists(address.ToString()); + if (result) + { + Log.Debug($"{address} is access denied"); + } + + return result; } } } diff --git a/NineChronicles.Headless/Services/SQLiteAccessControlService.cs b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs index 0a9c1e456..a124ee56d 100644 --- a/NineChronicles.Headless/Services/SQLiteAccessControlService.cs +++ b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs @@ -1,6 +1,7 @@ using Microsoft.Data.Sqlite; using Libplanet.Crypto; using Nekoyume.Blockchain; +using Serilog; namespace NineChronicles.Headless.Services { @@ -33,9 +34,16 @@ public bool IsAccessDenied(Address address) command.CommandText = CheckAccessSql; command.Parameters.AddWithValue("@Address", address.ToString()); - var result = command.ExecuteScalar(); + var queryResult = command.ExecuteScalar(); - return result is not null && (long)result == 1; + var result = queryResult is not null && (long)queryResult == 1; + + if (result) + { + Log.Debug($"{address} is access denied"); + } + + return result; } } } From 97d1d3f754e339fa856f5d84371c40d7f435788b Mon Sep 17 00:00:00 2001 From: Atralupus Date: Thu, 19 Oct 2023 17:30:58 +0900 Subject: [PATCH 31/54] Change log format for loki --- NineChronicles.Headless/Services/RedisAccessControlService.cs | 3 ++- NineChronicles.Headless/Services/SQLiteAccessControlService.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/NineChronicles.Headless/Services/RedisAccessControlService.cs b/NineChronicles.Headless/Services/RedisAccessControlService.cs index 8e0765746..fb5e240aa 100644 --- a/NineChronicles.Headless/Services/RedisAccessControlService.cs +++ b/NineChronicles.Headless/Services/RedisAccessControlService.cs @@ -20,7 +20,8 @@ public bool IsAccessDenied(Address address) var result = _db.KeyExists(address.ToString()); if (result) { - Log.Debug($"{address} is access denied"); + Log.ForContext("Source", nameof(IAccessControlService)) + .Debug("\"{Address}\" is access denied", address); } return result; diff --git a/NineChronicles.Headless/Services/SQLiteAccessControlService.cs b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs index a124ee56d..e3dae9221 100644 --- a/NineChronicles.Headless/Services/SQLiteAccessControlService.cs +++ b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs @@ -40,7 +40,8 @@ public bool IsAccessDenied(Address address) if (result) { - Log.Debug($"{address} is access denied"); + Log.ForContext("Source", nameof(IAccessControlService)) + .Debug("\"{Address}\" is access denied", address); } return result; From ee07a07f86958261a639ff5f30266247305e3ab7 Mon Sep 17 00:00:00 2001 From: area363 Date: Tue, 31 Oct 2023 09:27:18 +0900 Subject: [PATCH 32/54] bump lib9c --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index aa95dcffd..dd343eb6f 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit aa95dcffd5f6dac64da413aa1b67bbf6a61b0be9 +Subproject commit dd343eb6f69f857e8547126b88990cdd9d133fc3 From 7082c1b5f68bb5e7781e95d7c5995575472a7e11 Mon Sep 17 00:00:00 2001 From: area363 Date: Tue, 31 Oct 2023 09:27:29 +0900 Subject: [PATCH 33/54] fix test --- .../CrystalMonsterCollectionMultiplierSheetTypeTest.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/NineChronicles.Headless.Tests/GraphTypes/States/Models/CrystalMonsterCollectionMultiplierSheetTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/States/Models/CrystalMonsterCollectionMultiplierSheetTypeTest.cs index a1a20e7a5..491f17b83 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/States/Models/CrystalMonsterCollectionMultiplierSheetTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/States/Models/CrystalMonsterCollectionMultiplierSheetTypeTest.cs @@ -59,6 +59,16 @@ public async Task Query() ["level"] = 5, ["multiplier"] = 300, }, + new Dictionary + { + ["level"] = 6, + ["multiplier"] = 300, + }, + new Dictionary + { + ["level"] = 7, + ["multiplier"] = 300, + }, }; var expected = new Dictionary { { "orderedList", list } }; Assert.Equal(expected, data); From 57649db17c9a2cc9ffd87b3c27a11e3d4258005b Mon Sep 17 00:00:00 2001 From: moreal Date: Mon, 30 Oct 2023 12:13:10 +0900 Subject: [PATCH 34/54] Use Cocona.Docs --- .../NineChronicles.Headless.Executable.csproj | 1 + NineChronicles.Headless.Executable/Program.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/NineChronicles.Headless.Executable/NineChronicles.Headless.Executable.csproj b/NineChronicles.Headless.Executable/NineChronicles.Headless.Executable.csproj index 95b3ae808..433336e26 100644 --- a/NineChronicles.Headless.Executable/NineChronicles.Headless.Executable.csproj +++ b/NineChronicles.Headless.Executable/NineChronicles.Headless.Executable.csproj @@ -20,6 +20,7 @@ + diff --git a/NineChronicles.Headless.Executable/Program.cs b/NineChronicles.Headless.Executable/Program.cs index 731fb0380..9828513a3 100644 --- a/NineChronicles.Headless.Executable/Program.cs +++ b/NineChronicles.Headless.Executable/Program.cs @@ -43,6 +43,7 @@ namespace NineChronicles.Headless.Executable { + [HasSubCommands(typeof(Cocona.Docs.DocumentCommand), "docs")] [HasSubCommands(typeof(AccountCommand), "account")] [HasSubCommands(typeof(ValidationCommand), "validation")] [HasSubCommands(typeof(ChainCommand), "chain")] From 86e41e8a4cbf57d7f386f310a7919578a0e84f02 Mon Sep 17 00:00:00 2001 From: moreal Date: Mon, 30 Oct 2023 14:51:11 +0900 Subject: [PATCH 35/54] Upload CLI docs too --- .github/workflows/deploy_gh_pages.yaml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy_gh_pages.yaml b/.github/workflows/deploy_gh_pages.yaml index 85e36ddc8..da966aeae 100644 --- a/.github/workflows/deploy_gh_pages.yaml +++ b/.github/workflows/deploy_gh_pages.yaml @@ -50,12 +50,18 @@ jobs: -G "https://9c-dx.s3.ap-northeast-2.amazonaws.com/empty-genesis-block-20230511" & sleep 60s graphql-inspector introspect http://localhost:30000/graphql --write schema.graphql - - name: Build + - name: Build GraphQL Document run: | yarn global add spectaql - spectaql ./spectaql-config.yaml + spectaql --target-dir public/graphql ./spectaql-config.yaml + - name: Build CLI Document + run: | + mkdir -p public/cli + dotnet run --project NineChronicles.Headless.Executable -- \ + docs \ + public/cli - name: Copy GraphQL Schema to deploy - run: cp schema.graphql doc + run: cp schema.graphql public/schema.graphql - name: Deploy uses: peaceiris/actions-gh-pages@v3 with: From 029c37fbe87e1cb419948a5d74275769ea0e26b7 Mon Sep 17 00:00:00 2001 From: moreal Date: Tue, 31 Oct 2023 15:07:28 +0900 Subject: [PATCH 36/54] Update README --- README.md | 162 ++---------------------------------------------------- 1 file changed, 6 insertions(+), 156 deletions(-) diff --git a/README.md b/README.md index 28430b15f..08982cd5a 100644 --- a/README.md +++ b/README.md @@ -3,166 +3,16 @@ [![Planetarium Discord invite](https://img.shields.io/discord/539405872346955788?color=6278DA&label=Planetarium&logo=discord&logoColor=white)](https://discord.gg/JyujU8E4SD) [![Planetarium-Dev Discord Invite](https://img.shields.io/discord/928926944937013338?color=6278DA&label=Planetarium-dev&logo=discord&logoColor=white)](https://discord.gg/RYJDyFRYY7) -## Table of Contents - -- [Run](#run) -- [Docker Build](#docker-build) - * [Format](#format) -- [How to run NineChronicles Headless on AWS EC2 instance using Docker](#how-to-run-ninechronicles-headless-on-aws-ec2-instance-using-docker) - * [On Your AWS EC2 Instance](#on-your-aws-ec2-instance) - * [Building Your Own Docker Image from Your Local Machine](#building-your-own-docker-image-from-your-local-machine) -- [Nine Chronicles GraphQL API Documentation](#nine-chronicles-graphql-api-documentation) -- [Create A New Genesis Block](#create-a-new-genesis-block) - ## Run +If you want to run node to interact with mainnet, you can run the below command line: + ``` -$ dotnet run --project ./NineChronicles.Headless.Executable/ -- --help - -Usage: NineChronicles.Headless.Executable [command] -Usage: NineChronicles.Headless.Executable - -// basic -[--app-protocol-version ] -[--trusted-app-protocol-version-signer ...] -[--genesis-block-path ] -[--host ] -[--port ] -[--swarm-private-key ] - -// Policy -[--skip-preload] -[--chain-tip-stale-behavior-type ] -[--confirmations ] -[--tx-life-time ] -[--message-timeout ] -[--tip-timeout ] -[--demand-buffer ] -[--tx-quota-per-signer ] -[--maximum-poll-peers ] - -// Store -[--store-type ] -[--store-path ] -[--no-reduce-store] - -// Network -[--network-type ] -[--ice-server ...] -[--peer ...] -[--static-peer ...] -[--minimum-broadcast-target ] -[--bucket-size ] - -// render -[--nonblock-renderer] -[--nonblock-renderer-queue ] -[--strict-rendering] -[--log-action-renders] - -// consensus -[--consensus-port ] -[--consensus-private-key ] -[--consensus-seed ...] - -// RPC -[--rpc-server] -[--rpc-listen-host ] -[--rpc-listen-port ] -[--rpc-remote-server] -[--rpc-http-server] - -// GraphQL -[--graphql-server] -[--graphql-host ] -[--graphql-port ] -[--graphql-secret-token-path ] -[--no-cors] - -// Sentry -[--sentry-dsn ] -[--sentry-trace-sample-rate ] - -// ETC -[--config ] -[--help] -[--version] - -// Miner (Deprecated) -[--no-miner] -[--miner-count ] -[--miner-private-key ] -[--miner.block-interval ] - - -NineChronicles.Headless.Executable - -Commands: - account - validation - chain - key - apv - action - state - tx - market - genesis - replay - -Options: - -V, --app-protocol-version App protocol version token. - -G, --genesis-block-path Genesis block path of blockchain. Blockchain is recognized by its genesis block. - -H, --host Hostname of this node for another nodes to access. This is not listening host like 0.0.0.0 - -P, --port Port of this node for another nodes to access. - --swarm-private-key The private key used for signing messages and to specify your node. If you leave this null, a randomly generated value will be used. - --no-miner Disable block mining. - --miner-count The number of miner task(thread). - --miner-private-key The private key used for mining blocks. Must not be null if you want to turn on mining with libplanet-node. - --miner.block-interval The miner's break time after mining a block. The unit is millisecond. - --store-type The type of storage to store blockchain data. If not provided, "LiteDB" will be used as default. Available type: ["rocksdb", "memory"] - --store-path Path of storage. This value is required if you use persistent storage e.g. "rocksdb" - --no-reduce-store Do not reduce storage. Enabling this option will use enormous disk spaces. - -I, --ice-server ... ICE server to NAT traverse. - --peer ... Seed peer list to communicate to another nodes. - -T, --trusted-app-protocol-version-signer ... Trustworthy signers who claim new app protocol versions - --rpc-server Use this option if you want to make unity clients to communicate with this server with RPC - --rpc-listen-host RPC listen host - --rpc-listen-port RPC listen port - --rpc-remote-server Do a role as RPC remote server? If you enable this option, multiple Unity clients can connect to your RPC server. - --rpc-http-server If you enable this option with "rpcRemoteServer" option at the same time, RPC server will use HTTP/1, not gRPC. - --graphql-server Use this option if you want to enable GraphQL server to enable querying data. - --graphql-host GraphQL listen host - --graphql-port GraphQL listen port - --graphql-secret-token-path The path to write GraphQL secret token. If you want to protect this headless application, you should use this option and take it into headers. - --no-cors Run without CORS policy. - --confirmations The number of required confirmations to recognize a block. - --nonblock-renderer Uses non-blocking renderer, which prevents the blockchain & swarm from waiting slow rendering. Turned off by default. - --nonblock-renderer-queue The size of the queue used by the non-blocking renderer. Ignored if --nonblock-renderer is turned off. - --strict-rendering Flag to turn on validating action renderer. - --log-action-renders Log action renders besides block renders. --rpc-server implies this. - --network-type Network type. (Allowed values: Main, Internal, Permanent, Test, Default) - --tx-life-time The lifetime of each transaction, which uses minute as its unit. - --message-timeout The grace period for new messages, which uses second as its unit. - --tip-timeout The grace period for tip update, which uses second as its unit. - --demand-buffer A number of block size that determines how far behind the demand the tip of the chain will publish `NodeException` to GraphQL subscriptions. - --static-peer ... A list of peers that the node will continue to maintain. - --skip-preload Run node without preloading. - --minimum-broadcast-target Minimum number of peers to broadcast message. - --bucket-size Number of the peers can be stored in each bucket. - --chain-tip-stale-behavior-type Determines behavior when the chain's tip is stale. "reboot" and "preload" is available and "reboot" option is selected by default. - --tx-quota-per-signer The number of maximum transactions can be included in stage per signer. - --maximum-poll-peers The maximum number of peers to poll blocks. int.MaxValue by default. - --consensus-port Port used for communicating consensus related messages. null by default. - --consensus-private-key The private key used for signing consensus messages. Cannot be null. - --consensus-seed ... A list of seed peers to join the block consensus. - -C, --config Absolute path of "appsettings.json" file to provide headless configurations. (Default: appsettings.json) - --sentry-dsn Sentry DSN - --sentry-trace-sample-rate Trace sample rate for sentry - -h, --help Show help message - --version Show version +dotnet run --project NineChronicles.Headless.Executable -C appsettings.mainnet.json --store-path={PATH_TO_STORE} ``` +For more information on the command line options, refer to the [CLI Documentation](https://planetarium.github.io/NineChronicles.Headless/cli). + ### Use `appsettings.{network}.json` to provide CLI options You can provide headless CLI options using file, `appsettings.json`. You'll find the default file at [here](NineChronicles.Headless.Executable/appsettings.json). @@ -286,7 +136,7 @@ $ docker push [/]:[] Check out [Nine Chronicles GraphQL API Tutorial](https://www.notion.so/Getting-Started-with-Nine-Chronicles-GraphQL-API-a14388a910844a93ab8dc0a2fe269f06) to get you started with using GraphQL API with NineChronicles Headless. -For more information on the GraphQL API, refer to the [NineChronicles Headless GraphQL Documentation](http://api.nine-chronicles.com/). +For more information on the GraphQL API, refer to the [NineChronicles Headless GraphQL Documentation](https://planetarium.github.io/NineChronicles.Headless/graphql). --- From e3a87ccfcaa1dad1b34d09b2a2de64b32ae673fd Mon Sep 17 00:00:00 2001 From: moreal Date: Wed, 1 Nov 2023 13:23:56 +0900 Subject: [PATCH 37/54] Deploy landing page too --- .github/workflows/deploy_gh_pages.yaml | 2 ++ Docs/resources/landing.html | 25 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 Docs/resources/landing.html diff --git a/.github/workflows/deploy_gh_pages.yaml b/.github/workflows/deploy_gh_pages.yaml index da966aeae..2212312e7 100644 --- a/.github/workflows/deploy_gh_pages.yaml +++ b/.github/workflows/deploy_gh_pages.yaml @@ -60,6 +60,8 @@ jobs: dotnet run --project NineChronicles.Headless.Executable -- \ docs \ public/cli + - name: Copy Landing Page to deploy + run: cp Docs/resources/landing.html public/index.html - name: Copy GraphQL Schema to deploy run: cp schema.graphql public/schema.graphql - name: Deploy diff --git a/Docs/resources/landing.html b/Docs/resources/landing.html new file mode 100644 index 000000000..ebe92854f --- /dev/null +++ b/Docs/resources/landing.html @@ -0,0 +1,25 @@ + + + + NineChronicles Headless + + + + + +
+
+

NineChronicles.Headless

+ A headless node to validate, network, operate NineChronicles chain. +
+ +
+
+ + From e21ae1a4ab43db97b083dbf973073bb5e237ee76 Mon Sep 17 00:00:00 2001 From: area363 Date: Tue, 31 Oct 2023 08:57:21 +0900 Subject: [PATCH 38/54] bump lib9c --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index dd343eb6f..98bf3f444 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit dd343eb6f69f857e8547126b88990cdd9d133fc3 +Subproject commit 98bf3f444ba1785b577b9972f8bb180d55f05e65 From 71c5afc0cd735ca2ecae893e27e632be4270c590 Mon Sep 17 00:00:00 2001 From: area363 Date: Tue, 31 Oct 2023 08:58:16 +0900 Subject: [PATCH 39/54] update acc --- .../IMutableAccessControlService.cs | 2 ++ .../MutableRedisAccessControlService.cs | 16 +++++++++- .../MutableSqliteAccessControlService.cs | 29 ++++++++++++++++++- .../AccessControlServiceController.cs | 16 ++++++++++ 4 files changed, 61 insertions(+), 2 deletions(-) diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/IMutableAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/IMutableAccessControlService.cs index aa8207174..5e971f7a8 100644 --- a/NineChronicles.Headless.AccessControlCenter/AccessControlService/IMutableAccessControlService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/IMutableAccessControlService.cs @@ -8,6 +8,8 @@ public interface IMutableAccessControlService : IAccessControlService { void DenyAccess(Address address); void AllowAccess(Address address); + void AddTxQuota(Address address, int quota); + void RemoveTxQuota(Address address); List
ListBlockedAddresses(int offset, int limit); } } diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs index b3858de26..14e27d50e 100644 --- a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs @@ -18,10 +18,24 @@ public MutableRedisAccessControlService(string storageUri) public void DenyAccess(Address address) { - _db.StringSet(address.ToString(), "denied"); + _db.StringSet(address.ToString(), "0"); } public void AllowAccess(Address address) + { + var value = _db.StringGet(address.ToString()); + if (value == "0") + { + _db.KeyDelete(address.ToString()); + } + } + + public void AddTxQuota(Address address, int quota) + { + _db.StringSet(address.ToString(), quota.ToString()); + } + + public void RemoveTxQuota(Address address) { _db.KeyDelete(address.ToString()); } diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs index 2c10ed525..9faaa7855 100644 --- a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs @@ -9,9 +9,13 @@ namespace NineChronicles.Headless.AccessControlCenter.AccessControlService public class MutableSqliteAccessControlService : SQLiteAccessControlService, IMutableAccessControlService { private const string DenyAccessSql = - "INSERT OR IGNORE INTO blocklist (address) VALUES (@Address)"; + "INSERT OR IGNORE INTO blocklist (address, quota) VALUES (@Address, 0)"; private const string AllowAccessSql = "DELETE FROM blocklist WHERE address=@Address"; + private const string AddTxQuotaSql = + "INSERT OR IGNORE INTO blocklist (address, quota) VALUES (@Address, @Quota)"; + private const string RemoveTxQuotaSql = "DELETE FROM blocklist WHERE address=@Address"; + public MutableSqliteAccessControlService(string connectionString) : base(connectionString) { } @@ -38,6 +42,29 @@ public void AllowAccess(Address address) command.ExecuteNonQuery(); } + public void AddTxQuota(Address address, int quota) + { + using var connection = new SqliteConnection(_connectionString); + connection.Open(); + + using var command = connection.CreateCommand(); + command.CommandText = AddTxQuotaSql; + command.Parameters.AddWithValue("@Address", address.ToString()); + command.Parameters.AddWithValue("@Quota", quota.ToString()); + command.ExecuteNonQuery(); + } + + public void RemoveTxQuota(Address address) + { + using var connection = new SqliteConnection(_connectionString); + connection.Open(); + + using var command = connection.CreateCommand(); + command.CommandText = RemoveTxQuotaSql; + command.Parameters.AddWithValue("@Address", address.ToString()); + command.ExecuteNonQuery(); + } + public List
ListBlockedAddresses(int offset, int limit) { var blockedAddresses = new List
(); diff --git a/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs index 65f3b6d59..d59097b03 100644 --- a/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs +++ b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; using NineChronicles.Headless.AccessControlCenter.AccessControlService; @@ -36,6 +37,21 @@ public ActionResult AllowAccess(string address) return Ok(); } + [HttpPost("entries/add-tx-quota/{address}/{quota}")] + public ActionResult AddTxQuota(string address, string quota) + { + var txQuota = Convert.ToInt32(quota); + _accessControlService.AddTxQuota(new Address(address), txQuota); + return Ok(); + } + + [HttpPost("entries/remove-tx-quota/{address}")] + public ActionResult RemoveTxQuota(string address) + { + _accessControlService.RemoveTxQuota(new Address(address)); + return Ok(); + } + [HttpGet("entries")] public ActionResult> ListBlockedAddresses(int offset, int limit) { From e6299921946b8e0ffc5ed572e3b5e3a6b629ba15 Mon Sep 17 00:00:00 2001 From: area363 Date: Tue, 31 Oct 2023 08:58:34 +0900 Subject: [PATCH 40/54] update headless acc service --- .../Services/RedisAccessControlService.cs | 13 +++++++++++++ .../Services/SQLiteAccessControlService.cs | 19 ++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/NineChronicles.Headless/Services/RedisAccessControlService.cs b/NineChronicles.Headless/Services/RedisAccessControlService.cs index fb5e240aa..7f0556ea6 100644 --- a/NineChronicles.Headless/Services/RedisAccessControlService.cs +++ b/NineChronicles.Headless/Services/RedisAccessControlService.cs @@ -1,3 +1,4 @@ +using System; using StackExchange.Redis; using Libplanet.Crypto; using Nekoyume.Blockchain; @@ -26,5 +27,17 @@ public bool IsAccessDenied(Address address) return result; } + + public int? GetTxQuota(Address address) + { + RedisValue result = _db.StringGet(address.ToString()); + if (!result.IsNull) + { + Log.ForContext("Source", nameof(IAccessControlService)) + .Debug("\"{Address}\" access level: {level}", address, result); + } + + return Convert.ToInt32(result); + } } } diff --git a/NineChronicles.Headless/Services/SQLiteAccessControlService.cs b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs index e3dae9221..ba2263ee8 100644 --- a/NineChronicles.Headless/Services/SQLiteAccessControlService.cs +++ b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.Data.Sqlite; using Libplanet.Crypto; using Nekoyume.Blockchain; @@ -8,9 +9,11 @@ namespace NineChronicles.Headless.Services public class SQLiteAccessControlService : IAccessControlService { private const string CreateTableSql = - "CREATE TABLE IF NOT EXISTS blocklist (address VARCHAR(42))"; + "CREATE TABLE IF NOT EXISTS blocklist (address VARCHAR(42), quota INT)"; private const string CheckAccessSql = "SELECT EXISTS(SELECT 1 FROM blocklist WHERE address=@Address)"; + private const string GetTxQuotaSql = + "SELECT quota FROM blocklist WHERE address=@Address"; protected readonly string _connectionString; @@ -46,5 +49,19 @@ public bool IsAccessDenied(Address address) return result; } + + public int? GetTxQuota(Address address) + { + using var connection = new SqliteConnection(_connectionString); + connection.Open(); + + using var command = connection.CreateCommand(); + command.CommandText = GetTxQuotaSql; + command.Parameters.AddWithValue("@Address", address.ToString()); + + var queryResult = command.ExecuteScalar(); + + return Convert.ToInt32(queryResult); + } } } From 0589c2c1a6cb923c10298ce3f3596ec5819462c7 Mon Sep 17 00:00:00 2001 From: area363 Date: Wed, 1 Nov 2023 09:11:11 +0900 Subject: [PATCH 41/54] bump lib9c --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 98bf3f444..ae46c53d7 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 98bf3f444ba1785b577b9972f8bb180d55f05e65 +Subproject commit ae46c53d7de2c02dcecd036c3ca39b510f6100df From e6d645c4415068708818a700a8c9fb6911192b67 Mon Sep 17 00:00:00 2001 From: area363 Date: Wed, 1 Nov 2023 09:20:20 +0900 Subject: [PATCH 42/54] fix control service --- .../MutableSqliteAccessControlService.cs | 2 +- .../Services/RedisAccessControlService.cs | 5 +++-- .../Services/SQLiteAccessControlService.cs | 7 ++++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs index 9faaa7855..258f856d7 100644 --- a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs @@ -50,7 +50,7 @@ public void AddTxQuota(Address address, int quota) using var command = connection.CreateCommand(); command.CommandText = AddTxQuotaSql; command.Parameters.AddWithValue("@Address", address.ToString()); - command.Parameters.AddWithValue("@Quota", quota.ToString()); + command.Parameters.AddWithValue("@Quota", quota); command.ExecuteNonQuery(); } diff --git a/NineChronicles.Headless/Services/RedisAccessControlService.cs b/NineChronicles.Headless/Services/RedisAccessControlService.cs index 7f0556ea6..3e6eb7287 100644 --- a/NineChronicles.Headless/Services/RedisAccessControlService.cs +++ b/NineChronicles.Headless/Services/RedisAccessControlService.cs @@ -34,10 +34,11 @@ public bool IsAccessDenied(Address address) if (!result.IsNull) { Log.ForContext("Source", nameof(IAccessControlService)) - .Debug("\"{Address}\" access level: {level}", address, result); + .Debug("\"{Address}\" Tx Quota: {Quota}", address, result); + return Convert.ToInt32(result); } - return Convert.ToInt32(result); + return null; } } } diff --git a/NineChronicles.Headless/Services/SQLiteAccessControlService.cs b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs index ba2263ee8..8304cf365 100644 --- a/NineChronicles.Headless/Services/SQLiteAccessControlService.cs +++ b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs @@ -61,7 +61,12 @@ public bool IsAccessDenied(Address address) var queryResult = command.ExecuteScalar(); - return Convert.ToInt32(queryResult); + if (queryResult != null) + { + return Convert.ToInt32(queryResult); + } + + return null; } } } From b657de3ee0e2a611b2e6b504439c99392317b107 Mon Sep 17 00:00:00 2001 From: area363 Date: Wed, 1 Nov 2023 16:02:26 +0900 Subject: [PATCH 43/54] bump lib9c --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index ae46c53d7..788095a0a 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit ae46c53d7de2c02dcecd036c3ca39b510f6100df +Subproject commit 788095a0a8867159bb2623e7eface338e982c88f From 6bc451d95b510ec90c3351c8dc0e92c919b25785 Mon Sep 17 00:00:00 2001 From: area363 Date: Wed, 1 Nov 2023 16:51:07 +0900 Subject: [PATCH 44/54] bump lib9c --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 788095a0a..1d69b2f0e 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 788095a0a8867159bb2623e7eface338e982c88f +Subproject commit 1d69b2f0ee8046a438725ac3902f008b128bebcd From 49d8efdfbb4b0eee0b5f25e8891aef66f53c1bfe Mon Sep 17 00:00:00 2001 From: area363 Date: Wed, 1 Nov 2023 16:53:09 +0900 Subject: [PATCH 45/54] remove AllowAccess and DenyAccess in ACC --- .../IMutableAccessControlService.cs | 4 +- .../MutableRedisAccessControlService.cs | 20 +--------- .../MutableSqliteAccessControlService.cs | 40 ++++--------------- .../AccessControlServiceController.cs | 27 +++---------- 4 files changed, 16 insertions(+), 75 deletions(-) diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/IMutableAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/IMutableAccessControlService.cs index 5e971f7a8..b5d52f697 100644 --- a/NineChronicles.Headless.AccessControlCenter/AccessControlService/IMutableAccessControlService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/IMutableAccessControlService.cs @@ -6,10 +6,8 @@ namespace NineChronicles.Headless.AccessControlCenter.AccessControlService { public interface IMutableAccessControlService : IAccessControlService { - void DenyAccess(Address address); - void AllowAccess(Address address); void AddTxQuota(Address address, int quota); void RemoveTxQuota(Address address); - List
ListBlockedAddresses(int offset, int limit); + List
ListTxQuotaAddresses(int offset, int limit); } } diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs index 14e27d50e..9cb412d88 100644 --- a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableRedisAccessControlService.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using System.Linq; using Libplanet.Crypto; @@ -16,20 +15,6 @@ public MutableRedisAccessControlService(string storageUri) { } - public void DenyAccess(Address address) - { - _db.StringSet(address.ToString(), "0"); - } - - public void AllowAccess(Address address) - { - var value = _db.StringGet(address.ToString()); - if (value == "0") - { - _db.KeyDelete(address.ToString()); - } - } - public void AddTxQuota(Address address, int quota) { _db.StringSet(address.ToString(), quota.ToString()); @@ -40,7 +25,7 @@ public void RemoveTxQuota(Address address) _db.KeyDelete(address.ToString()); } - public List
ListBlockedAddresses(int offset, int limit) + public List
ListTxQuotaAddresses(int offset, int limit) { var server = _db.Multiplexer.GetServer(_db.Multiplexer.GetEndPoints().First()); @@ -48,11 +33,10 @@ public List
ListBlockedAddresses(int offset, int limit) server.Execute("SCAN", offset.ToString(), "COUNT", limit.ToString()); if (result != null) { - long newCursor = long.Parse((string)result[0]!); RedisKey[] keys = (RedisKey[])result[1]!; - return keys.Select(k => new Address(k.ToString())).ToList(); } + return new List
(); } } diff --git a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs index 258f856d7..daa2a9c37 100644 --- a/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs +++ b/NineChronicles.Headless.AccessControlCenter/AccessControlService/MutableSqliteAccessControlService.cs @@ -8,40 +8,14 @@ namespace NineChronicles.Headless.AccessControlCenter.AccessControlService { public class MutableSqliteAccessControlService : SQLiteAccessControlService, IMutableAccessControlService { - private const string DenyAccessSql = - "INSERT OR IGNORE INTO blocklist (address, quota) VALUES (@Address, 0)"; - private const string AllowAccessSql = "DELETE FROM blocklist WHERE address=@Address"; - private const string AddTxQuotaSql = - "INSERT OR IGNORE INTO blocklist (address, quota) VALUES (@Address, @Quota)"; - private const string RemoveTxQuotaSql = "DELETE FROM blocklist WHERE address=@Address"; + "INSERT OR IGNORE INTO txquotalist (address, quota) VALUES (@Address, @Quota)"; + private const string RemoveTxQuotaSql = "DELETE FROM txquotalist WHERE address=@Address"; public MutableSqliteAccessControlService(string connectionString) : base(connectionString) { } - public void DenyAccess(Address address) - { - using var connection = new SqliteConnection(_connectionString); - connection.Open(); - - using var command = connection.CreateCommand(); - command.CommandText = DenyAccessSql; - command.Parameters.AddWithValue("@Address", address.ToString()); - command.ExecuteNonQuery(); - } - - public void AllowAccess(Address address) - { - using var connection = new SqliteConnection(_connectionString); - connection.Open(); - - using var command = connection.CreateCommand(); - command.CommandText = AllowAccessSql; - command.Parameters.AddWithValue("@Address", address.ToString()); - command.ExecuteNonQuery(); - } - public void AddTxQuota(Address address, int quota) { using var connection = new SqliteConnection(_connectionString); @@ -65,25 +39,25 @@ public void RemoveTxQuota(Address address) command.ExecuteNonQuery(); } - public List
ListBlockedAddresses(int offset, int limit) + public List
ListTxQuotaAddresses(int offset, int limit) { - var blockedAddresses = new List
(); + var txQuotaAddresses = new List
(); using var connection = new SqliteConnection(_connectionString); connection.Open(); using var command = connection.CreateCommand(); - command.CommandText = $"SELECT address FROM blocklist LIMIT @Limit OFFSET @Offset"; + command.CommandText = $"SELECT address FROM txquotalist LIMIT @Limit OFFSET @Offset"; command.Parameters.AddWithValue("@Limit", limit); command.Parameters.AddWithValue("@Offset", offset); using var reader = command.ExecuteReader(); while (reader.Read()) { - blockedAddresses.Add(new Address(reader.GetString(0))); + txQuotaAddresses.Add(new Address(reader.GetString(0))); } - return blockedAddresses; + return txQuotaAddresses; } } } diff --git a/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs index d59097b03..d18cdd8c9 100644 --- a/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs +++ b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs @@ -18,30 +18,15 @@ public AccessControlServiceController(IMutableAccessControlService accessControl } [HttpGet("entries/{address}")] - public ActionResult IsAccessDenied(string address) + public ActionResult IsListed(string address) { - return _accessControlService.IsAccessDenied(new Address(address)); + return _accessControlService.IsListed(new Address(address)); } - [HttpPost("entries/{address}/deny")] - public ActionResult DenyAccess(string address) + [HttpPost("entries/add-tx-quota/{address}/{quota:int}")] + public ActionResult AddTxQuota(string address, int quota) { - _accessControlService.DenyAccess(new Address(address)); - return Ok(); - } - - [HttpPost("entries/{address}/allow")] - public ActionResult AllowAccess(string address) - { - _accessControlService.AllowAccess(new Address(address)); - return Ok(); - } - - [HttpPost("entries/add-tx-quota/{address}/{quota}")] - public ActionResult AddTxQuota(string address, string quota) - { - var txQuota = Convert.ToInt32(quota); - _accessControlService.AddTxQuota(new Address(address), txQuota); + _accessControlService.AddTxQuota(new Address(address), quota); return Ok(); } @@ -70,7 +55,7 @@ public ActionResult> ListBlockedAddresses(int offset, int limit) } return _accessControlService - .ListBlockedAddresses(offset, limit) + .ListTxQuotaAddresses(offset, limit) .Select(a => a.ToString()) .ToList(); } From d050657c4823b94605e59548bf4b967a280191b1 Mon Sep 17 00:00:00 2001 From: area363 Date: Wed, 1 Nov 2023 16:55:49 +0900 Subject: [PATCH 46/54] change IsAccessDenied to IsListed --- .../Services/RedisAccessControlService.cs | 4 ++-- .../Services/SQLiteAccessControlService.cs | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/NineChronicles.Headless/Services/RedisAccessControlService.cs b/NineChronicles.Headless/Services/RedisAccessControlService.cs index 3e6eb7287..912730b0f 100644 --- a/NineChronicles.Headless/Services/RedisAccessControlService.cs +++ b/NineChronicles.Headless/Services/RedisAccessControlService.cs @@ -16,13 +16,13 @@ public RedisAccessControlService(string storageUri) _db = redis.GetDatabase(); } - public bool IsAccessDenied(Address address) + public bool IsListed(Address address) { var result = _db.KeyExists(address.ToString()); if (result) { Log.ForContext("Source", nameof(IAccessControlService)) - .Debug("\"{Address}\" is access denied", address); + .Debug("\"{Address}\" is listed", address); } return result; diff --git a/NineChronicles.Headless/Services/SQLiteAccessControlService.cs b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs index 8304cf365..3b00b6e86 100644 --- a/NineChronicles.Headless/Services/SQLiteAccessControlService.cs +++ b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs @@ -9,11 +9,11 @@ namespace NineChronicles.Headless.Services public class SQLiteAccessControlService : IAccessControlService { private const string CreateTableSql = - "CREATE TABLE IF NOT EXISTS blocklist (address VARCHAR(42), quota INT)"; - private const string CheckAccessSql = - "SELECT EXISTS(SELECT 1 FROM blocklist WHERE address=@Address)"; + "CREATE TABLE IF NOT EXISTS txquotalist (address VARCHAR(42), quota INT)"; + private const string CheckAddressSql = + "SELECT EXISTS(SELECT 1 FROM txquotalist WHERE address=@Address)"; private const string GetTxQuotaSql = - "SELECT quota FROM blocklist WHERE address=@Address"; + "SELECT quota FROM txquotalist WHERE address=@Address"; protected readonly string _connectionString; @@ -28,13 +28,13 @@ public SQLiteAccessControlService(string connectionString) command.ExecuteNonQuery(); } - public bool IsAccessDenied(Address address) + public bool IsListed(Address address) { using var connection = new SqliteConnection(_connectionString); connection.Open(); using var command = connection.CreateCommand(); - command.CommandText = CheckAccessSql; + command.CommandText = CheckAddressSql; command.Parameters.AddWithValue("@Address", address.ToString()); var queryResult = command.ExecuteScalar(); @@ -44,7 +44,7 @@ public bool IsAccessDenied(Address address) if (result) { Log.ForContext("Source", nameof(IAccessControlService)) - .Debug("\"{Address}\" is access denied", address); + .Debug("\"{Address}\" is listed", address); } return result; From caa261582af5cd1325ab0e778483e677ef8fca43 Mon Sep 17 00:00:00 2001 From: area363 Date: Wed, 1 Nov 2023 20:47:35 +0900 Subject: [PATCH 47/54] bump lib9c --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 1d69b2f0e..012b96b2e 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 1d69b2f0ee8046a438725ac3902f008b128bebcd +Subproject commit 012b96b2ec15b2a16f931ad5f712be22467106ce From f9049fdb14089f7c06b7b98b6c8df6adba1fee86 Mon Sep 17 00:00:00 2001 From: area363 Date: Wed, 1 Nov 2023 20:47:43 +0900 Subject: [PATCH 48/54] remove IsListed --- .../AccessControlServiceController.cs | 4 ++-- .../Services/RedisAccessControlService.cs | 12 ---------- .../Services/SQLiteAccessControlService.cs | 24 ------------------- 3 files changed, 2 insertions(+), 38 deletions(-) diff --git a/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs index d18cdd8c9..1d7062c51 100644 --- a/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs +++ b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs @@ -18,9 +18,9 @@ public AccessControlServiceController(IMutableAccessControlService accessControl } [HttpGet("entries/{address}")] - public ActionResult IsListed(string address) + public ActionResult GetTxQuota(string address) { - return _accessControlService.IsListed(new Address(address)); + return _accessControlService.GetTxQuota(new Address(address)); } [HttpPost("entries/add-tx-quota/{address}/{quota:int}")] diff --git a/NineChronicles.Headless/Services/RedisAccessControlService.cs b/NineChronicles.Headless/Services/RedisAccessControlService.cs index 912730b0f..40769292d 100644 --- a/NineChronicles.Headless/Services/RedisAccessControlService.cs +++ b/NineChronicles.Headless/Services/RedisAccessControlService.cs @@ -16,18 +16,6 @@ public RedisAccessControlService(string storageUri) _db = redis.GetDatabase(); } - public bool IsListed(Address address) - { - var result = _db.KeyExists(address.ToString()); - if (result) - { - Log.ForContext("Source", nameof(IAccessControlService)) - .Debug("\"{Address}\" is listed", address); - } - - return result; - } - public int? GetTxQuota(Address address) { RedisValue result = _db.StringGet(address.ToString()); diff --git a/NineChronicles.Headless/Services/SQLiteAccessControlService.cs b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs index 3b00b6e86..f3e7263c8 100644 --- a/NineChronicles.Headless/Services/SQLiteAccessControlService.cs +++ b/NineChronicles.Headless/Services/SQLiteAccessControlService.cs @@ -10,8 +10,6 @@ public class SQLiteAccessControlService : IAccessControlService { private const string CreateTableSql = "CREATE TABLE IF NOT EXISTS txquotalist (address VARCHAR(42), quota INT)"; - private const string CheckAddressSql = - "SELECT EXISTS(SELECT 1 FROM txquotalist WHERE address=@Address)"; private const string GetTxQuotaSql = "SELECT quota FROM txquotalist WHERE address=@Address"; @@ -28,28 +26,6 @@ public SQLiteAccessControlService(string connectionString) command.ExecuteNonQuery(); } - public bool IsListed(Address address) - { - using var connection = new SqliteConnection(_connectionString); - connection.Open(); - - using var command = connection.CreateCommand(); - command.CommandText = CheckAddressSql; - command.Parameters.AddWithValue("@Address", address.ToString()); - - var queryResult = command.ExecuteScalar(); - - var result = queryResult is not null && (long)queryResult == 1; - - if (result) - { - Log.ForContext("Source", nameof(IAccessControlService)) - .Debug("\"{Address}\" is listed", address); - } - - return result; - } - public int? GetTxQuota(Address address) { using var connection = new SqliteConnection(_connectionString); From 7d3a51adca1d9bf5383d26f6a534c9852a2948d0 Mon Sep 17 00:00:00 2001 From: area363 Date: Wed, 1 Nov 2023 20:48:59 +0900 Subject: [PATCH 49/54] add limit to AddTxQuota --- .../Controllers/AccessControlServiceController.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs index 1d7062c51..6a92c77ab 100644 --- a/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs +++ b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs @@ -26,6 +26,12 @@ public AccessControlServiceController(IMutableAccessControlService accessControl [HttpPost("entries/add-tx-quota/{address}/{quota:int}")] public ActionResult AddTxQuota(string address, int quota) { + var maxLimit = 10; + if (quota > maxLimit) + { + return BadRequest($"The limit cannot exceed {maxLimit}."); + } + _accessControlService.AddTxQuota(new Address(address), quota); return Ok(); } From 5675e0341daf303564ce0237a602838d72de1a7b Mon Sep 17 00:00:00 2001 From: area363 Date: Thu, 2 Nov 2023 13:37:02 +0900 Subject: [PATCH 50/54] bump lib9c to development --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index 012b96b2e..b77fc8174 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit 012b96b2ec15b2a16f931ad5f712be22467106ce +Subproject commit b77fc8174af2f54bb276191d5a016009e600e7c7 From 088f8c83f8feeb188ac2d6ce87bdcd7f23d6bf69 Mon Sep 17 00:00:00 2001 From: area363 Date: Thu, 2 Nov 2023 13:37:09 +0900 Subject: [PATCH 51/54] unify terms --- .../Controllers/AccessControlServiceController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs index 6a92c77ab..40117f900 100644 --- a/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs +++ b/NineChronicles.Headless.AccessControlCenter/Controllers/AccessControlServiceController.cs @@ -26,10 +26,10 @@ public AccessControlServiceController(IMutableAccessControlService accessControl [HttpPost("entries/add-tx-quota/{address}/{quota:int}")] public ActionResult AddTxQuota(string address, int quota) { - var maxLimit = 10; - if (quota > maxLimit) + var maxQuota = 10; + if (quota > maxQuota) { - return BadRequest($"The limit cannot exceed {maxLimit}."); + return BadRequest($"The quota cannot exceed {maxQuota}."); } _accessControlService.AddTxQuota(new Address(address), quota); From 98341fc9dac6362fe03d9c60200864e12c373944 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Wed, 18 Oct 2023 10:21:42 +0900 Subject: [PATCH 52/54] Fix updated addresses for every render --- NineChronicles.Headless/ActionEvaluationPublisher.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/NineChronicles.Headless/ActionEvaluationPublisher.cs b/NineChronicles.Headless/ActionEvaluationPublisher.cs index 96f293c2b..623da1aa9 100644 --- a/NineChronicles.Headless/ActionEvaluationPublisher.cs +++ b/NineChronicles.Headless/ActionEvaluationPublisher.cs @@ -465,10 +465,15 @@ await _hub.BroadcastRenderBlockAsync( 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 updatedAddresses = diff.StateDiffs.Keys + .Union(diff.FungibleAssetValueDiffs.Select(kv => kv.Key.Item1)) + .Append(ev.Signer) + .ToHashSet(); + if (!TargetAddresses.Any(updatedAddresses.Contains)) { return; } + var encodeElapsedMilliseconds = stopwatch.ElapsedMilliseconds; var eval = new NCActionEvaluation(pa, ev.Signer, ev.BlockIndex, ev.OutputState, ev.Exception, ev.PreviousState, ev.RandomSeed, extra); From 1d6e0f8d0ede024b038a304f9daf5af5ac210f78 Mon Sep 17 00:00:00 2001 From: Say Cheong Date: Tue, 7 Nov 2023 12:09:38 +0900 Subject: [PATCH 53/54] Bump lib9c --- Lib9c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c b/Lib9c index b77fc8174..f8d681a6a 160000 --- a/Lib9c +++ b/Lib9c @@ -1 +1 @@ -Subproject commit b77fc8174af2f54bb276191d5a016009e600e7c7 +Subproject commit f8d681a6a622431eacb989477071613ce2b5fc5c From b0e1912abdf3d9208ffe86e30980e2a9d48982ce Mon Sep 17 00:00:00 2001 From: sonohoshi Date: Fri, 3 Nov 2023 13:44:02 +0900 Subject: [PATCH 54/54] fix: warning disable CS0618 about CombinationSlotState.UnlockStage --- .../GraphTypes/States/CombinationSlotStateType.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/NineChronicles.Headless/GraphTypes/States/CombinationSlotStateType.cs b/NineChronicles.Headless/GraphTypes/States/CombinationSlotStateType.cs index b6742f078..dbc19948c 100644 --- a/NineChronicles.Headless/GraphTypes/States/CombinationSlotStateType.cs +++ b/NineChronicles.Headless/GraphTypes/States/CombinationSlotStateType.cs @@ -16,10 +16,13 @@ public CombinationSlotStateType() nameof(CombinationSlotState.UnlockBlockIndex), description: "Block index at the combination slot can be usable.", resolve: context => context.Source.UnlockBlockIndex); +#pragma warning disable CS0618 Field>( - nameof(CombinationSlotState.UnlockStage), + nameof( + CombinationSlotState.UnlockStage), description: "Stage id at the combination slot unlock.", resolve: context => context.Source.UnlockStage); +#pragma warning restore CS0618 Field>( nameof(CombinationSlotState.StartBlockIndex), description: "Block index at the combination started.",