Skip to content

Commit

Permalink
feat: Add IRendererService and implementations of IObservable
Browse files Browse the repository at this point in the history
  • Loading branch information
s2quake committed Aug 30, 2024
1 parent cc05f77 commit 2206874
Show file tree
Hide file tree
Showing 13 changed files with 265 additions and 38 deletions.
40 changes: 40 additions & 0 deletions sdk/node/Libplanet.Node.Executable/BlockChainRendererTracer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Libplanet.Node.Services;

namespace Libplanet.Node.API;

internal sealed class BlockChainRendererTracer(
IRendererService rendererService, ILogger<BlockChainRendererTracer> logger)
: IObserver<RenderBlockInfo>, IHostedService
{
private readonly ILogger<BlockChainRendererTracer> _logger = logger;
private IDisposable? _subscription;

public Task StartAsync(CancellationToken cancellationToken)
{
_subscription = rendererService.RenderBlockEnd.Subscribe(this);
return Task.CompletedTask;
}

public Task StopAsync(CancellationToken cancellationToken)
{
_subscription?.Dispose();
_subscription = null;
return Task.CompletedTask;
}

void IObserver<RenderBlockInfo>.OnCompleted()
{
}

void IObserver<RenderBlockInfo>.OnError(Exception error)
{
}

void IObserver<RenderBlockInfo>.OnNext(RenderBlockInfo value)
{
_logger.LogInformation(
"#{Height} Block end: {Hash}",
value.NewTip.Index,
value.NewTip.Hash);
}
}
2 changes: 2 additions & 0 deletions sdk/node/Libplanet.Node.Executable/Program.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Libplanet.Node.API;
using Libplanet.Node.API.Explorer;
using Libplanet.Node.API.Services;
using Libplanet.Node.Extensions;
Expand Down Expand Up @@ -25,6 +26,7 @@
builder.Services.AddGrpc();
builder.Services.AddGrpcReflection();
builder.Services.AddLibplanetNode(builder.Configuration);
builder.Services.AddHostedService<BlockChainRendererTracer>();

if (builder.IsExplorerEnabled())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public static ILibplanetNodeBuilder AddLibplanetNode(
services.AddSingleton<IConfigureOptions<SoloOptions>, SoloOptionsConfigurator>();

services.AddSingleton<PolicyService>();
services.AddSingleton<IRendererService, RendererService>();
services.AddSingleton(s => (RendererService)s.GetRequiredService<IRendererService>());
services.AddSingleton<IBlockChainService, BlockChainService>();
services.AddSingleton<IReadChainService, ReadChainService>();
services.AddSingleton<TransactionService>();
Expand Down
16 changes: 7 additions & 9 deletions sdk/node/Libplanet.Node.Tests/Services/BlockChainServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using Libplanet.Crypto;
using Libplanet.Node.Options;
using Libplanet.Node.Services;
using Libplanet.Node.Services.Renderer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
Expand Down Expand Up @@ -35,8 +34,7 @@ public void Create_Test()
storeOptions: storeOptions,
policyService: policyService,
actionLoaderProviders: [],
rendererService: Mock.Of<IRenderObservables>(),
logger: logger);
rendererService: new RendererService(new(), new NullLoggerFactory().CreateLogger<RendererService>()));
var blockChain = blockChainService.BlockChain;

Assert.Equal(1, blockChain.Count);
Expand All @@ -59,13 +57,13 @@ public async Task BlockAppended_TestAsync()
var blockChainService = serviceProvider.GetRequiredService<BlockChainService>();
var blockChain = blockChainService.BlockChain;

var args = await Assert.RaisesAsync<BlockEventArgs>(
handler => blockChainService.BlockAppended += handler,
handler => blockChainService.BlockAppended -= handler,
async () => await AppendBlockAsync(new PrivateKey(), blockChain));
// var args = await Assert.RaisesAsync<BlockEventArgs>(
// handler => blockChainService.BlockAppended += handler,
// handler => blockChainService.BlockAppended -= handler,
// async () => await AppendBlockAsync(new PrivateKey(), blockChain));

Check warning on line 63 in sdk/node/Libplanet.Node.Tests/Services/BlockChainServiceTest.cs

View workflow job for this annotation

