From 9dcdc985435aa786756ee80b6407fd89a9914d31 Mon Sep 17 00:00:00 2001 From: s2quake Date: Tue, 27 Aug 2024 09:35:59 +0900 Subject: [PATCH] WIP --- .../Libplanet.Node.Executable.csproj | 2 + sdk/node/Libplanet.Node.Executable/Program.cs | 25 ++++++ .../appsettings-schema.json | 32 +++++++- .../LibplanetServicesExtensions.cs | 3 + .../Services/BlockChainServiceTest.cs | 6 +- .../Actions/ActionContextMarshaller.cs | 79 +++++++++++++++++++ .../Actions/ActionEvaluationMarshaller.cs | 48 +++++++++++ .../Actions/PluggedActionEvaluator.cs | 70 ++++++++++++++++ .../Actions/PluginKeyValueStore.cs | 36 +++++++++ .../Actions/PluginLoadContext.cs | 31 ++++++++ .../Libplanet.Node/Actions/PluginLoader.cs | 50 ++++++++++++ .../Actions/PreEvaluationBlockMarshaller.cs | 42 ++++++++++ .../Actions/TransactionMarshaller.cs | 23 ++++++ sdk/node/Libplanet.Node/Libplanet.Node.csproj | 3 +- .../Libplanet.Node/Options/ActionOptions.cs | 41 ++++++++++ .../Libplanet.Node/Options/SwarmOptions.cs | 4 + .../Services/BlockChainService.cs | 55 ++++++++----- .../Libplanet.Node/Services/SwarmService.cs | 28 +++++-- 18 files changed, 547 insertions(+), 31 deletions(-) create mode 100644 sdk/node/Libplanet.Node/Actions/ActionContextMarshaller.cs create mode 100644 sdk/node/Libplanet.Node/Actions/ActionEvaluationMarshaller.cs create mode 100644 sdk/node/Libplanet.Node/Actions/PluggedActionEvaluator.cs create mode 100644 sdk/node/Libplanet.Node/Actions/PluginKeyValueStore.cs create mode 100644 sdk/node/Libplanet.Node/Actions/PluginLoadContext.cs create mode 100644 sdk/node/Libplanet.Node/Actions/PluginLoader.cs create mode 100644 sdk/node/Libplanet.Node/Actions/PreEvaluationBlockMarshaller.cs create mode 100644 sdk/node/Libplanet.Node/Actions/TransactionMarshaller.cs create mode 100644 sdk/node/Libplanet.Node/Options/ActionOptions.cs diff --git a/sdk/node/Libplanet.Node.Executable/Libplanet.Node.Executable.csproj b/sdk/node/Libplanet.Node.Executable/Libplanet.Node.Executable.csproj index d43112eaed4..620462b7549 100644 --- a/sdk/node/Libplanet.Node.Executable/Libplanet.Node.Executable.csproj +++ b/sdk/node/Libplanet.Node.Executable/Libplanet.Node.Executable.csproj @@ -12,6 +12,7 @@ + @@ -21,6 +22,7 @@ + diff --git a/sdk/node/Libplanet.Node.Executable/Program.cs b/sdk/node/Libplanet.Node.Executable/Program.cs index 53ccd302d64..616209efe80 100644 --- a/sdk/node/Libplanet.Node.Executable/Program.cs +++ b/sdk/node/Libplanet.Node.Executable/Program.cs @@ -3,6 +3,8 @@ using Libplanet.Node.Options.Schema; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Server.Kestrel.Core; +using Serilog; +using Serilog.Events; var builder = WebHost.CreateDefaultBuilder(args); var assemblies = new string[] @@ -14,6 +16,16 @@ builder.ConfigureLogging(logging => { logging.AddConsole(); + + // Logging setting + var loggerConfig = new LoggerConfiguration(); + loggerConfig = loggerConfig.MinimumLevel.Information(); + loggerConfig = loggerConfig + .MinimumLevel.Override("Microsoft", LogEventLevel.Information) + .Enrich.FromLogContext() + .WriteTo.Console(); + + Log.Logger = loggerConfig.CreateLogger(); }); builder.ConfigureKestrel((context, options) => { @@ -26,6 +38,19 @@ }); builder.ConfigureServices((context, services) => { +// string pluginPath = "/Users/bin_bash_shell/Workspaces/planetarium/NineChronicles/" + +// "lib9c/Lib9c.NCActionLoader/bin/Debug/net6.0/Lib9c.NCActionLoader.dll"; +// string actionLoaderType = "Lib9c.NCActionLoader.NineChroniclesActionLoader"; +// string blockPolicyType = "Lib9c.NCActionLoader.NineChroniclesPolicyActionRegistry"; +// IActionLoader actionLoader = PluginLoader.LoadActionLoader(pluginPath, actionLoaderType); +// IPolicyActionsRegistry policyActionRegistry = +// PluginLoader.LoadPolicyActionRegistry(pluginPath, blockPolicyType); + +// Libplanet.Crypto.CryptoConfig.CryptoBackend = new Secp256k1CryptoBackend(); + +// builder.Services.AddSingleton(actionLoader); +// builder.Services.AddSingleton(policyActionRegistry); + services.AddGrpc(); services.AddGrpcReflection(); services.AddLibplanetNode(context.Configuration); diff --git a/sdk/node/Libplanet.Node.Executable/appsettings-schema.json b/sdk/node/Libplanet.Node.Executable/appsettings-schema.json index 7220e925481..c2782b052ea 100644 --- a/sdk/node/Libplanet.Node.Executable/appsettings-schema.json +++ b/sdk/node/Libplanet.Node.Executable/appsettings-schema.json @@ -1106,6 +1106,22 @@ } } }, + "Action": { + "title": "ActionOptions", + "type": "object", + "additionalProperties": false, + "properties": { + "ModulePath": { + "type": "string" + }, + "ActionLoaderType": { + "type": "string" + }, + "PolicyActionRegistryType": { + "type": "string" + } + } + }, "Genesis": { "title": "GenesisOptions", "type": "object", @@ -1114,12 +1130,12 @@ "properties": { "GenesisKey": { "type": "string", - "description": "The key of the genesis block.", + "description": "The PrivateKey used to generate the genesis block. This property cannot be used with GenesisBlockPath.", "pattern": "^[0-9a-fA-F]{64}$" }, "Validators": { "type": "array", - "description": "Public keys of the validators.", + "description": "Public keys of the validators. This property cannot be used with GenesisBlockPath.", "items": { "type": "string" } @@ -1131,7 +1147,7 @@ }, "GenesisBlockPath": { "type": "string", - "description": "The path of the genesis block." + "description": "The path of the genesis block, which can be a file path or a URI.This property cannot be used with GenesisKey." } } }, @@ -1221,6 +1237,12 @@ "type": "string", "description": "The endpoint of the node to block sync.", "pattern": "^$|^[0-9a-fA-F]{130}|[0-9a-fA-F]{66},\\s*(?:(?:[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" + } } } }, @@ -1260,6 +1282,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" diff --git a/sdk/node/Libplanet.Node.Extensions/LibplanetServicesExtensions.cs b/sdk/node/Libplanet.Node.Extensions/LibplanetServicesExtensions.cs index df39886e39e..e98423c2a99 100644 --- a/sdk/node/Libplanet.Node.Extensions/LibplanetServicesExtensions.cs +++ b/sdk/node/Libplanet.Node.Extensions/LibplanetServicesExtensions.cs @@ -24,6 +24,9 @@ public static ILibplanetNodeBuilder AddLibplanetNode( .Bind(configuration.GetSection(StoreOptions.Position)); services.AddSingleton, StoreOptionsConfigurator>(); + services.AddOptions() + .Bind(configuration.GetSection(ActionOptions.Position)); + services.AddOptions() .Bind(configuration.GetSection(SwarmOptions.Position)); services.AddSingleton, SwarmOptionsConfigurator>(); diff --git a/sdk/node/Libplanet.Node.Tests/Services/BlockChainServiceTest.cs b/sdk/node/Libplanet.Node.Tests/Services/BlockChainServiceTest.cs index dd8461585ed..989f8712980 100644 --- a/sdk/node/Libplanet.Node.Tests/Services/BlockChainServiceTest.cs +++ b/sdk/node/Libplanet.Node.Tests/Services/BlockChainServiceTest.cs @@ -1,3 +1,5 @@ +using Libplanet.Action; +using Libplanet.Action.Loader; using Libplanet.Blockchain; using Libplanet.Crypto; using Libplanet.Node.Options; @@ -20,6 +22,7 @@ public void Create_Test() services.AddSingleton, GenesisOptionsConfigurator>(); services.AddOptions(); services.AddSingleton, StoreOptionsConfigurator>(); + services.AddOptions(); services.AddOptions(); services.AddSingleton, SwarmOptionsConfigurator>(); @@ -28,11 +31,12 @@ public void Create_Test() var logger = new NullLoggerFactory().CreateLogger(); var genesisOptions = serviceProvider.GetRequiredService>(); var storeOptions = serviceProvider.GetRequiredService>(); + var actionOptions = serviceProvider.GetRequiredService>(); var blockChainService = new BlockChainService( genesisOptions: genesisOptions, storeOptions: storeOptions, + actionOptions: actionOptions, policyService: policyService, - actionLoaderProviders: [], logger: logger); var blockChain = blockChainService.BlockChain; diff --git a/sdk/node/Libplanet.Node/Actions/ActionContextMarshaller.cs b/sdk/node/Libplanet.Node/Actions/ActionContextMarshaller.cs new file mode 100644 index 00000000000..ff14888d775 --- /dev/null +++ b/sdk/node/Libplanet.Node/Actions/ActionContextMarshaller.cs @@ -0,0 +1,79 @@ +using System.Security.Cryptography; +using Bencodex; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Common; +using Libplanet.Crypto; +using Libplanet.Types.Tx; +using Boolean = Bencodex.Types.Boolean; + +namespace Libplanet.Node.Actions; + +public static class ActionContextMarshaller +{ + private static readonly Codec Codec = new Codec(); + + public static byte[] Serialize(this ICommittedActionContext actionContext) + { + return Codec.Encode(Marshal(actionContext)); + } + + public static Dictionary Marshal(this ICommittedActionContext actionContext) + { + var dictionary = Bencodex.Types.Dictionary.Empty + .Add("signer", actionContext.Signer.ToHex()) + .Add("miner", actionContext.Miner.ToHex()) + .Add("block_index", actionContext.BlockIndex) + .Add("block_protocol_version", actionContext.BlockProtocolVersion) + .Add("previous_states", actionContext.PreviousState.ByteArray) + .Add("random_seed", actionContext.RandomSeed) + .Add("block_action", actionContext.IsPolicyAction); + + if (actionContext.TxId is { } txId) + { + dictionary = dictionary.Add("tx_id", txId.ByteArray); + } + + return dictionary; + } + + public static ICommittedActionContext Unmarshal(Dictionary dictionary) + { + return new CommittedActionContext( + signer: new Address(((Text)dictionary["signer"]).Value), + txId: GetTxId(dictionary, "tx_id"), + miner: new Address(((Text)dictionary["miner"]).Value), + blockIndex: (Integer)dictionary["block_index"], + blockProtocolVersion: (Integer)dictionary["block_protocol_version"], + previousState: new HashDigest(dictionary["previous_states"]), + randomSeed: (Integer)dictionary["random_seed"], + isPolicyAction: (Boolean)dictionary["block_action"] + ); + + static TxId? GetTxId(Dictionary dictionary, string key) + { + if (dictionary.TryGetValue((Text)key, out IValue txIdValue) + && txIdValue is Binary txIdBinaryValue) + { + return new TxId(txIdBinaryValue.ByteArray); + } + else + { + return null; + } + } + } + + public static ICommittedActionContext Deserialize(byte[] serialized) + { + var decoded = Codec.Decode(serialized); + if (decoded is not Dictionary dictionary) + { + throw new ArgumentException( + message: $"Expected 'Dictionary' but {decoded.GetType().Name}", + paramName: nameof(serialized)); + } + + return Unmarshal(dictionary); + } +} diff --git a/sdk/node/Libplanet.Node/Actions/ActionEvaluationMarshaller.cs b/sdk/node/Libplanet.Node/Actions/ActionEvaluationMarshaller.cs new file mode 100644 index 00000000000..c73563fa1de --- /dev/null +++ b/sdk/node/Libplanet.Node/Actions/ActionEvaluationMarshaller.cs @@ -0,0 +1,48 @@ +using System.Security.Cryptography; +using Bencodex; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Common; + +namespace Libplanet.Node.Actions; + +public static class ActionEvaluationMarshaller +{ + private static readonly Codec Codec = new(); + + public static byte[] Serialize(this ICommittedActionEvaluation actionEvaluation) + { + return Codec.Encode(Marshal(actionEvaluation)); + } + + public static Dictionary Marshal(this ICommittedActionEvaluation actionEvaluation) => + Dictionary.Empty + .Add("action", actionEvaluation.Action) + .Add("output_states", actionEvaluation.OutputState.ByteArray) + .Add("input_context", actionEvaluation.InputContext.Marshal()) + .Add("exception", GetException(actionEvaluation.Exception)); + + public static ICommittedActionEvaluation Unmarshal(IValue value) + { + if (value is not Dictionary dictionary) + { + throw new ArgumentException("The value must be a dictionary.", nameof(value)); + } + + return new CommittedActionEvaluation( + dictionary["action"], + ActionContextMarshaller.Unmarshal((Dictionary)dictionary["input_context"]), + new HashDigest(dictionary["output_states"]), + dictionary["exception"] is Text typeName ? new Exception(typeName) : null + ); + } + + public static ICommittedActionEvaluation Deserialize(byte[] serialized) + { + var decoded = Codec.Decode(serialized); + return Unmarshal(decoded); + } + + private static IValue GetException(Exception? exception) + => exception?.GetType().FullName is { } typeName ? (Text)typeName : Null.Value; +} diff --git a/sdk/node/Libplanet.Node/Actions/PluggedActionEvaluator.cs b/sdk/node/Libplanet.Node/Actions/PluggedActionEvaluator.cs new file mode 100644 index 00000000000..1d28fb947eb --- /dev/null +++ b/sdk/node/Libplanet.Node/Actions/PluggedActionEvaluator.cs @@ -0,0 +1,70 @@ +using System.Diagnostics; +using System.Reflection; +using System.Security.Cryptography; +using Lib9c.Plugin.Shared; +using Libplanet.Action; +using Libplanet.Action.Loader; +using Libplanet.Common; +using Libplanet.Node.Actions; +using Libplanet.Store.Trie; +using Libplanet.Types.Blocks; + +namespace Libplanet.Node.Actions; + +public class PluggedActionEvaluator : IActionEvaluator +{ + private readonly IPluginActionEvaluator _pluginActionEvaluator; + + public PluggedActionEvaluator( + string pluginPath, + string typeName, + IKeyValueStore keyValueStore, + IActionLoader actionLoader) + { + _pluginActionEvaluator = CreateActionEvaluator(pluginPath, typeName, keyValueStore); + ActionLoader = actionLoader; + } + + public IActionLoader ActionLoader { get; } + + public static Assembly LoadPlugin(string absolutePath) + { + var loadContext = new PluginLoadContext(absolutePath); + var pluginBaseName = Path.GetFileNameWithoutExtension(absolutePath) + ?? throw new UnreachableException("Path.GetFileNameWithoutExtension returned null"); + var pluginAssemblyName = new AssemblyName(pluginBaseName); + return loadContext.LoadFromAssemblyName(pluginAssemblyName); + } + + public static IPluginActionEvaluator CreateActionEvaluator( + Assembly assembly, string typeName, IPluginKeyValueStore keyValueStore) + { + if (assembly.GetType(typeName) is Type type && + Activator.CreateInstance(type, args: keyValueStore) as IPluginActionEvaluator + is IPluginActionEvaluator pluginActionEvaluator) + { + return pluginActionEvaluator; + } + + throw new NullReferenceException("PluginActionEvaluator not found with given parameters"); + } + + public static IPluginActionEvaluator CreateActionEvaluator( + string pluginPath, string typeName, IKeyValueStore keyValueStore) + { + var assembly = LoadPlugin(pluginPath); + var pluginKeyValueStore = new PluginKeyValueStore(keyValueStore); + return CreateActionEvaluator(assembly, typeName, pluginKeyValueStore); + } + + public IReadOnlyList Evaluate( + IPreEvaluationBlock block, + HashDigest? baseStateRootHash) + { + var evaluations = _pluginActionEvaluator.Evaluate( + PreEvaluationBlockMarshaller.Serialize(block), + baseStateRootHash is { } srh ? srh.ToByteArray() : null) + .Select(ActionEvaluationMarshaller.Deserialize).ToList().AsReadOnly(); + return evaluations; + } +} diff --git a/sdk/node/Libplanet.Node/Actions/PluginKeyValueStore.cs b/sdk/node/Libplanet.Node/Actions/PluginKeyValueStore.cs new file mode 100644 index 00000000000..ef00af9572b --- /dev/null +++ b/sdk/node/Libplanet.Node/Actions/PluginKeyValueStore.cs @@ -0,0 +1,36 @@ +using System.Collections.Immutable; +using Lib9c.Plugin.Shared; +using Libplanet.Store.Trie; + +namespace Libplanet.Node.Actions; + +public class PluginKeyValueStore(IKeyValueStore keyValueStore) : IPluginKeyValueStore +{ + private readonly IKeyValueStore _keyValueStore = keyValueStore; + + public byte[] Get(in ImmutableArray key) => + _keyValueStore.Get(new KeyBytes(key)); + + public void Set(in ImmutableArray key, byte[] value) => + _keyValueStore.Set(new KeyBytes(key), value); + + public void Set(IDictionary, byte[]> values) => + _keyValueStore.Set( + values.ToDictionary(kv => new KeyBytes(kv.Key), kv => kv.Value)); + + public void Delete(in ImmutableArray key) => + _keyValueStore.Delete(new KeyBytes(key)); + + public void Delete(IEnumerable> keys) => + _keyValueStore.Delete( + keys.Select(key => new KeyBytes(key))); + + public bool Exists(in ImmutableArray key) => + _keyValueStore.Exists(new KeyBytes(key)); + + public IEnumerable> ListKeys() => + _keyValueStore.ListKeys().Select(key => key.ByteArray); + + public void Dispose() => + _keyValueStore.Dispose(); +} diff --git a/sdk/node/Libplanet.Node/Actions/PluginLoadContext.cs b/sdk/node/Libplanet.Node/Actions/PluginLoadContext.cs new file mode 100644 index 00000000000..11f5d5727ec --- /dev/null +++ b/sdk/node/Libplanet.Node/Actions/PluginLoadContext.cs @@ -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; + } +} diff --git a/sdk/node/Libplanet.Node/Actions/PluginLoader.cs b/sdk/node/Libplanet.Node/Actions/PluginLoader.cs new file mode 100644 index 00000000000..0f1be7b4395 --- /dev/null +++ b/sdk/node/Libplanet.Node/Actions/PluginLoader.cs @@ -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(assembly, typeName); + } + + public static IPolicyActionsRegistry LoadPolicyActionRegistry( + string relativePath, string typeName) + { + var assembly = LoadAssembly(relativePath); + return Create(assembly, typeName); + } + + private static T Create(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}"); + } +} diff --git a/sdk/node/Libplanet.Node/Actions/PreEvaluationBlockMarshaller.cs b/sdk/node/Libplanet.Node/Actions/PreEvaluationBlockMarshaller.cs new file mode 100644 index 00000000000..3a4bc99a3a0 --- /dev/null +++ b/sdk/node/Libplanet.Node/Actions/PreEvaluationBlockMarshaller.cs @@ -0,0 +1,42 @@ +using Bencodex; +using Bencodex.Types; +using Libplanet.Types.Blocks; + +namespace Libplanet.Node.Actions; + +public static class PreEvaluationBlockMarshaller +{ + private static readonly Codec Codec = new(); + + // Copied form Libplanet/Blocks/BlockMarshaler.cs + // Block fields: + private static readonly byte[] HeaderKey = "H"u8.ToArray(); // 'H' + private static readonly byte[] TransactionsKey = "T"u8.ToArray(); // 'T' + + public static Dictionary Marshal(this IPreEvaluationBlock block) + { + return Dictionary.Empty + .Add(HeaderKey, BlockMarshaler.MarshalPreEvaluationBlockHeader(block)) + .Add( + key: TransactionsKey, + value: new List(block.Transactions.Select(TransactionMarshaller.Serialize))); + } + + public static IPreEvaluationBlock Unmarshal(Dictionary dictionary) + { + return new PreEvaluationBlock( + BlockMarshaler.UnmarshalPreEvaluationBlockHeader((Dictionary)dictionary[HeaderKey]), + BlockMarshaler.UnmarshalBlockTransactions(dictionary), + BlockMarshaler.UnmarshalBlockEvidence(dictionary)); + } + + public static byte[] Serialize(this IPreEvaluationBlock block) + { + return Codec.Encode(Marshal(block)); + } + + public static IPreEvaluationBlock Deserialize(byte[] serialized) + { + return Unmarshal((Dictionary)Codec.Decode(serialized)); + } +} diff --git a/sdk/node/Libplanet.Node/Actions/TransactionMarshaller.cs b/sdk/node/Libplanet.Node/Actions/TransactionMarshaller.cs new file mode 100644 index 00000000000..fe6ef46795a --- /dev/null +++ b/sdk/node/Libplanet.Node/Actions/TransactionMarshaller.cs @@ -0,0 +1,23 @@ +using Bencodex; +using Bencodex.Types; +using Libplanet.Types.Tx; + +namespace Libplanet.Node.Actions; + +public static class TransactionMarshaller +{ + private static readonly Codec Codec = new(); + + // Copied from Libplanet/Tx/TxMarshaler.cs + private static readonly Binary SignatureKey = new("S"u8.ToArray()); // 'S' + + public static Dictionary Marshal(this ITransaction transaction) + { + return transaction.MarshalUnsignedTx().Add(SignatureKey, transaction.Signature); + } + + public static byte[] Serialize(this ITransaction transaction) + { + return Codec.Encode(Marshal(transaction)); + } +} diff --git a/sdk/node/Libplanet.Node/Libplanet.Node.csproj b/sdk/node/Libplanet.Node/Libplanet.Node.csproj index 19bd81fd736..a40c4b8d379 100644 --- a/sdk/node/Libplanet.Node/Libplanet.Node.csproj +++ b/sdk/node/Libplanet.Node/Libplanet.Node.csproj @@ -1,7 +1,7 @@ - + @@ -16,6 +16,7 @@ + diff --git a/sdk/node/Libplanet.Node/Options/ActionOptions.cs b/sdk/node/Libplanet.Node/Options/ActionOptions.cs new file mode 100644 index 00000000000..229fed957b0 --- /dev/null +++ b/sdk/node/Libplanet.Node/Options/ActionOptions.cs @@ -0,0 +1,41 @@ +using Libplanet.Action; +using Libplanet.Action.Loader; +using Libplanet.Node.Actions; + +namespace Libplanet.Node.Options; + +[Options(Position)] +public sealed class ActionOptions : OptionsBase +{ + 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; + + internal IActionLoader GetActionLoader() + { + if (ActionLoaderType != string.Empty) + { + var modulePath = ModulePath; + var actionLoaderType = ActionLoaderType; + return PluginLoader.LoadActionLoader(modulePath, actionLoaderType); + } + + return new AggregateTypedActionLoader(); + } + + internal IPolicyActionsRegistry GetPolicyActionsRegistry() + { + if (PolicyActionRegistryType != string.Empty) + { + var modulePath = ModulePath; + var policyActionRegistryType = PolicyActionRegistryType; + return PluginLoader.LoadPolicyActionRegistry(modulePath, policyActionRegistryType); + } + + return new PolicyActionsRegistry(); + } +} diff --git a/sdk/node/Libplanet.Node/Options/SwarmOptions.cs b/sdk/node/Libplanet.Node/Options/SwarmOptions.cs index 215b8223202..7728096a246 100644 --- a/sdk/node/Libplanet.Node/Options/SwarmOptions.cs +++ b/sdk/node/Libplanet.Node/Options/SwarmOptions.cs @@ -24,4 +24,8 @@ public sealed class SwarmOptions : OptionsBase [BoundPeer] [Description("The endpoint of the node to block sync.")] public string BlocksyncSeedPeer { get; set; } = string.Empty; + + // 030ffa9bd579ee1503ce008394f687c182279da913bfaec12baca34e79698a7cd1 + [PublicKeyArray] + public string[] TrustedAppProtocolVersionSigners { get; set; } = []; } diff --git a/sdk/node/Libplanet.Node/Services/BlockChainService.cs b/sdk/node/Libplanet.Node/Services/BlockChainService.cs index 2f528bce875..c629dccfad8 100644 --- a/sdk/node/Libplanet.Node/Services/BlockChainService.cs +++ b/sdk/node/Libplanet.Node/Services/BlockChainService.cs @@ -13,6 +13,7 @@ using Libplanet.Blockchain.Renderers; using Libplanet.Common; using Libplanet.Crypto; +using Libplanet.Node.Actions; using Libplanet.Node.Options; using Libplanet.RocksDBStore; using Libplanet.Store; @@ -37,8 +38,8 @@ internal sealed class BlockChainService : IBlockChainService, IActionRenderer public BlockChainService( IOptions genesisOptions, IOptions storeOptions, + IOptions actionOptions, PolicyService policyService, - IEnumerable actionLoaderProviders, ILogger logger) { _synchronizationContext = SynchronizationContext.Current ?? new(); @@ -46,9 +47,9 @@ public BlockChainService( _blockChain = CreateBlockChain( genesisOptions: genesisOptions.Value, storeOptions: storeOptions.Value, + actionOptions: actionOptions.Value, stagePolicy: policyService.StagePolicy, - renderers: [this], - actionLoaders: [.. actionLoaderProviders.Select(item => item.GetActionLoader())]); + renderers: [this]); } public event EventHandler? BlockAppended; @@ -84,7 +85,7 @@ void Action(object? state) } } - _logger.LogInformation("#{Height}: Block appended.", newTip.Index); + _logger.LogInformation("#{Height}: Block appended", newTip.Index); BlockAppended?.Invoke(this, new(newTip)); } } @@ -92,22 +93,33 @@ void Action(object? state) private static BlockChain CreateBlockChain( GenesisOptions genesisOptions, StoreOptions storeOptions, + ActionOptions actionOptions, IStagePolicy stagePolicy, - IRenderer[] renderers, - IActionLoader[] actionLoaders) + IRenderer[] renderers) { - var (store, stateStore) = CreateStore(storeOptions); - var actionLoader = new AggregateTypedActionLoader(actionLoaders); - var actionEvaluator = new ActionEvaluator( - policyActionsRegistry: new(), - stateStore, - actionLoader); + // var actionLoader = actionOptions.GetActionLoader(); + var policyActions = new PolicyActionsRegistry(); + + var (store, stateStore, keyValueStore) = CreateStore(storeOptions); + + var actionEvaluator = new PluggedActionEvaluator("/Users/jeesu/Downloads/osx-arm64 (2)/Lib9c.Plugin.dll", "Lib9c.Plugin.PluginActionEvaluator", keyValueStore, new AggregateTypedActionLoader()); + // var actionEvaluator = new ActionEvaluator( + // policyActionsRegistry: policyActions, + // stateStore, + // actionLoader); var genesisBlock = CreateGenesisBlock(genesisOptions); var policy = new BlockPolicy( - blockInterval: TimeSpan.FromSeconds(10), - getMaxTransactionsPerBlock: _ => int.MaxValue, - getMaxTransactionsBytes: _ => long.MaxValue); + policyActionsRegistry: policyActions, + 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) { @@ -133,7 +145,7 @@ private static BlockChain CreateBlockChain( renderers: renderers); } - private static (IStore, IStateStore) CreateStore(StoreOptions storeOptions) + private static (IStore, IStateStore, IKeyValueStore) CreateStore(StoreOptions storeOptions) { return storeOptions.Type switch { @@ -142,19 +154,20 @@ private static (IStore, IStateStore) CreateStore(StoreOptions storeOptions) _ => throw new NotSupportedException($"Unsupported store type: {storeOptions.Type}"), }; - (MemoryStore, TrieStateStore) CreateMemoryStore() + (MemoryStore, TrieStateStore, MemoryKeyValueStore) CreateMemoryStore() { var store = new MemoryStore(); - var stateStore = new TrieStateStore(new MemoryKeyValueStore()); - return (store, stateStore); + var keyValueStore = new MemoryKeyValueStore(); + var stateStore = new TrieStateStore(keyValueStore); + return (store, stateStore, keyValueStore); } - (RocksDBStore.RocksDBStore, TrieStateStore) CreateDiskStore() + (RocksDBStore.RocksDBStore, TrieStateStore, RocksDBKeyValueStore) CreateDiskStore() { var store = new RocksDBStore.RocksDBStore(storeOptions.StoreName); var keyValueStore = new RocksDBKeyValueStore(storeOptions.StateStoreName); var stateStore = new TrieStateStore(keyValueStore); - return (store, stateStore); + return (store, stateStore, keyValueStore); } } diff --git a/sdk/node/Libplanet.Node/Services/SwarmService.cs b/sdk/node/Libplanet.Node/Services/SwarmService.cs index 1c9f99c4937..7632a00c726 100644 --- a/sdk/node/Libplanet.Node/Services/SwarmService.cs +++ b/sdk/node/Libplanet.Node/Services/SwarmService.cs @@ -1,3 +1,4 @@ +using System.Collections.Immutable; using System.Net; using Libplanet.Common; using Libplanet.Crypto; @@ -75,11 +76,14 @@ public async Task StartAsync(CancellationToken cancellationToken) var nodeOptions = _options; var privateKey = PrivateKey.FromString(nodeOptions.PrivateKey); var appProtocolVersion = AppProtocolVersion.FromToken(nodeOptions.AppProtocolVersion); + var trustedAppProtocolVersionSigners = nodeOptions.TrustedAppProtocolVersionSigners + .Select(PublicKey.FromHex).ToArray(); var swarmEndPoint = (DnsEndPoint)EndPointUtility.Parse(nodeOptions.EndPoint); var swarmTransport = await CreateTransport( privateKey: privateKey, endPoint: swarmEndPoint, - appProtocolVersion: appProtocolVersion); + appProtocolVersion: appProtocolVersion, + trustedAppProtocolVersionSigners); var blocksyncSeedPeer = BoundPeerUtility.Parse(nodeOptions.BlocksyncSeedPeer); var swarmOptions = new Net.Options.SwarmOptions { @@ -92,7 +96,11 @@ public async Task StartAsync(CancellationToken cancellationToken) var consensusTransport = validatorOptions is not null ? await CreateConsensusTransportAsync( - privateKey, appProtocolVersion, validatorOptions, cancellationToken) + privateKey, + appProtocolVersion, + trustedAppProtocolVersionSigners, + validatorOptions, + cancellationToken) : null; var consensusReactorOption = validatorOptions is not null ? CreateConsensusReactorOption(privateKey, validatorOptions) @@ -179,14 +187,22 @@ public async ValueTask DisposeAsync() } private static async Task CreateTransport( - PrivateKey privateKey, DnsEndPoint endPoint, AppProtocolVersion appProtocolVersion) + PrivateKey privateKey, + DnsEndPoint endPoint, + AppProtocolVersion appProtocolVersion, + PublicKey[] trustedAppProtocolVersionSigners) { var appProtocolVersionOptions = new Net.Options.AppProtocolVersionOptions { AppProtocolVersion = appProtocolVersion, + TrustedAppProtocolVersionSigners = [.. trustedAppProtocolVersionSigners], }; var hostOptions = new Net.Options.HostOptions(endPoint.Host, [], endPoint.Port); - return await NetMQTransport.Create(privateKey, appProtocolVersionOptions, hostOptions); + return await NetMQTransport.Create( + privateKey, + appProtocolVersionOptions, + hostOptions, + TimeSpan.FromSeconds(60)); } private static ConsensusReactorOption CreateConsensusReactorOption( @@ -207,6 +223,7 @@ private static ConsensusReactorOption CreateConsensusReactorOption( private static async Task CreateConsensusTransportAsync( PrivateKey privateKey, AppProtocolVersion appProtocolVersion, + PublicKey[] trustedAppProtocolVersionSigners, ValidatorOptions options, CancellationToken cancellationToken) { @@ -215,6 +232,7 @@ private static async Task CreateConsensusTransportAsync( return await CreateTransport( privateKey: privateKey, endPoint: consensusEndPoint, - appProtocolVersion: appProtocolVersion); + appProtocolVersion: appProtocolVersion, + trustedAppProtocolVersionSigners); } }