Skip to content

Commit

Permalink
Enable programmatic configuration of RpcServer plugin (#206)
Browse files Browse the repository at this point in the history
* seperate out plugin from rpcServer functionality

* Update src/RpcServer/RpcServer.cs

* CR Feedback

* fix encoding

* fix build break

Co-authored-by: Harry Pierson <[email protected]>
Co-authored-by: Shargon <[email protected]>
  • Loading branch information
3 people authored Apr 15, 2020
1 parent f451ca8 commit d62b18b
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 75 deletions.
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 @@ -71,15 +71,15 @@ private JObject GetVersion(JArray _params)
private JObject SendRawTransaction(JArray _params)
{
Transaction tx = _params[0].AsString().HexToBytes().AsSerializable<Transaction>();
RelayResult reason = System.Blockchain.Ask<RelayResult>(tx).Result;
RelayResult reason = system.Blockchain.Ask<RelayResult>(tx).Result;
return GetRelayResult(reason.Result, tx.Hash);
}

[RpcMethod]
private JObject SubmitBlock(JArray _params)
{
Block block = _params[0].AsString().HexToBytes().AsSerializable<Block>();
RelayResult reason = System.Blockchain.Ask<RelayResult>(block).Result;
RelayResult reason = system.Blockchain.Ask<RelayResult>(block).Result;
return GetRelayResult(reason.Result, 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,
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.

0 comments on commit d62b18b

Please sign in to comment.