diff --git a/neo-modules.sln b/neo-modules.sln index bd53f4dd4..1f5f32fbc 100644 --- a/neo-modules.sln +++ b/neo-modules.sln @@ -8,12 +8,8 @@ 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}") = "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}") = "SystemLog", "src\SystemLog\SystemLog.csproj", "{14DB62D5-0EA1-4A98-8656-1AA2D0345206}" @@ -24,6 +20,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RocksDBStore", "src\RocksDB EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RpcClient", "src\RpcClient\RpcClient.csproj", "{8DC57A45-A192-4953-81B1-6907FB7C28D2}" 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.Network.RPC.Tests", "tests\Neo.Network.RPC.Tests\Neo.Network.RPC.Tests.csproj", "{D52460B3-AB5C-4D07-B400-9E7ADCB01FF5}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Plugins.Storage.Tests", "tests\Neo.Plugins.Storage.Tests\Neo.Plugins.Storage.Tests.csproj", "{9E7EA895-302A-4C0C-BA9B-54F9A67AD75C}" @@ -38,18 +36,10 @@ 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 {86531DB1-A231-46C4-823F-BE60972F7523}.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 @@ -70,6 +60,10 @@ Global {8DC57A45-A192-4953-81B1-6907FB7C28D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {8DC57A45-A192-4953-81B1-6907FB7C28D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {8DC57A45-A192-4953-81B1-6907FB7C28D2}.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 {D52460B3-AB5C-4D07-B400-9E7ADCB01FF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D52460B3-AB5C-4D07-B400-9E7ADCB01FF5}.Debug|Any CPU.Build.0 = Debug|Any CPU {D52460B3-AB5C-4D07-B400-9E7ADCB01FF5}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -84,14 +78,13 @@ 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} - {EF32A7E5-EDF9-438C-8041-8DA6E675A7FD} = {97E81C78-1637-481F-9485-DA1225E94C23} {BBE8AC15-12DF-4AF0-ABC1-F1557EB5DC8E} = {97E81C78-1637-481F-9485-DA1225E94C23} {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} {8DC57A45-A192-4953-81B1-6907FB7C28D2} = {97E81C78-1637-481F-9485-DA1225E94C23} + {1403FFE9-4265-4269-8E3D-5A79EFD108CA} = {97E81C78-1637-481F-9485-DA1225E94C23} {D52460B3-AB5C-4D07-B400-9E7ADCB01FF5} = {59D802AB-C552-422A-B9C3-64D329FBCDCC} {9E7EA895-302A-4C0C-BA9B-54F9A67AD75C} = {59D802AB-C552-422A-B9C3-64D329FBCDCC} EndGlobalSection 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/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 @@ + 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/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/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/RpcMethodAttribute.cs b/src/RpcServer/RpcMethodAttribute.cs new file mode 100644 index 000000000..f560ba555 --- /dev/null +++ b/src/RpcServer/RpcMethodAttribute.cs @@ -0,0 +1,10 @@ +using System; + +namespace Neo.Plugins +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public 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..60069ea75 --- /dev/null +++ b/src/RpcServer/RpcServer.SmartContract.cs @@ -0,0 +1,100 @@ +#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"; + } + ProcessInvokeWithWallet(json); + 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..281fa6726 --- /dev/null +++ b/src/RpcServer/RpcServer.Utilities.cs @@ -0,0 +1,47 @@ +#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/RpcWallet/RpcWallet.cs b/src/RpcServer/RpcServer.Wallet.cs similarity index 59% rename from src/RpcWallet/RpcWallet.cs rename to src/RpcServer/RpcServer.Wallet.cs index 12178ad10..7d183fd96 100644 --- a/src/RpcWallet/RpcWallet.cs +++ b/src/RpcServer/RpcServer.Wallet.cs @@ -1,187 +1,92 @@ +#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; 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 { - 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) + [RpcMethod] + private JObject CloseWallet(JArray _params) { - 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(); - } + wallet = null; + return true; } - 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 +97,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 +112,59 @@ private JObject ListAddress() }).ToArray(); } - private JObject SendFrom(UInt160 assetId, UInt160 from, UInt160 to, string value) + [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) + { + 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 +177,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 +192,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 +220,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 +240,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 +263,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 +277,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 new file mode 100644 index 000000000..d263f3dac --- /dev/null +++ b/src/RpcServer/RpcServer.cs @@ -0,0 +1,243 @@ +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 System; +using System.Collections.Generic; +using System.IO; +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 partial class RpcServer : Plugin + { + private static readonly Dictionary> methods = new Dictionary>(); + private IWebHost host; + + public RpcServer() + { + RegisterMethods(this); + } + + 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()); + } + + 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 override void Dispose() + { + base.Dispose(); + if (host != null) + { + host.Dispose(); + host = null; + } + } + + 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"); + }); + + services.Configure(options => + { + options.Level = CompressionLevel.Fastest; + }); + }) + .Build(); + + host.Start(); + } + + 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.ReadToEnd()); + } + 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"; + 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(); + 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"]); + 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 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); + } + } + } +} diff --git a/src/RpcSecurity/RpcSecurity.csproj b/src/RpcServer/RpcServer.csproj similarity index 68% rename from src/RpcSecurity/RpcSecurity.csproj rename to src/RpcServer/RpcServer.csproj index 2d5a348cf..7dc1754e0 100644 --- a/src/RpcSecurity/RpcSecurity.csproj +++ b/src/RpcServer/RpcServer.csproj @@ -7,14 +7,15 @@ - + PreserveNewest PreserveNewest - + + diff --git a/src/RpcServer/RpcServer/config.json b/src/RpcServer/RpcServer/config.json new file mode 100644 index 000000000..39d4639c0 --- /dev/null +++ b/src/RpcServer/RpcServer/config.json @@ -0,0 +1,14 @@ +{ + "PluginConfiguration": { + "BindAddress": "127.0.0.1", + "Port": 10332, + "SslCert": "", + "SslCertPassword": "", + "TrustedAuthorities": [], + "RpcUser": "", + "RpcPass": "", + "MaxGasInvoke": 10, + "MaxFee": 0.1, + "DisabledMethods": [ "openwallet" ] + } +} diff --git a/src/RpcServer/Settings.cs b/src/RpcServer/Settings.cs new file mode 100644 index 000000000..d955e48d8 --- /dev/null +++ b/src/RpcServer/Settings.cs @@ -0,0 +1,42 @@ +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 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; } + + 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.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) + { + Default = new Settings(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); - } - } -}