From ee562c23c8e7991c71af9a5233076261d61f76d4 Mon Sep 17 00:00:00 2001 From: erikzhang Date: Sun, 8 Dec 2019 00:45:52 +0800 Subject: [PATCH 01/12] Add RpcServer --- neo-plugins.sln | 8 +- src/RpcServer/RpcException.cs | 12 + src/RpcServer/RpcServer.cs | 650 +++++++++++++++++++++++++++++++++ src/RpcServer/RpcServer.csproj | 14 + 4 files changed, 683 insertions(+), 1 deletion(-) create mode 100644 src/RpcServer/RpcException.cs create mode 100644 src/RpcServer/RpcServer.cs create mode 100644 src/RpcServer/RpcServer.csproj diff --git a/neo-plugins.sln b/neo-plugins.sln index 107bd4127..7b707fe16 100644 --- a/neo-plugins.sln +++ b/neo-plugins.sln @@ -1,4 +1,3 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28729.10 @@ -27,6 +26,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LevelDBStore", "src\LevelDB EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RocksDBStore", "src\RocksDBStore\RocksDBStore.csproj", "{0E2AAF05-C55A-4B36-8750-F55743FBE4B3}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RpcServer", "src\RpcServer\RpcServer.csproj", "{1403FFE9-4265-4269-8E3D-5A79EFD108CA}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "neo-plugins.Tests", "tests\neo-plugins.Tests\neo-plugins.Tests.csproj", "{9E7EA895-302A-4C0C-BA9B-54F9A67AD75C}" EndProject Global @@ -75,6 +76,10 @@ Global {0E2AAF05-C55A-4B36-8750-F55743FBE4B3}.Debug|Any CPU.Build.0 = Debug|Any CPU {0E2AAF05-C55A-4B36-8750-F55743FBE4B3}.Release|Any CPU.ActiveCfg = Release|Any CPU {0E2AAF05-C55A-4B36-8750-F55743FBE4B3}.Release|Any CPU.Build.0 = Release|Any CPU + {1403FFE9-4265-4269-8E3D-5A79EFD108CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1403FFE9-4265-4269-8E3D-5A79EFD108CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1403FFE9-4265-4269-8E3D-5A79EFD108CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1403FFE9-4265-4269-8E3D-5A79EFD108CA}.Release|Any CPU.Build.0 = Release|Any CPU {9E7EA895-302A-4C0C-BA9B-54F9A67AD75C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9E7EA895-302A-4C0C-BA9B-54F9A67AD75C}.Debug|Any CPU.Build.0 = Debug|Any CPU {9E7EA895-302A-4C0C-BA9B-54F9A67AD75C}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -94,6 +99,7 @@ Global {14DB62D5-0EA1-4A98-8656-1AA2D0345206} = {97E81C78-1637-481F-9485-DA1225E94C23} {C66214CD-0B97-4EA5-B7A2-164F54346F19} = {97E81C78-1637-481F-9485-DA1225E94C23} {0E2AAF05-C55A-4B36-8750-F55743FBE4B3} = {97E81C78-1637-481F-9485-DA1225E94C23} + {1403FFE9-4265-4269-8E3D-5A79EFD108CA} = {97E81C78-1637-481F-9485-DA1225E94C23} {9E7EA895-302A-4C0C-BA9B-54F9A67AD75C} = {59D802AB-C552-422A-B9C3-64D329FBCDCC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/src/RpcServer/RpcException.cs b/src/RpcServer/RpcException.cs new file mode 100644 index 000000000..dbe03c109 --- /dev/null +++ b/src/RpcServer/RpcException.cs @@ -0,0 +1,12 @@ +using System; + +namespace Neo.Plugins +{ + public class RpcException : Exception + { + public RpcException(int code, string message) : base(message) + { + HResult = code; + } + } +} diff --git a/src/RpcServer/RpcServer.cs b/src/RpcServer/RpcServer.cs new file mode 100644 index 000000000..2affae40a --- /dev/null +++ b/src/RpcServer/RpcServer.cs @@ -0,0 +1,650 @@ +using Akka.Actor; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.ResponseCompression; +using Microsoft.AspNetCore.Server.Kestrel.Https; +using Microsoft.Extensions.DependencyInjection; +using Neo.IO; +using Neo.IO.Json; +using Neo.Ledger; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Net; +using System.Net.Security; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.Plugins +{ + public sealed class RpcServer : IDisposable + { + private class CheckWitnessHashes : IVerifiable + { + private readonly UInt160[] _scriptHashesForVerifying; + public Witness[] Witnesses { get; set; } + public int Size { get; } + + public CheckWitnessHashes(UInt160[] scriptHashesForVerifying) + { + _scriptHashesForVerifying = scriptHashesForVerifying; + } + + public void Serialize(BinaryWriter writer) + { + throw new NotImplementedException(); + } + + public void Deserialize(BinaryReader reader) + { + throw new NotImplementedException(); + } + + public void DeserializeUnsigned(BinaryReader reader) + { + throw new NotImplementedException(); + } + + public UInt160[] GetScriptHashesForVerifying(StoreView snapshot) + { + return _scriptHashesForVerifying; + } + + public void SerializeUnsigned(BinaryWriter writer) + { + throw new NotImplementedException(); + } + } + + public Wallet Wallet { get; set; } + public long MaxGasInvoke { get; } + + private IWebHost host; + private readonly NeoSystem system; + + public RpcServer(NeoSystem system, Wallet wallet = null, long maxGasInvoke = default) + { + this.system = system; + this.Wallet = wallet; + this.MaxGasInvoke = maxGasInvoke; + } + + private static JObject CreateErrorResponse(JObject id, int code, string message, JObject data = null) + { + JObject response = CreateResponse(id); + response["error"] = new JObject(); + response["error"]["code"] = code; + response["error"]["message"] = message; + if (data != null) + response["error"]["data"] = data; + return response; + } + + private static JObject CreateResponse(JObject id) + { + JObject response = new JObject(); + response["jsonrpc"] = "2.0"; + response["id"] = id; + return response; + } + + public void Dispose() + { + if (host != null) + { + host.Dispose(); + host = null; + } + } + + private JObject GetInvokeResult(byte[] script, IVerifiable checkWitnessHashes = null) + { + using ApplicationEngine engine = ApplicationEngine.Run(script, checkWitnessHashes, extraGAS: MaxGasInvoke); + JObject json = new JObject(); + json["script"] = script.ToHexString(); + json["state"] = engine.State; + json["gas_consumed"] = engine.GasConsumed.ToString(); + try + { + json["stack"] = new JArray(engine.ResultStack.Select(p => p.ToParameter().ToJson())); + } + catch (InvalidOperationException) + { + json["stack"] = "error: recursive reference"; + } + return json; + } + + private static JObject GetRelayResult(RelayResultReason reason, UInt256 hash) + { + if (reason == RelayResultReason.Succeed) + { + var ret = new JObject(); + ret["hash"] = hash.ToString(); + return ret; + } + else + { + throw new RpcException(-500, reason.ToString()); + } + } + + private JObject Process(string method, JArray _params) + { + switch (method) + { + case "getbestblockhash": + { + return GetBestBlockHash(); + } + case "getblock": + { + JObject key = _params[0]; + bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); + return GetBlock(key, verbose); + } + case "getblockcount": + { + return GetBlockCount(); + } + case "getblockhash": + { + uint height = uint.Parse(_params[0].AsString()); + return GetBlockHash(height); + } + case "getblockheader": + { + JObject key = _params[0]; + bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); + return GetBlockHeader(key, verbose); + } + case "getblocksysfee": + { + uint height = uint.Parse(_params[0].AsString()); + return GetBlockSysFee(height); + } + case "getconnectioncount": + { + return GetConnectionCount(); + } + case "getcontractstate": + { + UInt160 script_hash = UInt160.Parse(_params[0].AsString()); + return GetContractState(script_hash); + } + case "getpeers": + { + return GetPeers(); + } + case "getrawmempool": + { + bool shouldGetUnverified = _params.Count >= 1 && _params[0].AsBoolean(); + return GetRawMemPool(shouldGetUnverified); + } + case "getrawtransaction": + { + UInt256 hash = UInt256.Parse(_params[0].AsString()); + bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); + return GetRawTransaction(hash, verbose); + } + case "getstorage": + { + UInt160 script_hash = UInt160.Parse(_params[0].AsString()); + byte[] key = _params[1].AsString().HexToBytes(); + return GetStorage(script_hash, key); + } + case "gettransactionheight": + { + UInt256 hash = UInt256.Parse(_params[0].AsString()); + return GetTransactionHeight(hash); + } + case "getvalidators": + { + return GetValidators(); + } + case "getversion": + { + return GetVersion(); + } + case "invokefunction": + { + UInt160 script_hash = UInt160.Parse(_params[0].AsString()); + string operation = _params[1].AsString(); + ContractParameter[] args = _params.Count >= 3 ? ((JArray)_params[2]).Select(p => ContractParameter.FromJson(p)).ToArray() : new ContractParameter[0]; + return InvokeFunction(script_hash, operation, args); + } + case "invokescript": + { + byte[] script = _params[0].AsString().HexToBytes(); + CheckWitnessHashes checkWitnessHashes = null; + if (_params.Count > 1) + { + UInt160[] scriptHashesForVerifying = _params.Skip(1).Select(u => UInt160.Parse(u.AsString())).ToArray(); + checkWitnessHashes = new CheckWitnessHashes(scriptHashesForVerifying); + } + return GetInvokeResult(script, checkWitnessHashes); + } + case "listplugins": + { + return ListPlugins(); + } + case "sendrawtransaction": + { + Transaction tx = _params[0].AsString().HexToBytes().AsSerializable(); + return SendRawTransaction(tx); + } + case "submitblock": + { + Block block = _params[0].AsString().HexToBytes().AsSerializable(); + return SubmitBlock(block); + } + case "validateaddress": + { + string address = _params[0].AsString(); + return ValidateAddress(address); + } + default: + throw new RpcException(-32601, "Method not found"); + } + } + + private async Task ProcessAsync(HttpContext context) + { + context.Response.Headers["Access-Control-Allow-Origin"] = "*"; + context.Response.Headers["Access-Control-Allow-Methods"] = "GET, POST"; + context.Response.Headers["Access-Control-Allow-Headers"] = "Content-Type"; + context.Response.Headers["Access-Control-Max-Age"] = "31536000"; + if (context.Request.Method != "GET" && context.Request.Method != "POST") return; + JObject request = null; + if (context.Request.Method == "GET") + { + string jsonrpc = context.Request.Query["jsonrpc"]; + string id = context.Request.Query["id"]; + string method = context.Request.Query["method"]; + string _params = context.Request.Query["params"]; + if (!string.IsNullOrEmpty(id) && !string.IsNullOrEmpty(method) && !string.IsNullOrEmpty(_params)) + { + try + { + _params = Encoding.UTF8.GetString(Convert.FromBase64String(_params)); + } + catch (FormatException) { } + request = new JObject(); + if (!string.IsNullOrEmpty(jsonrpc)) + request["jsonrpc"] = jsonrpc; + request["id"] = id; + request["method"] = method; + request["params"] = JObject.Parse(_params); + } + } + else if (context.Request.Method == "POST") + { + using StreamReader reader = new StreamReader(context.Request.Body); + try + { + request = JObject.Parse(reader); + } + catch (FormatException) { } + } + JObject response; + if (request == null) + { + response = CreateErrorResponse(null, -32700, "Parse error"); + } + else if (request is JArray array) + { + if (array.Count == 0) + { + response = CreateErrorResponse(request["id"], -32600, "Invalid Request"); + } + else + { + response = array.Select(p => ProcessRequest(context, p)).Where(p => p != null).ToArray(); + } + } + else + { + response = ProcessRequest(context, request); + } + if (response == null || (response as JArray)?.Count == 0) return; + context.Response.ContentType = "application/json-rpc"; + await context.Response.WriteAsync(response.ToString(), Encoding.UTF8); + } + + private JObject ProcessRequest(HttpContext context, JObject request) + { + if (!request.ContainsProperty("id")) return null; + if (!request.ContainsProperty("method") || !request.ContainsProperty("params") || !(request["params"] is JArray)) + { + return CreateErrorResponse(request["id"], -32600, "Invalid Request"); + } + JObject response = CreateResponse(request["id"]); + try + { + string method = request["method"].AsString(); + JArray _params = (JArray)request["params"]; + response["result"] = Process(method, _params); + return response; + } + catch (FormatException) + { + return CreateErrorResponse(request["id"], -32602, "Invalid params"); + } + catch (IndexOutOfRangeException) + { + return CreateErrorResponse(request["id"], -32602, "Invalid params"); + } + catch (Exception ex) + { +#if DEBUG + return CreateErrorResponse(request["id"], ex.HResult, ex.Message, ex.StackTrace); +#else + return CreateErrorResponse(request["id"], ex.HResult, ex.Message); +#endif + } + } + + public void Start(IPAddress bindAddress, int port, string sslCert = null, string password = null, string[] trustedAuthorities = null) + { + host = new WebHostBuilder().UseKestrel(options => options.Listen(bindAddress, port, listenOptions => + { + if (string.IsNullOrEmpty(sslCert)) return; + listenOptions.UseHttps(sslCert, password, httpsConnectionAdapterOptions => + { + if (trustedAuthorities is null || 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 trustedAuthorities.Contains(authority.Thumbprint); + }; + }); + })) + .Configure(app => + { + app.UseResponseCompression(); + app.Run(ProcessAsync); + }) + .ConfigureServices(services => + { + services.AddResponseCompression(options => + { + // options.EnableForHttps = false; + options.Providers.Add(); + options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Append("application/json-rpc"); + }); + + services.Configure(options => + { + options.Level = CompressionLevel.Fastest; + }); + }) + .Build(); + + host.Start(); + } + + private JObject GetBestBlockHash() + { + return Blockchain.Singleton.CurrentBlockHash.ToString(); + } + + private JObject GetBlock(JObject key, bool verbose) + { + Block block; + if (key is JNumber) + { + uint index = uint.Parse(key.AsString()); + block = Blockchain.Singleton.GetBlock(index); + } + else + { + UInt256 hash = UInt256.Parse(key.AsString()); + block = Blockchain.Singleton.View.GetBlock(hash); + } + if (block == null) + throw new RpcException(-100, "Unknown block"); + if (verbose) + { + JObject json = block.ToJson(); + json["confirmations"] = Blockchain.Singleton.Height - block.Index + 1; + UInt256 hash = Blockchain.Singleton.GetNextBlockHash(block.Hash); + if (hash != null) + json["nextblockhash"] = hash.ToString(); + return json; + } + return block.ToArray().ToHexString(); + } + + private JObject GetBlockCount() + { + return Blockchain.Singleton.Height + 1; + } + + private JObject GetBlockHash(uint height) + { + if (height <= Blockchain.Singleton.Height) + { + return Blockchain.Singleton.GetBlockHash(height).ToString(); + } + throw new RpcException(-100, "Invalid Height"); + } + + private JObject GetBlockHeader(JObject key, bool verbose) + { + Header header; + if (key is JNumber) + { + uint height = uint.Parse(key.AsString()); + header = Blockchain.Singleton.GetHeader(height); + } + else + { + UInt256 hash = UInt256.Parse(key.AsString()); + header = Blockchain.Singleton.View.GetHeader(hash); + } + if (header == null) + throw new RpcException(-100, "Unknown block"); + + if (verbose) + { + JObject json = header.ToJson(); + json["confirmations"] = Blockchain.Singleton.Height - header.Index + 1; + UInt256 hash = Blockchain.Singleton.GetNextBlockHash(header.Hash); + if (hash != null) + json["nextblockhash"] = hash.ToString(); + return json; + } + + return header.ToArray().ToHexString(); + } + + private JObject GetBlockSysFee(uint height) + { + if (height <= Blockchain.Singleton.Height) + using (ApplicationEngine engine = NativeContract.GAS.TestCall("getSysFeeAmount", height)) + { + return engine.ResultStack.Peek().GetBigInteger().ToString(); + } + throw new RpcException(-100, "Invalid Height"); + } + + private JObject GetConnectionCount() + { + return LocalNode.Singleton.ConnectedCount; + } + + private JObject GetContractState(UInt160 script_hash) + { + ContractState contract = Blockchain.Singleton.View.Contracts.TryGet(script_hash); + return contract?.ToJson() ?? throw new RpcException(-100, "Unknown contract"); + } + + private JObject GetPeers() + { + JObject json = new JObject(); + json["unconnected"] = new JArray(LocalNode.Singleton.GetUnconnectedPeers().Select(p => + { + JObject peerJson = new JObject(); + peerJson["address"] = p.Address.ToString(); + peerJson["port"] = p.Port; + return peerJson; + })); + json["bad"] = new JArray(); //badpeers has been removed + json["connected"] = new JArray(LocalNode.Singleton.GetRemoteNodes().Select(p => + { + JObject peerJson = new JObject(); + peerJson["address"] = p.Remote.Address.ToString(); + peerJson["port"] = p.ListenerTcpPort; + return peerJson; + })); + return json; + } + + private JObject GetRawMemPool(bool shouldGetUnverified) + { + if (!shouldGetUnverified) + return new JArray(Blockchain.Singleton.MemPool.GetVerifiedTransactions().Select(p => (JObject)p.Hash.ToString())); + + JObject json = new JObject(); + json["height"] = Blockchain.Singleton.Height; + Blockchain.Singleton.MemPool.GetVerifiedAndUnverifiedTransactions( + out IEnumerable verifiedTransactions, + out IEnumerable unverifiedTransactions); + json["verified"] = new JArray(verifiedTransactions.Select(p => (JObject)p.Hash.ToString())); + json["unverified"] = new JArray(unverifiedTransactions.Select(p => (JObject)p.Hash.ToString())); + return json; + } + + private JObject GetRawTransaction(UInt256 hash, bool verbose) + { + Transaction tx = Blockchain.Singleton.GetTransaction(hash); + if (tx == null) + throw new RpcException(-100, "Unknown transaction"); + if (verbose) + { + JObject json = tx.ToJson(); + TransactionState txState = Blockchain.Singleton.View.Transactions.TryGet(hash); + if (txState != null) + { + Header header = Blockchain.Singleton.GetHeader(txState.BlockIndex); + json["blockhash"] = header.Hash.ToString(); + json["confirmations"] = Blockchain.Singleton.Height - header.Index + 1; + json["blocktime"] = header.Timestamp; + json["vmState"] = txState.VMState; + } + return json; + } + return tx.ToArray().ToHexString(); + } + + private JObject GetStorage(UInt160 script_hash, byte[] key) + { + StorageItem item = Blockchain.Singleton.View.Storages.TryGet(new StorageKey + { + ScriptHash = script_hash, + Key = key + }) ?? new StorageItem(); + return item.Value?.ToHexString(); + } + + private JObject GetTransactionHeight(UInt256 hash) + { + uint? height = Blockchain.Singleton.View.Transactions.TryGet(hash)?.BlockIndex; + if (height.HasValue) return height.Value; + throw new RpcException(-100, "Unknown transaction"); + } + + private JObject GetValidators() + { + using SnapshotView snapshot = Blockchain.Singleton.GetSnapshot(); + var validators = NativeContract.NEO.GetValidators(snapshot); + return NativeContract.NEO.GetRegisteredValidators(snapshot).Select(p => + { + JObject validator = new JObject(); + validator["publickey"] = p.PublicKey.ToString(); + validator["votes"] = p.Votes.ToString(); + validator["active"] = validators.Contains(p.PublicKey); + return validator; + }).ToArray(); + } + + private JObject GetVersion() + { + JObject json = new JObject(); + json["tcpPort"] = LocalNode.Singleton.ListenerTcpPort; + json["wsPort"] = LocalNode.Singleton.ListenerWsPort; + json["nonce"] = LocalNode.Nonce; + json["useragent"] = LocalNode.UserAgent; + return json; + } + + private JObject InvokeFunction(UInt160 script_hash, string operation, ContractParameter[] args) + { + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + script = sb.EmitAppCall(script_hash, operation, args).ToArray(); + } + return GetInvokeResult(script); + } + + private JObject ListPlugins() + { + return new JArray(Plugin.Plugins + .OrderBy(u => u.Name) + .Select(u => new JObject + { + ["name"] = u.Name, + ["version"] = u.Version.ToString(), + ["interfaces"] = new JArray(u.GetType().GetInterfaces() + .Select(p => p.Name) + .Where(p => p.EndsWith("Plugin")) + .Select(p => (JObject)p)) + })); + } + + private JObject SendRawTransaction(Transaction tx) + { + RelayResultReason reason = system.Blockchain.Ask(tx).Result; + return GetRelayResult(reason, tx.Hash); + } + + private JObject SubmitBlock(Block block) + { + RelayResultReason reason = system.Blockchain.Ask(block).Result; + return GetRelayResult(reason, block.Hash); + } + + private JObject ValidateAddress(string address) + { + JObject json = new JObject(); + UInt160 scriptHash; + try + { + scriptHash = address.ToScriptHash(); + } + catch + { + scriptHash = null; + } + json["address"] = address; + json["isvalid"] = scriptHash != null; + return json; + } + } +} diff --git a/src/RpcServer/RpcServer.csproj b/src/RpcServer/RpcServer.csproj new file mode 100644 index 000000000..cdc92ffd5 --- /dev/null +++ b/src/RpcServer/RpcServer.csproj @@ -0,0 +1,14 @@ + + + + 3.0.0-CI00825 + netstandard2.1 + Neo.Plugins + + + + + + + + From e51ea7590d9194fc37663d0e5eba8f9ed89cc2c5 Mon Sep 17 00:00:00 2001 From: erikzhang Date: Sun, 8 Dec 2019 01:13:01 +0800 Subject: [PATCH 02/12] Start as a plugin --- src/RpcServer/RpcServer.cs | 111 ++++++++++++++-------------- src/RpcServer/RpcServer.csproj | 7 ++ src/RpcServer/RpcServer/config.json | 10 +++ src/RpcServer/Settings.cs | 34 +++++++++ 4 files changed, 106 insertions(+), 56 deletions(-) create mode 100644 src/RpcServer/RpcServer/config.json create mode 100644 src/RpcServer/Settings.cs diff --git a/src/RpcServer/RpcServer.cs b/src/RpcServer/RpcServer.cs index 2affae40a..75bab3a2d 100644 --- a/src/RpcServer/RpcServer.cs +++ b/src/RpcServer/RpcServer.cs @@ -20,7 +20,6 @@ using System.IO; using System.IO.Compression; using System.Linq; -using System.Net; using System.Net.Security; using System.Security.Cryptography.X509Certificates; using System.Text; @@ -28,7 +27,7 @@ namespace Neo.Plugins { - public sealed class RpcServer : IDisposable + public sealed class RpcServer : Plugin { private class CheckWitnessHashes : IVerifiable { @@ -67,17 +66,12 @@ public void SerializeUnsigned(BinaryWriter writer) } } - public Wallet Wallet { get; set; } - public long MaxGasInvoke { get; } - private IWebHost host; - private readonly NeoSystem system; + private Wallet wallet; - public RpcServer(NeoSystem system, Wallet wallet = null, long maxGasInvoke = default) + protected override void Configure() { - this.system = system; - this.Wallet = wallet; - this.MaxGasInvoke = maxGasInvoke; + Settings.Load(GetConfiguration()); } private static JObject CreateErrorResponse(JObject id, int code, string message, JObject data = null) @@ -99,8 +93,9 @@ private static JObject CreateResponse(JObject id) return response; } - public void Dispose() + public override void Dispose() { + base.Dispose(); if (host != null) { host.Dispose(); @@ -110,7 +105,7 @@ public void Dispose() private JObject GetInvokeResult(byte[] script, IVerifiable checkWitnessHashes = null) { - using ApplicationEngine engine = ApplicationEngine.Run(script, checkWitnessHashes, extraGAS: MaxGasInvoke); + using ApplicationEngine engine = ApplicationEngine.Run(script, checkWitnessHashes, extraGAS: Settings.Default.MaxGasInvoke); JObject json = new JObject(); json["script"] = script.ToHexString(); json["state"] = engine.State; @@ -140,6 +135,49 @@ private static JObject GetRelayResult(RelayResultReason reason, UInt256 hash) } } + protected override void OnPluginsLoaded() + { + host = new WebHostBuilder().UseKestrel(options => options.Listen(Settings.Default.BindAddress, Settings.Default.Port, listenOptions => + { + if (string.IsNullOrEmpty(Settings.Default.SslCert)) return; + listenOptions.UseHttps(Settings.Default.SslCert, Settings.Default.SslCertPassword, httpsConnectionAdapterOptions => + { + if (Settings.Default.TrustedAuthorities is null || Settings.Default.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); + }; + }); + })) + .Configure(app => + { + app.UseResponseCompression(); + app.Run(ProcessAsync); + }) + .ConfigureServices(services => + { + services.AddResponseCompression(options => + { + // options.EnableForHttps = false; + options.Providers.Add(); + options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Append("application/json-rpc"); + }); + + services.Configure(options => + { + options.Level = CompressionLevel.Fastest; + }); + }) + .Build(); + + host.Start(); + } + private JObject Process(string method, JArray _params) { switch (method) @@ -355,48 +393,7 @@ private JObject ProcessRequest(HttpContext context, JObject request) } } - public void Start(IPAddress bindAddress, int port, string sslCert = null, string password = null, string[] trustedAuthorities = null) - { - host = new WebHostBuilder().UseKestrel(options => options.Listen(bindAddress, port, listenOptions => - { - if (string.IsNullOrEmpty(sslCert)) return; - listenOptions.UseHttps(sslCert, password, httpsConnectionAdapterOptions => - { - if (trustedAuthorities is null || 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 trustedAuthorities.Contains(authority.Thumbprint); - }; - }); - })) - .Configure(app => - { - app.UseResponseCompression(); - app.Run(ProcessAsync); - }) - .ConfigureServices(services => - { - services.AddResponseCompression(options => - { - // options.EnableForHttps = false; - options.Providers.Add(); - options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Append("application/json-rpc"); - }); - - services.Configure(options => - { - options.Level = CompressionLevel.Fastest; - }); - }) - .Build(); - - host.Start(); - } + #region RPC methods private JObject GetBestBlockHash() { @@ -620,13 +617,13 @@ private JObject ListPlugins() private JObject SendRawTransaction(Transaction tx) { - RelayResultReason reason = system.Blockchain.Ask(tx).Result; + RelayResultReason reason = System.Blockchain.Ask(tx).Result; return GetRelayResult(reason, tx.Hash); } private JObject SubmitBlock(Block block) { - RelayResultReason reason = system.Blockchain.Ask(block).Result; + RelayResultReason reason = System.Blockchain.Ask(block).Result; return GetRelayResult(reason, block.Hash); } @@ -646,5 +643,7 @@ private JObject ValidateAddress(string address) json["isvalid"] = scriptHash != null; return json; } + + #endregion } } diff --git a/src/RpcServer/RpcServer.csproj b/src/RpcServer/RpcServer.csproj index cdc92ffd5..23da0cb1e 100644 --- a/src/RpcServer/RpcServer.csproj +++ b/src/RpcServer/RpcServer.csproj @@ -6,6 +6,13 @@ Neo.Plugins + + + PreserveNewest + PreserveNewest + + + diff --git a/src/RpcServer/RpcServer/config.json b/src/RpcServer/RpcServer/config.json new file mode 100644 index 000000000..b0433623e --- /dev/null +++ b/src/RpcServer/RpcServer/config.json @@ -0,0 +1,10 @@ +{ + "PluginConfiguration": { + "BindAddress": "127.0.0.1", + "Port": 10332, + "SslCert": "", + "SslCertPassword": "", + "TrustedAuthorities": [], + "MaxGasInvoke": 10 + } +} diff --git a/src/RpcServer/Settings.cs b/src/RpcServer/Settings.cs new file mode 100644 index 000000000..625f8543f --- /dev/null +++ b/src/RpcServer/Settings.cs @@ -0,0 +1,34 @@ +using Microsoft.Extensions.Configuration; +using Neo.SmartContract.Native; +using System.Linq; +using System.Net; + +namespace Neo.Plugins +{ + internal class Settings + { + public IPAddress BindAddress { get; } + public ushort Port { get; } + public string SslCert { get; } + public string SslCertPassword { get; } + public string[] TrustedAuthorities { get; } + public long MaxGasInvoke { get; } + + public static Settings Default { get; private set; } + + private Settings(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()).ToArray(); + this.MaxGasInvoke = (long)BigDecimal.Parse(section.GetValue("MaxGasInvoke", "10"), NativeContract.GAS.Decimals).Value; + } + + public static void Load(IConfigurationSection section) + { + Default = new Settings(section); + } + } +} From 02462fe4b43873d0107b37746379918d4c62a729 Mon Sep 17 00:00:00 2001 From: erikzhang Date: Sun, 8 Dec 2019 02:27:25 +0800 Subject: [PATCH 03/12] Modularize --- src/RpcServer/RpcException.cs | 2 +- src/RpcServer/RpcMethodAttribute.cs | 10 + src/RpcServer/RpcServer.Blockchain.cs | 204 ++++++++++ src/RpcServer/RpcServer.Node.cs | 85 +++++ src/RpcServer/RpcServer.SmartContract.cs | 99 +++++ src/RpcServer/RpcServer.Utilities.cs | 48 +++ src/RpcServer/RpcServer.cs | 465 +---------------------- 7 files changed, 462 insertions(+), 451 deletions(-) create mode 100644 src/RpcServer/RpcMethodAttribute.cs create mode 100644 src/RpcServer/RpcServer.Blockchain.cs create mode 100644 src/RpcServer/RpcServer.Node.cs create mode 100644 src/RpcServer/RpcServer.SmartContract.cs create mode 100644 src/RpcServer/RpcServer.Utilities.cs diff --git a/src/RpcServer/RpcException.cs b/src/RpcServer/RpcException.cs index dbe03c109..e3bf6f939 100644 --- a/src/RpcServer/RpcException.cs +++ b/src/RpcServer/RpcException.cs @@ -2,7 +2,7 @@ namespace Neo.Plugins { - public class RpcException : Exception + internal class RpcException : Exception { public RpcException(int code, string message) : base(message) { diff --git a/src/RpcServer/RpcMethodAttribute.cs b/src/RpcServer/RpcMethodAttribute.cs new file mode 100644 index 000000000..010558b42 --- /dev/null +++ b/src/RpcServer/RpcMethodAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace Neo.Plugins +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + internal class RpcMethodAttribute : Attribute + { + public string Name { get; set; } + } +} diff --git a/src/RpcServer/RpcServer.Blockchain.cs b/src/RpcServer/RpcServer.Blockchain.cs new file mode 100644 index 000000000..2511736ca --- /dev/null +++ b/src/RpcServer/RpcServer.Blockchain.cs @@ -0,0 +1,204 @@ +#pragma warning disable IDE0051 +#pragma warning disable IDE0060 + +using Neo.IO; +using Neo.IO.Json; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.Plugins +{ + partial class RpcServer + { + [RpcMethod] + private JObject GetBestBlockHash(JArray _params) + { + return Blockchain.Singleton.CurrentBlockHash.ToString(); + } + + [RpcMethod] + private JObject GetBlock(JArray _params) + { + JObject key = _params[0]; + bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); + Block block; + if (key is JNumber) + { + uint index = uint.Parse(key.AsString()); + block = Blockchain.Singleton.GetBlock(index); + } + else + { + UInt256 hash = UInt256.Parse(key.AsString()); + block = Blockchain.Singleton.View.GetBlock(hash); + } + if (block == null) + throw new RpcException(-100, "Unknown block"); + if (verbose) + { + JObject json = block.ToJson(); + json["confirmations"] = Blockchain.Singleton.Height - block.Index + 1; + UInt256 hash = Blockchain.Singleton.GetNextBlockHash(block.Hash); + if (hash != null) + json["nextblockhash"] = hash.ToString(); + return json; + } + return block.ToArray().ToHexString(); + } + + [RpcMethod] + private JObject GetBlockCount(JArray _params) + { + return Blockchain.Singleton.Height + 1; + } + + [RpcMethod] + private JObject GetBlockHash(JArray _params) + { + uint height = uint.Parse(_params[0].AsString()); + if (height <= Blockchain.Singleton.Height) + { + return Blockchain.Singleton.GetBlockHash(height).ToString(); + } + throw new RpcException(-100, "Invalid Height"); + } + + [RpcMethod] + private JObject GetBlockHeader(JArray _params) + { + JObject key = _params[0]; + bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); + Header header; + if (key is JNumber) + { + uint height = uint.Parse(key.AsString()); + header = Blockchain.Singleton.GetHeader(height); + } + else + { + UInt256 hash = UInt256.Parse(key.AsString()); + header = Blockchain.Singleton.View.GetHeader(hash); + } + if (header == null) + throw new RpcException(-100, "Unknown block"); + + if (verbose) + { + JObject json = header.ToJson(); + json["confirmations"] = Blockchain.Singleton.Height - header.Index + 1; + UInt256 hash = Blockchain.Singleton.GetNextBlockHash(header.Hash); + if (hash != null) + json["nextblockhash"] = hash.ToString(); + return json; + } + + return header.ToArray().ToHexString(); + } + + [RpcMethod] + private JObject GetBlockSysFee(JArray _params) + { + uint height = uint.Parse(_params[0].AsString()); + if (height <= Blockchain.Singleton.Height) + using (ApplicationEngine engine = NativeContract.GAS.TestCall("getSysFeeAmount", height)) + { + return engine.ResultStack.Peek().GetBigInteger().ToString(); + } + throw new RpcException(-100, "Invalid Height"); + } + + [RpcMethod] + private JObject GetContractState(JArray _params) + { + UInt160 script_hash = UInt160.Parse(_params[0].AsString()); + ContractState contract = Blockchain.Singleton.View.Contracts.TryGet(script_hash); + return contract?.ToJson() ?? throw new RpcException(-100, "Unknown contract"); + } + + [RpcMethod] + private JObject GetRawMemPool(JArray _params) + { + bool shouldGetUnverified = _params.Count >= 1 && _params[0].AsBoolean(); + if (!shouldGetUnverified) + return new JArray(Blockchain.Singleton.MemPool.GetVerifiedTransactions().Select(p => (JObject)p.Hash.ToString())); + + JObject json = new JObject(); + json["height"] = Blockchain.Singleton.Height; + Blockchain.Singleton.MemPool.GetVerifiedAndUnverifiedTransactions( + out IEnumerable verifiedTransactions, + out IEnumerable unverifiedTransactions); + json["verified"] = new JArray(verifiedTransactions.Select(p => (JObject)p.Hash.ToString())); + json["unverified"] = new JArray(unverifiedTransactions.Select(p => (JObject)p.Hash.ToString())); + return json; + } + + [RpcMethod] + private JObject GetRawTransaction(JArray _params) + { + UInt256 hash = UInt256.Parse(_params[0].AsString()); + bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); + Transaction tx = Blockchain.Singleton.GetTransaction(hash); + if (tx == null) + throw new RpcException(-100, "Unknown transaction"); + if (verbose) + { + JObject json = tx.ToJson(); + TransactionState txState = Blockchain.Singleton.View.Transactions.TryGet(hash); + if (txState != null) + { + Header header = Blockchain.Singleton.GetHeader(txState.BlockIndex); + json["blockhash"] = header.Hash.ToString(); + json["confirmations"] = Blockchain.Singleton.Height - header.Index + 1; + json["blocktime"] = header.Timestamp; + json["vmState"] = txState.VMState; + } + return json; + } + return tx.ToArray().ToHexString(); + } + + [RpcMethod] + private JObject GetStorage(JArray _params) + { + UInt160 script_hash = UInt160.Parse(_params[0].AsString()); + byte[] key = _params[1].AsString().HexToBytes(); + StorageItem item = Blockchain.Singleton.View.Storages.TryGet(new StorageKey + { + ScriptHash = script_hash, + Key = key + }) ?? new StorageItem(); + return item.Value?.ToHexString(); + } + + [RpcMethod] + private JObject GetTransactionHeight(JArray _params) + { + UInt256 hash = UInt256.Parse(_params[0].AsString()); + uint? height = Blockchain.Singleton.View.Transactions.TryGet(hash)?.BlockIndex; + if (height.HasValue) return height.Value; + throw new RpcException(-100, "Unknown transaction"); + } + + [RpcMethod] + private JObject GetValidators(JArray _params) + { + using SnapshotView snapshot = Blockchain.Singleton.GetSnapshot(); + var validators = NativeContract.NEO.GetValidators(snapshot); + return NativeContract.NEO.GetRegisteredValidators(snapshot).Select(p => + { + JObject validator = new JObject(); + validator["publickey"] = p.PublicKey.ToString(); + validator["votes"] = p.Votes.ToString(); + validator["active"] = validators.Contains(p.PublicKey); + return validator; + }).ToArray(); + } + } +} diff --git a/src/RpcServer/RpcServer.Node.cs b/src/RpcServer/RpcServer.Node.cs new file mode 100644 index 000000000..2b1018e8d --- /dev/null +++ b/src/RpcServer/RpcServer.Node.cs @@ -0,0 +1,85 @@ +#pragma warning disable IDE0051 +#pragma warning disable IDE0060 + +using Akka.Actor; +using Neo.IO; +using Neo.IO.Json; +using Neo.Ledger; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using System.Linq; + +namespace Neo.Plugins +{ + partial class RpcServer + { + [RpcMethod] + private JObject GetConnectionCount(JArray _params) + { + return LocalNode.Singleton.ConnectedCount; + } + + [RpcMethod] + private JObject GetPeers(JArray _params) + { + JObject json = new JObject(); + json["unconnected"] = new JArray(LocalNode.Singleton.GetUnconnectedPeers().Select(p => + { + JObject peerJson = new JObject(); + peerJson["address"] = p.Address.ToString(); + peerJson["port"] = p.Port; + return peerJson; + })); + json["bad"] = new JArray(); //badpeers has been removed + json["connected"] = new JArray(LocalNode.Singleton.GetRemoteNodes().Select(p => + { + JObject peerJson = new JObject(); + peerJson["address"] = p.Remote.Address.ToString(); + peerJson["port"] = p.ListenerTcpPort; + return peerJson; + })); + return json; + } + + private static JObject GetRelayResult(RelayResultReason reason, UInt256 hash) + { + if (reason == RelayResultReason.Succeed) + { + var ret = new JObject(); + ret["hash"] = hash.ToString(); + return ret; + } + else + { + throw new RpcException(-500, reason.ToString()); + } + } + + [RpcMethod] + private JObject GetVersion(JArray _params) + { + JObject json = new JObject(); + json["tcpPort"] = LocalNode.Singleton.ListenerTcpPort; + json["wsPort"] = LocalNode.Singleton.ListenerWsPort; + json["nonce"] = LocalNode.Nonce; + json["useragent"] = LocalNode.UserAgent; + return json; + } + + [RpcMethod] + private JObject SendRawTransaction(JArray _params) + { + Transaction tx = _params[0].AsString().HexToBytes().AsSerializable(); + RelayResultReason reason = System.Blockchain.Ask(tx).Result; + return GetRelayResult(reason, tx.Hash); + } + + [RpcMethod] + private JObject SubmitBlock(JArray _params) + { + Block block = _params[0].AsString().HexToBytes().AsSerializable(); + RelayResultReason reason = System.Blockchain.Ask(block).Result; + return GetRelayResult(reason, block.Hash); + } + } +} diff --git a/src/RpcServer/RpcServer.SmartContract.cs b/src/RpcServer/RpcServer.SmartContract.cs new file mode 100644 index 000000000..80ce67438 --- /dev/null +++ b/src/RpcServer/RpcServer.SmartContract.cs @@ -0,0 +1,99 @@ +#pragma warning disable IDE0051 +#pragma warning disable IDE0060 + +using Neo.IO.Json; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.VM; +using System; +using System.IO; +using System.Linq; + +namespace Neo.Plugins +{ + partial class RpcServer + { + private class CheckWitnessHashes : IVerifiable + { + private readonly UInt160[] _scriptHashesForVerifying; + public Witness[] Witnesses { get; set; } + public int Size { get; } + + public CheckWitnessHashes(UInt160[] scriptHashesForVerifying) + { + _scriptHashesForVerifying = scriptHashesForVerifying; + } + + public void Serialize(BinaryWriter writer) + { + throw new NotImplementedException(); + } + + public void Deserialize(BinaryReader reader) + { + throw new NotImplementedException(); + } + + public void DeserializeUnsigned(BinaryReader reader) + { + throw new NotImplementedException(); + } + + public UInt160[] GetScriptHashesForVerifying(StoreView snapshot) + { + return _scriptHashesForVerifying; + } + + public void SerializeUnsigned(BinaryWriter writer) + { + throw new NotImplementedException(); + } + } + + private JObject GetInvokeResult(byte[] script, IVerifiable checkWitnessHashes = null) + { + using ApplicationEngine engine = ApplicationEngine.Run(script, checkWitnessHashes, extraGAS: Settings.Default.MaxGasInvoke); + JObject json = new JObject(); + json["script"] = script.ToHexString(); + json["state"] = engine.State; + json["gas_consumed"] = engine.GasConsumed.ToString(); + try + { + json["stack"] = new JArray(engine.ResultStack.Select(p => p.ToParameter().ToJson())); + } + catch (InvalidOperationException) + { + json["stack"] = "error: recursive reference"; + } + return json; + } + + [RpcMethod] + private JObject InvokeFunction(JArray _params) + { + UInt160 script_hash = UInt160.Parse(_params[0].AsString()); + string operation = _params[1].AsString(); + ContractParameter[] args = _params.Count >= 3 ? ((JArray)_params[2]).Select(p => ContractParameter.FromJson(p)).ToArray() : new ContractParameter[0]; + byte[] script; + using (ScriptBuilder sb = new ScriptBuilder()) + { + script = sb.EmitAppCall(script_hash, operation, args).ToArray(); + } + return GetInvokeResult(script); + } + + [RpcMethod] + private JObject InvokeScript(JArray _params) + { + byte[] script = _params[0].AsString().HexToBytes(); + CheckWitnessHashes checkWitnessHashes = null; + if (_params.Count > 1) + { + UInt160[] scriptHashesForVerifying = _params.Skip(1).Select(u => UInt160.Parse(u.AsString())).ToArray(); + checkWitnessHashes = new CheckWitnessHashes(scriptHashesForVerifying); + } + return GetInvokeResult(script, checkWitnessHashes); + } + } +} diff --git a/src/RpcServer/RpcServer.Utilities.cs b/src/RpcServer/RpcServer.Utilities.cs new file mode 100644 index 000000000..0ddc31578 --- /dev/null +++ b/src/RpcServer/RpcServer.Utilities.cs @@ -0,0 +1,48 @@ +#pragma warning disable IDE0051 +#pragma warning disable IDE0060 + +using Neo.IO.Json; +using Neo.Wallets; +using System.Linq; + +namespace Neo.Plugins +{ + partial class RpcServer + { + + [RpcMethod] + private JObject ListPlugins(JArray _params) + { + return new JArray(Plugins + .OrderBy(u => u.Name) + .Select(u => new JObject + { + ["name"] = u.Name, + ["version"] = u.Version.ToString(), + ["interfaces"] = new JArray(u.GetType().GetInterfaces() + .Select(p => p.Name) + .Where(p => p.EndsWith("Plugin")) + .Select(p => (JObject)p)) + })); + } + + [RpcMethod] + private JObject ValidateAddress(JArray _params) + { + string address = _params[0].AsString(); + JObject json = new JObject(); + UInt160 scriptHash; + try + { + scriptHash = address.ToScriptHash(); + } + catch + { + scriptHash = null; + } + json["address"] = address; + json["isvalid"] = scriptHash != null; + return json; + } + } +} diff --git a/src/RpcServer/RpcServer.cs b/src/RpcServer/RpcServer.cs index 75bab3a2d..a30352212 100644 --- a/src/RpcServer/RpcServer.cs +++ b/src/RpcServer/RpcServer.cs @@ -1,4 +1,3 @@ -using Akka.Actor; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -7,13 +6,6 @@ using Microsoft.Extensions.DependencyInjection; using Neo.IO; using Neo.IO.Json; -using Neo.Ledger; -using Neo.Network.P2P; -using Neo.Network.P2P.Payloads; -using Neo.Persistence; -using Neo.SmartContract; -using Neo.SmartContract.Native; -using Neo.VM; using Neo.Wallets; using System; using System.Collections.Generic; @@ -21,54 +13,30 @@ using System.IO.Compression; using System.Linq; using System.Net.Security; +using System.Reflection; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; namespace Neo.Plugins { - public sealed class RpcServer : Plugin + public sealed partial class RpcServer : Plugin { - private class CheckWitnessHashes : IVerifiable - { - private readonly UInt160[] _scriptHashesForVerifying; - public Witness[] Witnesses { get; set; } - public int Size { get; } - - public CheckWitnessHashes(UInt160[] scriptHashesForVerifying) - { - _scriptHashesForVerifying = scriptHashesForVerifying; - } - - public void Serialize(BinaryWriter writer) - { - throw new NotImplementedException(); - } - - public void Deserialize(BinaryReader reader) - { - throw new NotImplementedException(); - } - - public void DeserializeUnsigned(BinaryReader reader) - { - throw new NotImplementedException(); - } - - public UInt160[] GetScriptHashesForVerifying(StoreView snapshot) - { - return _scriptHashesForVerifying; - } + private readonly Dictionary> methods = new Dictionary>(); + private IWebHost host; + private Wallet wallet; - public void SerializeUnsigned(BinaryWriter writer) + public RpcServer() + { + foreach (MethodInfo method in typeof(RpcServer).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { - throw new NotImplementedException(); + RpcMethodAttribute attribute = method.GetCustomAttribute(); + if (attribute is null) continue; + string name = string.IsNullOrEmpty(attribute.Name) ? method.Name.ToLowerInvariant() : attribute.Name; + methods[name] = (Func)method.CreateDelegate(typeof(Func), this); } } - private IWebHost host; - private Wallet wallet; - protected override void Configure() { Settings.Load(GetConfiguration()); @@ -103,38 +71,6 @@ public override void Dispose() } } - private JObject GetInvokeResult(byte[] script, IVerifiable checkWitnessHashes = null) - { - using ApplicationEngine engine = ApplicationEngine.Run(script, checkWitnessHashes, extraGAS: Settings.Default.MaxGasInvoke); - JObject json = new JObject(); - json["script"] = script.ToHexString(); - json["state"] = engine.State; - json["gas_consumed"] = engine.GasConsumed.ToString(); - try - { - json["stack"] = new JArray(engine.ResultStack.Select(p => p.ToParameter().ToJson())); - } - catch (InvalidOperationException) - { - json["stack"] = "error: recursive reference"; - } - return json; - } - - private static JObject GetRelayResult(RelayResultReason reason, UInt256 hash) - { - if (reason == RelayResultReason.Succeed) - { - var ret = new JObject(); - ret["hash"] = hash.ToString(); - return ret; - } - else - { - throw new RpcException(-500, reason.ToString()); - } - } - protected override void OnPluginsLoaded() { host = new WebHostBuilder().UseKestrel(options => options.Listen(Settings.Default.BindAddress, Settings.Default.Port, listenOptions => @@ -178,125 +114,6 @@ protected override void OnPluginsLoaded() host.Start(); } - private JObject Process(string method, JArray _params) - { - switch (method) - { - case "getbestblockhash": - { - return GetBestBlockHash(); - } - case "getblock": - { - JObject key = _params[0]; - bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); - return GetBlock(key, verbose); - } - case "getblockcount": - { - return GetBlockCount(); - } - case "getblockhash": - { - uint height = uint.Parse(_params[0].AsString()); - return GetBlockHash(height); - } - case "getblockheader": - { - JObject key = _params[0]; - bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); - return GetBlockHeader(key, verbose); - } - case "getblocksysfee": - { - uint height = uint.Parse(_params[0].AsString()); - return GetBlockSysFee(height); - } - case "getconnectioncount": - { - return GetConnectionCount(); - } - case "getcontractstate": - { - UInt160 script_hash = UInt160.Parse(_params[0].AsString()); - return GetContractState(script_hash); - } - case "getpeers": - { - return GetPeers(); - } - case "getrawmempool": - { - bool shouldGetUnverified = _params.Count >= 1 && _params[0].AsBoolean(); - return GetRawMemPool(shouldGetUnverified); - } - case "getrawtransaction": - { - UInt256 hash = UInt256.Parse(_params[0].AsString()); - bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); - return GetRawTransaction(hash, verbose); - } - case "getstorage": - { - UInt160 script_hash = UInt160.Parse(_params[0].AsString()); - byte[] key = _params[1].AsString().HexToBytes(); - return GetStorage(script_hash, key); - } - case "gettransactionheight": - { - UInt256 hash = UInt256.Parse(_params[0].AsString()); - return GetTransactionHeight(hash); - } - case "getvalidators": - { - return GetValidators(); - } - case "getversion": - { - return GetVersion(); - } - case "invokefunction": - { - UInt160 script_hash = UInt160.Parse(_params[0].AsString()); - string operation = _params[1].AsString(); - ContractParameter[] args = _params.Count >= 3 ? ((JArray)_params[2]).Select(p => ContractParameter.FromJson(p)).ToArray() : new ContractParameter[0]; - return InvokeFunction(script_hash, operation, args); - } - case "invokescript": - { - byte[] script = _params[0].AsString().HexToBytes(); - CheckWitnessHashes checkWitnessHashes = null; - if (_params.Count > 1) - { - UInt160[] scriptHashesForVerifying = _params.Skip(1).Select(u => UInt160.Parse(u.AsString())).ToArray(); - checkWitnessHashes = new CheckWitnessHashes(scriptHashesForVerifying); - } - return GetInvokeResult(script, checkWitnessHashes); - } - case "listplugins": - { - return ListPlugins(); - } - case "sendrawtransaction": - { - Transaction tx = _params[0].AsString().HexToBytes().AsSerializable(); - return SendRawTransaction(tx); - } - case "submitblock": - { - Block block = _params[0].AsString().HexToBytes().AsSerializable(); - return SubmitBlock(block); - } - case "validateaddress": - { - string address = _params[0].AsString(); - return ValidateAddress(address); - } - default: - throw new RpcException(-32601, "Method not found"); - } - } - private async Task ProcessAsync(HttpContext context) { context.Response.Headers["Access-Control-Allow-Origin"] = "*"; @@ -371,8 +188,9 @@ private JObject ProcessRequest(HttpContext context, JObject request) try { string method = request["method"].AsString(); - JArray _params = (JArray)request["params"]; - response["result"] = Process(method, _params); + if (!methods.TryGetValue(method, out var func)) + throw new RpcException(-32601, "Method not found"); + response["result"] = func((JArray)request["params"]); return response; } catch (FormatException) @@ -392,258 +210,5 @@ private JObject ProcessRequest(HttpContext context, JObject request) #endif } } - - #region RPC methods - - private JObject GetBestBlockHash() - { - return Blockchain.Singleton.CurrentBlockHash.ToString(); - } - - private JObject GetBlock(JObject key, bool verbose) - { - Block block; - if (key is JNumber) - { - uint index = uint.Parse(key.AsString()); - block = Blockchain.Singleton.GetBlock(index); - } - else - { - UInt256 hash = UInt256.Parse(key.AsString()); - block = Blockchain.Singleton.View.GetBlock(hash); - } - if (block == null) - throw new RpcException(-100, "Unknown block"); - if (verbose) - { - JObject json = block.ToJson(); - json["confirmations"] = Blockchain.Singleton.Height - block.Index + 1; - UInt256 hash = Blockchain.Singleton.GetNextBlockHash(block.Hash); - if (hash != null) - json["nextblockhash"] = hash.ToString(); - return json; - } - return block.ToArray().ToHexString(); - } - - private JObject GetBlockCount() - { - return Blockchain.Singleton.Height + 1; - } - - private JObject GetBlockHash(uint height) - { - if (height <= Blockchain.Singleton.Height) - { - return Blockchain.Singleton.GetBlockHash(height).ToString(); - } - throw new RpcException(-100, "Invalid Height"); - } - - private JObject GetBlockHeader(JObject key, bool verbose) - { - Header header; - if (key is JNumber) - { - uint height = uint.Parse(key.AsString()); - header = Blockchain.Singleton.GetHeader(height); - } - else - { - UInt256 hash = UInt256.Parse(key.AsString()); - header = Blockchain.Singleton.View.GetHeader(hash); - } - if (header == null) - throw new RpcException(-100, "Unknown block"); - - if (verbose) - { - JObject json = header.ToJson(); - json["confirmations"] = Blockchain.Singleton.Height - header.Index + 1; - UInt256 hash = Blockchain.Singleton.GetNextBlockHash(header.Hash); - if (hash != null) - json["nextblockhash"] = hash.ToString(); - return json; - } - - return header.ToArray().ToHexString(); - } - - private JObject GetBlockSysFee(uint height) - { - if (height <= Blockchain.Singleton.Height) - using (ApplicationEngine engine = NativeContract.GAS.TestCall("getSysFeeAmount", height)) - { - return engine.ResultStack.Peek().GetBigInteger().ToString(); - } - throw new RpcException(-100, "Invalid Height"); - } - - private JObject GetConnectionCount() - { - return LocalNode.Singleton.ConnectedCount; - } - - private JObject GetContractState(UInt160 script_hash) - { - ContractState contract = Blockchain.Singleton.View.Contracts.TryGet(script_hash); - return contract?.ToJson() ?? throw new RpcException(-100, "Unknown contract"); - } - - private JObject GetPeers() - { - JObject json = new JObject(); - json["unconnected"] = new JArray(LocalNode.Singleton.GetUnconnectedPeers().Select(p => - { - JObject peerJson = new JObject(); - peerJson["address"] = p.Address.ToString(); - peerJson["port"] = p.Port; - return peerJson; - })); - json["bad"] = new JArray(); //badpeers has been removed - json["connected"] = new JArray(LocalNode.Singleton.GetRemoteNodes().Select(p => - { - JObject peerJson = new JObject(); - peerJson["address"] = p.Remote.Address.ToString(); - peerJson["port"] = p.ListenerTcpPort; - return peerJson; - })); - return json; - } - - private JObject GetRawMemPool(bool shouldGetUnverified) - { - if (!shouldGetUnverified) - return new JArray(Blockchain.Singleton.MemPool.GetVerifiedTransactions().Select(p => (JObject)p.Hash.ToString())); - - JObject json = new JObject(); - json["height"] = Blockchain.Singleton.Height; - Blockchain.Singleton.MemPool.GetVerifiedAndUnverifiedTransactions( - out IEnumerable verifiedTransactions, - out IEnumerable unverifiedTransactions); - json["verified"] = new JArray(verifiedTransactions.Select(p => (JObject)p.Hash.ToString())); - json["unverified"] = new JArray(unverifiedTransactions.Select(p => (JObject)p.Hash.ToString())); - return json; - } - - private JObject GetRawTransaction(UInt256 hash, bool verbose) - { - Transaction tx = Blockchain.Singleton.GetTransaction(hash); - if (tx == null) - throw new RpcException(-100, "Unknown transaction"); - if (verbose) - { - JObject json = tx.ToJson(); - TransactionState txState = Blockchain.Singleton.View.Transactions.TryGet(hash); - if (txState != null) - { - Header header = Blockchain.Singleton.GetHeader(txState.BlockIndex); - json["blockhash"] = header.Hash.ToString(); - json["confirmations"] = Blockchain.Singleton.Height - header.Index + 1; - json["blocktime"] = header.Timestamp; - json["vmState"] = txState.VMState; - } - return json; - } - return tx.ToArray().ToHexString(); - } - - private JObject GetStorage(UInt160 script_hash, byte[] key) - { - StorageItem item = Blockchain.Singleton.View.Storages.TryGet(new StorageKey - { - ScriptHash = script_hash, - Key = key - }) ?? new StorageItem(); - return item.Value?.ToHexString(); - } - - private JObject GetTransactionHeight(UInt256 hash) - { - uint? height = Blockchain.Singleton.View.Transactions.TryGet(hash)?.BlockIndex; - if (height.HasValue) return height.Value; - throw new RpcException(-100, "Unknown transaction"); - } - - private JObject GetValidators() - { - using SnapshotView snapshot = Blockchain.Singleton.GetSnapshot(); - var validators = NativeContract.NEO.GetValidators(snapshot); - return NativeContract.NEO.GetRegisteredValidators(snapshot).Select(p => - { - JObject validator = new JObject(); - validator["publickey"] = p.PublicKey.ToString(); - validator["votes"] = p.Votes.ToString(); - validator["active"] = validators.Contains(p.PublicKey); - return validator; - }).ToArray(); - } - - private JObject GetVersion() - { - JObject json = new JObject(); - json["tcpPort"] = LocalNode.Singleton.ListenerTcpPort; - json["wsPort"] = LocalNode.Singleton.ListenerWsPort; - json["nonce"] = LocalNode.Nonce; - json["useragent"] = LocalNode.UserAgent; - return json; - } - - private JObject InvokeFunction(UInt160 script_hash, string operation, ContractParameter[] args) - { - byte[] script; - using (ScriptBuilder sb = new ScriptBuilder()) - { - script = sb.EmitAppCall(script_hash, operation, args).ToArray(); - } - return GetInvokeResult(script); - } - - private JObject ListPlugins() - { - return new JArray(Plugin.Plugins - .OrderBy(u => u.Name) - .Select(u => new JObject - { - ["name"] = u.Name, - ["version"] = u.Version.ToString(), - ["interfaces"] = new JArray(u.GetType().GetInterfaces() - .Select(p => p.Name) - .Where(p => p.EndsWith("Plugin")) - .Select(p => (JObject)p)) - })); - } - - private JObject SendRawTransaction(Transaction tx) - { - RelayResultReason reason = System.Blockchain.Ask(tx).Result; - return GetRelayResult(reason, tx.Hash); - } - - private JObject SubmitBlock(Block block) - { - RelayResultReason reason = System.Blockchain.Ask(block).Result; - return GetRelayResult(reason, block.Hash); - } - - private JObject ValidateAddress(string address) - { - JObject json = new JObject(); - UInt160 scriptHash; - try - { - scriptHash = address.ToScriptHash(); - } - catch - { - scriptHash = null; - } - json["address"] = address; - json["isvalid"] = scriptHash != null; - return json; - } - - #endregion } } From bbdc7007f4f8d34926bfd2131a7b8a0a4bd79753 Mon Sep 17 00:00:00 2001 From: erikzhang Date: Sun, 8 Dec 2019 03:00:12 +0800 Subject: [PATCH 04/12] Move RpcWallet to RpcServer --- neo-plugins.sln | 7 - src/RpcServer/RpcServer.SmartContract.cs | 1 + .../RpcServer.Wallet.cs} | 232 +++++++----------- src/RpcServer/RpcServer.cs | 2 - src/RpcServer/RpcServer/config.json | 3 +- src/RpcServer/Settings.cs | 2 + src/RpcWallet/RpcWallet.csproj | 20 -- src/RpcWallet/RpcWallet/config.json | 5 - src/RpcWallet/Settings.cs | 22 -- 9 files changed, 92 insertions(+), 202 deletions(-) rename src/{RpcWallet/RpcWallet.cs => RpcServer/RpcServer.Wallet.cs} (57%) delete mode 100644 src/RpcWallet/RpcWallet.csproj delete mode 100644 src/RpcWallet/RpcWallet/config.json delete mode 100644 src/RpcWallet/Settings.cs diff --git a/neo-plugins.sln b/neo-plugins.sln index 7b707fe16..fe1774f72 100644 --- a/neo-plugins.sln +++ b/neo-plugins.sln @@ -14,8 +14,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StatesDumper", "src\StatesD EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImportBlocks", "src\ImportBlocks\ImportBlocks.csproj", "{B7A42984-57BB-4F8D-967B-23B0E841B726}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RpcWallet", "src\RpcWallet\RpcWallet.csproj", "{EF32A7E5-EDF9-438C-8041-8DA6E675A7FD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RpcNep5Tracker", "src\RpcNep5Tracker\RpcNep5Tracker.csproj", "{BBE8AC15-12DF-4AF0-ABC1-F1557EB5DC8E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CoreMetrics", "src\CoreMetrics\CoreMetrics.csproj", "{AEFFF003-3500-416B-AD9B-8C838C33C1F4}" @@ -52,10 +50,6 @@ Global {B7A42984-57BB-4F8D-967B-23B0E841B726}.Debug|Any CPU.Build.0 = Debug|Any CPU {B7A42984-57BB-4F8D-967B-23B0E841B726}.Release|Any CPU.ActiveCfg = Release|Any CPU {B7A42984-57BB-4F8D-967B-23B0E841B726}.Release|Any CPU.Build.0 = Release|Any CPU - {EF32A7E5-EDF9-438C-8041-8DA6E675A7FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EF32A7E5-EDF9-438C-8041-8DA6E675A7FD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EF32A7E5-EDF9-438C-8041-8DA6E675A7FD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EF32A7E5-EDF9-438C-8041-8DA6E675A7FD}.Release|Any CPU.Build.0 = Release|Any CPU {BBE8AC15-12DF-4AF0-ABC1-F1557EB5DC8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BBE8AC15-12DF-4AF0-ABC1-F1557EB5DC8E}.Debug|Any CPU.Build.0 = Debug|Any CPU {BBE8AC15-12DF-4AF0-ABC1-F1557EB5DC8E}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -93,7 +87,6 @@ Global {6800D782-8EC0-49E9-98C4-195C8F781A1F} = {97E81C78-1637-481F-9485-DA1225E94C23} {86531DB1-A231-46C4-823F-BE60972F7523} = {97E81C78-1637-481F-9485-DA1225E94C23} {B7A42984-57BB-4F8D-967B-23B0E841B726} = {97E81C78-1637-481F-9485-DA1225E94C23} - {EF32A7E5-EDF9-438C-8041-8DA6E675A7FD} = {97E81C78-1637-481F-9485-DA1225E94C23} {BBE8AC15-12DF-4AF0-ABC1-F1557EB5DC8E} = {97E81C78-1637-481F-9485-DA1225E94C23} {AEFFF003-3500-416B-AD9B-8C838C33C1F4} = {97E81C78-1637-481F-9485-DA1225E94C23} {14DB62D5-0EA1-4A98-8656-1AA2D0345206} = {97E81C78-1637-481F-9485-DA1225E94C23} diff --git a/src/RpcServer/RpcServer.SmartContract.cs b/src/RpcServer/RpcServer.SmartContract.cs index 80ce67438..60069ea75 100644 --- a/src/RpcServer/RpcServer.SmartContract.cs +++ b/src/RpcServer/RpcServer.SmartContract.cs @@ -66,6 +66,7 @@ private JObject GetInvokeResult(byte[] script, IVerifiable checkWitnessHashes = { json["stack"] = "error: recursive reference"; } + ProcessInvokeWithWallet(json); return json; } diff --git a/src/RpcWallet/RpcWallet.cs b/src/RpcServer/RpcServer.Wallet.cs similarity index 57% rename from src/RpcWallet/RpcWallet.cs rename to src/RpcServer/RpcServer.Wallet.cs index 12178ad10..9dbe50372 100644 --- a/src/RpcWallet/RpcWallet.cs +++ b/src/RpcServer/RpcServer.Wallet.cs @@ -1,11 +1,12 @@ +#pragma warning disable IDE0051 +#pragma warning disable IDE0060 + using Akka.Actor; -using Microsoft.AspNetCore.Http; using Neo.IO; using Neo.IO.Json; using Neo.Ledger; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; -using Neo.Network.RPC; using Neo.Persistence; using Neo.SmartContract; using Neo.SmartContract.Native; @@ -16,172 +17,65 @@ namespace Neo.Plugins { - public class RpcWallet : Plugin, IRpcPlugin + partial class RpcServer { - private Wallet Wallet => System.RpcServer.Wallet; - - protected override void Configure() - { - Settings.Load(GetConfiguration()); - } - - public void PreProcess(HttpContext context, string method, JArray _params) - { - } - - public JObject OnProcess(HttpContext context, string method, JArray _params) - { - switch (method) - { - case "dumpprivkey": - { - UInt160 scriptHash = _params[0].AsString().ToScriptHash(); - return DumpPrivKey(scriptHash); - } - case "getbalance": - { - UInt160 asset_id = UInt160.Parse(_params[0].AsString()); - return GetBalance(asset_id); - } - case "getnewaddress": - { - return GetNewAddress(); - } - case "getunclaimedgas": - { - return GetUnclaimedGas(); - } - case "importprivkey": - { - string privkey = _params[0].AsString(); - return ImportPrivKey(privkey); - } - case "listaddress": - { - return ListAddress(); - } - case "sendfrom": - { - UInt160 assetId = UInt160.Parse(_params[0].AsString()); - UInt160 from = _params[1].AsString().ToScriptHash(); - UInt160 to = _params[2].AsString().ToScriptHash(); - string value = _params[3].AsString(); - return SendFrom(assetId, from, to, value); - } - case "sendmany": - { - int to_start = 0; - UInt160 from = null; - if (_params[0] is JString) - { - from = _params[0].AsString().ToScriptHash(); - to_start = 1; - } - JArray to = (JArray)_params[to_start]; - return SendMany(from, to); - } - case "sendtoaddress": - { - UInt160 assetId = UInt160.Parse(_params[0].AsString()); - UInt160 scriptHash = _params[1].AsString().ToScriptHash(); - string value = _params[2].AsString(); - return SendToAddress(assetId, scriptHash, value); - } - default: - return null; - } - } - - public void PostProcess(HttpContext context, string method, JArray _params, JObject result) - { - switch (method) - { - case "invoke": - case "invokefunction": - case "invokescript": - ProcessInvoke(result); - break; - } - } - - private void ProcessInvoke(JObject result) - { - if (Wallet != null) - { - Transaction tx = Wallet.MakeTransaction(result["script"].AsString().HexToBytes()); - ContractParametersContext context = new ContractParametersContext(tx); - Wallet.Sign(context); - if (context.Completed) - tx.Witnesses = context.GetWitnesses(); - else - tx = null; - result["tx"] = tx?.ToArray().ToHexString(); - } - } + private Wallet wallet; private void CheckWallet() { - if (Wallet is null) + if (wallet is null) throw new RpcException(-400, "Access denied"); } - private JObject SignAndRelay(Transaction tx) - { - ContractParametersContext context = new ContractParametersContext(tx); - Wallet.Sign(context); - if (context.Completed) - { - tx.Witnesses = context.GetWitnesses(); - System.LocalNode.Tell(new LocalNode.Relay { Inventory = tx }); - return tx.ToJson(); - } - else - { - return context.ToJson(); - } - } - - private JObject DumpPrivKey(UInt160 scriptHash) + [RpcMethod] + private JObject DumpPrivKey(JArray _params) { CheckWallet(); - WalletAccount account = Wallet.GetAccount(scriptHash); + UInt160 scriptHash = _params[0].AsString().ToScriptHash(); + WalletAccount account = wallet.GetAccount(scriptHash); return account.GetKey().Export(); } - private JObject GetBalance(UInt160 asset_id) + [RpcMethod] + private JObject GetBalance(JArray _params) { CheckWallet(); + UInt160 asset_id = UInt160.Parse(_params[0].AsString()); JObject json = new JObject(); - json["balance"] = Wallet.GetAvailable(asset_id).Value.ToString(); + json["balance"] = wallet.GetAvailable(asset_id).Value.ToString(); return json; } - private JObject GetNewAddress() + [RpcMethod] + private JObject GetNewAddress(JArray _params) { CheckWallet(); - WalletAccount account = Wallet.CreateAccount(); - if (Wallet is NEP6Wallet nep6) + WalletAccount account = wallet.CreateAccount(); + if (wallet is NEP6Wallet nep6) nep6.Save(); return account.Address; } - private JObject GetUnclaimedGas() + [RpcMethod] + private JObject GetUnclaimedGas(JArray _params) { CheckWallet(); BigInteger gas = BigInteger.Zero; using (SnapshotView snapshot = Blockchain.Singleton.GetSnapshot()) - foreach (UInt160 account in Wallet.GetAccounts().Select(p => p.ScriptHash)) + foreach (UInt160 account in wallet.GetAccounts().Select(p => p.ScriptHash)) { gas += NativeContract.NEO.UnclaimedGas(snapshot, account, snapshot.Height + 1); } return gas.ToString(); } - private JObject ImportPrivKey(string privkey) + [RpcMethod] + private JObject ImportPrivKey(JArray _params) { CheckWallet(); - WalletAccount account = Wallet.Import(privkey); - if (Wallet is NEP6Wallet nep6wallet) + string privkey = _params[0].AsString(); + WalletAccount account = wallet.Import(privkey); + if (wallet is NEP6Wallet nep6wallet) nep6wallet.Save(); return new JObject { @@ -192,10 +86,11 @@ private JObject ImportPrivKey(string privkey) }; } - private JObject ListAddress() + [RpcMethod] + private JObject ListAddress(JArray _params) { CheckWallet(); - return Wallet.GetAccounts().Select(p => + return wallet.GetAccounts().Select(p => { JObject account = new JObject(); account["address"] = p.Address; @@ -206,14 +101,33 @@ private JObject ListAddress() }).ToArray(); } - private JObject SendFrom(UInt160 assetId, UInt160 from, UInt160 to, string value) + private void ProcessInvokeWithWallet(JObject result) + { + if (wallet != null) + { + Transaction tx = wallet.MakeTransaction(result["script"].AsString().HexToBytes()); + ContractParametersContext context = new ContractParametersContext(tx); + wallet.Sign(context); + if (context.Completed) + tx.Witnesses = context.GetWitnesses(); + else + tx = null; + result["tx"] = tx?.ToArray().ToHexString(); + } + } + + [RpcMethod] + private JObject SendFrom(JArray _params) { CheckWallet(); + UInt160 assetId = UInt160.Parse(_params[0].AsString()); + UInt160 from = _params[1].AsString().ToScriptHash(); + UInt160 to = _params[2].AsString().ToScriptHash(); AssetDescriptor descriptor = new AssetDescriptor(assetId); - BigDecimal amount = BigDecimal.Parse(value, descriptor.Decimals); + BigDecimal amount = BigDecimal.Parse(_params[3].AsString(), descriptor.Decimals); if (amount.Sign <= 0) throw new RpcException(-32602, "Invalid params"); - Transaction tx = Wallet.MakeTransaction(new[] + Transaction tx = wallet.MakeTransaction(new[] { new TransferOutput { @@ -226,7 +140,7 @@ private JObject SendFrom(UInt160 assetId, UInt160 from, UInt160 to, string value throw new RpcException(-300, "Insufficient funds"); ContractParametersContext transContext = new ContractParametersContext(tx); - Wallet.Sign(transContext); + wallet.Sign(transContext); if (!transContext.Completed) return transContext.ToJson(); tx.Witnesses = transContext.GetWitnesses(); @@ -241,9 +155,18 @@ private JObject SendFrom(UInt160 assetId, UInt160 from, UInt160 to, string value return SignAndRelay(tx); } - private JObject SendMany(UInt160 from, JArray to) + [RpcMethod] + private JObject SendMany(JArray _params) { CheckWallet(); + int to_start = 0; + UInt160 from = null; + if (_params[0] is JString) + { + from = _params[0].AsString().ToScriptHash(); + to_start = 1; + } + JArray to = (JArray)_params[to_start]; if (to.Count == 0) throw new RpcException(-32602, "Invalid params"); TransferOutput[] outputs = new TransferOutput[to.Count]; @@ -260,12 +183,12 @@ private JObject SendMany(UInt160 from, JArray to) if (outputs[i].Value.Sign <= 0) throw new RpcException(-32602, "Invalid params"); } - Transaction tx = Wallet.MakeTransaction(outputs, from); + Transaction tx = wallet.MakeTransaction(outputs, from); if (tx == null) throw new RpcException(-300, "Insufficient funds"); ContractParametersContext transContext = new ContractParametersContext(tx); - Wallet.Sign(transContext); + wallet.Sign(transContext); if (!transContext.Completed) return transContext.ToJson(); tx.Witnesses = transContext.GetWitnesses(); @@ -280,14 +203,17 @@ private JObject SendMany(UInt160 from, JArray to) return SignAndRelay(tx); } - private JObject SendToAddress(UInt160 assetId, UInt160 scriptHash, string value) + [RpcMethod] + private JObject SendToAddress(JArray _params) { CheckWallet(); + UInt160 assetId = UInt160.Parse(_params[0].AsString()); + UInt160 scriptHash = _params[1].AsString().ToScriptHash(); AssetDescriptor descriptor = new AssetDescriptor(assetId); - BigDecimal amount = BigDecimal.Parse(value, descriptor.Decimals); + BigDecimal amount = BigDecimal.Parse(_params[2].AsString(), descriptor.Decimals); if (amount.Sign <= 0) throw new RpcException(-32602, "Invalid params"); - Transaction tx = Wallet.MakeTransaction(new[] + Transaction tx = wallet.MakeTransaction(new[] { new TransferOutput { @@ -300,7 +226,7 @@ private JObject SendToAddress(UInt160 assetId, UInt160 scriptHash, string value) throw new RpcException(-300, "Insufficient funds"); ContractParametersContext transContext = new ContractParametersContext(tx); - Wallet.Sign(transContext); + wallet.Sign(transContext); if (!transContext.Completed) return transContext.ToJson(); tx.Witnesses = transContext.GetWitnesses(); @@ -314,5 +240,21 @@ private JObject SendToAddress(UInt160 assetId, UInt160 scriptHash, string value) 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); } + + private JObject SignAndRelay(Transaction tx) + { + ContractParametersContext context = new ContractParametersContext(tx); + wallet.Sign(context); + if (context.Completed) + { + tx.Witnesses = context.GetWitnesses(); + System.LocalNode.Tell(new LocalNode.Relay { Inventory = tx }); + return tx.ToJson(); + } + else + { + return context.ToJson(); + } + } } } diff --git a/src/RpcServer/RpcServer.cs b/src/RpcServer/RpcServer.cs index a30352212..9efec17e3 100644 --- a/src/RpcServer/RpcServer.cs +++ b/src/RpcServer/RpcServer.cs @@ -6,7 +6,6 @@ using Microsoft.Extensions.DependencyInjection; using Neo.IO; using Neo.IO.Json; -using Neo.Wallets; using System; using System.Collections.Generic; using System.IO; @@ -24,7 +23,6 @@ public sealed partial class RpcServer : Plugin { private readonly Dictionary> methods = new Dictionary>(); private IWebHost host; - private Wallet wallet; public RpcServer() { diff --git a/src/RpcServer/RpcServer/config.json b/src/RpcServer/RpcServer/config.json index b0433623e..71995b18f 100644 --- a/src/RpcServer/RpcServer/config.json +++ b/src/RpcServer/RpcServer/config.json @@ -5,6 +5,7 @@ "SslCert": "", "SslCertPassword": "", "TrustedAuthorities": [], - "MaxGasInvoke": 10 + "MaxGasInvoke": 10, + "MaxFee": 0.1 } } diff --git a/src/RpcServer/Settings.cs b/src/RpcServer/Settings.cs index 625f8543f..cb5da2c56 100644 --- a/src/RpcServer/Settings.cs +++ b/src/RpcServer/Settings.cs @@ -13,6 +13,7 @@ internal class Settings public string SslCertPassword { get; } public string[] TrustedAuthorities { get; } public long MaxGasInvoke { get; } + public long MaxFee { get; } public static Settings Default { get; private set; } @@ -24,6 +25,7 @@ private Settings(IConfigurationSection section) this.SslCertPassword = section.GetSection("SslCertPassword").Value; this.TrustedAuthorities = section.GetSection("TrustedAuthorities").GetChildren().Select(p => p.Get()).ToArray(); 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; } public static void Load(IConfigurationSection section) diff --git a/src/RpcWallet/RpcWallet.csproj b/src/RpcWallet/RpcWallet.csproj deleted file mode 100644 index e85fcc9e7..000000000 --- a/src/RpcWallet/RpcWallet.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - 3.0.0-preview1 - netstandard2.1 - Neo.Plugins - - - - - PreserveNewest - PreserveNewest - - - - - - - - diff --git a/src/RpcWallet/RpcWallet/config.json b/src/RpcWallet/RpcWallet/config.json deleted file mode 100644 index 752d78ff9..000000000 --- a/src/RpcWallet/RpcWallet/config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "PluginConfiguration": { - "MaxFee": 0.1 - } -} diff --git a/src/RpcWallet/Settings.cs b/src/RpcWallet/Settings.cs deleted file mode 100644 index 371032b5c..000000000 --- a/src/RpcWallet/Settings.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Microsoft.Extensions.Configuration; -using Neo.SmartContract.Native; - -namespace Neo.Plugins -{ - internal class Settings - { - public long MaxFee { get; } - - public static Settings Default { get; private set; } - - private Settings(IConfigurationSection section) - { - this.MaxFee = (long)BigDecimal.Parse(section.GetValue("MaxFee", "0.1"), NativeContract.GAS.Decimals).Value; - } - - public static void Load(IConfigurationSection section) - { - Default = new Settings(section); - } - } -} From ca7e9a5dd0a009498495acf6c4921ce3ca98af8d Mon Sep 17 00:00:00 2001 From: erikzhang Date: Sun, 8 Dec 2019 03:12:49 +0800 Subject: [PATCH 05/12] Move RpcSecurity to RpcServer --- neo-plugins.sln | 7 --- src/RpcSecurity/RpcSecurity.cs | 59 ------------------------- src/RpcSecurity/RpcSecurity.csproj | 20 --------- src/RpcSecurity/RpcSecurity/config.json | 7 --- src/RpcSecurity/Settings.cs | 26 ----------- src/RpcServer/RpcServer.cs | 26 +++++++++++ src/RpcServer/RpcServer/config.json | 5 ++- src/RpcServer/Settings.cs | 6 +++ 8 files changed, 36 insertions(+), 120 deletions(-) delete mode 100644 src/RpcSecurity/RpcSecurity.cs delete mode 100644 src/RpcSecurity/RpcSecurity.csproj delete mode 100644 src/RpcSecurity/RpcSecurity/config.json delete mode 100644 src/RpcSecurity/Settings.cs diff --git a/neo-plugins.sln b/neo-plugins.sln index fe1774f72..6bfff7a31 100644 --- a/neo-plugins.sln +++ b/neo-plugins.sln @@ -8,8 +8,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{59D802AB EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationLogs", "src\ApplicationLogs\ApplicationLogs.csproj", "{84DA8EA6-EF60-4FCD-B1C6-65C1A8323B3F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RpcSecurity", "src\RpcSecurity\RpcSecurity.csproj", "{6800D782-8EC0-49E9-98C4-195C8F781A1F}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StatesDumper", "src\StatesDumper\StatesDumper.csproj", "{86531DB1-A231-46C4-823F-BE60972F7523}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImportBlocks", "src\ImportBlocks\ImportBlocks.csproj", "{B7A42984-57BB-4F8D-967B-23B0E841B726}" @@ -38,10 +36,6 @@ Global {84DA8EA6-EF60-4FCD-B1C6-65C1A8323B3F}.Debug|Any CPU.Build.0 = Debug|Any CPU {84DA8EA6-EF60-4FCD-B1C6-65C1A8323B3F}.Release|Any CPU.ActiveCfg = Release|Any CPU {84DA8EA6-EF60-4FCD-B1C6-65C1A8323B3F}.Release|Any CPU.Build.0 = Release|Any CPU - {6800D782-8EC0-49E9-98C4-195C8F781A1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6800D782-8EC0-49E9-98C4-195C8F781A1F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6800D782-8EC0-49E9-98C4-195C8F781A1F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6800D782-8EC0-49E9-98C4-195C8F781A1F}.Release|Any CPU.Build.0 = Release|Any CPU {86531DB1-A231-46C4-823F-BE60972F7523}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {86531DB1-A231-46C4-823F-BE60972F7523}.Debug|Any CPU.Build.0 = Debug|Any CPU {86531DB1-A231-46C4-823F-BE60972F7523}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -84,7 +78,6 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {84DA8EA6-EF60-4FCD-B1C6-65C1A8323B3F} = {97E81C78-1637-481F-9485-DA1225E94C23} - {6800D782-8EC0-49E9-98C4-195C8F781A1F} = {97E81C78-1637-481F-9485-DA1225E94C23} {86531DB1-A231-46C4-823F-BE60972F7523} = {97E81C78-1637-481F-9485-DA1225E94C23} {B7A42984-57BB-4F8D-967B-23B0E841B726} = {97E81C78-1637-481F-9485-DA1225E94C23} {BBE8AC15-12DF-4AF0-ABC1-F1557EB5DC8E} = {97E81C78-1637-481F-9485-DA1225E94C23} diff --git a/src/RpcSecurity/RpcSecurity.cs b/src/RpcSecurity/RpcSecurity.cs deleted file mode 100644 index a63f8b88c..000000000 --- a/src/RpcSecurity/RpcSecurity.cs +++ /dev/null @@ -1,59 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Neo.IO.Json; -using Neo.Network.RPC; -using System; -using System.Linq; -using System.Text; - -namespace Neo.Plugins -{ - public class RpcSecurity : Plugin, IRpcPlugin - { - protected override void Configure() - { - Settings.Load(GetConfiguration()); - } - - public void PreProcess(HttpContext context, string method, JArray _params) - { - if (!CheckAuth(context) || Settings.Default.DisabledMethods.Contains(method)) - throw new RpcException(-400, "Access denied"); - } - - public JObject OnProcess(HttpContext context, string method, JArray _params) - { - return null; - } - - public void PostProcess(HttpContext context, string method, JArray _params, JObject result) - { - } - - private bool CheckAuth(HttpContext context) - { - if (string.IsNullOrEmpty(Settings.Default.RpcUser)) - { - return true; - } - - context.Response.Headers["WWW-Authenticate"] = "Basic realm=\"Restricted\""; - - string reqauth = context.Request.Headers["Authorization"]; - string authstring = null; - try - { - authstring = Encoding.UTF8.GetString(Convert.FromBase64String(reqauth.Replace("Basic ", "").Trim())); - } - catch - { - return false; - } - - string[] authvalues = authstring.Split(new string[] { ":" }, StringSplitOptions.RemoveEmptyEntries); - if (authvalues.Length < 2) - return false; - - return authvalues[0] == Settings.Default.RpcUser && authvalues[1] == Settings.Default.RpcPass; - } - } -} diff --git a/src/RpcSecurity/RpcSecurity.csproj b/src/RpcSecurity/RpcSecurity.csproj deleted file mode 100644 index 2d5a348cf..000000000 --- a/src/RpcSecurity/RpcSecurity.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - 3.0.0-preview1 - netstandard2.1 - Neo.Plugins - - - - - PreserveNewest - PreserveNewest - - - - - - - - diff --git a/src/RpcSecurity/RpcSecurity/config.json b/src/RpcSecurity/RpcSecurity/config.json deleted file mode 100644 index 8890f20b0..000000000 --- a/src/RpcSecurity/RpcSecurity/config.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "PluginConfiguration": { - "RpcUser": "", - "RpcPass": "", - "DisabledMethods": [] - } -} diff --git a/src/RpcSecurity/Settings.cs b/src/RpcSecurity/Settings.cs deleted file mode 100644 index 4d83ff7d5..000000000 --- a/src/RpcSecurity/Settings.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.Extensions.Configuration; -using System.Linq; - -namespace Neo.Plugins -{ - internal class Settings - { - public string RpcUser { get; } - public string RpcPass { get; } - public string[] DisabledMethods { get; } - - public static Settings Default { get; private set; } - - private Settings(IConfigurationSection section) - { - this.RpcUser = section.GetSection("RpcUser").Value; - this.RpcPass = section.GetSection("RpcPass").Value; - this.DisabledMethods = section.GetSection("DisabledMethods").GetChildren().Select(p => p.Value).ToArray(); - } - - public static void Load(IConfigurationSection section) - { - Default = new Settings(section); - } - } -} diff --git a/src/RpcServer/RpcServer.cs b/src/RpcServer/RpcServer.cs index 9efec17e3..fb4f594cd 100644 --- a/src/RpcServer/RpcServer.cs +++ b/src/RpcServer/RpcServer.cs @@ -35,6 +35,30 @@ public RpcServer() } } + private bool CheckAuth(HttpContext context) + { + if (string.IsNullOrEmpty(Settings.Default.RpcUser)) return true; + + context.Response.Headers["WWW-Authenticate"] = "Basic realm=\"Restricted\""; + + string reqauth = context.Request.Headers["Authorization"]; + string authstring; + try + { + authstring = Encoding.UTF8.GetString(Convert.FromBase64String(reqauth.Replace("Basic ", "").Trim())); + } + catch + { + return false; + } + + string[] authvalues = authstring.Split(new string[] { ":" }, StringSplitOptions.RemoveEmptyEntries); + if (authvalues.Length < 2) + return false; + + return authvalues[0] == Settings.Default.RpcUser && authvalues[1] == Settings.Default.RpcPass; + } + protected override void Configure() { Settings.Load(GetConfiguration()); @@ -186,6 +210,8 @@ private JObject ProcessRequest(HttpContext context, JObject request) try { string method = request["method"].AsString(); + if (!CheckAuth(context) || Settings.Default.DisabledMethods.Contains(method)) + throw new RpcException(-400, "Access denied"); if (!methods.TryGetValue(method, out var func)) throw new RpcException(-32601, "Method not found"); response["result"] = func((JArray)request["params"]); diff --git a/src/RpcServer/RpcServer/config.json b/src/RpcServer/RpcServer/config.json index 71995b18f..a8ff6f5e8 100644 --- a/src/RpcServer/RpcServer/config.json +++ b/src/RpcServer/RpcServer/config.json @@ -5,7 +5,10 @@ "SslCert": "", "SslCertPassword": "", "TrustedAuthorities": [], + "RpcUser": "", + "RpcPass": "", "MaxGasInvoke": 10, - "MaxFee": 0.1 + "MaxFee": 0.1, + "DisabledMethods": [] } } diff --git a/src/RpcServer/Settings.cs b/src/RpcServer/Settings.cs index cb5da2c56..d955e48d8 100644 --- a/src/RpcServer/Settings.cs +++ b/src/RpcServer/Settings.cs @@ -12,8 +12,11 @@ internal class Settings public string SslCert { get; } public string SslCertPassword { get; } public string[] TrustedAuthorities { get; } + public string RpcUser { get; } + public string RpcPass { get; } public long MaxGasInvoke { get; } public long MaxFee { get; } + public string[] DisabledMethods { get; } public static Settings Default { get; private set; } @@ -24,8 +27,11 @@ private Settings(IConfigurationSection section) this.SslCert = section.GetSection("SslCert").Value; this.SslCertPassword = section.GetSection("SslCertPassword").Value; this.TrustedAuthorities = section.GetSection("TrustedAuthorities").GetChildren().Select(p => p.Get()).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()).ToArray(); } public static void Load(IConfigurationSection section) From 948de65eadcdb890dd9360b218b70e8c5341e298 Mon Sep 17 00:00:00 2001 From: erikzhang Date: Sun, 8 Dec 2019 03:25:37 +0800 Subject: [PATCH 06/12] Add `openwallet` and `closewallet` --- src/RpcServer/RpcServer.Wallet.cs | 37 +++++++++++++++++++++++++++++ src/RpcServer/RpcServer/config.json | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/src/RpcServer/RpcServer.Wallet.cs b/src/RpcServer/RpcServer.Wallet.cs index 9dbe50372..7d183fd96 100644 --- a/src/RpcServer/RpcServer.Wallet.cs +++ b/src/RpcServer/RpcServer.Wallet.cs @@ -12,8 +12,12 @@ using Neo.SmartContract.Native; using Neo.Wallets; using Neo.Wallets.NEP6; +using Neo.Wallets.SQLite; +using System; +using System.IO; using System.Linq; using System.Numerics; +using static System.IO.Path; namespace Neo.Plugins { @@ -27,6 +31,13 @@ private void CheckWallet() throw new RpcException(-400, "Access denied"); } + [RpcMethod] + private JObject CloseWallet(JArray _params) + { + wallet = null; + return true; + } + [RpcMethod] private JObject DumpPrivKey(JArray _params) { @@ -101,6 +112,32 @@ private JObject ListAddress(JArray _params) }).ToArray(); } + [RpcMethod] + private JObject OpenWallet(JArray _params) + { + string path = _params[0].AsString(); + string password = _params[1].AsString(); + if (!File.Exists(path)) throw new FileNotFoundException(); + switch (GetExtension(path)) + { + case ".db3": + { + wallet = UserWallet.Open(path, password); + break; + } + case ".json": + { + NEP6Wallet nep6wallet = new NEP6Wallet(path); + nep6wallet.Unlock(password); + wallet = nep6wallet; + break; + } + default: + throw new NotSupportedException(); + } + return true; + } + private void ProcessInvokeWithWallet(JObject result) { if (wallet != null) diff --git a/src/RpcServer/RpcServer/config.json b/src/RpcServer/RpcServer/config.json index a8ff6f5e8..39d4639c0 100644 --- a/src/RpcServer/RpcServer/config.json +++ b/src/RpcServer/RpcServer/config.json @@ -9,6 +9,6 @@ "RpcPass": "", "MaxGasInvoke": 10, "MaxFee": 0.1, - "DisabledMethods": [] + "DisabledMethods": [ "openwallet" ] } } From 9caabf53d1dcf969f3c29c9704c35f615bfd9826 Mon Sep 17 00:00:00 2001 From: erikzhang Date: Sun, 8 Dec 2019 03:36:23 +0800 Subject: [PATCH 07/12] Fix ApplicationLogs --- src/ApplicationLogs/ApplicationLogs.csproj | 1 + src/ApplicationLogs/LogReader.cs | 17 ++++------------- src/RpcServer/RpcException.cs | 2 +- src/RpcServer/RpcMethodAttribute.cs | 2 +- src/RpcServer/RpcServer.cs | 21 +++++++++++++-------- 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/ApplicationLogs/ApplicationLogs.csproj b/src/ApplicationLogs/ApplicationLogs.csproj index d8976e093..f545f43d3 100644 --- a/src/ApplicationLogs/ApplicationLogs.csproj +++ b/src/ApplicationLogs/ApplicationLogs.csproj @@ -15,6 +15,7 @@ + diff --git a/src/ApplicationLogs/LogReader.cs b/src/ApplicationLogs/LogReader.cs index 969094293..af577dcd3 100644 --- a/src/ApplicationLogs/LogReader.cs +++ b/src/ApplicationLogs/LogReader.cs @@ -1,9 +1,7 @@ -using Microsoft.AspNetCore.Http; using Neo.IO; using Neo.IO.Data.LevelDB; using Neo.IO.Json; using Neo.Ledger; -using Neo.Network.RPC; using Neo.Persistence; using Neo.VM; using System; @@ -14,7 +12,7 @@ namespace Neo.Plugins { - public class LogReader : Plugin, IRpcPlugin, IPersistencePlugin + public class LogReader : Plugin, IPersistencePlugin { private readonly DB db; @@ -23,6 +21,7 @@ public class LogReader : Plugin, IRpcPlugin, IPersistencePlugin public LogReader() { db = DB.Open(GetFullPath(Settings.Default.Path), new Options { CreateIfMissing = true }); + RpcServer.RegisterMethods(this); } protected override void Configure() @@ -30,13 +29,9 @@ protected override void Configure() Settings.Load(GetConfiguration()); } - public void PreProcess(HttpContext context, string method, JArray _params) + [RpcMethod] + public JObject GetApplicationLog(JArray _params) { - } - - public JObject OnProcess(HttpContext context, string method, JArray _params) - { - if (method != "getapplicationlog") return null; UInt256 hash = UInt256.Parse(_params[0].AsString()); byte[] value = db.Get(ReadOptions.Default, hash.ToArray()); if (value is null) @@ -44,10 +39,6 @@ public JObject OnProcess(HttpContext context, string method, JArray _params) return JObject.Parse(Encoding.UTF8.GetString(value)); } - public void PostProcess(HttpContext context, string method, JArray _params, JObject result) - { - } - public void OnPersist(StoreView snapshot, IReadOnlyList applicationExecutedList) { WriteBatch writeBatch = new WriteBatch(); diff --git a/src/RpcServer/RpcException.cs b/src/RpcServer/RpcException.cs index e3bf6f939..dbe03c109 100644 --- a/src/RpcServer/RpcException.cs +++ b/src/RpcServer/RpcException.cs @@ -2,7 +2,7 @@ namespace Neo.Plugins { - internal class RpcException : Exception + public class RpcException : Exception { public RpcException(int code, string message) : base(message) { diff --git a/src/RpcServer/RpcMethodAttribute.cs b/src/RpcServer/RpcMethodAttribute.cs index 010558b42..f560ba555 100644 --- a/src/RpcServer/RpcMethodAttribute.cs +++ b/src/RpcServer/RpcMethodAttribute.cs @@ -3,7 +3,7 @@ namespace Neo.Plugins { [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - internal class RpcMethodAttribute : Attribute + public class RpcMethodAttribute : Attribute { public string Name { get; set; } } diff --git a/src/RpcServer/RpcServer.cs b/src/RpcServer/RpcServer.cs index fb4f594cd..ff662f95e 100644 --- a/src/RpcServer/RpcServer.cs +++ b/src/RpcServer/RpcServer.cs @@ -21,18 +21,12 @@ namespace Neo.Plugins { public sealed partial class RpcServer : Plugin { - private readonly Dictionary> methods = new Dictionary>(); + private static readonly Dictionary> methods = new Dictionary>(); private IWebHost host; public RpcServer() { - foreach (MethodInfo method in typeof(RpcServer).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) - { - RpcMethodAttribute attribute = method.GetCustomAttribute(); - if (attribute is null) continue; - string name = string.IsNullOrEmpty(attribute.Name) ? method.Name.ToLowerInvariant() : attribute.Name; - methods[name] = (Func)method.CreateDelegate(typeof(Func), this); - } + RegisterMethods(this); } private bool CheckAuth(HttpContext context) @@ -234,5 +228,16 @@ private JObject ProcessRequest(HttpContext context, JObject request) #endif } } + + public static void RegisterMethods(object handler) + { + foreach (MethodInfo method in handler.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) + { + RpcMethodAttribute attribute = method.GetCustomAttribute(); + if (attribute is null) continue; + string name = string.IsNullOrEmpty(attribute.Name) ? method.Name.ToLowerInvariant() : attribute.Name; + methods[name] = (Func)method.CreateDelegate(typeof(Func), handler); + } + } } } From a0fe64aed0e5019a41277284d0dd936e51b698d2 Mon Sep 17 00:00:00 2001 From: erikzhang Date: Sun, 8 Dec 2019 03:41:12 +0800 Subject: [PATCH 08/12] Fix CoreMetrics --- src/CoreMetrics/CoreMetrics.cs | 28 +++++++--------------------- src/CoreMetrics/CoreMetrics.csproj | 2 +- 2 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/CoreMetrics/CoreMetrics.cs b/src/CoreMetrics/CoreMetrics.cs index 8b5d9aedd..daf547b40 100644 --- a/src/CoreMetrics/CoreMetrics.cs +++ b/src/CoreMetrics/CoreMetrics.cs @@ -1,35 +1,21 @@ -using Microsoft.AspNetCore.Http; using Neo.IO.Json; using Neo.Ledger; using Neo.Network.P2P.Payloads; namespace Neo.Plugins { - public class CoreMetrics : Plugin, IRpcPlugin + public class CoreMetrics : Plugin { - protected override void Configure() { } - - public void PreProcess(HttpContext context, string method, JArray _params) { } - - public JObject OnProcess(HttpContext context, string method, JArray _params) + public CoreMetrics() { - switch (method) - { - case "getmetricblocktimestamp": - { - uint nBlocks = (uint)_params[0].AsNumber(); - uint lastHeight = _params.Count >= 2 ? (uint)_params[1].AsNumber() : 0; - return GetBlocksTime(nBlocks, lastHeight); - } - default: - return null; - } + RpcServer.RegisterMethods(this); } - public void PostProcess(HttpContext context, string method, JArray _params, JObject result) { } - - private JObject GetBlocksTime(uint nBlocks, uint lastHeight) + [RpcMethod] + public JObject GetMetricBlockTimestamp(JArray _params) { + uint nBlocks = (uint)_params[0].AsNumber(); + uint lastHeight = _params.Count >= 2 ? (uint)_params[1].AsNumber() : 0; // It is currently limited to query blocks generated in the last 24hours (86400 seconds) uint maxNBlocksPerDay = 86400 / (Blockchain.MillisecondsPerBlock / 1000); if (lastHeight != 0) diff --git a/src/CoreMetrics/CoreMetrics.csproj b/src/CoreMetrics/CoreMetrics.csproj index fe79e4b58..3259bcf44 100644 --- a/src/CoreMetrics/CoreMetrics.csproj +++ b/src/CoreMetrics/CoreMetrics.csproj @@ -7,7 +7,7 @@ - + From 382c7da193fa413695c6af172ad0f245a94fbfda Mon Sep 17 00:00:00 2001 From: erikzhang Date: Sun, 8 Dec 2019 03:46:53 +0800 Subject: [PATCH 09/12] Fix RpcNep5Tracker --- src/RpcNep5Tracker/RpcNep5Tracker.cs | 30 ++++++++++-------------- src/RpcNep5Tracker/RpcNep5Tracker.csproj | 1 + 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/RpcNep5Tracker/RpcNep5Tracker.cs b/src/RpcNep5Tracker/RpcNep5Tracker.cs index 183ae23b3..e89948890 100644 --- a/src/RpcNep5Tracker/RpcNep5Tracker.cs +++ b/src/RpcNep5Tracker/RpcNep5Tracker.cs @@ -5,7 +5,6 @@ using Neo.IO.Json; using Neo.Ledger; using Neo.Network.P2P.Payloads; -using Neo.Network.RPC; using Neo.Persistence; using Neo.SmartContract; using Neo.VM; @@ -18,7 +17,7 @@ namespace Neo.Plugins { - public class RpcNep5Tracker : Plugin, IPersistencePlugin, IRpcPlugin + public class RpcNep5Tracker : Plugin, IPersistencePlugin { private const byte Nep5BalancePrefix = 0xf8; private const byte Nep5TransferSentPrefix = 0xf9; @@ -33,6 +32,11 @@ public class RpcNep5Tracker : Plugin, IPersistencePlugin, IRpcPlugin private uint _maxResults; private Neo.IO.Data.LevelDB.Snapshot _levelDbSnapshot; + public RpcNep5Tracker() + { + RpcServer.RegisterMethods(this); + } + protected override void Configure() { if (_db == null) @@ -237,8 +241,11 @@ private UInt160 GetScriptHashFromParam(string addressOrScriptHash) return addressOrScriptHash.Length < 40 ? addressOrScriptHash.ToScriptHash() : UInt160.Parse(addressOrScriptHash); } - private JObject GetNep5Transfers(JArray _params) + + [RpcMethod] + public JObject GetNep5Transfers(JArray _params) { + if (!_shouldTrackHistory) throw new RpcException(-32601, "Method not found"); UInt160 userScriptHash = GetScriptHashFromParam(_params[0].AsString()); // If start time not present, default to 1 week of history. ulong startTime = _params.Count > 1 ? (ulong)_params[1].AsNumber() : @@ -258,7 +265,8 @@ private JObject GetNep5Transfers(JArray _params) return json; } - private JObject GetNep5Balances(JArray _params) + [RpcMethod] + public JObject GetNep5Balances(JArray _params) { UInt160 userScriptHash = GetScriptHashFromParam(_params[0].AsString()); @@ -278,19 +286,5 @@ private JObject GetNep5Balances(JArray _params) } return json; } - - public void PreProcess(HttpContext context, string method, JArray _params) - { - } - - public JObject OnProcess(HttpContext context, string method, JArray _params) - { - if (_shouldTrackHistory && method == "getnep5transfers") return GetNep5Transfers(_params); - return method == "getnep5balances" ? GetNep5Balances(_params) : null; - } - - public void PostProcess(HttpContext context, string method, JArray _params, JObject result) - { - } } } diff --git a/src/RpcNep5Tracker/RpcNep5Tracker.csproj b/src/RpcNep5Tracker/RpcNep5Tracker.csproj index bc700a468..059356665 100644 --- a/src/RpcNep5Tracker/RpcNep5Tracker.csproj +++ b/src/RpcNep5Tracker/RpcNep5Tracker.csproj @@ -12,5 +12,6 @@ + From f42c319020b5e62bcce2851cd081e3d07383b053 Mon Sep 17 00:00:00 2001 From: erikzhang Date: Mon, 9 Dec 2019 15:47:00 +0800 Subject: [PATCH 10/12] Fix ContentType --- src/RpcServer/RpcServer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RpcServer/RpcServer.cs b/src/RpcServer/RpcServer.cs index ff662f95e..59a313c22 100644 --- a/src/RpcServer/RpcServer.cs +++ b/src/RpcServer/RpcServer.cs @@ -117,7 +117,7 @@ protected override void OnPluginsLoaded() { // options.EnableForHttps = false; options.Providers.Add(); - options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Append("application/json-rpc"); + options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Append("application/json"); }); services.Configure(options => @@ -189,7 +189,7 @@ private async Task ProcessAsync(HttpContext context) response = ProcessRequest(context, request); } if (response == null || (response as JArray)?.Count == 0) return; - context.Response.ContentType = "application/json-rpc"; + context.Response.ContentType = "application/json"; await context.Response.WriteAsync(response.ToString(), Encoding.UTF8); } From 4cefb35069702a0690f24ba7871c2c72e83d5429 Mon Sep 17 00:00:00 2001 From: erikzhang Date: Tue, 10 Dec 2019 15:02:44 +0800 Subject: [PATCH 11/12] Neo v3.0.0-CI00828 --- src/RpcServer/RpcServer.cs | 2 +- src/RpcServer/RpcServer.csproj | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/RpcServer/RpcServer.cs b/src/RpcServer/RpcServer.cs index 59a313c22..d263f3dac 100644 --- a/src/RpcServer/RpcServer.cs +++ b/src/RpcServer/RpcServer.cs @@ -164,7 +164,7 @@ private async Task ProcessAsync(HttpContext context) using StreamReader reader = new StreamReader(context.Request.Body); try { - request = JObject.Parse(reader); + request = JObject.Parse(reader.ReadToEnd()); } catch (FormatException) { } } diff --git a/src/RpcServer/RpcServer.csproj b/src/RpcServer/RpcServer.csproj index 23da0cb1e..7dc1754e0 100644 --- a/src/RpcServer/RpcServer.csproj +++ b/src/RpcServer/RpcServer.csproj @@ -1,7 +1,7 @@ - 3.0.0-CI00825 + 3.0.0-preview1 netstandard2.1 Neo.Plugins @@ -15,7 +15,7 @@ - + From fae8c17baffde1cf105bd21f8eed35e9a0f96eda Mon Sep 17 00:00:00 2001 From: Shargon Date: Wed, 11 Dec 2019 13:22:57 +0100 Subject: [PATCH 12/12] Remove enter --- src/RpcServer/RpcServer.Utilities.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/RpcServer/RpcServer.Utilities.cs b/src/RpcServer/RpcServer.Utilities.cs index 0ddc31578..281fa6726 100644 --- a/src/RpcServer/RpcServer.Utilities.cs +++ b/src/RpcServer/RpcServer.Utilities.cs @@ -9,7 +9,6 @@ namespace Neo.Plugins { partial class RpcServer { - [RpcMethod] private JObject ListPlugins(JArray _params) {