Skip to content

Commit

Permalink
feat: Add IActionService to load action loader from external assembly
Browse files Browse the repository at this point in the history
  • Loading branch information
s2quake committed Sep 5, 2024
1 parent 9e31fa0 commit 286cc35
Show file tree
Hide file tree
Showing 13 changed files with 227 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.7.0" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.7.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.7" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
</ItemGroup>

<ItemGroup>
Expand All @@ -26,6 +27,7 @@

<ItemGroup>
<ProjectReference Include="..\..\..\tools\Libplanet.Explorer.Executable\Libplanet.Explorer.Executable.csproj" />
<ProjectReference Include="..\..\..\src\Libplanet.Crypto.Secp256k1\Libplanet.Crypto.Secp256k1.csproj" />
<ProjectReference Include="..\Libplanet.Node.Extensions\Libplanet.Node.Extensions.csproj" />
<ProjectReference Include="..\Libplanet.Node\Libplanet.Node.csproj" />
</ItemGroup>
Expand Down
2 changes: 2 additions & 0 deletions sdk/node/Libplanet.Node.Executable/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using Libplanet.Node.API.Services;
using Libplanet.Node.Extensions;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Serilog;
using Serilog.Events;

var builder = WebApplication.CreateBuilder(args);
builder.Logging.AddConsole();
Expand Down
26 changes: 26 additions & 0 deletions sdk/node/Libplanet.Node.Executable/appsettings-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1095,6 +1095,22 @@
}
}
},
"Action": {
"title": "ActionOptions",
"type": "object",
"additionalProperties": false,
"properties": {
"ModulePath": {
"type": "string"
},
"ActionLoaderType": {
"type": "string"
},
"PolicyActionRegistryType": {
"type": "string"
}
}
},
"Genesis": {
"title": "GenesisOptions",
"type": "object",
Expand Down Expand Up @@ -1207,6 +1223,12 @@
"type": "string",
"description": "The endpoint of the node to block sync.",
"pattern": "^$|^(?:[0-9a-fA-F]{130}|[0-9a-fA-F]{66}),(?:(?:[a-zA-Z0-9\\-\\.]+)|(?:\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3})),\\d{1,5}$"
},
"TrustedAppProtocolVersionSigners": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
Expand Down Expand Up @@ -1252,6 +1274,10 @@
"description": "Type 'ExplorerOptions' does not have a description.",
"$ref": "#/definitions/Explorer"
},
"Action": {
"description": "Type 'ActionOptions' does not have a description.",
"$ref": "#/definitions/Action"
},
"Genesis": {
"description": "Options for the genesis block.",
"$ref": "#/definitions/Genesis"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public static ILibplanetNodeBuilder AddLibplanetNode(
.Bind(configuration.GetSection(StoreOptions.Position));
services.AddSingleton<IConfigureOptions<StoreOptions>, StoreOptionsConfigurator>();

services.AddOptions<ActionOptions>()
.Bind(configuration.GetSection(ActionOptions.Position));

services.AddOptions<SwarmOptions>()
.Bind(configuration.GetSection(SwarmOptions.Position));
services.AddSingleton<IConfigureOptions<SwarmOptions>, SwarmOptionsConfigurator>();
Expand All @@ -43,6 +46,8 @@ public static ILibplanetNodeBuilder AddLibplanetNode(
services.AddSingleton<PolicyService>();
services.AddSingleton<StoreService>();
services.AddSingleton(s => (IStoreService)s.GetRequiredService<StoreService>());
services.AddSingleton<ActionService>();
services.AddSingleton(s => (IActionService)s.GetRequiredService<ActionService>());
services.AddSingleton<IBlockChainService, BlockChainService>();
services.AddSingleton<IReadChainService, ReadChainService>();
services.AddSingleton<TransactionService>();
Expand Down
31 changes: 31 additions & 0 deletions sdk/node/Libplanet.Node/Actions/PluginLoadContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Reflection;
using System.Runtime.Loader;

namespace Libplanet.Node.Actions;

internal sealed class PluginLoadContext(string pluginPath) : AssemblyLoadContext
{
private readonly AssemblyDependencyResolver _resolver = new(pluginPath);

protected override Assembly? Load(AssemblyName assemblyName)
{
var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath is not null)
{
return LoadFromAssemblyPath(assemblyPath);
}

return null;
}

protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
var libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath is not null)
{
return LoadUnmanagedDllFromPath(libraryPath);
}

return IntPtr.Zero;
}
}
50 changes: 50 additions & 0 deletions sdk/node/Libplanet.Node/Actions/PluginLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.Reflection;
using Libplanet.Action;
using Libplanet.Action.Loader;

