Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable programmatic configuration of RpcServer plugin #206

Merged
merged 7 commits into from
Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ApplicationLogs/LogReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class LogReader : Plugin, IPersistencePlugin
public LogReader()
{
db = DB.Open(GetFullPath(Settings.Default.Path), new Options { CreateIfMissing = true });
RpcServer.RegisterMethods(this);
RpcServerPlugin.RegisterMethods(this);
}

protected override void Configure()
Expand Down
2 changes: 1 addition & 1 deletion src/RpcNep5Tracker/RpcNep5Tracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public class RpcNep5Tracker : Plugin, IPersistencePlugin

public RpcNep5Tracker()
{
RpcServer.RegisterMethods(this);
RpcServerPlugin.RegisterMethods(this);
}

protected override void Configure()
Expand Down
4 changes: 2 additions & 2 deletions src/RpcServer/RpcServer.Node.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ private JObject GetVersion(JArray _params)
private JObject SendRawTransaction(JArray _params)
{
Transaction tx = _params[0].AsString().HexToBytes().AsSerializable<Transaction>();
RelayResultReason reason = System.Blockchain.Ask<RelayResultReason>(tx).Result;
RelayResultReason reason = system.Blockchain.Ask<RelayResultReason>(tx).Result;
return GetRelayResult(reason, tx.Hash);
}

