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);
- }
- }
-}