From 4e8714ab23d32a3f82c3d760decd3a1c569e22a0 Mon Sep 17 00:00:00 2001 From: Suho Lee Date: Sat, 17 Aug 2024 19:44:20 +0900 Subject: [PATCH 1/5] introduce: IRenderObservables --- .../Services/BlockChainServiceTest.cs | 16 ----- sdk/node/Libplanet.Node/Libplanet.Node.csproj | 1 + .../Services/BlockChainService.cs | 55 +++------------- .../Services/IBlockChainService.cs | 2 - .../Services/Renderer/IRenderObservables.cs | 12 ++++ .../Renderer/RenderActionErrorObservable.cs | 65 ++++++++++++++++++ .../Renderer/RenderActionObservable.cs | 64 ++++++++++++++++++ .../Renderer/RenderBlockEndObservable.cs | 65 ++++++++++++++++++ .../Renderer/RenderBlockObservable.cs | 66 +++++++++++++++++++ .../Services/Renderer/RenderObservables.cs | 17 +++++ 10 files changed, 300 insertions(+), 63 deletions(-) create mode 100644 sdk/node/Libplanet.Node/Services/Renderer/IRenderObservables.cs create mode 100644 sdk/node/Libplanet.Node/Services/Renderer/RenderActionErrorObservable.cs create mode 100644 sdk/node/Libplanet.Node/Services/Renderer/RenderActionObservable.cs create mode 100644 sdk/node/Libplanet.Node/Services/Renderer/RenderBlockEndObservable.cs create mode 100644 sdk/node/Libplanet.Node/Services/Renderer/RenderBlockObservable.cs create mode 100644 sdk/node/Libplanet.Node/Services/Renderer/RenderObservables.cs diff --git a/sdk/node/Libplanet.Node.Tests/Services/BlockChainServiceTest.cs b/sdk/node/Libplanet.Node.Tests/Services/BlockChainServiceTest.cs index 662821647cd..7e3a67e8316 100644 --- a/sdk/node/Libplanet.Node.Tests/Services/BlockChainServiceTest.cs +++ b/sdk/node/Libplanet.Node.Tests/Services/BlockChainServiceTest.cs @@ -16,20 +16,4 @@ public void Create_Test() Assert.Equal(1, blockChain.Count); } - - [Fact] - public async Task BlockAppended_TestAsync() - { - var serviceProvider = TestUtility.CreateServiceProvider(); - var blockChainService = serviceProvider.GetRequiredService(); - var blockChain = blockChainService.BlockChain; - - var args = await Assert.RaisesAsync( - handler => blockChainService.BlockAppended += handler, - handler => blockChainService.BlockAppended -= handler, - async () => await BlockChainUtility.AppendBlockAsync(blockChain)); - - Assert.Equal(args.Arguments.Block, blockChain.Tip); - Assert.Equal(2, blockChain.Count); - } } diff --git a/sdk/node/Libplanet.Node/Libplanet.Node.csproj b/sdk/node/Libplanet.Node/Libplanet.Node.csproj index a40c4b8d379..c4f5e4198eb 100644 --- a/sdk/node/Libplanet.Node/Libplanet.Node.csproj +++ b/sdk/node/Libplanet.Node/Libplanet.Node.csproj @@ -9,6 +9,7 @@ + diff --git a/sdk/node/Libplanet.Node/Services/BlockChainService.cs b/sdk/node/Libplanet.Node/Services/BlockChainService.cs index 48237101f28..b21f71867c9 100644 --- a/sdk/node/Libplanet.Node/Services/BlockChainService.cs +++ b/sdk/node/Libplanet.Node/Services/BlockChainService.cs @@ -1,8 +1,6 @@ -using System.Collections.Concurrent; using System.Collections.Immutable; using System.Diagnostics; using System.Numerics; -using System.Security.Cryptography; using Bencodex; using Bencodex.Types; using Libplanet.Action; @@ -11,9 +9,9 @@ using Libplanet.Blockchain; using Libplanet.Blockchain.Policies; using Libplanet.Blockchain.Renderers; -using Libplanet.Common; using Libplanet.Crypto; using Libplanet.Node.Options; +using Libplanet.Node.Services.Renderer; using Libplanet.Store; using Libplanet.Types.Blocks; using Libplanet.Types.Consensus; @@ -23,23 +21,20 @@ namespace Libplanet.Node.Services; -internal sealed class BlockChainService : IBlockChainService, IActionRenderer +internal sealed class BlockChainService : IBlockChainService { private static readonly Codec _codec = new(); - private readonly SynchronizationContext _synchronizationContext; private readonly ILogger _logger; private readonly BlockChain _blockChain; - private readonly ConcurrentDictionary _eventByTxId = []; - private readonly ConcurrentDictionary _exceptionByAction = []; public BlockChainService( IOptions genesisOptions, IStoreService storeService, IActionService actionService, PolicyService policyService, + IRenderObservables rendererService, ILogger logger) { - _synchronizationContext = SynchronizationContext.Current ?? new(); _logger = logger; _blockChain = CreateBlockChain( genesisOptions: genesisOptions.Value, @@ -48,47 +43,17 @@ public BlockChainService( actionLoader: actionService.ActionLoader, policyActionsRegistry: actionService.PolicyActionsRegistry, stagePolicy: policyService.StagePolicy, - renderers: [this]); + renderers: + [ + rendererService.RenderActionObservable, + rendererService.RenderActionErrorObservable, + rendererService.RenderBlockObservable, + rendererService.RenderBlockEndObservable, + ]); } - public event EventHandler? BlockAppended; - public BlockChain BlockChain => _blockChain; - void IRenderer.RenderBlock(Block oldTip, Block newTip) - { - } - - void IActionRenderer.RenderAction( - IValue action, ICommittedActionContext context, HashDigest nextState) - { - } - - void IActionRenderer.RenderActionError( - IValue action, ICommittedActionContext context, Exception exception) - { - _exceptionByAction.AddOrUpdate(action, exception, (_, _) => exception); - } - - void IActionRenderer.RenderBlockEnd(Block oldTip, Block newTip) - { - _synchronizationContext.Post(Action, state: null); - - void Action(object? state) - { - foreach (var transaction in newTip.Transactions) - { - if (_eventByTxId.TryGetValue(transaction.Id, out var manualResetEvent)) - { - manualResetEvent.Set(); - } - } - - _logger.LogInformation("#{Height}: Block appended", newTip.Index); - BlockAppended?.Invoke(this, new(newTip)); - } - } - private static BlockChain CreateBlockChain( GenesisOptions genesisOptions, IStore store, diff --git a/sdk/node/Libplanet.Node/Services/IBlockChainService.cs b/sdk/node/Libplanet.Node/Services/IBlockChainService.cs index 12b543bf49b..bc70ea2954b 100644 --- a/sdk/node/Libplanet.Node/Services/IBlockChainService.cs +++ b/sdk/node/Libplanet.Node/Services/IBlockChainService.cs @@ -4,7 +4,5 @@ namespace Libplanet.Node.Services; public interface IBlockChainService { - event EventHandler? BlockAppended; - BlockChain BlockChain { get; } } diff --git a/sdk/node/Libplanet.Node/Services/Renderer/IRenderObservables.cs b/sdk/node/Libplanet.Node/Services/Renderer/IRenderObservables.cs new file mode 100644 index 00000000000..e78fe79dc63 --- /dev/null +++ b/sdk/node/Libplanet.Node/Services/Renderer/IRenderObservables.cs @@ -0,0 +1,12 @@ +namespace Libplanet.Node.Services.Renderer; + +public interface IRenderObservables +{ + public RenderActionObservable RenderActionObservable { get; } + + public RenderActionErrorObservable RenderActionErrorObservable { get; } + + public RenderBlockObservable RenderBlockObservable { get; } + + public RenderBlockEndObservable RenderBlockEndObservable { get; } +} diff --git a/sdk/node/Libplanet.Node/Services/Renderer/RenderActionErrorObservable.cs b/sdk/node/Libplanet.Node/Services/Renderer/RenderActionErrorObservable.cs new file mode 100644 index 00000000000..80a422972de --- /dev/null +++ b/sdk/node/Libplanet.Node/Services/Renderer/RenderActionErrorObservable.cs @@ -0,0 +1,65 @@ +using System.Security.Cryptography; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Blockchain.Renderers; +using Libplanet.Common; +using Libplanet.Types.Blocks; +using R3; +using Output = ( + Bencodex.Types.IValue, + Libplanet.Action.ICommittedActionContext, + System.Exception); + +namespace Libplanet.Node.Services.Renderer; + +public class RenderActionErrorObservable : IObservable, IActionRenderer, IDisposable +{ + private readonly List> _observers = []; + private readonly BooleanDisposable _disposable = new BooleanDisposable(); + + public IDisposable Subscribe(IObserver observer) + { + _observers.Add(observer); + return _disposable; + } + + public void RenderBlock(Block oldTip, Block newTip) + { + } + + public void RenderAction( + IValue action, + ICommittedActionContext context, + HashDigest nextState) + { + } + + public void RenderActionError(IValue action, ICommittedActionContext context, Exception exception) + { + if (_disposable.IsDisposed) + { + return; + } + + foreach (IObserver observer in _observers) + { + observer.OnNext((action, context, exception)); + } + } + + public void RenderBlockEnd(Block oldTip, Block newTip) + { + } + + public void Dispose() + { + _disposable.Dispose(); + + foreach (IObserver observer in _observers) + { + observer.OnCompleted(); + } + + GC.SuppressFinalize(this); + } +} diff --git a/sdk/node/Libplanet.Node/Services/Renderer/RenderActionObservable.cs b/sdk/node/Libplanet.Node/Services/Renderer/RenderActionObservable.cs new file mode 100644 index 00000000000..369a6da98ba --- /dev/null +++ b/sdk/node/Libplanet.Node/Services/Renderer/RenderActionObservable.cs @@ -0,0 +1,64 @@ +using System.Security.Cryptography; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Blockchain.Renderers; +using Libplanet.Common; +using Libplanet.Types.Blocks; +using R3; +using Output = ( + Bencodex.Types.IValue, + Libplanet.Action.ICommittedActionContext, + Libplanet.Common.HashDigest); + +namespace Libplanet.Node.Services.Renderer; + +public class RenderActionObservable : IObservable, IActionRenderer, IDisposable +{ + private readonly List> _observers = []; + private readonly BooleanDisposable _disposable = new BooleanDisposable(); + + public IDisposable Subscribe(IObserver observer) + { + _observers.Add(observer); + return _disposable; + } + + public void RenderBlock(Block oldTip, Block newTip) + { + } + + public void RenderAction( + IValue action, + ICommittedActionContext context, + HashDigest nextState) + { + if (_disposable.IsDisposed) + { + return; + } + + foreach (IObserver observer in _observers) + { + observer.OnNext((action, context, nextState)); + } + } + + public void RenderActionError(IValue action, ICommittedActionContext context, Exception exception) + { + } + + public void RenderBlockEnd(Block oldTip, Block newTip) + { + } + + public void Dispose() + { + _disposable.Dispose(); + foreach (IObserver observer in _observers) + { + observer.OnCompleted(); + } + + GC.SuppressFinalize(this); + } +} diff --git a/sdk/node/Libplanet.Node/Services/Renderer/RenderBlockEndObservable.cs b/sdk/node/Libplanet.Node/Services/Renderer/RenderBlockEndObservable.cs new file mode 100644 index 00000000000..2bc371c8c00 --- /dev/null +++ b/sdk/node/Libplanet.Node/Services/Renderer/RenderBlockEndObservable.cs @@ -0,0 +1,65 @@ +using System.Security.Cryptography; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Blockchain.Renderers; +using Libplanet.Common; +using Libplanet.Types.Blocks; +using R3; + +namespace Libplanet.Node.Services.Renderer; + +public class RenderBlockEndObservable : IObservable<(Block, Block)>, IActionRenderer, IDisposable +{ + private readonly List> _observers + = new List>(); + + private readonly BooleanDisposable _disposable = new BooleanDisposable(); + + public IDisposable Subscribe(IObserver<(Block, Block)> observer) + { + _observers.Add(observer); + return _disposable; + } + + public void RenderBlock(Block oldTip, Block newTip) + { + } + + public void RenderAction( + IValue action, + ICommittedActionContext context, + HashDigest nextState) + { + } + + public void RenderActionError( + IValue action, + ICommittedActionContext context, + Exception exception) + { + } + + public void RenderBlockEnd(Block oldTip, Block newTip) + { + if (_disposable.IsDisposed) + { + return; + } + + foreach (IObserver<(Block, Block)> observer in _observers) + { + observer.OnNext((oldTip, newTip)); + } + } + + public void Dispose() + { + _disposable.Dispose(); + foreach (IObserver<(Block, Block)> observer in _observers) + { + observer.OnCompleted(); + } + + GC.SuppressFinalize(this); + } +} diff --git a/sdk/node/Libplanet.Node/Services/Renderer/RenderBlockObservable.cs b/sdk/node/Libplanet.Node/Services/Renderer/RenderBlockObservable.cs new file mode 100644 index 00000000000..07d02580f08 --- /dev/null +++ b/sdk/node/Libplanet.Node/Services/Renderer/RenderBlockObservable.cs @@ -0,0 +1,66 @@ +using System.Security.Cryptography; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Blockchain.Renderers; +using Libplanet.Common; +using Libplanet.Types.Blocks; +using R3; + +namespace Libplanet.Node.Services.Renderer; + +public class RenderBlockObservable : IObservable<(Block, Block)>, IActionRenderer, IDisposable +{ + private readonly List> _observers + = new List>(); + + private readonly BooleanDisposable _disposable = new BooleanDisposable(); + + public IDisposable Subscribe(IObserver<(Block, Block)> observer) + { + _observers.Add(observer); + return _disposable; + } + + public void RenderBlock(Block oldTip, Block newTip) + { + if (_disposable.IsDisposed) + { + return; + } + + foreach (IObserver<(Block, Block)> observer in _observers) + { + observer.OnNext((oldTip, newTip)); + } + } + + public void RenderAction( + IValue action, + ICommittedActionContext context, + HashDigest nextState) + { + } + + + public void RenderActionError( + IValue action, + ICommittedActionContext context, + Exception exception) + { + } + + public void RenderBlockEnd(Block oldTip, Block newTip) + { + } + + public void Dispose() + { + _disposable.Dispose(); + foreach (IObserver<(Block, Block)> observer in _observers) + { + observer.OnCompleted(); + } + + GC.SuppressFinalize(this); + } +} diff --git a/sdk/node/Libplanet.Node/Services/Renderer/RenderObservables.cs b/sdk/node/Libplanet.Node/Services/Renderer/RenderObservables.cs new file mode 100644 index 00000000000..bee9c5f510f --- /dev/null +++ b/sdk/node/Libplanet.Node/Services/Renderer/RenderObservables.cs @@ -0,0 +1,17 @@ +namespace Libplanet.Node.Services.Renderer; + +public class RenderObservables( + RenderActionObservable renderActionObservable, + RenderActionErrorObservable renderActionErrorObservable, + RenderBlockObservable renderBlockObservable, + RenderBlockEndObservable renderBlockEndObservable) + : IRenderObservables +{ + public RenderActionObservable RenderActionObservable { get; } = renderActionObservable; + + public RenderActionErrorObservable RenderActionErrorObservable { get; } = renderActionErrorObservable; + + public RenderBlockObservable RenderBlockObservable { get; } = renderBlockObservable; + + public RenderBlockEndObservable RenderBlockEndObservable { get; } = renderBlockEndObservable; +} From 4907795f71d4e19d9ac510ce5df4307306850f8b Mon Sep 17 00:00:00 2001 From: s2quake Date: Fri, 13 Sep 2024 15:45:30 +0900 Subject: [PATCH 2/5] chore: Remove unused code and clean code --- .../Explorer/ExplorerOptions.cs | 2 +- sdk/node/Libplanet.Node/DumbAction.cs | 18 ----- .../Libplanet.Node/Options/IEnabledOptions.cs | 6 -- .../Options/Schema/OptionsSchemaGenerator.cs | 4 -- .../Libplanet.Node/Options/SoloOptions.cs | 2 +- .../Libplanet.Node/Options/SwarmOptions.cs | 2 +- .../Options/ValidatorOptions.cs | 2 +- .../Options/ValidatorOptionsConfigurator.cs | 2 - .../Libplanet.Node/Services/BlockEventArgs.cs | 8 --- .../Services/IActionLoaderProvider.cs | 8 --- .../Services/Renderer/IRenderObservables.cs | 12 ---- .../Renderer/RenderActionErrorObservable.cs | 65 ------------------ .../Renderer/RenderActionObservable.cs | 64 ------------------ .../Renderer/RenderBlockEndObservable.cs | 65 ------------------ .../Renderer/RenderBlockObservable.cs | 66 ------------------- .../Services/Renderer/RenderObservables.cs | 17 ----- 16 files changed, 4 insertions(+), 339 deletions(-) delete mode 100644 sdk/node/Libplanet.Node/DumbAction.cs delete mode 100644 sdk/node/Libplanet.Node/Options/IEnabledOptions.cs delete mode 100644 sdk/node/Libplanet.Node/Services/BlockEventArgs.cs delete mode 100644 sdk/node/Libplanet.Node/Services/IActionLoaderProvider.cs delete mode 100644 sdk/node/Libplanet.Node/Services/Renderer/IRenderObservables.cs delete mode 100644 sdk/node/Libplanet.Node/Services/Renderer/RenderActionErrorObservable.cs delete mode 100644 sdk/node/Libplanet.Node/Services/Renderer/RenderActionObservable.cs delete mode 100644 sdk/node/Libplanet.Node/Services/Renderer/RenderBlockEndObservable.cs delete mode 100644 sdk/node/Libplanet.Node/Services/Renderer/RenderBlockObservable.cs delete mode 100644 sdk/node/Libplanet.Node/Services/Renderer/RenderObservables.cs diff --git a/sdk/node/Libplanet.Node.Executable/Explorer/ExplorerOptions.cs b/sdk/node/Libplanet.Node.Executable/Explorer/ExplorerOptions.cs index f09b95a6832..7a06d6b8715 100644 --- a/sdk/node/Libplanet.Node.Executable/Explorer/ExplorerOptions.cs +++ b/sdk/node/Libplanet.Node.Executable/Explorer/ExplorerOptions.cs @@ -4,7 +4,7 @@ namespace Libplanet.Node.API.Explorer; [Options(Position)] -public sealed class ExplorerOptions : OptionsBase, IEnabledOptions +public sealed class ExplorerOptions : OptionsBase { public const string Position = "Explorer"; diff --git a/sdk/node/Libplanet.Node/DumbAction.cs b/sdk/node/Libplanet.Node/DumbAction.cs deleted file mode 100644 index 1df69398acd..00000000000 --- a/sdk/node/Libplanet.Node/DumbAction.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Bencodex.Types; -using Libplanet.Action; -using Libplanet.Action.State; - -namespace Libplanet.Node; - -public sealed class DumbAction : IAction -{ - public IValue PlainValue => Dictionary.Empty; - - public void LoadPlainValue(IValue plainValue) - { - // Do nothing. - } - - public IWorld Execute(IActionContext context) => - context.PreviousState; -} diff --git a/sdk/node/Libplanet.Node/Options/IEnabledOptions.cs b/sdk/node/Libplanet.Node/Options/IEnabledOptions.cs deleted file mode 100644 index 7b1328fe1ed..00000000000 --- a/sdk/node/Libplanet.Node/Options/IEnabledOptions.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Libplanet.Node.Options; - -public interface IEnabledOptions -{ - bool IsEnabled { get; } -} diff --git a/sdk/node/Libplanet.Node/Options/Schema/OptionsSchemaGenerator.cs b/sdk/node/Libplanet.Node/Options/Schema/OptionsSchemaGenerator.cs index eeddd6fcd8c..a0f7dcb334e 100644 --- a/sdk/node/Libplanet.Node/Options/Schema/OptionsSchemaGenerator.cs +++ b/sdk/node/Libplanet.Node/Options/Schema/OptionsSchemaGenerator.cs @@ -1,8 +1,4 @@ -using System.ComponentModel; using System.ComponentModel.DataAnnotations; -using System.Globalization; -using System.Reflection; -using System.Runtime.Serialization; using Libplanet.Node.DataAnnotations; using Namotion.Reflection; using NJsonSchema; diff --git a/sdk/node/Libplanet.Node/Options/SoloOptions.cs b/sdk/node/Libplanet.Node/Options/SoloOptions.cs index 85ac33c3017..f275a276de0 100644 --- a/sdk/node/Libplanet.Node/Options/SoloOptions.cs +++ b/sdk/node/Libplanet.Node/Options/SoloOptions.cs @@ -4,7 +4,7 @@ namespace Libplanet.Node.Options; [Options(Position)] -public class SoloOptions : OptionsBase, IEnabledOptions +public class SoloOptions : OptionsBase { public const string Position = "Solo"; diff --git a/sdk/node/Libplanet.Node/Options/SwarmOptions.cs b/sdk/node/Libplanet.Node/Options/SwarmOptions.cs index b51b41aeeed..2df82ced0ab 100644 --- a/sdk/node/Libplanet.Node/Options/SwarmOptions.cs +++ b/sdk/node/Libplanet.Node/Options/SwarmOptions.cs @@ -4,7 +4,7 @@ namespace Libplanet.Node.Options; [Options(Position)] -public sealed class SwarmOptions : AppProtocolOptionsBase, IEnabledOptions +public sealed class SwarmOptions : AppProtocolOptionsBase { public const string Position = "Swarm"; diff --git a/sdk/node/Libplanet.Node/Options/ValidatorOptions.cs b/sdk/node/Libplanet.Node/Options/ValidatorOptions.cs index 68059fba4b5..c6a336abeec 100644 --- a/sdk/node/Libplanet.Node/Options/ValidatorOptions.cs +++ b/sdk/node/Libplanet.Node/Options/ValidatorOptions.cs @@ -4,7 +4,7 @@ namespace Libplanet.Node.Options; [Options(Position)] -public sealed class ValidatorOptions : AppProtocolOptionsBase, IEnabledOptions +public sealed class ValidatorOptions : AppProtocolOptionsBase { public const string Position = "Validator"; diff --git a/sdk/node/Libplanet.Node/Options/ValidatorOptionsConfigurator.cs b/sdk/node/Libplanet.Node/Options/ValidatorOptionsConfigurator.cs index 3f391d21552..e100cdc62bd 100644 --- a/sdk/node/Libplanet.Node/Options/ValidatorOptionsConfigurator.cs +++ b/sdk/node/Libplanet.Node/Options/ValidatorOptionsConfigurator.cs @@ -1,5 +1,3 @@ -using Libplanet.Common; -using Libplanet.Crypto; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; diff --git a/sdk/node/Libplanet.Node/Services/BlockEventArgs.cs b/sdk/node/Libplanet.Node/Services/BlockEventArgs.cs deleted file mode 100644 index f722df3b3df..00000000000 --- a/sdk/node/Libplanet.Node/Services/BlockEventArgs.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Libplanet.Types.Blocks; - -namespace Libplanet.Node.Services; - -public sealed class BlockEventArgs(Block block) : EventArgs -{ - public Block Block { get; } = block; -} diff --git a/sdk/node/Libplanet.Node/Services/IActionLoaderProvider.cs b/sdk/node/Libplanet.Node/Services/IActionLoaderProvider.cs deleted file mode 100644 index 2d0918342a6..00000000000 --- a/sdk/node/Libplanet.Node/Services/IActionLoaderProvider.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Libplanet.Action.Loader; - -namespace Libplanet.Node.Services; - -public interface IActionLoaderProvider -{ - IActionLoader GetActionLoader(); -} diff --git a/sdk/node/Libplanet.Node/Services/Renderer/IRenderObservables.cs b/sdk/node/Libplanet.Node/Services/Renderer/IRenderObservables.cs deleted file mode 100644 index e78fe79dc63..00000000000 --- a/sdk/node/Libplanet.Node/Services/Renderer/IRenderObservables.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Libplanet.Node.Services.Renderer; - -public interface IRenderObservables -{ - public RenderActionObservable RenderActionObservable { get; } - - public RenderActionErrorObservable RenderActionErrorObservable { get; } - - public RenderBlockObservable RenderBlockObservable { get; } - - public RenderBlockEndObservable RenderBlockEndObservable { get; } -} diff --git a/sdk/node/Libplanet.Node/Services/Renderer/RenderActionErrorObservable.cs b/sdk/node/Libplanet.Node/Services/Renderer/RenderActionErrorObservable.cs deleted file mode 100644 index 80a422972de..00000000000 --- a/sdk/node/Libplanet.Node/Services/Renderer/RenderActionErrorObservable.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Security.Cryptography; -using Bencodex.Types; -using Libplanet.Action; -using Libplanet.Blockchain.Renderers; -using Libplanet.Common; -using Libplanet.Types.Blocks; -using R3; -using Output = ( - Bencodex.Types.IValue, - Libplanet.Action.ICommittedActionContext, - System.Exception); - -namespace Libplanet.Node.Services.Renderer; - -public class RenderActionErrorObservable : IObservable, IActionRenderer, IDisposable -{ - private readonly List> _observers = []; - private readonly BooleanDisposable _disposable = new BooleanDisposable(); - - public IDisposable Subscribe(IObserver observer) - { - _observers.Add(observer); - return _disposable; - } - - public void RenderBlock(Block oldTip, Block newTip) - { - } - - public void RenderAction( - IValue action, - ICommittedActionContext context, - HashDigest nextState) - { - } - - public void RenderActionError(IValue action, ICommittedActionContext context, Exception exception) - { - if (_disposable.IsDisposed) - { - return; - } - - foreach (IObserver observer in _observers) - { - observer.OnNext((action, context, exception)); - } - } - - public void RenderBlockEnd(Block oldTip, Block newTip) - { - } - - public void Dispose() - { - _disposable.Dispose(); - - foreach (IObserver observer in _observers) - { - observer.OnCompleted(); - } - - GC.SuppressFinalize(this); - } -} diff --git a/sdk/node/Libplanet.Node/Services/Renderer/RenderActionObservable.cs b/sdk/node/Libplanet.Node/Services/Renderer/RenderActionObservable.cs deleted file mode 100644 index 369a6da98ba..00000000000 --- a/sdk/node/Libplanet.Node/Services/Renderer/RenderActionObservable.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System.Security.Cryptography; -using Bencodex.Types; -using Libplanet.Action; -using Libplanet.Blockchain.Renderers; -using Libplanet.Common; -using Libplanet.Types.Blocks; -using R3; -using Output = ( - Bencodex.Types.IValue, - Libplanet.Action.ICommittedActionContext, - Libplanet.Common.HashDigest); - -namespace Libplanet.Node.Services.Renderer; - -public class RenderActionObservable : IObservable, IActionRenderer, IDisposable -{ - private readonly List> _observers = []; - private readonly BooleanDisposable _disposable = new BooleanDisposable(); - - public IDisposable Subscribe(IObserver observer) - { - _observers.Add(observer); - return _disposable; - } - - public void RenderBlock(Block oldTip, Block newTip) - { - } - - public void RenderAction( - IValue action, - ICommittedActionContext context, - HashDigest nextState) - { - if (_disposable.IsDisposed) - { - return; - } - - foreach (IObserver observer in _observers) - { - observer.OnNext((action, context, nextState)); - } - } - - public void RenderActionError(IValue action, ICommittedActionContext context, Exception exception) - { - } - - public void RenderBlockEnd(Block oldTip, Block newTip) - { - } - - public void Dispose() - { - _disposable.Dispose(); - foreach (IObserver observer in _observers) - { - observer.OnCompleted(); - } - - GC.SuppressFinalize(this); - } -} diff --git a/sdk/node/Libplanet.Node/Services/Renderer/RenderBlockEndObservable.cs b/sdk/node/Libplanet.Node/Services/Renderer/RenderBlockEndObservable.cs deleted file mode 100644 index 2bc371c8c00..00000000000 --- a/sdk/node/Libplanet.Node/Services/Renderer/RenderBlockEndObservable.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Security.Cryptography; -using Bencodex.Types; -using Libplanet.Action; -using Libplanet.Blockchain.Renderers; -using Libplanet.Common; -using Libplanet.Types.Blocks; -using R3; - -namespace Libplanet.Node.Services.Renderer; - -public class RenderBlockEndObservable : IObservable<(Block, Block)>, IActionRenderer, IDisposable -{ - private readonly List> _observers - = new List>(); - - private readonly BooleanDisposable _disposable = new BooleanDisposable(); - - public IDisposable Subscribe(IObserver<(Block, Block)> observer) - { - _observers.Add(observer); - return _disposable; - } - - public void RenderBlock(Block oldTip, Block newTip) - { - } - - public void RenderAction( - IValue action, - ICommittedActionContext context, - HashDigest nextState) - { - } - - public void RenderActionError( - IValue action, - ICommittedActionContext context, - Exception exception) - { - } - - public void RenderBlockEnd(Block oldTip, Block newTip) - { - if (_disposable.IsDisposed) - { - return; - } - - foreach (IObserver<(Block, Block)> observer in _observers) - { - observer.OnNext((oldTip, newTip)); - } - } - - public void Dispose() - { - _disposable.Dispose(); - foreach (IObserver<(Block, Block)> observer in _observers) - { - observer.OnCompleted(); - } - - GC.SuppressFinalize(this); - } -} diff --git a/sdk/node/Libplanet.Node/Services/Renderer/RenderBlockObservable.cs b/sdk/node/Libplanet.Node/Services/Renderer/RenderBlockObservable.cs deleted file mode 100644 index 07d02580f08..00000000000 --- a/sdk/node/Libplanet.Node/Services/Renderer/RenderBlockObservable.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Security.Cryptography; -using Bencodex.Types; -using Libplanet.Action; -using Libplanet.Blockchain.Renderers; -using Libplanet.Common; -using Libplanet.Types.Blocks; -using R3; - -namespace Libplanet.Node.Services.Renderer; - -public class RenderBlockObservable : IObservable<(Block, Block)>, IActionRenderer, IDisposable -{ - private readonly List> _observers - = new List>(); - - private readonly BooleanDisposable _disposable = new BooleanDisposable(); - - public IDisposable Subscribe(IObserver<(Block, Block)> observer) - { - _observers.Add(observer); - return _disposable; - } - - public void RenderBlock(Block oldTip, Block newTip) - { - if (_disposable.IsDisposed) - { - return; - } - - foreach (IObserver<(Block, Block)> observer in _observers) - { - observer.OnNext((oldTip, newTip)); - } - } - - public void RenderAction( - IValue action, - ICommittedActionContext context, - HashDigest nextState) - { - } - - - public void RenderActionError( - IValue action, - ICommittedActionContext context, - Exception exception) - { - } - - public void RenderBlockEnd(Block oldTip, Block newTip) - { - } - - public void Dispose() - { - _disposable.Dispose(); - foreach (IObserver<(Block, Block)> observer in _observers) - { - observer.OnCompleted(); - } - - GC.SuppressFinalize(this); - } -} diff --git a/sdk/node/Libplanet.Node/Services/Renderer/RenderObservables.cs b/sdk/node/Libplanet.Node/Services/Renderer/RenderObservables.cs deleted file mode 100644 index bee9c5f510f..00000000000 --- a/sdk/node/Libplanet.Node/Services/Renderer/RenderObservables.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Libplanet.Node.Services.Renderer; - -public class RenderObservables( - RenderActionObservable renderActionObservable, - RenderActionErrorObservable renderActionErrorObservable, - RenderBlockObservable renderBlockObservable, - RenderBlockEndObservable renderBlockEndObservable) - : IRenderObservables -{ - public RenderActionObservable RenderActionObservable { get; } = renderActionObservable; - - public RenderActionErrorObservable RenderActionErrorObservable { get; } = renderActionErrorObservable; - - public RenderBlockObservable RenderBlockObservable { get; } = renderBlockObservable; - - public RenderBlockEndObservable RenderBlockEndObservable { get; } = renderBlockEndObservable; -} From 4521c16c2a3e15b9255534fa1c3d7152f48e257f Mon Sep 17 00:00:00 2001 From: s2quake Date: Fri, 13 Sep 2024 15:46:52 +0900 Subject: [PATCH 3/5] fix: Fix an issue with assembly reload --- .../Libplanet.Node/Actions/PluginLoader.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/sdk/node/Libplanet.Node/Actions/PluginLoader.cs b/sdk/node/Libplanet.Node/Actions/PluginLoader.cs index e4b9be28750..352c1e0e12e 100644 --- a/sdk/node/Libplanet.Node/Actions/PluginLoader.cs +++ b/sdk/node/Libplanet.Node/Actions/PluginLoader.cs @@ -1,4 +1,6 @@ +using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Runtime.Loader; using Libplanet.Action; using Libplanet.Action.Loader; @@ -37,8 +39,33 @@ private static T Create(Assembly assembly, string typeName) return obj; } + private static bool TryGetLoadedAssembly( + string modulePath, [MaybeNullWhen(false)] out Assembly assembly) + { + var loadedAssemblies = AssemblyLoadContext.All + .SelectMany(context => context.Assemblies) + .ToList(); + var comparison = StringComparison.OrdinalIgnoreCase; + var comparer = new Predicate( + assembly => string.Equals(assembly.Location, modulePath, comparison)); + + if (loadedAssemblies.Find(comparer) is Assembly loadedAssembly) + { + assembly = loadedAssembly; + return true; + } + + assembly = null; + return false; + } + private static Assembly LoadAssembly(string modulePath) { + if (TryGetLoadedAssembly(modulePath, out var assembly)) + { + return assembly; + } + var loadContext = new PluginLoadContext(modulePath); if (File.Exists(modulePath)) { From 23075225b33185f5af03312365fdfc1642895f09 Mon Sep 17 00:00:00 2001 From: s2quake Date: Fri, 13 Sep 2024 15:48:04 +0900 Subject: [PATCH 4/5] feat: Add IRendererService, and change event pattern to objervable pattern using R3 --- .../BlockChainRendererTracer.cs | 28 +++++ sdk/node/Libplanet.Node.Executable/Program.cs | 2 + .../LibplanetServicesExtensions.cs | 2 + .../Services/BlockChainService.cs | 44 +++---- .../Services/IRendererService.cs | 12 ++ .../Libplanet.Node/Services/ISwarmService.cs | 5 +- .../Services/RenderActionErrorInfo.cs | 9 ++ .../Services/RenderActionInfo.cs | 11 ++ .../Services/RenderBlockInfo.cs | 7 ++ .../Services/RendererService.cs | 117 ++++++++++++++++++ .../Libplanet.Node/Services/SwarmService.cs | 17 ++- 11 files changed, 217 insertions(+), 37 deletions(-) create mode 100644 sdk/node/Libplanet.Node.Executable/BlockChainRendererTracer.cs create mode 100644 sdk/node/Libplanet.Node/Services/IRendererService.cs create mode 100644 sdk/node/Libplanet.Node/Services/RenderActionErrorInfo.cs create mode 100644 sdk/node/Libplanet.Node/Services/RenderActionInfo.cs create mode 100644 sdk/node/Libplanet.Node/Services/RenderBlockInfo.cs create mode 100644 sdk/node/Libplanet.Node/Services/RendererService.cs diff --git a/sdk/node/Libplanet.Node.Executable/BlockChainRendererTracer.cs b/sdk/node/Libplanet.Node.Executable/BlockChainRendererTracer.cs new file mode 100644 index 00000000000..7fc3566939b --- /dev/null +++ b/sdk/node/Libplanet.Node.Executable/BlockChainRendererTracer.cs @@ -0,0 +1,28 @@ +using Libplanet.Node.Services; + +namespace Libplanet.Node.API; + +internal sealed class BlockChainRendererTracer( + IRendererService rendererService, ILogger logger) + : IHostedService +{ + private readonly ILogger _logger = logger; + private IDisposable? _observer; + + public Task StartAsync(CancellationToken cancellationToken) + { + rendererService.RenderBlockEnd.Subscribe( + info => _logger.LogInformation( + "-Pattern2- #{Height} Block end: {Hash}", + info.NewTip.Index, + info.NewTip.Hash)); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _observer?.Dispose(); + _observer = null; + return Task.CompletedTask; + } +} diff --git a/sdk/node/Libplanet.Node.Executable/Program.cs b/sdk/node/Libplanet.Node.Executable/Program.cs index 48aac2560f7..f825da970f3 100644 --- a/sdk/node/Libplanet.Node.Executable/Program.cs +++ b/sdk/node/Libplanet.Node.Executable/Program.cs @@ -1,3 +1,4 @@ +using Libplanet.Node.API; using Libplanet.Node.API.Explorer; using Libplanet.Node.API.Services; using Libplanet.Node.Extensions; @@ -27,6 +28,7 @@ builder.Services.AddGrpc(); builder.Services.AddGrpcReflection(); builder.Services.AddLibplanetNode(builder.Configuration); +builder.Services.AddHostedService(); if (builder.IsExplorerEnabled()) { diff --git a/sdk/node/Libplanet.Node.Extensions/LibplanetServicesExtensions.cs b/sdk/node/Libplanet.Node.Extensions/LibplanetServicesExtensions.cs index 27aa98428e1..e9634e78421 100644 --- a/sdk/node/Libplanet.Node.Extensions/LibplanetServicesExtensions.cs +++ b/sdk/node/Libplanet.Node.Extensions/LibplanetServicesExtensions.cs @@ -48,6 +48,8 @@ public static ILibplanetNodeBuilder AddLibplanetNode( services.AddSingleton(s => (IStoreService)s.GetRequiredService()); services.AddSingleton(); services.AddSingleton(s => (IActionService)s.GetRequiredService()); + services.AddSingleton(); + services.AddSingleton(s => (IRendererService)s.GetRequiredService()); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/sdk/node/Libplanet.Node/Services/BlockChainService.cs b/sdk/node/Libplanet.Node/Services/BlockChainService.cs index b21f71867c9..fa635248dd1 100644 --- a/sdk/node/Libplanet.Node/Services/BlockChainService.cs +++ b/sdk/node/Libplanet.Node/Services/BlockChainService.cs @@ -11,46 +11,30 @@ using Libplanet.Blockchain.Renderers; using Libplanet.Crypto; using Libplanet.Node.Options; -using Libplanet.Node.Services.Renderer; using Libplanet.Store; using Libplanet.Types.Blocks; using Libplanet.Types.Consensus; using Libplanet.Types.Tx; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Libplanet.Node.Services; -internal sealed class BlockChainService : IBlockChainService +internal sealed class BlockChainService( + IOptions genesisOptions, + IStoreService storeService, + IActionService actionService, + PolicyService policyService, + RendererService rendererService) : IBlockChainService { private static readonly Codec _codec = new(); - private readonly ILogger _logger; - private readonly BlockChain _blockChain; - - public BlockChainService( - IOptions genesisOptions, - IStoreService storeService, - IActionService actionService, - PolicyService policyService, - IRenderObservables rendererService, - ILogger logger) - { - _logger = logger; - _blockChain = CreateBlockChain( - genesisOptions: genesisOptions.Value, - store: storeService.Store, - stateStore: storeService.StateStore, - actionLoader: actionService.ActionLoader, - policyActionsRegistry: actionService.PolicyActionsRegistry, - stagePolicy: policyService.StagePolicy, - renderers: - [ - rendererService.RenderActionObservable, - rendererService.RenderActionErrorObservable, - rendererService.RenderBlockObservable, - rendererService.RenderBlockEndObservable, - ]); - } + private readonly BlockChain _blockChain = CreateBlockChain( + genesisOptions: genesisOptions.Value, + store: storeService.Store, + stateStore: storeService.StateStore, + actionLoader: actionService.ActionLoader, + policyActionsRegistry: actionService.PolicyActionsRegistry, + stagePolicy: policyService.StagePolicy, + renderers: [rendererService]); public BlockChain BlockChain => _blockChain; diff --git a/sdk/node/Libplanet.Node/Services/IRendererService.cs b/sdk/node/Libplanet.Node/Services/IRendererService.cs new file mode 100644 index 00000000000..2bf191341e9 --- /dev/null +++ b/sdk/node/Libplanet.Node/Services/IRendererService.cs @@ -0,0 +1,12 @@ +namespace Libplanet.Node.Services; + +public interface IRendererService +{ + IObservable RenderBlock { get; } + + IObservable RenderAction { get; } + + IObservable RenderActionError { get; } + + IObservable RenderBlockEnd { get; } +} diff --git a/sdk/node/Libplanet.Node/Services/ISwarmService.cs b/sdk/node/Libplanet.Node/Services/ISwarmService.cs index c2e8da534c3..07cc6782037 100644 --- a/sdk/node/Libplanet.Node/Services/ISwarmService.cs +++ b/sdk/node/Libplanet.Node/Services/ISwarmService.cs @@ -1,12 +1,13 @@ using Libplanet.Net; +using R3; namespace Libplanet.Node.Services; public interface ISwarmService { - public event EventHandler? Started; + IObservable Started { get; } - public event EventHandler? Stopped; + IObservable Stopped { get; } Swarm Swarm { get; } diff --git a/sdk/node/Libplanet.Node/Services/RenderActionErrorInfo.cs b/sdk/node/Libplanet.Node/Services/RenderActionErrorInfo.cs new file mode 100644 index 00000000000..eb917d1e3d3 --- /dev/null +++ b/sdk/node/Libplanet.Node/Services/RenderActionErrorInfo.cs @@ -0,0 +1,9 @@ +using Bencodex.Types; +using Libplanet.Action; + +namespace Libplanet.Node.Services; + +public readonly record struct RenderActionErrorInfo( + IValue Action, + ICommittedActionContext Context, + Exception Exception); diff --git a/sdk/node/Libplanet.Node/Services/RenderActionInfo.cs b/sdk/node/Libplanet.Node/Services/RenderActionInfo.cs new file mode 100644 index 00000000000..f433c13bf25 --- /dev/null +++ b/sdk/node/Libplanet.Node/Services/RenderActionInfo.cs @@ -0,0 +1,11 @@ +using System.Security.Cryptography; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Common; + +namespace Libplanet.Node.Services; + +public readonly record struct RenderActionInfo( + IValue Action, + ICommittedActionContext Context, + HashDigest NextState); diff --git a/sdk/node/Libplanet.Node/Services/RenderBlockInfo.cs b/sdk/node/Libplanet.Node/Services/RenderBlockInfo.cs new file mode 100644 index 00000000000..7d2c23cda11 --- /dev/null +++ b/sdk/node/Libplanet.Node/Services/RenderBlockInfo.cs @@ -0,0 +1,7 @@ +using Libplanet.Types.Blocks; + +namespace Libplanet.Node.Services; + +public readonly record struct RenderBlockInfo( + Block OldTip, + Block NewTip); diff --git a/sdk/node/Libplanet.Node/Services/RendererService.cs b/sdk/node/Libplanet.Node/Services/RendererService.cs new file mode 100644 index 00000000000..8925167cb04 --- /dev/null +++ b/sdk/node/Libplanet.Node/Services/RendererService.cs @@ -0,0 +1,117 @@ +using System.Security.Cryptography; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Blockchain.Renderers; +using Libplanet.Common; +using Libplanet.Types.Blocks; +using Microsoft.Extensions.Logging; +using R3; + +namespace Libplanet.Node.Services; + +internal sealed class RendererService : IRendererService, IActionRenderer, IDisposable +{ + private readonly Subject _renderBlock = new(); + private readonly Subject _renderAction = new(); + private readonly Subject _renderActionError = new(); + private readonly Subject _renderBlockEnd = new(); + private readonly SynchronizationContext _synchronizationContext; + private readonly ILogger _logger; + + private IObservable? _renderBlockObservable; + private IObservable? _renderActionObservable; + private IObservable? _renderActionErrorObservable; + private IObservable? _renderBlockEndObservable; + + public RendererService( + SynchronizationContext synchronizationContext, + ILogger logger) + { + _synchronizationContext = synchronizationContext; + _logger = logger; + _renderBlockObservable = _renderBlock.AsSystemObservable(); + _renderActionObservable = _renderAction.AsSystemObservable(); + _renderActionErrorObservable = _renderActionError.AsSystemObservable(); + _renderBlockEndObservable = _renderBlock.AsSystemObservable(); + } + + IObservable IRendererService.RenderBlock + => _renderBlockObservable ??= _renderBlock.AsSystemObservable(); + + IObservable IRendererService.RenderAction + => _renderActionObservable ??= _renderAction.AsSystemObservable(); + + IObservable IRendererService.RenderActionError + => _renderActionErrorObservable ??= _renderActionError.AsSystemObservable(); + + IObservable IRendererService.RenderBlockEnd + => _renderBlockEndObservable ??= _renderBlock.AsSystemObservable(); + + public void Dispose() + { + _renderBlock.Dispose(); + _renderAction.Dispose(); + _renderActionError.Dispose(); + _renderBlockEnd.Dispose(); + } + + void IActionRenderer.RenderAction( + IValue action, ICommittedActionContext context, HashDigest nextState) + { + _synchronizationContext.Post( + state => + { + _renderAction.OnNext(new(action, context, nextState)); + _logger.LogDebug( + "Rendered an action: {Action} {Context} {NextState}", + action, + context, + nextState); + }, + null); + } + + void IActionRenderer.RenderActionError( + IValue action, ICommittedActionContext context, Exception exception) + { + _synchronizationContext.Post( + state => + { + _renderActionError.OnNext(new(action, context, exception)); + _logger.LogError( + exception, + "Failed to render an action: {Action} {Context}", + action, + context); + }, + null); + } + + void IRenderer.RenderBlock(Block oldTip, Block newTip) + { + _synchronizationContext.Post( + state => + { + _renderBlock.OnNext(new(oldTip, newTip)); + _logger.LogDebug( + "Rendered a block: {OldTip} {NewTip}", + oldTip, + newTip); + }, + null); + } + + void IActionRenderer.RenderBlockEnd(Block oldTip, Block newTip) + { + _synchronizationContext.Post( + state => + { + _renderBlockEnd.OnNext(new(oldTip, newTip)); + _logger.LogDebug( + "Rendered a block end: {OldTip} {NewTip}", + oldTip, + newTip); + }, + null); + } +} diff --git a/sdk/node/Libplanet.Node/Services/SwarmService.cs b/sdk/node/Libplanet.Node/Services/SwarmService.cs index eed37c4357d..8b872d05fe9 100644 --- a/sdk/node/Libplanet.Node/Services/SwarmService.cs +++ b/sdk/node/Libplanet.Node/Services/SwarmService.cs @@ -1,4 +1,3 @@ -using System.Collections.Immutable; using System.Net; using Libplanet.Common; using Libplanet.Crypto; @@ -9,6 +8,7 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using R3; namespace Libplanet.Node.Services; @@ -22,15 +22,22 @@ internal sealed class SwarmService( private readonly SwarmOptions _options = options.Value; private readonly ValidatorOptions _validatorOptions = validatorOptions.Value; private readonly ILogger _logger = logger; + private readonly Subject _started = new(); + private readonly Subject _stopped = new(); + + private IObservable? _startedObservable; + private IObservable? _stoppedObservable; private Swarm? _swarm; private Task _startTask = Task.CompletedTask; private Seed? _blocksyncSeed; private Seed? _consensusSeed; - public event EventHandler? Started; + IObservable ISwarmService.Started + => _startedObservable ??= _started.AsSystemObservable(); - public event EventHandler? Stopped; + IObservable ISwarmService.Stopped + => _stoppedObservable ??= _stopped.AsSystemObservable(); public bool IsRunning => _swarm is not null; @@ -114,7 +121,7 @@ public async Task StartAsync(CancellationToken cancellationToken) _logger.LogDebug("Node.Swarm is starting: {Address}", _swarm.Address); await _swarm.BootstrapAsync(cancellationToken: default); _logger.LogDebug("Node.Swarm is bootstrapped: {Address}", _swarm.Address); - Started?.Invoke(this, EventArgs.Empty); + _started.OnNext(Unit.Default); } public async Task StopAsync(CancellationToken cancellationToken) @@ -145,7 +152,7 @@ public async Task StopAsync(CancellationToken cancellationToken) _blocksyncSeed = null; } - Stopped?.Invoke(this, EventArgs.Empty); + _stopped.OnNext(Unit.Default); } public async ValueTask DisposeAsync() From 586f0d5294b88bc6689bf866594d400f4993ef30 Mon Sep 17 00:00:00 2001 From: s2quake Date: Fri, 13 Sep 2024 15:48:54 +0900 Subject: [PATCH 5/5] test: Add test code for observable patterns of IRendererService and ISwarmService --- .../Libplanet.Node.Tests/BlockChainUtility.cs | 34 +++++- sdk/node/Libplanet.Node.Tests/DumbAction.cs | 36 ++++++ .../Libplanet.Node.Tests/DumbActionLoader.cs | 21 ++++ .../Services/RendererServiceTest.cs | 108 ++++++++++++++++++ .../Services/SwarmServiceTest.cs | 15 ++- sdk/node/Libplanet.Node.Tests/TestObserver.cs | 32 ++++++ 6 files changed, 238 insertions(+), 8 deletions(-) create mode 100644 sdk/node/Libplanet.Node.Tests/DumbAction.cs create mode 100644 sdk/node/Libplanet.Node.Tests/DumbActionLoader.cs create mode 100644 sdk/node/Libplanet.Node.Tests/Services/RendererServiceTest.cs create mode 100644 sdk/node/Libplanet.Node.Tests/TestObserver.cs diff --git a/sdk/node/Libplanet.Node.Tests/BlockChainUtility.cs b/sdk/node/Libplanet.Node.Tests/BlockChainUtility.cs index 2ca17d2a828..a399be00c24 100644 --- a/sdk/node/Libplanet.Node.Tests/BlockChainUtility.cs +++ b/sdk/node/Libplanet.Node.Tests/BlockChainUtility.cs @@ -1,6 +1,8 @@ +using Libplanet.Action; using Libplanet.Blockchain; using Libplanet.Crypto; using Libplanet.Types.Blocks; +using Libplanet.Types.Tx; namespace Libplanet.Node.Tests; @@ -14,8 +16,8 @@ public static async Task AppendBlockAsync(BlockChain blockChain, PrivateK var tip = blockChain.Tip; var height = tip.Index + 1; var block = blockChain.ProposeBlock( - privateKey, - blockChain.GetBlockCommit(tip.Hash)); + proposer: privateKey, + lastCommit: blockChain.GetBlockCommit(tip.Hash)); blockChain.Append( block, blockChain.GetBlockCommit(tip.Hash), @@ -30,4 +32,32 @@ public static async Task AppendBlockAsync(BlockChain blockChain, PrivateK return block; } + + public static void StageTransaction( + BlockChain blockChain, IAction[] actions) + => StageTransaction(blockChain, new PrivateKey(), actions); + + public static void StageTransaction( + BlockChain blockChain, PrivateKey privateKey, IAction[] actions) + { + var transaction = CreateTransaction(blockChain, privateKey, actions); + blockChain.StageTransaction(transaction); + } + + public static Transaction CreateTransaction( + BlockChain blockChain, IAction[] actions) + => CreateTransaction(blockChain, new PrivateKey(), actions); + + public static Transaction CreateTransaction( + BlockChain blockChain, PrivateKey privateKey, IAction[] actions) + { + var genesisBlock = blockChain.Genesis; + var nonce = blockChain.GetNextTxNonce(privateKey.Address); + var values = actions.Select(item => item.PlainValue).ToArray(); + return Transaction.Create( + nonce: nonce, + privateKey: privateKey, + genesisHash: genesisBlock.Hash, + actions: new TxActionList(values)); + } } diff --git a/sdk/node/Libplanet.Node.Tests/DumbAction.cs b/sdk/node/Libplanet.Node.Tests/DumbAction.cs new file mode 100644 index 00000000000..544314501ec --- /dev/null +++ b/sdk/node/Libplanet.Node.Tests/DumbAction.cs @@ -0,0 +1,36 @@ +using System.Diagnostics; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Action.State; + +namespace Libplanet.Node.Tests; + +public class DumbAction : IAction +{ + public string ErrorMessage { get; set; } = string.Empty; + + public IValue PlainValue => Dictionary.Empty + .Add("error_message", ErrorMessage); + + public void LoadPlainValue(IValue plainValue) + { + if (plainValue is Dictionary dictionary) + { + ErrorMessage = (Text)dictionary["error_message"]; + } + else + { + throw new UnreachableException("The plain value of DumbAction must be a dictionary."); + } + } + + public IWorld Execute(IActionContext context) + { + if (ErrorMessage != string.Empty) + { + throw new InvalidOperationException(ErrorMessage); + } + + return context.PreviousState; + } +} diff --git a/sdk/node/Libplanet.Node.Tests/DumbActionLoader.cs b/sdk/node/Libplanet.Node.Tests/DumbActionLoader.cs new file mode 100644 index 00000000000..1a8894d27d2 --- /dev/null +++ b/sdk/node/Libplanet.Node.Tests/DumbActionLoader.cs @@ -0,0 +1,21 @@ +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Action.Loader; +using Libplanet.Action.Sys; + +namespace Libplanet.Node.Tests; + +public sealed class DumbActionLoader : IActionLoader +{ + public IAction LoadAction(long index, IValue value) + { + if (Registry.IsSystemAction(value)) + { + return Registry.Deserialize(value); + } + + var action = new DumbAction(); + action.LoadPlainValue(value); + return action; + } +} diff --git a/sdk/node/Libplanet.Node.Tests/Services/RendererServiceTest.cs b/sdk/node/Libplanet.Node.Tests/Services/RendererServiceTest.cs new file mode 100644 index 00000000000..782fca91ae5 --- /dev/null +++ b/sdk/node/Libplanet.Node.Tests/Services/RendererServiceTest.cs @@ -0,0 +1,108 @@ +using Libplanet.Action; +using Libplanet.Node.Options; +using Libplanet.Node.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Libplanet.Node.Tests.Services; + +public class RendererServiceTest +{ + [Fact] + public async Task RenderBlock_TestAsync() + { + var serviceProvider = TestUtility.CreateServiceProvider(); + var blockChainService = serviceProvider.GetRequiredService(); + var rendererService = serviceProvider.GetRequiredService(); + var blockChain = blockChainService.BlockChain; + + using var observer = new TestObserver(rendererService.RenderBlock); + await Assert.RaisesAnyAsync( + attach: handler => observer.Next += handler, + detach: handler => observer.Next -= handler, + testCode: async () => await BlockChainUtility.AppendBlockAsync(blockChain)); + } + + [Fact] + public async Task RenderAction_TestAsync() + { + var settings = new Dictionary + { + [$"{ActionOptions.Position}:{nameof(ActionOptions.ModulePath)}"] + = typeof(DumbActionLoader).Assembly.Location, + [$"{ActionOptions.Position}:{nameof(ActionOptions.ActionLoaderType)}"] + = typeof(DumbActionLoader).FullName, + }; + + var serviceProvider = TestUtility.CreateServiceProvider(settings); + var blockChainService = serviceProvider.GetRequiredService(); + var rendererService = serviceProvider.GetRequiredService(); + var blockChain = blockChainService.BlockChain; + + var actions = new IAction[] + { + new DumbAction(), + new DumbAction(), + new DumbAction(), + }; + + using var observer = new TestObserver(rendererService.RenderAction); + await Assert.RaisesAnyAsync( + attach: handler => observer.Next += handler, + detach: handler => observer.Next -= handler, + testCode: async () => + { + BlockChainUtility.StageTransaction(blockChain, actions); + await BlockChainUtility.AppendBlockAsync(blockChain); + }); + } + + [Fact] + public async Task RenderActionError_TestAsync() + { + var settings = new Dictionary + { + [$"{ActionOptions.Position}:{nameof(ActionOptions.ModulePath)}"] + = typeof(DumbActionLoader).Assembly.Location, + [$"{ActionOptions.Position}:{nameof(ActionOptions.ActionLoaderType)}"] + = typeof(DumbActionLoader).FullName, + }; + + var serviceProvider = TestUtility.CreateServiceProvider(settings); + var blockChainService = serviceProvider.GetRequiredService(); + var rendererService = serviceProvider.GetRequiredService(); + var blockChain = blockChainService.BlockChain; + var errorMessage = "123"; + + var actions = new IAction[] + { + new DumbAction() { ErrorMessage = errorMessage }, + }; + + using var observer = new TestObserver( + rendererService.RenderActionError); + var errorInfo = await Assert.RaisesAnyAsync( + attach: handler => observer.Next += handler, + detach: handler => observer.Next -= handler, + testCode: async () => + { + BlockChainUtility.StageTransaction(blockChain, actions); + await BlockChainUtility.AppendBlockAsync(blockChain); + }); + Assert.Equal(errorMessage, errorInfo.Arguments.Exception.InnerException!.Message); + } + + [Fact] + public async Task RenderBlockEnd_TestAsync() + { + var serviceProvider = TestUtility.CreateServiceProvider(); + var blockChainService = serviceProvider.GetRequiredService(); + var rendererService = serviceProvider.GetRequiredService(); + var blockChain = blockChainService.BlockChain; + + using var observer = new TestObserver(rendererService.RenderBlockEnd); + await Assert.RaisesAnyAsync( + attach: handler => observer.Next += handler, + detach: handler => observer.Next -= handler, + testCode: async () => await BlockChainUtility.AppendBlockAsync(blockChain)); + } +} diff --git a/sdk/node/Libplanet.Node.Tests/Services/SwarmServiceTest.cs b/sdk/node/Libplanet.Node.Tests/Services/SwarmServiceTest.cs index 4817a9fc0bd..93cfadbfc14 100644 --- a/sdk/node/Libplanet.Node.Tests/Services/SwarmServiceTest.cs +++ b/sdk/node/Libplanet.Node.Tests/Services/SwarmServiceTest.cs @@ -1,6 +1,7 @@ using Libplanet.Node.Options; using Libplanet.Node.Services; using Microsoft.Extensions.DependencyInjection; +using R3; namespace Libplanet.Node.Tests.Services; @@ -33,9 +34,10 @@ public async Task Start_TestAsync() var swarmService = serviceProvider.GetRequiredService(); var swarmServiceHost = serviceProvider.GetRequiredService(); - await Assert.RaisesAnyAsync( - handler => swarmService.Started += handler, - handler => swarmService.Started -= handler, + using var observer = new TestObserver(swarmService.Started); + await Assert.RaisesAnyAsync( + attach: handler => observer.Next += handler, + detach: handler => observer.Next -= handler, async () => await swarmServiceHost.StartAsync(default)); Assert.True(swarmService.IsRunning); } @@ -60,9 +62,10 @@ public async Task Stop_TestAsync() var swarmServiceHost = serviceProvider.GetRequiredService(); await swarmServiceHost.StartAsync(default); - await Assert.RaisesAnyAsync( - handler => swarmService.Stopped += handler, - handler => swarmService.Stopped -= handler, + using var observer = new TestObserver(swarmService.Stopped); + await Assert.RaisesAnyAsync( + attach: handler => observer.Next += handler, + detach: handler => observer.Next -= handler, async () => await swarmServiceHost.StopAsync(default)); Assert.False(swarmService.IsRunning); } diff --git a/sdk/node/Libplanet.Node.Tests/TestObserver.cs b/sdk/node/Libplanet.Node.Tests/TestObserver.cs new file mode 100644 index 00000000000..43afb991ecb --- /dev/null +++ b/sdk/node/Libplanet.Node.Tests/TestObserver.cs @@ -0,0 +1,32 @@ +namespace Libplanet.Node.Tests; + +internal sealed class TestObserver : IObserver, IDisposable +{ + private IDisposable? _subscription; + + public TestObserver(IObservable observable) + { + _subscription = observable.Subscribe(this); + } + + public event EventHandler? Completed; + + public event EventHandler? Error; + + public event EventHandler? Next; + + public void Dispose() + { + if (_subscription is not null) + { + _subscription.Dispose(); + _subscription = null; + } + } + + void IObserver.OnCompleted() => Completed?.Invoke(this, EventArgs.Empty); + + void IObserver.OnError(Exception error) => Error?.Invoke(this, EventArgs.Empty); + + void IObserver.OnNext(T value) => Next?.Invoke(this, value); +}