namespace Libplanet.Node.Actions;

internal static class PluginLoader
{
public static IActionLoader LoadActionLoader(string modulePath, string typeName)
{
var assembly = LoadAssembly(modulePath);
return Create<IActionLoader>(assembly, typeName);
}

public static IPolicyActionsRegistry LoadPolicyActionRegistry(
string relativePath, string typeName)
{
var assembly = LoadAssembly(relativePath);
return Create<IPolicyActionsRegistry>(assembly, typeName);
}

private static T Create<T>(Assembly assembly, string typeName)
where T : class
{
if (assembly.GetType(typeName) is not { } type)
{
throw new ApplicationException(
$"Can't find {typeName} in {assembly} from {assembly.Location}");
}

if (Activator.CreateInstance(type) is not T obj)
{
throw new ApplicationException(
$"Can't create an instance of {type} in {assembly} from {assembly.Location}");
}

return obj;
}

private static Assembly LoadAssembly(string modulePath)
{
var loadContext = new PluginLoadContext(modulePath);
if (Path.GetFileNameWithoutExtension(modulePath) is { } filename)
{
return loadContext.LoadFromAssemblyName(new AssemblyName(filename));
}

throw new ApplicationException($"Can't load plugin from {modulePath}");
}
}
3 changes: 2 additions & 1 deletion sdk/node/Libplanet.Node/Libplanet.Node.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<ItemGroup>
<PackageReference Include="Grpc.AspNetCore" Version="2.40.0"/>
<PackageReference Include="Grpc.AspNetCore" Version="2.40.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
Expand All @@ -16,6 +16,7 @@
<ProjectReference Include="..\..\..\src\Libplanet.Store\Libplanet.Store.csproj" />
<ProjectReference Include="..\..\..\src\Libplanet\Libplanet.csproj" />
<ProjectReference Include="..\..\..\src\Libplanet.Net\Libplanet.Net.csproj" />
<ProjectReference Include="..\..\..\..\..\suho\suho-lib9c\.Lib9c.Plugin.Shared\Lib9c.Plugin.Shared.csproj" />
</ItemGroup>

</Project>
13 changes: 13 additions & 0 deletions sdk/node/Libplanet.Node/Options/ActionOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Libplanet.Node.Options;

[Options(Position)]
public sealed class ActionOptions : OptionsBase<ActionOptions>
{
public const string Position = "Action";

public string ModulePath { get; set; } = string.Empty;

public string ActionLoaderType { get; set; } = string.Empty;

public string PolicyActionRegistryType { get; set; } = string.Empty;
}
3 changes: 3 additions & 0 deletions sdk/node/Libplanet.Node/Options/SwarmOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ public sealed class SwarmOptions : AppProtocolOptionsBase<SwarmOptions>, IEnable
[BoundPeer]
[Description("The endpoint of the node to block sync.")]
public string BlocksyncSeedPeer { get; set; } = string.Empty;

[PublicKeyArray]
public string[] TrustedAppProtocolVersionSigners { get; set; } = [];
}
40 changes: 40 additions & 0 deletions sdk/node/Libplanet.Node/Services/ActionService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Libplanet.Action;
using Libplanet.Action.Loader;
using Libplanet.Node.Actions;
using Libplanet.Node.Options;
using Microsoft.Extensions.Options;

namespace Libplanet.Node.Services;

