diff --git a/Lib9c b/Lib9c
index 1dc61aa5f..c8247aaa0 160000
--- a/Lib9c
+++ b/Lib9c
@@ -1 +1 @@
-Subproject commit 1dc61aa5f4844a0112ba9dad79bc9dbdc732a54e
+Subproject commit c8247aaa03dd1d009c430e701a6c0aa020de3e52
diff --git a/Libplanet.Extensions.PluggedActionEvaluator/Libplanet.Extensions.PluggedActionEvaluator.csproj b/Libplanet.Extensions.PluggedActionEvaluator/Libplanet.Extensions.PluggedActionEvaluator.csproj
new file mode 100644
index 000000000..78e3b7dcd
--- /dev/null
+++ b/Libplanet.Extensions.PluggedActionEvaluator/Libplanet.Extensions.PluggedActionEvaluator.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
+
+
+
+
+
+
diff --git a/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs b/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs
new file mode 100644
index 000000000..c2ee18264
--- /dev/null
+++ b/Libplanet.Extensions.PluggedActionEvaluator/PluggedActionEvaluator.cs
@@ -0,0 +1,51 @@
+using System.Reflection;
+using System.Security.Cryptography;
+using Lib9c.Plugin.Shared;
+using Libplanet.Action;
+using Libplanet.Action.Loader;
+using Libplanet.Common;
+using Libplanet.Extensions.ActionEvaluatorCommonComponents;
+using Libplanet.Store.Trie;
+using Libplanet.Types.Blocks;
+
+namespace Libplanet.Extensions.PluggedActionEvaluator
+{
+ public class PluggedActionEvaluator : IActionEvaluator
+ {
+ private readonly IPluginActionEvaluator _pluginActionEvaluator;
+
+ public IActionLoader ActionLoader => throw new NotImplementedException();
+
+ public PluggedActionEvaluator(string pluginPath, string typeName, IKeyValueStore keyValueStore)
+ {
+ _pluginActionEvaluator = CreateActionEvaluator(pluginPath, typeName, keyValueStore);
+ }
+
+ public static Assembly LoadPlugin(string absolutePath)
+ {
+ PluginLoadContext loadContext = new PluginLoadContext(absolutePath);
+ return loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(absolutePath)));
+ }
+
+ 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)
+ => CreateActionEvaluator(LoadPlugin(pluginPath), typeName, new PluginKeyValueStore(keyValueStore));
+
+ public IReadOnlyList Evaluate(IPreEvaluationBlock block, HashDigest? baseStateRootHash)
+ => _pluginActionEvaluator.Evaluate(
+ PreEvaluationBlockMarshaller.Serialize(block),
+ baseStateRootHash is { } srh ? srh.ToByteArray() : null)
+ .Select(eval => ActionEvaluationMarshaller.Deserialize(eval)).ToList().AsReadOnly();
+ }
+}
diff --git a/Libplanet.Extensions.PluggedActionEvaluator/PluginKeyValueStore.cs b/Libplanet.Extensions.PluggedActionEvaluator/PluginKeyValueStore.cs
new file mode 100644
index 000000000..e2172fff5
--- /dev/null
+++ b/Libplanet.Extensions.PluggedActionEvaluator/PluginKeyValueStore.cs
@@ -0,0 +1,42 @@
+using System.Collections.Immutable;
+using Lib9c.Plugin.Shared;
+using Libplanet.Store.Trie;
+
+namespace Libplanet.Extensions.PluggedActionEvaluator
+{
+ public class PluginKeyValueStore : IPluginKeyValueStore
+ {
+ private readonly IKeyValueStore _keyValueStore;
+
+ public PluginKeyValueStore(IKeyValueStore keyValueStore)
+ {
+ _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/Libplanet.Extensions.PluggedActionEvaluator/PluginLoadContext.cs b/Libplanet.Extensions.PluggedActionEvaluator/PluginLoadContext.cs
new file mode 100644
index 000000000..497e9a791
--- /dev/null
+++ b/Libplanet.Extensions.PluggedActionEvaluator/PluginLoadContext.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.Loader;
+
+namespace Libplanet.Extensions.PluggedActionEvaluator
+{
+ public class PluginLoadContext : AssemblyLoadContext
+ {
+ private readonly AssemblyDependencyResolver _resolver;
+
+ public PluginLoadContext(string pluginPath)
+ {
+ _resolver = new AssemblyDependencyResolver(pluginPath);
+ }
+
+ protected override Assembly? Load(AssemblyName assemblyName)
+ {
+ if (_resolver.ResolveAssemblyToPath(assemblyName) is { } assemblyPath)
+ {
+ return LoadFromAssemblyPath(assemblyPath);
+ }
+
+ return null;
+ }
+
+ protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
+ {
+ if (_resolver.ResolveUnmanagedDllToPath(unmanagedDllName) is { } libraryPath)
+ {
+ return LoadUnmanagedDllFromPath(libraryPath);
+ }
+
+ return IntPtr.Zero;
+ }
+ }
+}
+
diff --git a/Libplanet.Headless/Hosting/ActionEvaluatorType.cs b/Libplanet.Headless/Hosting/ActionEvaluatorType.cs
index 9d9498b61..445198c0c 100644
--- a/Libplanet.Headless/Hosting/ActionEvaluatorType.cs
+++ b/Libplanet.Headless/Hosting/ActionEvaluatorType.cs
@@ -4,5 +4,5 @@ public enum ActionEvaluatorType
{
Default, // ActionEvaluator
ForkableActionEvaluator,
- RemoteActionEvaluator,
+ PluggedActionEvaluator,
}
diff --git a/Libplanet.Headless/Hosting/LibplanetNodeService.cs b/Libplanet.Headless/Hosting/LibplanetNodeService.cs
index cc5a3d55a..11ecc161b 100644
--- a/Libplanet.Headless/Hosting/LibplanetNodeService.cs
+++ b/Libplanet.Headless/Hosting/LibplanetNodeService.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
+using System.IO.Compression;
using System.Linq;
using System.Net.Http;
using System.Threading;
@@ -15,7 +16,7 @@
using Libplanet.Types.Blocks;
using Libplanet.Crypto;
using Libplanet.Extensions.ForkableActionEvaluator;
-using Libplanet.Extensions.RemoteActionEvaluator;
+using Libplanet.Extensions.PluggedActionEvaluator;
using Libplanet.Net;
using Libplanet.Net.Consensus;
using Libplanet.Net.Options;
@@ -80,8 +81,7 @@ public LibplanetNodeService(
Action preloadStatusHandlerAction,
IActionLoader actionLoader,
bool ignoreBootstrapFailure = false,
- bool ignorePreloadFailure = false,
- bool useRemoteActionEvaluator = false
+ bool ignorePreloadFailure = false
)
{
if (blockPolicy is null)
@@ -95,7 +95,7 @@ public LibplanetNodeService(
var iceServers = Properties.IceServers;
- (Store, StateStore) = LoadStore(
+ (Store, StateStore, IKeyValueStore keyValueStore) = LoadStore(
Properties.StorePath,
Properties.StoreType,
Properties.StoreStatesCacheSize);
@@ -116,23 +116,25 @@ public LibplanetNodeService(
}
var blockChainStates = new BlockChainStates(Store, StateStore);
+
IActionEvaluator BuildActionEvaluator(IActionEvaluatorConfiguration actionEvaluatorConfiguration)
{
return actionEvaluatorConfiguration switch
{
- RemoteActionEvaluatorConfiguration remoteActionEvaluatorConfiguration => new RemoteActionEvaluator(
- new Uri(remoteActionEvaluatorConfiguration.StateServiceEndpoint)),
- DefaultActionEvaluatorConfiguration _ => new ActionEvaluator(
- _ => blockPolicy.BlockAction,
- stateStore: StateStore,
- actionTypeLoader: actionLoader
- ),
- ForkableActionEvaluatorConfiguration forkableActionEvaluatorConfiguration => new
- ForkableActionEvaluator(
- forkableActionEvaluatorConfiguration.Pairs.Select(pair => (
- (pair.Item1.Start, pair.Item1.End), BuildActionEvaluator(pair.Item2)
- ))
- ),
+ PluggedActionEvaluatorConfiguration pluginActionEvaluatorConfiguration =>
+ new PluggedActionEvaluator(
+ ResolvePluginPath(pluginActionEvaluatorConfiguration.PluginPath),
+ pluginActionEvaluatorConfiguration.TypeName,
+ keyValueStore),
+ DefaultActionEvaluatorConfiguration _ =>
+ new ActionEvaluator(
+ _ => blockPolicy.BlockAction,
+ stateStore: StateStore,
+ actionTypeLoader: actionLoader),
+ ForkableActionEvaluatorConfiguration forkableActionEvaluatorConfiguration =>
+ new ForkableActionEvaluator(
+ forkableActionEvaluatorConfiguration.Pairs.Select(
+ pair => ((pair.Item1.Start, pair.Item1.End), BuildActionEvaluator(pair.Item2)))),
_ => throw new InvalidOperationException("Unexpected type."),
};
}
@@ -302,7 +304,7 @@ public override async Task StopAsync(CancellationToken cancellationToken)
}
}
- protected (IStore, IStateStore) LoadStore(string path, string type, int statesCacheSize)
+ protected (IStore, IStateStore, IKeyValueStore) LoadStore(string path, string type, int statesCacheSize)
{
IStore store = null;
if (type == "rocksdb")
@@ -346,7 +348,7 @@ public override async Task StopAsync(CancellationToken cancellationToken)
IKeyValueStore stateKeyValueStore = new RocksDBKeyValueStore(Path.Combine(path, "states"));
IStateStore stateStore = new TrieStateStore(stateKeyValueStore);
- return (store, stateStore);
+ return (store, stateStore, stateKeyValueStore);
}
private async Task StartSwarm(bool preload, CancellationToken cancellationToken)
@@ -568,7 +570,7 @@ protected async Task CheckPeerTable(CancellationToken cancellationToken = defaul
if (grace == count)
{
var message = "No any peers are connected even seed peers were given. " +
- $"(grace: {grace}";
+ $"(grace: {grace}";
Log.Error(message);
// _exceptionHandlerAction(RPCException.NetworkException, message);
Properties.NodeExceptionOccurred(NodeExceptionType.NoAnyPeer, message);
@@ -629,6 +631,32 @@ public override void Dispose()
Log.Debug("Store disposed.");
}
+ private string ResolvePluginPath(string path) =>
+ Uri.IsWellFormedUriString(path, UriKind.Absolute)
+ ? DownloadPlugin(path).Result
+ : path;
+
+ private async Task DownloadPlugin(string url)
+ {
+ var path = Path.Combine(Environment.CurrentDirectory, "plugins");
+ Directory.CreateDirectory(path);
+ var hashed = url.GetHashCode().ToString();
+ var logger = Log.ForContext("LibplanetNodeService", hashed);
+ using var httpClient = new HttpClient();
+ var downloadPath = Path.Join(path, hashed + ".zip");
+ var extractPath = Path.Join(path, hashed);
+ logger.Debug("Downloading...");
+ await File.WriteAllBytesAsync(
+ downloadPath,
+ await httpClient.GetByteArrayAsync(url, SwarmCancellationToken),
+ SwarmCancellationToken);
+ logger.Debug("Finished downloading.");
+ logger.Debug("Extracting...");
+ ZipFile.ExtractToDirectory(downloadPath, extractPath);
+ logger.Debug("Finished extracting.");
+ return Path.Combine(extractPath, "Lib9c.Plugin.dll");
+ }
+
// FIXME: Request libplanet provide default implementation.
private sealed class ActionTypeLoaderContext : IActionTypeLoaderContext
{
diff --git a/Libplanet.Headless/Hosting/PluggedActionEvaluatorConfiguration.cs b/Libplanet.Headless/Hosting/PluggedActionEvaluatorConfiguration.cs
new file mode 100644
index 000000000..cca6d3a4d
--- /dev/null
+++ b/Libplanet.Headless/Hosting/PluggedActionEvaluatorConfiguration.cs
@@ -0,0 +1,10 @@
+namespace Libplanet.Headless.Hosting;
+
+public class PluggedActionEvaluatorConfiguration : IActionEvaluatorConfiguration
+{
+ public ActionEvaluatorType Type => ActionEvaluatorType.PluggedActionEvaluator;
+
+ public string PluginPath { get; init; }
+
+ public string TypeName => "Lib9c.Plugin.PluginActionEvaluator";
+}
diff --git a/Libplanet.Headless/Hosting/RemoteActionEvaluatorConfiguration.cs b/Libplanet.Headless/Hosting/RemoteActionEvaluatorConfiguration.cs
deleted file mode 100644
index 270d2a824..000000000
--- a/Libplanet.Headless/Hosting/RemoteActionEvaluatorConfiguration.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-namespace Libplanet.Headless.Hosting;
-
-public class RemoteActionEvaluatorConfiguration : IActionEvaluatorConfiguration
-{
- public ActionEvaluatorType Type => ActionEvaluatorType.RemoteActionEvaluator;
-
- public string StateServiceEndpoint { get; init; }
-}
diff --git a/Libplanet.Headless/Libplanet.Headless.csproj b/Libplanet.Headless/Libplanet.Headless.csproj
index 94ce16144..b8e446219 100644
--- a/Libplanet.Headless/Libplanet.Headless.csproj
+++ b/Libplanet.Headless/Libplanet.Headless.csproj
@@ -23,7 +23,7 @@
-
+
diff --git a/NineChronicles.Headless.Executable.Tests/Commands/TxCommandTest.cs b/NineChronicles.Headless.Executable.Tests/Commands/TxCommandTest.cs
index 732d040fc..d9697dd19 100644
--- a/NineChronicles.Headless.Executable.Tests/Commands/TxCommandTest.cs
+++ b/NineChronicles.Headless.Executable.Tests/Commands/TxCommandTest.cs
@@ -131,11 +131,7 @@ private void Assert_Tx(long txNonce, string filePath, bool gas)
{
var timeStamp = DateTimeOffset.FromUnixTimeSeconds(DateTimeOffset.UtcNow.ToUnixTimeSeconds());
var hashHex = ByteUtil.Hex(_blockHash.ByteArray);
- long? maxGasPrice = null;
- if (gas)
- {
- maxGasPrice = 1L;
- }
+ long? maxGasPrice = gas ? (long?)1L : null;
_command.Sign(ByteUtil.Hex(_privateKey.ByteArray), txNonce, hashHex, timeStamp.ToString(),
new[] { filePath }, maxGasPrice: maxGasPrice);
var output = _console.Out.ToString();
@@ -146,11 +142,12 @@ private void Assert_Tx(long txNonce, string filePath, bool gas)
Assert.Equal(_privateKey.Address, tx.Signer);
Assert.Equal(timeStamp, tx.Timestamp);
ActionBase action = (ActionBase)new NCActionLoader().LoadAction(1L, tx.Actions.Single());
- long expectedGasLimit = 1L;
- if (action is ITransferAsset || action is ITransferAssets)
- {
- expectedGasLimit = 4L;
- }
+ long? expectedGasLimit = gas
+ ? action is ITransferAsset || action is ITransferAssets
+ ? (long?)4L
+ : (long?)1L
+ : null;
+
Assert.Equal(expectedGasLimit, tx.GasLimit);
if (gas)
{
diff --git a/NineChronicles.Headless.Executable.sln b/NineChronicles.Headless.Executable.sln
index b14931789..6e9604ede 100644
--- a/NineChronicles.Headless.Executable.sln
+++ b/NineChronicles.Headless.Executable.sln
@@ -60,25 +60,25 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Extensions.Forkab
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Libplanet", ".Libplanet", "{69F04D28-2B2E-454D-9B15-4D708EEEA8B5}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Action", "Lib9c\.Libplanet\Libplanet.Action\Libplanet.Action.csproj", "{EB464A50-9976-4DEA-B170-F72C4FB73A9C}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Action", "Lib9c\.Libplanet\Libplanet.Action\Libplanet.Action.csproj", "{EB464A50-9976-4DEA-B170-F72C4FB73A9C}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Common", "Lib9c\.Libplanet\Libplanet.Common\Libplanet.Common.csproj", "{95FB2620-540C-4498-9DAE-65198E89680C}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Common", "Lib9c\.Libplanet\Libplanet.Common\Libplanet.Common.csproj", "{95FB2620-540C-4498-9DAE-65198E89680C}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Types", "Lib9c\.Libplanet\Libplanet.Types\Libplanet.Types.csproj", "{FC65B031-F6EE-4561-A365-47B6FDD1C114}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Types", "Lib9c\.Libplanet\Libplanet.Types\Libplanet.Types.csproj", "{FC65B031-F6EE-4561-A365-47B6FDD1C114}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Store", "Lib9c\.Libplanet\Libplanet.Store\Libplanet.Store.csproj", "{2FF6DADC-5E7A-4F03-94D5-2CF50DED8C29}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Store", "Lib9c\.Libplanet\Libplanet.Store\Libplanet.Store.csproj", "{2FF6DADC-5E7A-4F03-94D5-2CF50DED8C29}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Crypto", "Lib9c\.Libplanet\Libplanet.Crypto\Libplanet.Crypto.csproj", "{2C3AD392-38A1-4E07-B1F9-694EE4A1E0C0}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Crypto", "Lib9c\.Libplanet\Libplanet.Crypto\Libplanet.Crypto.csproj", "{2C3AD392-38A1-4E07-B1F9-694EE4A1E0C0}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Extensions.ActionEvaluatorCommonComponents", "Lib9c\.Libplanet.Extensions.ActionEvaluatorCommonComponents\Libplanet.Extensions.ActionEvaluatorCommonComponents.csproj", "{A6922395-36E5-4B0A-BEBD-9BCE34D08722}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Extensions.ActionEvaluatorCommonComponents", "Lib9c\.Libplanet.Extensions.ActionEvaluatorCommonComponents\Libplanet.Extensions.ActionEvaluatorCommonComponents.csproj", "{A6922395-36E5-4B0A-BEBD-9BCE34D08722}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lib9c.StateService.Shared", "Lib9c\.Lib9c.StateService.Shared\Lib9c.StateService.Shared.csproj", "{6A410F06-134A-46D9-8B39-381FA2ED861F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Extensions.RemoteBlockChainStates", "Lib9c\.Libplanet.Extensions.RemoteBlockChainStates\Libplanet.Extensions.RemoteBlockChainStates.csproj", "{8F9E5505-C157-4DF3-A419-FF0108731397}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Extensions.RemoteActionEvaluator", "Lib9c\.Libplanet.Extensions.RemoteActionEvaluator\Libplanet.Extensions.RemoteActionEvaluator.csproj", "{C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NineChronicles.Headless.AccessControlCenter", "NineChronicles.Headless.AccessControlCenter\NineChronicles.Headless.AccessControlCenter.csproj", "{162C0F4B-A1D9-4132-BC34-31F1247BC26B}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libplanet.Extensions.RemoteBlockChainStates", "Lib9c\.Libplanet.Extensions.RemoteBlockChainStates\Libplanet.Extensions.RemoteBlockChainStates.csproj", "{8F9E5505-C157-4DF3-A419-FF0108731397}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Libplanet.Extensions.PluggedActionEvaluator", "Libplanet.Extensions.PluggedActionEvaluator\Libplanet.Extensions.PluggedActionEvaluator.csproj", "{DE91C36D-3999-47B6-A0BD-848C8EBA2A76}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NineChronicles.Headless.AccessControlCenter", "NineChronicles.Headless.AccessControlCenter\NineChronicles.Headless.AccessControlCenter.csproj", "{162C0F4B-A1D9-4132-BC34-31F1247BC26B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lib9c.Plugin.Shared", "Lib9c\.Lib9c.Plugin.Shared\Lib9c.Plugin.Shared.csproj", "{3D32DA34-E619-429F-8421-848FF4F14417}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -651,42 +651,6 @@ Global
{A6922395-36E5-4B0A-BEBD-9BCE34D08722}.Release|x64.Build.0 = Release|Any CPU
{A6922395-36E5-4B0A-BEBD-9BCE34D08722}.Release|x86.ActiveCfg = Release|Any CPU
{A6922395-36E5-4B0A-BEBD-9BCE34D08722}.Release|x86.Build.0 = Release|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.Debug|x64.ActiveCfg = Debug|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.Debug|x64.Build.0 = Debug|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.Debug|x86.ActiveCfg = Debug|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.Debug|x86.Build.0 = Debug|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.DevEx|Any CPU.ActiveCfg = Debug|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.DevEx|Any CPU.Build.0 = Debug|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.DevEx|x64.ActiveCfg = Debug|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.DevEx|x64.Build.0 = Debug|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.DevEx|x86.ActiveCfg = Debug|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.DevEx|x86.Build.0 = Debug|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.Release|Any CPU.Build.0 = Release|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.Release|x64.ActiveCfg = Release|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.Release|x64.Build.0 = Release|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.Release|x86.ActiveCfg = Release|Any CPU
- {6A410F06-134A-46D9-8B39-381FA2ED861F}.Release|x86.Build.0 = Release|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.Debug|x64.ActiveCfg = Debug|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.Debug|x64.Build.0 = Debug|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.Debug|x86.ActiveCfg = Debug|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.Debug|x86.Build.0 = Debug|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.DevEx|Any CPU.ActiveCfg = Debug|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.DevEx|Any CPU.Build.0 = Debug|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.DevEx|x64.ActiveCfg = Debug|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.DevEx|x64.Build.0 = Debug|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.DevEx|x86.ActiveCfg = Debug|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.DevEx|x86.Build.0 = Debug|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.Release|Any CPU.Build.0 = Release|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.Release|x64.ActiveCfg = Release|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.Release|x64.Build.0 = Release|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.Release|x86.ActiveCfg = Release|Any CPU
- {C41BA817-5D5B-42A5-9CF8-E8F29D1B71EF}.Release|x86.Build.0 = Release|Any CPU
{8F9E5505-C157-4DF3-A419-FF0108731397}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8F9E5505-C157-4DF3-A419-FF0108731397}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8F9E5505-C157-4DF3-A419-FF0108731397}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -723,6 +687,42 @@ Global
{162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Release|x64.Build.0 = Release|Any CPU
{162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Release|x86.ActiveCfg = Release|Any CPU
{162C0F4B-A1D9-4132-BC34-31F1247BC26B}.Release|x86.Build.0 = Release|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Debug|x64.Build.0 = Debug|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Debug|x86.Build.0 = Debug|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.DevEx|Any CPU.ActiveCfg = Debug|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.DevEx|Any CPU.Build.0 = Debug|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.DevEx|x64.ActiveCfg = Debug|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.DevEx|x64.Build.0 = Debug|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.DevEx|x86.ActiveCfg = Debug|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.DevEx|x86.Build.0 = Debug|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Release|x64.ActiveCfg = Release|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Release|x64.Build.0 = Release|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Release|x86.ActiveCfg = Release|Any CPU
+ {DE91C36D-3999-47B6-A0BD-848C8EBA2A76}.Release|x86.Build.0 = Release|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.Debug|x64.Build.0 = Debug|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.Debug|x86.Build.0 = Debug|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.DevEx|Any CPU.ActiveCfg = Debug|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.DevEx|Any CPU.Build.0 = Debug|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.DevEx|x64.ActiveCfg = Debug|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.DevEx|x64.Build.0 = Debug|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.DevEx|x86.ActiveCfg = Debug|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.DevEx|x86.Build.0 = Debug|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.Release|x64.ActiveCfg = Release|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.Release|x64.Build.0 = Release|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.Release|x86.ActiveCfg = Release|Any CPU
+ {3D32DA34-E619-429F-8421-848FF4F14417}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs
index 861b678c0..ee3c1ff4b 100644
--- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs
+++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.Privates.cs
@@ -297,14 +297,8 @@ ActionContext CreateActionContext(
randomSeed: randomSeed);
}
- byte[] hashedSignature;
- using (var hasher = SHA1.Create())
- {
- hashedSignature = hasher.ComputeHash(signature);
- }
-
byte[] preEvaluationHashBytes = preEvaluationHash.ToByteArray();
- int seed = ActionEvaluator.GenerateRandomSeed(preEvaluationHashBytes, hashedSignature, signature, 0);
+ int seed = ActionEvaluator.GenerateRandomSeed(preEvaluationHashBytes, signature, 0);
IAccount states = previousStates;
foreach (IAction action in actions)
diff --git a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs
index 9467301f8..dd06de842 100644
--- a/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs
+++ b/NineChronicles.Headless.Executable/Commands/ReplayCommand.cs
@@ -145,19 +145,20 @@ public int Tx(
outputSw?.WriteLine(msg);
}
+ var inputState = actionEvaluation.InputContext.PreviousState;
+ var outputState = actionEvaluation.OutputState;
+ var accountDiff = AccountDiff.Create(inputState.Trie, outputState.Trie);
+
var states = actionEvaluation.OutputState;
var addressNum = 1;
- foreach (var (updatedAddress, updatedState) in states.Delta.States)
+ foreach (var (updatedAddress, stateDiff) in accountDiff.StateDiffs)
{
if (verbose)
{
- msg = $"- action #{actionNum} updated address #{addressNum}({updatedAddress}) beginning..";
- _console.Out.WriteLine(msg);
- outputSw?.WriteLine(msg);
- msg = $"{updatedState}";
+ msg = $"- action #{actionNum} updated value at address #{addressNum} ({updatedAddress})";
_console.Out.WriteLine(msg);
outputSw?.WriteLine(msg);
- msg = $"- action #{actionNum} updated address #{addressNum}({updatedAddress}) end..";
+ msg = $" from {stateDiff.Item1} to {stateDiff.Item2}";
_console.Out.WriteLine(msg);
outputSw?.WriteLine(msg);
}
@@ -643,13 +644,13 @@ private void PrintEvaluation(ActionEvaluation evaluation, int index)
_console.Out.WriteLine($"- action #{index + 1}: {type.Name}(\"{actionType}\")");
}
- var states = evaluation.OutputState.Delta.States;
- var indexedStates = states.Select((x, i) => (x.Key, x.Value, i: i));
- foreach (var (updatedAddress, updatedState, addressIndex) in indexedStates)
+ var inputState = evaluation.InputContext.PreviousState;
+ var outputState = evaluation.OutputState;
+ var accountDiff = AccountDiff.Create(inputState.Trie, outputState.Trie);
+ foreach (var (updatedAddress, stateDiff, addressIndex) in accountDiff.StateDiffs.Select((x, i) => (x.Key, x.Value, i)))
{
- _console.Out.WriteLine($"- action #{index + 1} updated address #{addressIndex + 1}({updatedAddress}) beginning...");
- _console.Out.WriteLine(updatedState);
- _console.Out.WriteLine($"- action #{index + 1} updated address #{addressIndex + 1}({updatedAddress}) end...");
+ _console.Out.WriteLine($"- action #{index + 1} updated value at address #{addressIndex + 1} ({updatedAddress})");
+ _console.Out.WriteLine($" from {stateDiff.Item1} to {stateDiff.Item2}");
}
}
}
diff --git a/NineChronicles.Headless.Executable/Commands/StateCommand.cs b/NineChronicles.Headless.Executable/Commands/StateCommand.cs
index b88506bb6..28a930868 100644
--- a/NineChronicles.Headless.Executable/Commands/StateCommand.cs
+++ b/NineChronicles.Headless.Executable/Commands/StateCommand.cs
@@ -429,69 +429,6 @@ IEnumerable IKeyValueStore.ListKeys() =>
_dictionary.Keys;
}
- private static ImmutableDictionary GetTotalDelta(
- IReadOnlyList actionEvaluations,
- Func toStateKey,
- Func<(Address, Currency), string> toFungibleAssetKey,
- Func toTotalSupplyKey,
- string validatorSetKey)
- {
- IImmutableSet stateUpdatedAddresses = actionEvaluations
- .SelectMany(a => a.OutputState.Delta.StateUpdatedAddresses)
- .ToImmutableHashSet();
- IImmutableSet<(Address, Currency)> updatedFungibleAssets = actionEvaluations
- .SelectMany(a => a.OutputState.Delta.UpdatedFungibleAssets)
- .ToImmutableHashSet();
- IImmutableSet updatedTotalSupplies = actionEvaluations
- .SelectMany(a => a.OutputState.Delta.UpdatedTotalSupplyCurrencies)
- .ToImmutableHashSet();
-
- if (actionEvaluations.Count == 0)
- {
- return ImmutableDictionary.Empty;
- }
-
- IAccount lastStates = actionEvaluations[actionEvaluations.Count - 1].OutputState;
-
- ImmutableDictionary totalDelta =
- stateUpdatedAddresses.ToImmutableDictionary(
- toStateKey,
- a => lastStates.GetState(a) ??
- throw new InvalidOperationException(
- "If it was updated well, the output states will include it also.")
- ).SetItems(
- updatedFungibleAssets.Select(pair =>
- new KeyValuePair(
- toFungibleAssetKey(pair),
- new Bencodex.Types.Integer(
- lastStates.GetBalance(pair.Item1, pair.Item2).RawValue
- )
- )
- )
- );
-
- foreach (var currency in updatedTotalSupplies)
- {
- if (lastStates.GetTotalSupply(currency).RawValue is { } rawValue)
- {
- totalDelta = totalDelta.SetItem(
- toTotalSupplyKey(currency),
- new Bencodex.Types.Integer(rawValue)
- );
- }
- }
-
- if (lastStates.GetValidatorSet() is { } validatorSet && validatorSet.Validators.Any())
- {
- totalDelta = totalDelta.SetItem(
- validatorSetKey,
- validatorSet.Bencoded
- );
- }
-
- return totalDelta;
- }
-
private static string ToStateKey(Address address) => ByteUtil.Hex(address.ByteArray);
private static string ToFungibleAssetKey(Address address, Currency currency) =>
diff --git a/NineChronicles.Headless.Executable/Commands/TxCommand.cs b/NineChronicles.Headless.Executable/Commands/TxCommand.cs
index 88bef4faa..07482d9b6 100644
--- a/NineChronicles.Headless.Executable/Commands/TxCommand.cs
+++ b/NineChronicles.Headless.Executable/Commands/TxCommand.cs
@@ -87,15 +87,18 @@ public void Sign(
return action;
}).ToList();
+#pragma warning disable S3358 // Extract ternary condition.
Transaction tx = Transaction.Create(
nonce: nonce,
privateKey: new PrivateKey(ByteUtil.ParseHex(privateKey)),
genesisHash: BlockHash.FromString(genesisHash),
timestamp: DateTimeOffset.Parse(timestamp),
- gasLimit: parsedActions.Any(a => a is ITransferAssets or ITransferAsset) ? 4 : 1,
+ gasLimit: maxGasPrice.HasValue
+ ? parsedActions.Any(a => a is ITransferAssets or ITransferAsset) ? 4 : 1
+ : null,
maxGasPrice: maxGasPrice.HasValue ? maxGasPrice.Value * Currencies.Mead : null,
- actions: parsedActions.ToPlainValues()
- );
+ actions: parsedActions.ToPlainValues());
+#pragma warning restore S3358
byte[] raw = tx.Serialize();
if (bytes)
diff --git a/NineChronicles.Headless.Executable/Program.cs b/NineChronicles.Headless.Executable/Program.cs
index d3ada1c07..48391e338 100644
--- a/NineChronicles.Headless.Executable/Program.cs
+++ b/NineChronicles.Headless.Executable/Program.cs
@@ -258,10 +258,6 @@ public async Task Run(
return actionEvaluatorType switch
{
ActionEvaluatorType.Default => new DefaultActionEvaluatorConfiguration(),
- ActionEvaluatorType.RemoteActionEvaluator => new RemoteActionEvaluatorConfiguration
- {
- StateServiceEndpoint = configuration.GetValue("StateServiceEndpoint"),
- },
ActionEvaluatorType.ForkableActionEvaluator => new ForkableActionEvaluatorConfiguration
{
Pairs = (configuration.GetSection("Pairs") ??
@@ -275,6 +271,10 @@ public async Task Run(
return (range, actionEvaluatorConfiguration);
}).ToImmutableArray()
},
+ ActionEvaluatorType.PluggedActionEvaluator => new PluggedActionEvaluatorConfiguration
+ {
+ PluginPath = configuration.GetValue("PluginPath"),
+ },
_ => throw new InvalidOperationException("Unexpected type."),
};
}
diff --git a/NineChronicles.Headless.Executable/appsettings-schema.json b/NineChronicles.Headless.Executable/appsettings-schema.json
index 19dd91e11..55a6e225a 100644
--- a/NineChronicles.Headless.Executable/appsettings-schema.json
+++ b/NineChronicles.Headless.Executable/appsettings-schema.json
@@ -217,7 +217,21 @@
"type": "string"
}
},
- "required": ["stateServiceEndpoint"],
+ "required": [ "stateServiceEndpoint" ],
+ "additionalProperties": false
+ },
+ {
+ "type": "object",
+ "properties": {
+ "type": {
+ "const": "PluggedActionEvaluator"
+ },
+ "pluginPath": {
+ "$comment": "Local path or URI. If it is URI, download it under ./plugin",
+ "type": "string"
+ }
+ },
+ "required": [ "pluginPath" ],
"additionalProperties": false
},
{
@@ -238,7 +252,7 @@
"$ref": "#/definitions/action-evaluator"
}
},
- "required": ["range", "actionEvaluator"],
+ "required": [ "range", "actionEvaluator" ],
"additionalProperties": false
}
}
diff --git a/NineChronicles.Headless.Executable/appsettings.json b/NineChronicles.Headless.Executable/appsettings.json
index aef98715b..4cce07805 100644
--- a/NineChronicles.Headless.Executable/appsettings.json
+++ b/NineChronicles.Headless.Executable/appsettings.json
@@ -128,5 +128,10 @@
"ManagementTimeMinutes": 60,
"TxIntervalMinutes": 60,
"ThresholdCount": 29
+ },
+ "Jwt": {
+ "EnableJwtAuthentication": false,
+ "Key": "secretKey",
+ "Issuer": "planetariumhq.com"
}
}
diff --git a/NineChronicles.Headless/BlockChainService.cs b/NineChronicles.Headless/BlockChainService.cs
index 82fb5cb18..a05b128f7 100644
--- a/NineChronicles.Headless/BlockChainService.cs
+++ b/NineChronicles.Headless/BlockChainService.cs
@@ -221,7 +221,7 @@ public UnaryResult> GetSheets(
foreach (var b in addressBytesList)
{
var address = new Address(b);
- if (_memoryCache.TryGetValue(address.ToString(), out byte[] cached))
+ if (_memoryCache.TryGetSheet(address.ToString(), out byte[] cached))
{
result.TryAdd(b, cached);
}
diff --git a/NineChronicles.Headless/GraphQLService.cs b/NineChronicles.Headless/GraphQLService.cs
index 80be90055..de3b3b115 100644
--- a/NineChronicles.Headless/GraphQLService.cs
+++ b/NineChronicles.Headless/GraphQLService.cs
@@ -13,7 +13,6 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
-using Microsoft.Extensions.Options;
using NineChronicles.Headless.GraphTypes;
using NineChronicles.Headless.Middleware;
using NineChronicles.Headless.Properties;
@@ -25,6 +24,8 @@ public class GraphQLService
{
public const string LocalPolicyKey = "LocalPolicy";
+ public const string JwtPolicyKey = "JwtPolicy";
+
public const string NoCorsPolicyName = "AllowAllOrigins";
public const string SecretTokenKey = "secret";
@@ -129,6 +130,13 @@ public void ConfigureServices(IServiceCollection services)
services.Configure(Configuration.GetSection("MultiAccountManaging"));
}
+ var jwtOptions = Configuration.GetSection("Jwt");
+ if (Convert.ToBoolean(jwtOptions["EnableJwtAuthentication"]))
+ {
+ services.Configure(jwtOptions);
+ services.AddTransient();
+ }
+
if (!(Configuration[NoCorsKey] is null))
{
services.AddCors(
@@ -161,12 +169,19 @@ public void ConfigureServices(IServiceCollection services)
.AddLibplanetExplorer()
.AddUserContextBuilder()
.AddGraphQLAuthorization(
- options => options.AddPolicy(
- LocalPolicyKey,
- p =>
- p.RequireClaim(
- "role",
- "Admin")));
+ options =>
+ {
+ options.AddPolicy(
+ LocalPolicyKey,
+ p =>
+ p.RequireClaim(
+ "role",
+ "Admin"));
+ options.AddPolicy(
+ JwtPolicyKey,
+ p =>
+ p.RequireClaim("iss", jwtOptions["Issuer"]));
+ });
services.AddGraphTypes();
}
@@ -190,6 +205,11 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
app.UseMiddleware();
app.UseMiddleware();
+ if (Convert.ToBoolean(Configuration.GetSection("Jwt")["EnableJwtAuthentication"]))
+ {
+ app.UseMiddleware();
+ }
+
if (Configuration[NoCorsKey] is null)
{
app.UseCors();
diff --git a/NineChronicles.Headless/GraphTypes/StandaloneMutation.cs b/NineChronicles.Headless/GraphTypes/StandaloneMutation.cs
index b356498c8..dd1b523c7 100644
--- a/NineChronicles.Headless/GraphTypes/StandaloneMutation.cs
+++ b/NineChronicles.Headless/GraphTypes/StandaloneMutation.cs
@@ -28,6 +28,10 @@ IConfiguration configuration
{
this.AuthorizeWith(GraphQLService.LocalPolicyKey);
}
+ else if (Convert.ToBoolean(configuration.GetSection("Jwt")["EnableJwtAuthentication"]))
+ {
+ this.AuthorizeWith(GraphQLService.JwtPolicyKey);
+ }
Field(
name: "keyStore",
diff --git a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs
index 413d20e12..869f38e04 100644
--- a/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs
+++ b/NineChronicles.Headless/GraphTypes/StandaloneQuery.cs
@@ -31,6 +31,10 @@ public class StandaloneQuery : ObjectGraphType
public StandaloneQuery(StandaloneContext standaloneContext, IConfiguration configuration, ActionEvaluationPublisher publisher, StateMemoryCache stateMemoryCache)
{
bool useSecretToken = configuration[GraphQLService.SecretTokenKey] is { };
+ if (Convert.ToBoolean(configuration.GetSection("Jwt")["EnableJwtAuthentication"]))
+ {
+ this.AuthorizeWith(GraphQLService.JwtPolicyKey);
+ }
Field>(name: "stateQuery", arguments: new QueryArguments(
new QueryArgument
diff --git a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs
index bf3c025b7..d0c10c9b0 100644
--- a/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs
+++ b/NineChronicles.Headless/GraphTypes/StandaloneSubscription.cs
@@ -26,6 +26,7 @@
using Libplanet.Blockchain;
using Libplanet.Store;
using Libplanet.Types.Tx;
+using Microsoft.Extensions.Configuration;
using Serilog;
namespace NineChronicles.Headless.GraphTypes
@@ -129,9 +130,17 @@ public PreloadStateType()
private StandaloneContext StandaloneContext { get; }
- public StandaloneSubscription(StandaloneContext standaloneContext)
+ private IConfiguration Configuration { get; }
+
+ public StandaloneSubscription(StandaloneContext standaloneContext, IConfiguration configuration)
{
StandaloneContext = standaloneContext;
+ Configuration = configuration;
+ if (Convert.ToBoolean(configuration.GetSection("Jwt")["EnableJwtAuthentication"]))
+ {
+ this.AuthorizeWith(GraphQLService.JwtPolicyKey);
+ }
+
AddField(new EventStreamFieldType
{
Name = "tipChanged",
diff --git a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs
index 979e21b97..557a0a12b 100644
--- a/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs
+++ b/NineChronicles.Headless/GraphTypes/TransactionHeadlessQuery.cs
@@ -199,11 +199,8 @@ public TransactionHeadlessQuery(StandaloneContext standaloneContext)
Field>>(
name: "transactionResults",
arguments: new QueryArguments(
- new QueryArgument>>>
- {
- Name = "txIds",
- Description = "transaction ids."
- }
+ new QueryArgument>>
+ { Name = "txIds", Description = "transaction ids." }
),
resolve: context =>
{
diff --git a/NineChronicles.Headless/MemoryCacheExtensions.cs b/NineChronicles.Headless/MemoryCacheExtensions.cs
index 2c6366d83..3cda79d22 100644
--- a/NineChronicles.Headless/MemoryCacheExtensions.cs
+++ b/NineChronicles.Headless/MemoryCacheExtensions.cs
@@ -18,9 +18,14 @@ public static byte[] SetSheet(this MemoryCache cache, string cacheKey, IValue va
return compressed;
}
+ public static bool TryGetSheet(this MemoryCache cache, string cacheKey, out T cached)
+ {
+ return cache.TryGetValue(cacheKey, out cached);
+ }
+
public static string? GetSheet(this MemoryCache cache, string cacheKey)
{
- if (cache.TryGetValue(cacheKey, out byte[] cached))
+ if (cache.TryGetSheet(cacheKey, out byte[] cached))
{
return (Text)Codec.Decode(MessagePackSerializer.Deserialize(cached, Lz4Options));
}
diff --git a/NineChronicles.Headless/Middleware/JwtAuthenticationMiddleware.cs b/NineChronicles.Headless/Middleware/JwtAuthenticationMiddleware.cs
new file mode 100644
index 000000000..f30a5a933
--- /dev/null
+++ b/NineChronicles.Headless/Middleware/JwtAuthenticationMiddleware.cs
@@ -0,0 +1,85 @@
+using System;
+using System.IdentityModel.Tokens.Jwt;
+using System.Linq;
+using System.Security.Claims;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Primitives;
+using Microsoft.IdentityModel.Tokens;
+using Newtonsoft.Json;
+using Serilog;
+
+namespace NineChronicles.Headless.Middleware;
+
+public class JwtAuthenticationMiddleware : IMiddleware
+{
+ private readonly ILogger _logger;
+ private readonly JwtSecurityTokenHandler _tokenHandler = new JwtSecurityTokenHandler();
+ private readonly TokenValidationParameters _validationParams;
+
+ public JwtAuthenticationMiddleware(IConfiguration configuration)
+ {
+ _logger = Log.Logger.ForContext();
+ var jwtConfig = configuration.GetSection("Jwt");
+ var issuer = jwtConfig["Issuer"] ?? "";
+ var key = jwtConfig["Key"] ?? "";
+ _validationParams = new TokenValidationParameters
+ {
+ ValidateIssuer = true,
+ ValidateAudience = false,
+ ValidateLifetime = true,
+ ValidateIssuerSigningKey = true,
+ ValidIssuer = issuer,
+ IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(key.PadRight(512 / 8, '\0')))
+ };
+ }
+
+ public async Task InvokeAsync(HttpContext context, RequestDelegate next)
+ {
+ context.Request.Headers.TryGetValue("Authorization", out var authorization);
+ if (authorization.Count > 0)
+ {
+ try
+ {
+ var (scheme, token) = ExtractSchemeAndToken(authorization);
+ if (scheme == "Bearer")
+ {
+ ValidateTokenAndAddClaims(context, token);
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.Error($"Authorization error {e.Message}");
+ context.Response.StatusCode = 401;
+ context.Response.ContentType = "application/json";
+ await context.Response.WriteAsync(
+ JsonConvert.SerializeObject(
+ new { errpr = e.Message }
+ ));
+ return;
+ }
+ }
+ await next(context);
+ }
+
+ private (string scheme, string token) ExtractSchemeAndToken(StringValues authorizationHeader)
+ {
+ var headerValues = authorizationHeader[0].Split(" ");
+ if (headerValues.Length < 2)
+ {
+ throw new ArgumentException("Invalid Authorization header format.");
+ }
+
+ return (headerValues[0], headerValues[1]);
+ }
+
+ private void ValidateTokenAndAddClaims(HttpContext context, string token)
+ {
+ _tokenHandler.ValidateToken(token, _validationParams, out SecurityToken validatedToken);
+ var jwt = (JwtSecurityToken)validatedToken;
+ var claims = jwt.Claims.Select(claim => new Claim(claim.Type, claim.Value));
+ context.User.AddIdentity(new ClaimsIdentity(claims));
+ }
+}
diff --git a/NineChronicles.Headless/NineChronicles.Headless.csproj b/NineChronicles.Headless/NineChronicles.Headless.csproj
index 6bd8f6ce0..28e2dc0f3 100644
--- a/NineChronicles.Headless/NineChronicles.Headless.csproj
+++ b/NineChronicles.Headless/NineChronicles.Headless.csproj
@@ -39,6 +39,7 @@
+
diff --git a/NineChronicles.Headless/Properties/JwtOptions.cs b/NineChronicles.Headless/Properties/JwtOptions.cs
new file mode 100644
index 000000000..7fb6b8871
--- /dev/null
+++ b/NineChronicles.Headless/Properties/JwtOptions.cs
@@ -0,0 +1,10 @@
+namespace NineChronicles.Headless.Properties;
+
+public class JwtOptions
+{
+ public bool EnableJwtAuthentication { get; }
+
+ public string Key { get; } = "";
+
+ public string Issuer { get; } = "planetariumhq.com";
+}