GitHub Actions / docs

Remove this commented out code. (https://rules.sonarsource.com/csharp/RSPEC-125)

Check warning on line 63 in sdk/node/Libplanet.Node.Tests/Services/BlockChainServiceTest.cs

View workflow job for this annotation

GitHub Actions / docs

Remove this commented out code. (https://rules.sonarsource.com/csharp/RSPEC-125)

Assert.Equal(args.Arguments.Block, blockChain.Tip);
Assert.Equal(2, blockChain.Count);
// Assert.Equal(args.Arguments.Block, blockChain.Tip);
// Assert.Equal(2, blockChain.Count);
}

private static async Task AppendBlockAsync(PrivateKey privateKey, BlockChain blockChain)

Check warning on line 69 in sdk/node/Libplanet.Node.Tests/Services/BlockChainServiceTest.cs

View workflow job for this annotation

GitHub Actions / docs

Remove the unused private method 'AppendBlockAsync'. (https://rules.sonarsource.com/csharp/RSPEC-1144)

Check warning on line 69 in sdk/node/Libplanet.Node.Tests/Services/BlockChainServiceTest.cs

View workflow job for this annotation

GitHub Actions / docs

Remove the unused private method 'AppendBlockAsync'. (https://rules.sonarsource.com/csharp/RSPEC-1144)
Expand Down
41 changes: 12 additions & 29 deletions sdk/node/Libplanet.Node/Services/BlockChainService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using Libplanet.Blockchain.Renderers;
using Libplanet.Crypto;
using Libplanet.Node.Options;
using Libplanet.Node.Services.Renderer;
using Libplanet.RocksDBStore;
using Libplanet.Store;
using Libplanet.Store.Trie;
Expand All @@ -20,35 +19,19 @@

namespace Libplanet.Node.Services;

internal sealed class BlockChainService : IBlockChainService
internal sealed class BlockChainService(
IOptions<GenesisOptions> genesisOptions,
IOptions<StoreOptions> storeOptions,
PolicyService policyService,
IEnumerable<IActionLoaderProvider> actionLoaderProviders,
RendererService rendererService) : IBlockChainService
{
private readonly ILogger<BlockChainService> _logger;
private readonly BlockChain _blockChain;

public BlockChainService(
IOptions<GenesisOptions> genesisOptions,
IOptions<StoreOptions> storeOptions,
PolicyService policyService,
IEnumerable<IActionLoaderProvider> actionLoaderProviders,
IRenderObservables rendererService,
ILogger<BlockChainService> logger)
{
_logger = logger;
_blockChain = CreateBlockChain(
genesisOptions: genesisOptions.Value,
storeOptions: storeOptions.Value,
stagePolicy: policyService.StagePolicy,
renderers:
[
rendererService.RenderActionObservable,
rendererService.RenderActionErrorObservable,
rendererService.RenderBlockObservable,
rendererService.RenderBlockEndObservable,
],
actionLoaders: [.. actionLoaderProviders.Select(item => item.GetActionLoader())]);
}

public event EventHandler<BlockEventArgs>? BlockAppended;
private readonly BlockChain _blockChain = CreateBlockChain(
genesisOptions: genesisOptions.Value,
storeOptions: storeOptions.Value,
stagePolicy: policyService.StagePolicy,
renderers: [rendererService],
actionLoaders: [.. actionLoaderProviders.Select(item => item.GetActionLoader())]);

public BlockChain BlockChain => _blockChain;

Expand Down
1 change: 1 addition & 0 deletions sdk/node/Libplanet.Node/Services/IBlockChainService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Libplanet.Blockchain;
using Libplanet.Types.Blocks;

namespace Libplanet.Node.Services;

Expand Down
12 changes: 12 additions & 0 deletions sdk/node/Libplanet.Node/Services/IRendererService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Libplanet.Node.Services;

public interface IRendererService
{
IObservable<RenderBlockInfo> RenderBlock { get; }

IObservable<RenderActionInfo> RenderAction { get; }

IObservable<RenderActionErrorInfo> RenderActionError { get; }

IObservable<RenderBlockInfo> RenderBlockEnd { get; }
}
51 changes: 51 additions & 0 deletions sdk/node/Libplanet.Node/Services/Observable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
namespace Libplanet.Node.Services;

public sealed class Observable<T> : IObservable<T>, IDisposable
{
private readonly List<IObserver<T>> _observerList = [];
private bool _isDisposed;

public void Dispose()
{
if (!_isDisposed)
{
foreach (var observer in _observerList)
{
observer.OnCompleted();
}

_observerList.Clear();
_isDisposed = true;
}

GC.SuppressFinalize(this);
}

public void Invoke(T value)
{
_observerList.ForEach(observer => observer.OnNext(value));
}

public void InvokeError(Exception exception)
{
_observerList.ForEach(observer => observer.OnError(exception));
}

IDisposable IObservable<T>.Subscribe(IObserver<T> observer)
{
_observerList.Add(observer);
return new Unsubscriber(_observerList, observer);
}

private sealed class Unsubscriber(List<IObserver<T>> observerList, IObserver<T> observer)
: IDisposable
{
private readonly IObserver<T> _observer = observer;
private readonly List<IObserver<T>> _observerList = observerList;

public void Dispose()
{
_observerList.Remove(_observer);
}
}
}
16 changes: 16 additions & 0 deletions sdk/node/Libplanet.Node/Services/Observer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Libplanet.Node.Services;

public sealed class Observer<T> : IObserver<T>
{
public System.Action? Completed { get; init; }

public Action<Exception>? Error { get; init; }

public Action<T>? Next { get; init; }

void IObserver<T>.OnCompleted() => Completed?.Invoke();

void IObserver<T>.OnError(Exception error) => Error?.Invoke(error);

void IObserver<T>.OnNext(T value) => Next?.Invoke(value);
}
9 changes: 9 additions & 0 deletions sdk/node/Libplanet.Node/Services/RenderActionErrorInfo.cs
Original file line number Diff line number Diff line change
@@ -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);
11 changes: 11 additions & 0 deletions sdk/node/Libplanet.Node/Services/RenderActionInfo.cs
Original file line number Diff line number Diff line change
@@ -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<SHA256> NextState);
7 changes: 7 additions & 0 deletions sdk/node/Libplanet.Node/Services/RenderBlockInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using Libplanet.Types.Blocks;

namespace Libplanet.Node.Services;

public readonly record struct RenderBlockInfo(
Block OldTip,
Block NewTip);
95 changes: 95 additions & 0 deletions sdk/node/Libplanet.Node/Services/RendererService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
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;

namespace Libplanet.Node.Services;

internal sealed class RendererService(
SynchronizationContext synchronizationContext,
ILogger<RendererService> logger) : IRendererService, IActionRenderer, IDisposable
{
private readonly Observable<RenderBlockInfo> _renderBlock = new();
private readonly Observable<RenderActionInfo> _renderAction = new();
private readonly Observable<RenderActionErrorInfo> _renderActionError = new();
private readonly Observable<RenderBlockInfo> _renderBlockEnd = new();

IObservable<RenderBlockInfo> IRendererService.RenderBlock => _renderBlock;

IObservable<RenderActionInfo> IRendererService.RenderAction => _renderAction;

IObservable<RenderActionErrorInfo> IRendererService.RenderActionError => _renderActionError;

IObservable<RenderBlockInfo> IRendererService.RenderBlockEnd => _renderBlockEnd;

public void Dispose()
{
_renderBlock.Dispose();
_renderAction.Dispose();
_renderActionError.Dispose();
_renderBlockEnd.Dispose();
}

void IActionRenderer.RenderAction(
IValue action, ICommittedActionContext context, HashDigest<SHA256> nextState)
{
synchronizationContext.Post(
state =>
{
_renderAction.Invoke(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.Invoke(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.Invoke(new(oldTip, newTip));
logger.LogDebug(
"Rendered a block: {OldTip} {NewTip}",
oldTip,
newTip);
},
null);
}

void IActionRenderer.RenderBlockEnd(Block oldTip, Block newTip)
{
synchronizationContext.Post(
state =>
{
_renderBlockEnd.Invoke(new(oldTip, newTip));
logger.LogDebug(
"Rendered a block end: {OldTip} {NewTip}",
oldTip,
newTip);
},
null);
}
}

0 comments on commit 2206874

Please sign in to comment.