internal sealed class ActionService(IOptions<ActionOptions> options)
: IActionService
{
public IActionLoader ActionLoader { get; } = GetActionLoader(options.Value);

public IPolicyActionsRegistry PolicyActionsRegistry { get; }
= GetPolicyActionsRegistry(options.Value);

private static IActionLoader GetActionLoader(ActionOptions options)
{
if (options.ActionLoaderType != string.Empty)
{
var modulePath = options.ModulePath;
var actionLoaderType = options.ActionLoaderType;
return PluginLoader.LoadActionLoader(modulePath, actionLoaderType);
}

return new AggregateTypedActionLoader();
}

private static IPolicyActionsRegistry GetPolicyActionsRegistry(ActionOptions options)
{
if (options.PolicyActionRegistryType != string.Empty)
{
var modulePath = options.ModulePath;
var policyActionRegistryType = options.PolicyActionRegistryType;
return PluginLoader.LoadPolicyActionRegistry(modulePath, policyActionRegistryType);
}

return new PolicyActionsRegistry();
}
}
31 changes: 19 additions & 12 deletions sdk/node/Libplanet.Node/Services/BlockChainService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ internal sealed class BlockChainService : IBlockChainService, IActionRenderer
public BlockChainService(
IOptions<GenesisOptions> genesisOptions,
IStoreService storeService,
IActionService actionService,
PolicyService policyService,
IEnumerable<IActionLoaderProvider> actionLoaderProviders,
ILogger<BlockChainService> logger)
{
_synchronizationContext = SynchronizationContext.Current ?? new();
Expand All @@ -45,9 +45,10 @@ public BlockChainService(
genesisOptions: genesisOptions.Value,
store: storeService.Store,
stateStore: storeService.StateStore,
actionLoader: actionService.ActionLoader,
policyActionsRegistry: actionService.PolicyActionsRegistry,
stagePolicy: policyService.StagePolicy,
renderers: [this],
actionLoaders: [.. actionLoaderProviders.Select(item => item.GetActionLoader())]);
renderers: [this]);
}

public event EventHandler<BlockEventArgs>? BlockAppended;
Expand Down Expand Up @@ -83,7 +84,7 @@ void Action(object? state)
}
}

_logger.LogInformation("#{Height}: Block appended.", newTip.Index);
_logger.LogInformation("#{Height}: Block appended", newTip.Index);
BlockAppended?.Invoke(this, new(newTip));
}
}
Expand All @@ -92,21 +93,27 @@ private static BlockChain CreateBlockChain(
GenesisOptions genesisOptions,
IStore store,
IStateStore stateStore,
IActionLoader actionLoader,
IPolicyActionsRegistry policyActionsRegistry,
IStagePolicy stagePolicy,
IRenderer[] renderers,
IActionLoader[] actionLoaders)
IRenderer[] renderers)
{
var actionLoader = new AggregateTypedActionLoader(actionLoaders);
var actionEvaluator = new ActionEvaluator(
policyActionsRegistry: new(),
policyActionsRegistry: policyActionsRegistry,
stateStore,
actionLoader);

var genesisBlock = CreateGenesisBlock(genesisOptions);
var policy = new BlockPolicy(
blockInterval: TimeSpan.FromSeconds(10),
getMaxTransactionsPerBlock: _ => int.MaxValue,
getMaxTransactionsBytes: _ => long.MaxValue);
policyActionsRegistry: policyActionsRegistry,
blockInterval: TimeSpan.FromSeconds(8),
validateNextBlockTx: (chain, transaction) => null,
validateNextBlock: (chain, block) => null,
getMaxTransactionsBytes: l => long.MaxValue,
getMinTransactionsPerBlock: l => 0,
getMaxTransactionsPerBlock: l => int.MaxValue,
getMaxTransactionsPerSignerPerBlock: l => int.MaxValue
);

var blockChainStates = new BlockChainStates(store, stateStore);
if (store.GetCanonicalChainId() is null)
{
Expand Down
11 changes: 11 additions & 0 deletions sdk/node/Libplanet.Node/Services/IActionService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Libplanet.Action;
using Libplanet.Action.Loader;

namespace Libplanet.Node.Services;

public interface IActionService
{
IActionLoader ActionLoader { get; }

IPolicyActionsRegistry PolicyActionsRegistry { get; }
}
Loading

0 comments on commit 286cc35

Please sign in to comment.