[RpcMethod]
private JObject SubmitBlock(JArray _params)
{
Block block = _params[0].AsString().HexToBytes().AsSerializable<Block>();
RelayResultReason reason = System.Blockchain.Ask<RelayResultReason>(block).Result;
RelayResultReason reason = system.Blockchain.Ask<RelayResultReason>(block).Result;
return GetRelayResult(reason, block.Hash);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/RpcServer/RpcServer.SmartContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public void SerializeUnsigned(BinaryWriter writer)

private JObject GetInvokeResult(byte[] script, IVerifiable checkWitnessHashes = null)
{
using ApplicationEngine engine = ApplicationEngine.Run(script, checkWitnessHashes, extraGAS: Settings.Default.MaxGasInvoke);
using ApplicationEngine engine = ApplicationEngine.Run(script, checkWitnessHashes, extraGAS: settings.MaxGasInvoke);
JObject json = new JObject();
json["script"] = script.ToHexString();
json["state"] = engine.State;
Expand Down
2 changes: 1 addition & 1 deletion src/RpcServer/RpcServer.Utilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ partial class RpcServer
[RpcMethod]
private JObject ListPlugins(JArray _params)
{
return new JArray(Plugins
return new JArray(Neo.Plugins.Plugin.Plugins
.OrderBy(u => u.Name)
.Select(u => new JObject
{
Expand Down
8 changes: 4 additions & 4 deletions src/RpcServer/RpcServer.Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ private JObject SendFrom(JArray _params)
if (tx.NetworkFee < calFee)
tx.NetworkFee = calFee;
}
if (tx.NetworkFee > Settings.Default.MaxFee)
if (tx.NetworkFee > settings.MaxFee)
throw new RpcException(-301, "The necessary fee is more than the Max_fee, this transaction is failed. Please increase your Max_fee value.");
return SignAndRelay(tx);
}
Expand Down Expand Up @@ -235,7 +235,7 @@ private JObject SendMany(JArray _params)
if (tx.NetworkFee < calFee)
tx.NetworkFee = calFee;
}
if (tx.NetworkFee > Settings.Default.MaxFee)
if (tx.NetworkFee > settings.MaxFee)
throw new RpcException(-301, "The necessary fee is more than the Max_fee, this transaction is failed. Please increase your Max_fee value.");
return SignAndRelay(tx);
}
Expand Down Expand Up @@ -273,7 +273,7 @@ private JObject SendToAddress(JArray _params)
if (tx.NetworkFee < calFee)
tx.NetworkFee = calFee;
}
if (tx.NetworkFee > Settings.Default.MaxFee)
if (tx.NetworkFee > settings.MaxFee)
throw new RpcException(-301, "The necessary fee is more than the Max_fee, this transaction is failed. Please increase your Max_fee value.");
return SignAndRelay(tx);
}
Expand All @@ -285,7 +285,7 @@ private JObject SignAndRelay(Transaction tx)
if (context.Completed)
{
tx.Witnesses = context.GetWitnesses();
System.LocalNode.Tell(new LocalNode.Relay { Inventory = tx });
system.LocalNode.Tell(new LocalNode.Relay { Inventory = tx });
return tx.ToJson();
}
else
Expand Down
41 changes: 20 additions & 21 deletions src/RpcServer/RpcServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,24 @@

namespace Neo.Plugins
{
public sealed partial class RpcServer : Plugin
public sealed partial class RpcServer : IDisposable
{
private static readonly Dictionary<string, Func<JArray, JObject>> methods = new Dictionary<string, Func<JArray, JObject>>();
private readonly Dictionary<string, Func<JArray, JObject>> methods = new Dictionary<string, Func<JArray, JObject>>();

private IWebHost host;
private readonly RpcServerSettings settings;
private readonly NeoSystem system;

public RpcServer()
public RpcServer(NeoSystem system, RpcServerSettings settings)
{
this.system = system;
this.settings = settings;
RegisterMethods(this);
}

private bool CheckAuth(HttpContext context)
{
if (string.IsNullOrEmpty(Settings.Default.RpcUser)) return true;
if (string.IsNullOrEmpty(settings.RpcUser)) return true;

context.Response.Headers["WWW-Authenticate"] = "Basic realm=\"Restricted\"";

Expand All @@ -50,12 +55,7 @@ private bool CheckAuth(HttpContext context)
if (authvalues.Length < 2)
return false;

return authvalues[0] == Settings.Default.RpcUser && authvalues[1] == Settings.Default.RpcPass;
}

protected override void Configure()
{
Settings.Load(GetConfiguration());
return authvalues[0] == settings.RpcUser && authvalues[1] == settings.RpcPass;
}

private static JObject CreateErrorResponse(JObject id, int code, string message, JObject data = null)
Expand All @@ -77,39 +77,38 @@ private static JObject CreateResponse(JObject id)
return response;
}

public override void Dispose()
public void Dispose()
{
base.Dispose();
if (host != null)
{
host.Dispose();
host = null;
}
}

protected override void OnPluginsLoaded()
public void StartRpcServer()
{
host = new WebHostBuilder().UseKestrel(options => options.Listen(Settings.Default.BindAddress, Settings.Default.Port, listenOptions =>
host = new WebHostBuilder().UseKestrel(options => options.Listen(settings.BindAddress, settings.Port, listenOptions =>
{
// Default value is unlimited
options.Limits.MaxConcurrentConnections = Settings.Default.MaxConcurrentConnections;
options.Limits.MaxConcurrentConnections = settings.MaxConcurrentConnections;
// Default value is 2 minutes
options.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(1);
// Default value is 30 seconds
options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(15);

if (string.IsNullOrEmpty(Settings.Default.SslCert)) return;
listenOptions.UseHttps(Settings.Default.SslCert, Settings.Default.SslCertPassword, httpsConnectionAdapterOptions =>
if (string.IsNullOrEmpty(settings.SslCert)) return;
listenOptions.UseHttps(settings.SslCert, settings.SslCertPassword, httpsConnectionAdapterOptions =>
{
if (Settings.Default.TrustedAuthorities is null || Settings.Default.TrustedAuthorities.Length == 0)
if (settings.TrustedAuthorities is null || settings.TrustedAuthorities.Length == 0)
return;
httpsConnectionAdapterOptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
httpsConnectionAdapterOptions.ClientCertificateValidation = (cert, chain, err) =>
{
if (err != SslPolicyErrors.None)
return false;
X509Certificate2 authority = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
return Settings.Default.TrustedAuthorities.Contains(authority.Thumbprint);
return settings.TrustedAuthorities.Contains(authority.Thumbprint);
};
});
}))
Expand Down Expand Up @@ -211,7 +210,7 @@ private JObject ProcessRequest(HttpContext context, JObject request)
try
{
string method = request["method"].AsString();
if (!CheckAuth(context) || Settings.Default.DisabledMethods.Contains(method))
if (!CheckAuth(context) || settings.DisabledMethods.Contains(method))
throw new RpcException(-400, "Access denied");
if (!methods.TryGetValue(method, out var func))
throw new RpcException(-32601, "Method not found");
Expand All @@ -236,7 +235,7 @@ private JObject ProcessRequest(HttpContext context, JObject request)
}
}

public static void RegisterMethods(object handler)
public void RegisterMethods(object handler)
{
foreach (MethodInfo method in handler.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
Expand Down
63 changes: 63 additions & 0 deletions src/RpcServer/RpcServerPlugin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System.Collections.Generic;

namespace Neo.Plugins
{
public sealed class RpcServerPlugin : Plugin
{
static List<object> handlers = new List<object>();
RpcServer server;
RpcServerSettings settings;

protected override void Configure()
{
var loadedSettings = new RpcServerSettings(GetConfiguration());
if (this.settings == null)
{
this.settings = loadedSettings;
}
else
{
this.settings.UpdateSettings(loadedSettings);
}
}

public override void Dispose()
{
base.Dispose();
if (server != null)
{
server.Dispose();
server = null;
}
}

protected override void OnPluginsLoaded()
{
this.server = new RpcServer(System, settings);

foreach (var handler in handlers)
{
this.server.RegisterMethods(handler);
}
handlers.Clear();

server.StartRpcServer();
}

public static void RegisterMethods(object handler)
{
// if RpcServerPlugin is already loaded, call RegisterMethods directly
foreach (var plugin in Plugin.Plugins)
{
if (plugin is RpcServerPlugin rpcServerPlugin)
{
rpcServerPlugin.server.RegisterMethods(handler);
return;
}
}

// otherwise, save the handler for use during RpcServerPlugin load
handlers.Add(handler);
}
}
}
81 changes: 81 additions & 0 deletions src/RpcServer/RpcServerSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using Microsoft.Extensions.Configuration;
using Neo.SmartContract.Native;
using System;
using System.Linq;
using System.Net;
using System.Numerics;

namespace Neo.Plugins
{
public class RpcServerSettings
{
// These read-only properties are used to in the creation
// of the WebHost RPC endpoint. These cannot be changed
// while the server is running

public IPAddress BindAddress { get; }
public ushort Port { get; }
public string SslCert { get; }
public string SslCertPassword { get; }
public string[] TrustedAuthorities { get; }
public int MaxConcurrentConnections { get; }

// these read-write properties can be changed while
// the server is running via auto-reconfiguration

public string RpcUser { get; private set; }
public string RpcPass { get; private set; }
public long MaxGasInvoke { get; private set; }
public long MaxFee { get; private set; }
public string[] DisabledMethods { get; private set; }

public RpcServerSettings(IPAddress bindAddress = null,
devhawk marked this conversation as resolved.
Show resolved Hide resolved
ushort port = 10332,
string sslCert = "",
string sslCertPassword = "",
string[] trustedAuthorities = null,
string rpcUser = "",
string rpcPass = "",
decimal maxGasInvoke = 10,
decimal maxFee = (decimal)0.1,
string[] disabledMethods = null,
int maxConcurrentConnections = 40)
{
this.BindAddress = bindAddress ?? IPAddress.Loopback;
this.Port = port;
this.SslCert = sslCert;
this.SslCertPassword = sslCertPassword;
this.TrustedAuthorities = trustedAuthorities ?? Array.Empty<string>();
this.RpcUser = rpcUser;
this.RpcPass = rpcPass;
this.MaxGasInvoke = (long)BigDecimal.Parse(maxGasInvoke.ToString(), NativeContract.GAS.Decimals).Value;
this.MaxFee = (long)BigDecimal.Parse(maxFee.ToString(), NativeContract.GAS.Decimals).Value;
this.DisabledMethods = disabledMethods ?? Array.Empty<string>();
this.MaxConcurrentConnections = maxConcurrentConnections;
}

public RpcServerSettings(IConfigurationSection section)
{
this.BindAddress = IPAddress.Parse(section.GetSection("BindAddress").Value);
this.Port = ushort.Parse(section.GetSection("Port").Value);
this.SslCert = section.GetSection("SslCert").Value;
this.SslCertPassword = section.GetSection("SslCertPassword").Value;
this.TrustedAuthorities = section.GetSection("TrustedAuthorities").GetChildren().Select(p => p.Get<string>()).ToArray();
this.RpcUser = section.GetSection("RpcUser").Value;
this.RpcPass = section.GetSection("RpcPass").Value;
this.MaxGasInvoke = (long)BigDecimal.Parse(section.GetValue("MaxGasInvoke", "10"), NativeContract.GAS.Decimals).Value;
this.MaxFee = (long)BigDecimal.Parse(section.GetValue("MaxFee", "0.1"), NativeContract.GAS.Decimals).Value;
this.DisabledMethods = section.GetSection("DisabledMethods").GetChildren().Select(p => p.Get<string>()).ToArray();
this.MaxConcurrentConnections = section.GetValue("MaxConcurrentConnections", 40);
}

public void UpdateSettings(RpcServerSettings settings)
{
this.RpcUser = settings.RpcUser;
this.RpcPass = settings.RpcPass;
this.MaxGasInvoke = settings.MaxGasInvoke;
this.MaxFee = settings.MaxFee;
this.DisabledMethods = settings.DisabledMethods;
}
}
}
44 changes: 0 additions & 44 deletions src/RpcServer/Settings.cs

This file was deleted.