From d74f87742f961819019352f7d1618595e655ca7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=BF=97=E5=90=8C?= Date: Thu, 26 Nov 2020 04:02:49 -0600 Subject: [PATCH 1/5] [RpcServer] Querying contracts by ID/name (#378) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fixed-bug-1021 * Update src/RpcServer/RpcServer.SmartContract.cs * 😂 * draft * draft * update * fixed bug with decimal of GAS consumed in invokefunction/invokescript * remove modify of invokescript * Querying contracts by ID/name, server side * update * Enable using native.name for search Enable using native.name for search * Using keyword instead of addressOrScriptHash * revert * _initialize * split * update * Update exception message in ApplicationLog * Update src/ApplicationLogs/LogReader.cs Co-authored-by: Luchuan * update * a * More fix * fix * Fixed UT * Simplify Code * Simplify Code 2 * Update RpcServer * update Co-authored-by: Shargon Co-authored-by: Owen Zhang <38493437+superboyiii@users.noreply.github.com> Co-authored-by: superboyiii <573504781@qq.com> Co-authored-by: Vitor Nazário Coelho Co-authored-by: Luchuan --- src/ApplicationLogs/LogReader.cs | 13 +++---- src/RpcClient/Models/RpcApplicationLog.cs | 6 ++-- src/RpcClient/Models/RpcBlock.cs | 2 +- src/RpcClient/Models/RpcInvokeResult.cs | 4 ++- src/RpcClient/Models/RpcTransaction.cs | 2 +- src/RpcClient/TransactionManagerFactory.cs | 3 +- src/RpcClient/Utility.cs | 19 ++++++++-- src/RpcServer/RpcServer.Blockchain.cs | 17 +++++++-- src/RpcServer/RpcServer.SmartContract.cs | 2 +- src/RpcServer/RpcServer.Wallet.cs | 2 +- src/RpcServer/RpcServer.cs | 2 +- src/RpcServer/Utility.cs | 28 +++++++++++++++ tests/Neo.Network.RPC.Tests/RpcTestCases.json | 36 +++++++++---------- .../UT_TransactionManager.cs | 2 +- 14 files changed, 99 insertions(+), 39 deletions(-) create mode 100644 src/RpcServer/Utility.cs diff --git a/src/ApplicationLogs/LogReader.cs b/src/ApplicationLogs/LogReader.cs index 90c772691..f68ebaede 100644 --- a/src/ApplicationLogs/LogReader.cs +++ b/src/ApplicationLogs/LogReader.cs @@ -4,6 +4,7 @@ using Neo.Ledger; using Neo.Persistence; using Neo.SmartContract; +using Neo.SmartContract.Native; using Neo.VM; using System; using System.Collections.Generic; @@ -40,7 +41,7 @@ public JObject GetApplicationLog(JArray _params) if (value is null) throw new RpcException(-100, "Unknown transaction/blockhash"); - var raw = JObject.Parse(Utility.StrictUTF8.GetString(value)); + var raw = JObject.Parse(Neo.Utility.StrictUTF8.GetString(value)); //Additional optional "trigger" parameter to getapplicationlog for clients to be able to get just one execution result for a block. if (_params.Count >= 2 && Enum.TryParse(_params[1].AsString(), true, out TriggerType trigger)) { @@ -66,7 +67,7 @@ public static JObject TxLogToJson(Blockchain.ApplicationExecuted appExec) trigger["trigger"] = appExec.Trigger; trigger["vmstate"] = appExec.VMState; trigger["exception"] = GetExceptionMessage(appExec.Exception); - trigger["gasconsumed"] = appExec.GasConsumed.ToString(); + trigger["gasconsumed"] = new BigDecimal(appExec.GasConsumed, NativeContract.GAS.Decimals).ToString(); try { trigger["stack"] = appExec.Stack.Select(q => q.ToJson()).ToArray(); @@ -109,7 +110,7 @@ public static JObject BlockLogToJson(StoreView snapshot, IReadOnlyList q.ToJson()).ToArray(); @@ -150,14 +151,14 @@ public void OnPersist(StoreView snapshot, IReadOnlyList p.Transaction != null)) { var txJson = TxLogToJson(appExec); - writeBatch.Put(appExec.Transaction.Hash.ToArray(), Utility.StrictUTF8.GetBytes(txJson.ToString())); + writeBatch.Put(appExec.Transaction.Hash.ToArray(), Neo.Utility.StrictUTF8.GetBytes(txJson.ToString())); } //processing log for block var blockJson = BlockLogToJson(snapshot, applicationExecutedList); if (blockJson != null) { - writeBatch.Put(snapshot.PersistingBlock.Hash.ToArray(), Utility.StrictUTF8.GetBytes(blockJson.ToString())); + writeBatch.Put(snapshot.PersistingBlock.Hash.ToArray(), Neo.Utility.StrictUTF8.GetBytes(blockJson.ToString())); } db.Write(WriteOptions.Default, writeBatch); } @@ -173,7 +174,7 @@ public bool ShouldThrowExceptionFromCommit(Exception ex) static string GetExceptionMessage(Exception exception) { - if (exception == null) return "Engine faulted."; + if (exception == null) return null; if (exception.InnerException != null) { diff --git a/src/RpcClient/Models/RpcApplicationLog.cs b/src/RpcClient/Models/RpcApplicationLog.cs index cfdfbcc7e..42fa20833 100644 --- a/src/RpcClient/Models/RpcApplicationLog.cs +++ b/src/RpcClient/Models/RpcApplicationLog.cs @@ -1,7 +1,9 @@ using Neo.IO.Json; using Neo.SmartContract; +using Neo.SmartContract.Native; using Neo.VM; using Neo.VM.Types; +using System; using System.Collections.Generic; using System.Linq; @@ -54,7 +56,7 @@ public JObject ToJson() JObject json = new JObject(); json["trigger"] = Trigger; json["vmstate"] = VMState; - json["gasconsumed"] = GasConsumed.ToString(); + json["gasconsumed"] = new BigDecimal(GasConsumed, NativeContract.GAS.Decimals).ToString(); json["stack"] = Stack.Select(q => q.ToJson()).ToArray(); json["notifications"] = Notifications.Select(q => q.ToJson()).ToArray(); return json; @@ -66,7 +68,7 @@ public static Execution FromJson(JObject json) { Trigger = json["trigger"].TryGetEnum(), VMState = json["vmstate"].TryGetEnum(), - GasConsumed = long.Parse(json["gasconsumed"].AsString()), + GasConsumed = (long)BigDecimal.Parse(json["gasconsumed"].AsString(), NativeContract.GAS.Decimals).Value, Stack = ((JArray)json["stack"]).Select(p => Utility.StackItemFromJson(p)).ToList(), Notifications = ((JArray)json["notifications"]).Select(p => RpcNotifyEventArgs.FromJson(p)).ToList() }; diff --git a/src/RpcClient/Models/RpcBlock.cs b/src/RpcClient/Models/RpcBlock.cs index 24687812f..8d706ca74 100644 --- a/src/RpcClient/Models/RpcBlock.cs +++ b/src/RpcClient/Models/RpcBlock.cs @@ -13,7 +13,7 @@ public class RpcBlock public JObject ToJson() { - JObject json = Block.ToJson(); + JObject json = Utility.BlockToJson(Block); json["confirmations"] = Confirmations; json["nextblockhash"] = NextBlockHash?.ToString(); return json; diff --git a/src/RpcClient/Models/RpcInvokeResult.cs b/src/RpcClient/Models/RpcInvokeResult.cs index e57310894..8ef6e50a6 100644 --- a/src/RpcClient/Models/RpcInvokeResult.cs +++ b/src/RpcClient/Models/RpcInvokeResult.cs @@ -1,8 +1,10 @@ using Neo.IO.Json; +using Neo.SmartContract.Native; using Neo.VM; using Neo.VM.Types; using System; using System.Linq; +using System.Numerics; namespace Neo.Network.RPC.Models { @@ -25,7 +27,7 @@ public JObject ToJson() JObject json = new JObject(); json["script"] = Script; json["state"] = State; - json["gasconsumed"] = GasConsumed; + json["gasconsumed"] = GasConsumed.ToString(); if (!string.IsNullOrEmpty(Exception)) json["exception"] = Exception; try diff --git a/src/RpcClient/Models/RpcTransaction.cs b/src/RpcClient/Models/RpcTransaction.cs index 74aae5566..2ccd2687b 100644 --- a/src/RpcClient/Models/RpcTransaction.cs +++ b/src/RpcClient/Models/RpcTransaction.cs @@ -18,7 +18,7 @@ public class RpcTransaction public JObject ToJson() { - JObject json = Transaction.ToJson(); + JObject json = Utility.TransactionToJson(Transaction); if (Confirmations != null) { json["blockhash"] = BlockHash.ToString(); diff --git a/src/RpcClient/TransactionManagerFactory.cs b/src/RpcClient/TransactionManagerFactory.cs index 0f7277a2c..92e50b310 100644 --- a/src/RpcClient/TransactionManagerFactory.cs +++ b/src/RpcClient/TransactionManagerFactory.cs @@ -1,5 +1,6 @@ using Neo.Network.P2P.Payloads; using Neo.Network.RPC.Models; +using Neo.SmartContract.Native; using System; using System.Threading.Tasks; @@ -47,7 +48,7 @@ public async Task MakeTransactionAsync(byte[] script, Signer Script = script, Signers = signers ?? Array.Empty(), ValidUntilBlock = blockCount - 1 + Transaction.MaxValidUntilBlockIncrement, - SystemFee = long.Parse(invokeResult.GasConsumed), + SystemFee = (long)BigDecimal.Parse(invokeResult.GasConsumed.ToString(), NativeContract.GAS.Decimals).Value, Attributes = attributes ?? Array.Empty(), }; diff --git a/src/RpcClient/Utility.cs b/src/RpcClient/Utility.cs index dae537f2b..2da05a1b4 100644 --- a/src/RpcClient/Utility.cs +++ b/src/RpcClient/Utility.cs @@ -129,6 +129,13 @@ public static Block BlockFromJson(JObject json) return block; } + public static JObject BlockToJson(Block block) + { + JObject json = block.ToJson(); + json["tx"] = block.Transactions.Select(p => TransactionToJson(p)).ToArray(); + return json; + } + public static void FromJson(this BlockBase block, JObject json) { block.Version = (uint)json["version"].AsNumber(); @@ -147,8 +154,8 @@ public static Transaction TransactionFromJson(JObject json) Version = byte.Parse(json["version"].AsString()), Nonce = uint.Parse(json["nonce"].AsString()), Signers = ((JArray)json["signers"]).Select(p => SignerFromJson(p)).ToArray(), - SystemFee = long.Parse(json["sysfee"].AsString()), - NetworkFee = long.Parse(json["netfee"].AsString()), + SystemFee = (long)BigDecimal.Parse(json["sysfee"].AsString(), NativeContract.GAS.Decimals).Value, + NetworkFee = (long)BigDecimal.Parse(json["netfee"].AsString(), NativeContract.GAS.Decimals).Value, ValidUntilBlock = uint.Parse(json["validuntilblock"].AsString()), Attributes = ((JArray)json["attributes"]).Select(p => TransactionAttributeFromJson(p)).ToArray(), Script = Convert.FromBase64String(json["script"].AsString()), @@ -156,6 +163,14 @@ public static Transaction TransactionFromJson(JObject json) }; } + public static JObject TransactionToJson(Transaction tx) + { + JObject json = tx.ToJson(); + json["sysfee"] = new BigDecimal(tx.SystemFee, NativeContract.GAS.Decimals).ToString(); + json["netfee"] = new BigDecimal(tx.NetworkFee, NativeContract.GAS.Decimals).ToString(); + return json; + } + public static Header HeaderFromJson(JObject json) { Header header = new Header(); diff --git a/src/RpcServer/RpcServer.Blockchain.cs b/src/RpcServer/RpcServer.Blockchain.cs index 4ef57fc18..137d484e4 100644 --- a/src/RpcServer/RpcServer.Blockchain.cs +++ b/src/RpcServer/RpcServer.Blockchain.cs @@ -41,7 +41,7 @@ protected virtual JObject GetBlock(JArray _params) throw new RpcException(-100, "Unknown block"); if (verbose) { - JObject json = block.ToJson(); + JObject json = Utility.BlockToJson(block); json["confirmations"] = Blockchain.Singleton.Height - block.Index + 1; UInt256 hash = Blockchain.Singleton.GetNextBlockHash(block.Hash); if (hash != null) @@ -103,11 +103,22 @@ protected virtual JObject GetBlockHeader(JArray _params) [RpcMethod] protected virtual JObject GetContractState(JArray _params) { - UInt160 script_hash = UInt160.Parse(_params[0].AsString()); + UInt160 script_hash = ToScriptHash(_params[0].AsString()); ContractState contract = Blockchain.Singleton.View.Contracts.TryGet(script_hash); return contract?.ToJson() ?? throw new RpcException(-100, "Unknown contract"); } + private static UInt160 ToScriptHash(string keyword) + { + foreach (var native in NativeContract.Contracts) + { + if (keyword.Equals(native.Name, StringComparison.InvariantCultureIgnoreCase) || keyword == native.Id.ToString()) + return native.Hash; + } + + return UInt160.Parse(keyword); + } + [RpcMethod] protected virtual JObject GetRawMemPool(JArray _params) { @@ -135,7 +146,7 @@ protected virtual JObject GetRawTransaction(JArray _params) throw new RpcException(-100, "Unknown transaction"); if (verbose) { - JObject json = tx.ToJson(); + JObject json = Utility.TransactionToJson(tx); TransactionState txState = Blockchain.Singleton.View.Transactions.TryGet(hash); if (txState != null) { diff --git a/src/RpcServer/RpcServer.SmartContract.cs b/src/RpcServer/RpcServer.SmartContract.cs index 4fed465ad..a48ede69a 100644 --- a/src/RpcServer/RpcServer.SmartContract.cs +++ b/src/RpcServer/RpcServer.SmartContract.cs @@ -73,7 +73,7 @@ private JObject GetInvokeResult(byte[] script, Signers signers = null) JObject json = new JObject(); json["script"] = Convert.ToBase64String(script); json["state"] = engine.State; - json["gasconsumed"] = engine.GasConsumed.ToString(); + json["gasconsumed"] = new BigDecimal(engine.GasConsumed, NativeContract.GAS.Decimals).ToString(); json["exception"] = GetExceptionMessage(engine.FaultException); try { diff --git a/src/RpcServer/RpcServer.Wallet.cs b/src/RpcServer/RpcServer.Wallet.cs index 2256c0a6d..214df1bee 100644 --- a/src/RpcServer/RpcServer.Wallet.cs +++ b/src/RpcServer/RpcServer.Wallet.cs @@ -332,7 +332,7 @@ private JObject SignAndRelay(Transaction tx) { tx.Witnesses = context.GetWitnesses(); system.Blockchain.Tell(tx); - return tx.ToJson(); + return Utility.TransactionToJson(tx); } else { diff --git a/src/RpcServer/RpcServer.cs b/src/RpcServer/RpcServer.cs index a17d51d22..b20a22caf 100644 --- a/src/RpcServer/RpcServer.cs +++ b/src/RpcServer/RpcServer.cs @@ -107,7 +107,7 @@ public void StartRpcServer() { if (err != SslPolicyErrors.None) return false; - X509Certificate2 authority = chain.ChainElements[chain.ChainElements.Count - 1].Certificate; + X509Certificate2 authority = chain.ChainElements[^1].Certificate; return settings.TrustedAuthorities.Contains(authority.Thumbprint); }; }); diff --git a/src/RpcServer/Utility.cs b/src/RpcServer/Utility.cs new file mode 100644 index 000000000..0ac336351 --- /dev/null +++ b/src/RpcServer/Utility.cs @@ -0,0 +1,28 @@ +using Neo.IO.Json; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract.Native; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Neo.Plugins +{ + public static class Utility + { + public static JObject BlockToJson(Block block) + { + JObject json = block.ToJson(); + json["tx"] = block.Transactions.Select(p => TransactionToJson(p)).ToArray(); + return json; + } + + public static JObject TransactionToJson(Transaction tx) + { + JObject json = tx.ToJson(); + json["sysfee"] = new BigDecimal(tx.SystemFee, NativeContract.GAS.Decimals).ToString(); + json["netfee"] = new BigDecimal(tx.NetworkFee, NativeContract.GAS.Decimals).ToString(); + return json; + } + } +} diff --git a/tests/Neo.Network.RPC.Tests/RpcTestCases.json b/tests/Neo.Network.RPC.Tests/RpcTestCases.json index 5de1433b6..8833fed6b 100644 --- a/tests/Neo.Network.RPC.Tests/RpcTestCases.json +++ b/tests/Neo.Network.RPC.Tests/RpcTestCases.json @@ -96,8 +96,8 @@ "version": 0, "nonce": 631973574, "sender": "NikvsLcNP1jWhrFPrfS3n4spEASgdNYTG2", - "sysfee": "9007990", - "netfee": "1248450", + "sysfee": "0.0900799", + "netfee": "0.0124845", "validuntilblock": 2102405, "signers": [ { @@ -157,8 +157,8 @@ "version": 0, "nonce": 631973574, "sender": "NikvsLcNP1jWhrFPrfS3n4spEASgdNYTG2", - "sysfee": "9007990", - "netfee": "1248450", + "sysfee": "0.0900799", + "netfee": "0.0124845", "validuntilblock": 2102405, "signers": [ { @@ -520,8 +520,8 @@ "version": 0, "nonce": 969006668, "sender": "NikvsLcNP1jWhrFPrfS3n4spEASgdNYTG2", - "sysfee": "100000000", - "netfee": "1272390", + "sysfee": "1", + "netfee": "0.0127239", "validuntilblock": 2104625, "signers": [ { @@ -731,7 +731,7 @@ "result": { "script": "0c1426ae7c6c9861ec418468c1f0fdc4a7f2963eb89111c00c0962616c616e63654f660c143b7d3711c6f0ccf9b1dca903d1bfa1d896f1238c41627d5b52", "state": "HALT", - "gasconsumed": "2007570", + "gasconsumed": "0.0200757", "stack": [ { "type": "Integer", @@ -756,7 +756,7 @@ "result": { "script": "EMMMBG5hbWUMFDt9NxHG8Mz5sdypA9G/odiW8SOMQWJ9W1IQwwwGc3ltYm9sDBQ7fTcRxvDM+bHcqQPRv6HYlvEjjEFifVtSEMMMCGRlY2ltYWxzDBQ7fTcRxvDM+bHcqQPRv6HYlvEjjEFifVtSEMMMC3RvdGFsU3VwcGx5DBQ7fTcRxvDM+bHcqQPRv6HYlvEjjEFifVtS", "state": "HALT", - "gasconsumed": "5061560", + "gasconsumed": "0.0506156", "stack": [ { "type": "Array", @@ -942,7 +942,7 @@ "result": { "script": "EMMMCGRlY2ltYWxzDBQ7fTcRxvDM+bHcqQPRv6HYlvEjjEFifVtS", "state": "HALT", - "gasconsumed": "5061560", + "gasconsumed": "0.0506156", "stack": [ { "type": "Integer", @@ -1074,8 +1074,8 @@ "version": 0, "nonce": 1553700339, "sender": "NVVwFw6XyhtRCFQ8SpUTMdPyYt4Vd9A1XQ", - "sysfee": "100000000", - "netfee": "1272390", + "sysfee": "1", + "netfee": "0.0127239", "validuntilblock": 2105487, "attributes": [], "cosigners": [ @@ -1125,8 +1125,8 @@ "version": 0, "nonce": 34429660, "sender": "NUMK37TV9yYKbJr1Gufh74nZiM623eBLqX", - "sysfee": "100000000", - "netfee": "2483780", + "sysfee": "1", + "netfee": "0.0248378", "validuntilblock": 2105494, "attributes": [], "cosigners": [ @@ -1170,8 +1170,8 @@ "version": 0, "nonce": 330056065, "sender": "NUMK37TV9yYKbJr1Gufh74nZiM623eBLqX", - "sysfee": "100000000", - "netfee": "2381780", + "sysfee": "1", + "netfee": "0.0238178", "validuntilblock": 2105500, "attributes": [], "cosigners": [ @@ -1213,7 +1213,7 @@ { "trigger": "OnPersist", "vmstate": "HALT", - "gasconsumed": "2031260", + "gasconsumed": "0.0203126", "stack": [], "notifications": [ { @@ -1261,7 +1261,7 @@ { "trigger": "PostPersist", "vmstate": "HALT", - "gasconsumed": "2031260", + "gasconsumed": "0.0203126", "stack": [], "notifications": [ { @@ -1307,7 +1307,7 @@ { "trigger": "OnPersist", "vmstate": "HALT", - "gasconsumed": "2031260", + "gasconsumed": "0.0203126", "stack": [], "notifications": [ { diff --git a/tests/Neo.Network.RPC.Tests/UT_TransactionManager.cs b/tests/Neo.Network.RPC.Tests/UT_TransactionManager.cs index 32744c2e2..bc740fd1c 100644 --- a/tests/Neo.Network.RPC.Tests/UT_TransactionManager.cs +++ b/tests/Neo.Network.RPC.Tests/UT_TransactionManager.cs @@ -164,7 +164,7 @@ await txManager Assert.IsTrue(Crypto.VerifySignature(tx.GetHashData(), signature, keyPair1.PublicKey)); // verify network fee and system fee Assert.AreEqual(100000000/*Mock*/, tx.NetworkFee); - Assert.AreEqual(100, tx.SystemFee); + Assert.AreEqual(10000000000, tx.SystemFee); // duplicate sign should not add new witness await txManager.AddSignature(keyPair1).SignAsync(); From 72bf97c78a2cce53decdaf611bee867f4a513fc2 Mon Sep 17 00:00:00 2001 From: ZhangTao Date: Fri, 27 Nov 2020 15:26:35 +0800 Subject: [PATCH 2/5] MPT in StateService from core (#410) --- neo-modules.sln | 16 +- src/StateService/MPT/BranchNode.cs | 35 ++ src/StateService/MPT/ExtensionNode.cs | 30 ++ src/StateService/MPT/HashNode.cs | 41 +++ src/StateService/MPT/LeafNode.cs | 36 ++ src/StateService/MPT/MPTNode.cs | 62 ++++ src/StateService/MPT/MPTNodeType.cs | 16 + src/StateService/MPT/MPTTrie.Delete.cs | 120 +++++++ src/StateService/MPT/MPTTrie.Find.cs | 110 ++++++ src/StateService/MPT/MPTTrie.Get.cs | 61 ++++ src/StateService/MPT/MPTTrie.Proof.cs | 73 ++++ src/StateService/MPT/MPTTrie.Put.cs | 158 +++++++++ src/StateService/MPT/MPTTrie.cs | 58 ++++ src/StateService/StateService.csproj | 9 + .../MPT/UT_MPTNode.cs | 30 ++ .../MPT/UT_MPTTrie.cs | 320 ++++++++++++++++++ .../Neo.Plugins.StateService.Tests.csproj | 16 + 17 files changed, 1190 insertions(+), 1 deletion(-) create mode 100644 src/StateService/MPT/BranchNode.cs create mode 100644 src/StateService/MPT/ExtensionNode.cs create mode 100644 src/StateService/MPT/HashNode.cs create mode 100644 src/StateService/MPT/LeafNode.cs create mode 100644 src/StateService/MPT/MPTNode.cs create mode 100644 src/StateService/MPT/MPTNodeType.cs create mode 100644 src/StateService/MPT/MPTTrie.Delete.cs create mode 100644 src/StateService/MPT/MPTTrie.Find.cs create mode 100644 src/StateService/MPT/MPTTrie.Get.cs create mode 100644 src/StateService/MPT/MPTTrie.Proof.cs create mode 100644 src/StateService/MPT/MPTTrie.Put.cs create mode 100644 src/StateService/MPT/MPTTrie.cs create mode 100644 src/StateService/StateService.csproj create mode 100644 tests/Neo.Plugins.StateService.Tests/MPT/UT_MPTNode.cs create mode 100644 tests/Neo.Plugins.StateService.Tests/MPT/UT_MPTTrie.cs create mode 100644 tests/Neo.Plugins.StateService.Tests/Neo.Plugins.StateService.Tests.csproj diff --git a/neo-modules.sln b/neo-modules.sln index 763a89627..24f303eb4 100644 --- a/neo-modules.sln +++ b/neo-modules.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.28729.10 MinimumVisualStudioVersion = 10.0.40219.1 @@ -24,6 +24,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Network.RPC.Tests", "te 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}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StateService", "src\StateService\StateService.csproj", "{A0F4A66F-6F87-4B99-B8BE-A779BC002F47}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Plugins.StateService.Tests", "tests\Neo.Plugins.StateService.Tests\Neo.Plugins.StateService.Tests.csproj", "{149822EC-4E0C-425F-A032-4196B615BFEB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -66,6 +70,14 @@ Global {9E7EA895-302A-4C0C-BA9B-54F9A67AD75C}.Debug|Any CPU.Build.0 = Debug|Any CPU {9E7EA895-302A-4C0C-BA9B-54F9A67AD75C}.Release|Any CPU.ActiveCfg = Release|Any CPU {9E7EA895-302A-4C0C-BA9B-54F9A67AD75C}.Release|Any CPU.Build.0 = Release|Any CPU + {A0F4A66F-6F87-4B99-B8BE-A779BC002F47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A0F4A66F-6F87-4B99-B8BE-A779BC002F47}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A0F4A66F-6F87-4B99-B8BE-A779BC002F47}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A0F4A66F-6F87-4B99-B8BE-A779BC002F47}.Release|Any CPU.Build.0 = Release|Any CPU + {149822EC-4E0C-425F-A032-4196B615BFEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {149822EC-4E0C-425F-A032-4196B615BFEB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {149822EC-4E0C-425F-A032-4196B615BFEB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {149822EC-4E0C-425F-A032-4196B615BFEB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -80,6 +92,8 @@ Global {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} + {A0F4A66F-6F87-4B99-B8BE-A779BC002F47} = {97E81C78-1637-481F-9485-DA1225E94C23} + {149822EC-4E0C-425F-A032-4196B615BFEB} = {59D802AB-C552-422A-B9C3-64D329FBCDCC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {61D3ADE6-BBFC-402D-AB42-1C71C9F9EDE3} diff --git a/src/StateService/MPT/BranchNode.cs b/src/StateService/MPT/BranchNode.cs new file mode 100644 index 000000000..f729412b5 --- /dev/null +++ b/src/StateService/MPT/BranchNode.cs @@ -0,0 +1,35 @@ +using System.IO; + +namespace Neo.Plugins.MPT +{ + public class BranchNode : MPTNode + { + public const int ChildCount = 17; + public readonly MPTNode[] Children = new MPTNode[ChildCount]; + + protected override NodeType Type => NodeType.BranchNode; + + public BranchNode() + { + for (int i = 0; i < ChildCount; i++) + { + Children[i] = HashNode.EmptyNode; + } + } + + internal override void EncodeSpecific(BinaryWriter writer) + { + for (int i = 0; i < ChildCount; i++) + WriteHash(writer, Children[i].Hash); + } + + internal override void DecodeSpecific(BinaryReader reader) + { + for (int i = 0; i < ChildCount; i++) + { + Children[i] = new HashNode(); + Children[i].DecodeSpecific(reader); + } + } + } +} diff --git a/src/StateService/MPT/ExtensionNode.cs b/src/StateService/MPT/ExtensionNode.cs new file mode 100644 index 000000000..00f107240 --- /dev/null +++ b/src/StateService/MPT/ExtensionNode.cs @@ -0,0 +1,30 @@ +using Neo.IO; +using Neo.SmartContract; +using System.IO; + +namespace Neo.Plugins.MPT +{ + public class ExtensionNode : MPTNode + { + //max lenght when store StorageKey + public const int MaxKeyLength = (ApplicationEngine.MaxStorageValueSize + sizeof(int)) * 2; + + public byte[] Key; + public MPTNode Next; + + protected override NodeType Type => NodeType.ExtensionNode; + + internal override void EncodeSpecific(BinaryWriter writer) + { + writer.WriteVarBytes(Key); + WriteHash(writer, Next.Hash); + } + + internal override void DecodeSpecific(BinaryReader reader) + { + Key = reader.ReadVarBytes(MaxKeyLength); + Next = new HashNode(); + Next.DecodeSpecific(reader); + } + } +} diff --git a/src/StateService/MPT/HashNode.cs b/src/StateService/MPT/HashNode.cs new file mode 100644 index 000000000..7053f0a3b --- /dev/null +++ b/src/StateService/MPT/HashNode.cs @@ -0,0 +1,41 @@ +using Neo.IO; +using System; +using System.IO; + +namespace Neo.Plugins.MPT +{ + public class HashNode : MPTNode + { + private UInt256 hash; + + public override UInt256 Hash => hash; + protected override NodeType Type => NodeType.HashNode; + public bool IsEmpty => Hash is null; + public static HashNode EmptyNode { get; } = new HashNode(); + + public HashNode() + { + } + + public HashNode(UInt256 hash) + { + this.hash = hash; + } + + internal override void EncodeSpecific(BinaryWriter writer) + { + WriteHash(writer, hash); + } + + internal override void DecodeSpecific(BinaryReader reader) + { + byte[] buffer = reader.ReadVarBytes(UInt256.Length); + hash = buffer.Length switch + { + 0 => null, + UInt256.Length => new UInt256(buffer), + _ => throw new FormatException() + }; + } + } +} diff --git a/src/StateService/MPT/LeafNode.cs b/src/StateService/MPT/LeafNode.cs new file mode 100644 index 000000000..2a17ef4e2 --- /dev/null +++ b/src/StateService/MPT/LeafNode.cs @@ -0,0 +1,36 @@ +using Neo.IO; +using Neo.SmartContract; +using System; +using System.IO; + +namespace Neo.Plugins.MPT +{ + public class LeafNode : MPTNode + { + //the max size when store StorageItem + public const int MaxValueLength = 3 + ApplicationEngine.MaxStorageValueSize + sizeof(bool); + + public byte[] Value; + + protected override NodeType Type => NodeType.LeafNode; + + public LeafNode() + { + } + + public LeafNode(ReadOnlySpan value) + { + Value = value.ToArray(); + } + + internal override void EncodeSpecific(BinaryWriter writer) + { + writer.WriteVarBytes(Value); + } + + internal override void DecodeSpecific(BinaryReader reader) + { + Value = reader.ReadVarBytes(MaxValueLength); + } + } +} diff --git a/src/StateService/MPT/MPTNode.cs b/src/StateService/MPT/MPTNode.cs new file mode 100644 index 000000000..197031cdf --- /dev/null +++ b/src/StateService/MPT/MPTNode.cs @@ -0,0 +1,62 @@ +using Neo.Cryptography; +using Neo.IO; +using Neo.IO.Caching; +using System; +using System.IO; + +namespace Neo.Plugins.MPT +{ + public abstract class MPTNode + { + private UInt256 hash; + + public virtual UInt256 Hash => hash ??= new UInt256(Crypto.Hash256(Encode())); + protected abstract NodeType Type { get; } + + public void SetDirty() + { + hash = null; + } + + public byte[] Encode() + { + using MemoryStream ms = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(ms); + + writer.Write((byte)Type); + EncodeSpecific(writer); + writer.Flush(); + + return ms.ToArray(); + } + + internal abstract void EncodeSpecific(BinaryWriter writer); + + public static unsafe MPTNode Decode(ReadOnlySpan data) + { + if (data.IsEmpty) return null; + + fixed (byte* pointer = data) + { + using UnmanagedMemoryStream stream = new UnmanagedMemoryStream(pointer, data.Length); + using BinaryReader reader = new BinaryReader(stream); + + MPTNode n = (MPTNode)ReflectionCache.CreateInstance((NodeType)reader.ReadByte()); + if (n is null) throw new InvalidOperationException(); + + n.DecodeSpecific(reader); + return n; + } + } + + internal abstract void DecodeSpecific(BinaryReader reader); + + protected void WriteHash(BinaryWriter writer, UInt256 hash) + { + if (hash is null) + writer.Write((byte)0); + else + writer.WriteVarBytes(hash.ToArray()); + } + } +} diff --git a/src/StateService/MPT/MPTNodeType.cs b/src/StateService/MPT/MPTNodeType.cs new file mode 100644 index 000000000..05370a645 --- /dev/null +++ b/src/StateService/MPT/MPTNodeType.cs @@ -0,0 +1,16 @@ +using Neo.IO.Caching; + +namespace Neo.Plugins.MPT +{ + public enum NodeType : byte + { + [ReflectionCache(typeof(BranchNode))] + BranchNode = 0x00, + [ReflectionCache(typeof(ExtensionNode))] + ExtensionNode = 0x01, + [ReflectionCache(typeof(HashNode))] + HashNode = 0x02, + [ReflectionCache(typeof(LeafNode))] + LeafNode = 0x03, + } +} diff --git a/src/StateService/MPT/MPTTrie.Delete.cs b/src/StateService/MPT/MPTTrie.Delete.cs new file mode 100644 index 000000000..0470c2393 --- /dev/null +++ b/src/StateService/MPT/MPTTrie.Delete.cs @@ -0,0 +1,120 @@ +using Neo.IO; +using System; +using System.Collections.Generic; +using static Neo.Helper; + +namespace Neo.Plugins.MPT +{ + partial class MPTTrie + { + public bool Delete(TKey key) + { + var path = ToNibbles(key.ToArray()); + if (path.Length == 0) return false; + return TryDelete(ref root, path); + } + + private bool TryDelete(ref MPTNode node, ReadOnlySpan path) + { + switch (node) + { + case LeafNode _: + { + if (path.IsEmpty) + { + node = HashNode.EmptyNode; + return true; + } + return false; + } + case ExtensionNode extensionNode: + { + if (path.StartsWith(extensionNode.Key)) + { + var result = TryDelete(ref extensionNode.Next, path[extensionNode.Key.Length..]); + if (!result) return false; + if (extensionNode.Next is HashNode hashNode && hashNode.IsEmpty) + { + node = extensionNode.Next; + return true; + } + if (extensionNode.Next is ExtensionNode sn) + { + extensionNode.Key = Concat(extensionNode.Key, sn.Key); + extensionNode.Next = sn.Next; + } + extensionNode.SetDirty(); + PutToStore(extensionNode); + return true; + } + return false; + } + case BranchNode branchNode: + { + bool result; + if (path.IsEmpty) + { + result = TryDelete(ref branchNode.Children[BranchNode.ChildCount - 1], path); + } + else + { + result = TryDelete(ref branchNode.Children[path[0]], path[1..]); + } + if (!result) return false; + List childrenIndexes = new List(BranchNode.ChildCount); + for (int i = 0; i < BranchNode.ChildCount; i++) + { + if (branchNode.Children[i] is HashNode hn && hn.IsEmpty) continue; + childrenIndexes.Add((byte)i); + } + if (childrenIndexes.Count > 1) + { + branchNode.SetDirty(); + PutToStore(branchNode); + return true; + } + var lastChildIndex = childrenIndexes[0]; + var lastChild = branchNode.Children[lastChildIndex]; + if (lastChildIndex == BranchNode.ChildCount - 1) + { + node = lastChild; + return true; + } + if (lastChild is HashNode hashNode) + { + lastChild = Resolve(hashNode); + if (lastChild is null) return false; + } + if (lastChild is ExtensionNode exNode) + { + exNode.Key = Concat(childrenIndexes.ToArray(), exNode.Key); + exNode.SetDirty(); + PutToStore(exNode); + node = exNode; + return true; + } + node = new ExtensionNode() + { + Key = childrenIndexes.ToArray(), + Next = lastChild, + }; + PutToStore(node); + return true; + } + case HashNode hashNode: + { + if (hashNode.IsEmpty) + { + return true; + } + var newNode = Resolve(hashNode); + if (newNode is null) return false; + node = newNode; + return TryDelete(ref node, path); + } + default: + return false; + } + } + } +} diff --git a/src/StateService/MPT/MPTTrie.Find.cs b/src/StateService/MPT/MPTTrie.Find.cs new file mode 100644 index 000000000..6d072fd60 --- /dev/null +++ b/src/StateService/MPT/MPTTrie.Find.cs @@ -0,0 +1,110 @@ +using Neo.IO; +using System; +using System.Collections.Generic; +using System.Linq; +using static Neo.Helper; + +namespace Neo.Plugins.MPT +{ + partial class MPTTrie + { + private ReadOnlySpan Seek(ref MPTNode node, ReadOnlySpan path, out MPTNode start) + { + switch (node) + { + case LeafNode leafNode: + { + if (path.IsEmpty) + { + start = leafNode; + return ReadOnlySpan.Empty; + } + break; + } + case HashNode hashNode: + { + if (hashNode.IsEmpty) break; + var newNode = Resolve(hashNode); + if (newNode is null) break; + node = newNode; + return Seek(ref node, path, out start); + } + case BranchNode branchNode: + { + if (path.IsEmpty) + { + start = branchNode; + return ReadOnlySpan.Empty; + } + return Concat(path[..1], Seek(ref branchNode.Children[path[0]], path[1..], out start)); + } + case ExtensionNode extensionNode: + { + if (path.IsEmpty) + { + start = extensionNode.Next; + return extensionNode.Key; + } + if (path.StartsWith(extensionNode.Key)) + { + return Concat(extensionNode.Key, Seek(ref extensionNode.Next, path[extensionNode.Key.Length..], out start)); + } + if (extensionNode.Key.AsSpan().StartsWith(path)) + { + start = extensionNode.Next; + return extensionNode.Key; + } + break; + } + } + start = null; + return ReadOnlySpan.Empty; + } + + public IEnumerable<(TKey Key, TValue Value)> Find(ReadOnlySpan prefix) + { + var path = ToNibbles(prefix); + path = Seek(ref root, path, out MPTNode start).ToArray(); + return Travers(start, path) + .Select(p => (FromNibbles(p.Key).AsSerializable(), p.Value.AsSerializable())); + } + + private IEnumerable<(byte[] Key, byte[] Value)> Travers(MPTNode node, byte[] path) + { + if (node is null) yield break; + switch (node) + { + case LeafNode leafNode: + { + yield return (path, (byte[])leafNode.Value.Clone()); + break; + } + case HashNode hashNode: + { + if (hashNode.IsEmpty) break; + var newNode = Resolve(hashNode); + if (newNode is null) break; + node = newNode; + foreach (var item in Travers(node, path)) + yield return item; + break; + } + case BranchNode branchNode: + { + for (int i = 0; i < BranchNode.ChildCount; i++) + { + foreach (var item in Travers(branchNode.Children[i], i == BranchNode.ChildCount - 1 ? path : Concat(path, new byte[] { (byte)i }))) + yield return item; + } + break; + } + case ExtensionNode extensionNode: + { + foreach (var item in Travers(extensionNode.Next, Concat(path, extensionNode.Key))) + yield return item; + break; + } + } + } + } +} diff --git a/src/StateService/MPT/MPTTrie.Get.cs b/src/StateService/MPT/MPTTrie.Get.cs new file mode 100644 index 000000000..49dc6ed5d --- /dev/null +++ b/src/StateService/MPT/MPTTrie.Get.cs @@ -0,0 +1,61 @@ +using Neo.IO; +using System; + +namespace Neo.Plugins.MPT +{ + partial class MPTTrie + { + public TValue this[TKey key] + { + get + { + var path = ToNibbles(key.ToArray()); + if (path.Length == 0) return null; + var result = TryGet(ref root, path, out var value); + return result ? value.AsSerializable() : null; + } + } + + private bool TryGet(ref MPTNode node, ReadOnlySpan path, out ReadOnlySpan value) + { + switch (node) + { + case LeafNode leafNode: + { + if (path.IsEmpty) + { + value = leafNode.Value; + return true; + } + break; + } + case HashNode hashNode: + { + if (hashNode.IsEmpty) break; + var newNode = Resolve(hashNode); + if (newNode is null) break; + node = newNode; + return TryGet(ref node, path, out value); + } + case BranchNode branchNode: + { + if (path.IsEmpty) + { + return TryGet(ref branchNode.Children[BranchNode.ChildCount - 1], path, out value); + } + return TryGet(ref branchNode.Children[path[0]], path[1..], out value); + } + case ExtensionNode extensionNode: + { + if (path.StartsWith(extensionNode.Key)) + { + return TryGet(ref extensionNode.Next, path[extensionNode.Key.Length..], out value); + } + break; + } + } + value = default; + return false; + } + } +} diff --git a/src/StateService/MPT/MPTTrie.Proof.cs b/src/StateService/MPT/MPTTrie.Proof.cs new file mode 100644 index 000000000..f6e194bf8 --- /dev/null +++ b/src/StateService/MPT/MPTTrie.Proof.cs @@ -0,0 +1,73 @@ +using Neo.Cryptography; +using Neo.IO; +using Neo.Persistence; +using System; +using System.Collections.Generic; + +namespace Neo.Plugins.MPT +{ + partial class MPTTrie + { + public HashSet GetProof(TKey key) + { + var path = ToNibbles(key.ToArray()); + if (path.Length == 0) return null; + HashSet set = new HashSet(ByteArrayEqualityComparer.Default); + if (!GetProof(ref root, path, set)) return null; + return set; + } + + private bool GetProof(ref MPTNode node, ReadOnlySpan path, HashSet set) + { + switch (node) + { + case LeafNode leafNode: + { + if (path.IsEmpty) + { + set.Add(leafNode.Encode()); + return true; + } + break; + } + case HashNode hashNode: + { + if (hashNode.IsEmpty) break; + var newNode = Resolve(hashNode); + if (newNode is null) break; + node = newNode; + return GetProof(ref node, path, set); + } + case BranchNode branchNode: + { + set.Add(branchNode.Encode()); + if (path.IsEmpty) + { + return GetProof(ref branchNode.Children[BranchNode.ChildCount - 1], path, set); + } + return GetProof(ref branchNode.Children[path[0]], path[1..], set); + } + case ExtensionNode extensionNode: + { + if (path.StartsWith(extensionNode.Key)) + { + set.Add(extensionNode.Encode()); + return GetProof(ref extensionNode.Next, path[extensionNode.Key.Length..], set); + } + break; + } + } + return false; + } + + public static TValue VerifyProof(UInt256 root, TKey key, HashSet proof) + { + using var memoryStore = new MemoryStore(); + foreach (byte[] data in proof) + memoryStore.Put(Prefix, Crypto.Hash256(data), data); + using ISnapshot snapshot = memoryStore.GetSnapshot(); + var trie = new MPTTrie(snapshot, root); + return trie[key]; + } + } +} diff --git a/src/StateService/MPT/MPTTrie.Put.cs b/src/StateService/MPT/MPTTrie.Put.cs new file mode 100644 index 000000000..9ddaed16a --- /dev/null +++ b/src/StateService/MPT/MPTTrie.Put.cs @@ -0,0 +1,158 @@ +using Neo.IO; +using System; + +namespace Neo.Plugins.MPT +{ + partial class MPTTrie + { + private static ReadOnlySpan CommonPrefix(ReadOnlySpan a, ReadOnlySpan b) + { + var minLen = a.Length <= b.Length ? a.Length : b.Length; + int i = 0; + if (a.Length != 0 && b.Length != 0) + { + for (i = 0; i < minLen; i++) + { + if (a[i] != b[i]) break; + } + } + return a[..i]; + } + + public bool Put(TKey key, TValue value) + { + var path = ToNibbles(key.ToArray()); + var val = value.ToArray(); + if (path.Length == 0 || path.Length > ExtensionNode.MaxKeyLength) + return false; + if (val.Length > LeafNode.MaxValueLength) + return false; + if (val.Length == 0) + return TryDelete(ref root, path); + var n = new LeafNode(val); + return Put(ref root, path, n); + } + + private bool Put(ref MPTNode node, ReadOnlySpan path, MPTNode val) + { + switch (node) + { + case LeafNode leafNode: + { + if (val is LeafNode v) + { + if (path.IsEmpty) + { + node = v; + PutToStore(node); + return true; + } + var branch = new BranchNode(); + branch.Children[BranchNode.ChildCount - 1] = leafNode; + Put(ref branch.Children[path[0]], path[1..], v); + PutToStore(branch); + node = branch; + return true; + } + return false; + } + case ExtensionNode extensionNode: + { + if (path.StartsWith(extensionNode.Key)) + { + var result = Put(ref extensionNode.Next, path[extensionNode.Key.Length..], val); + if (result) + { + extensionNode.SetDirty(); + PutToStore(extensionNode); + } + return result; + } + var prefix = CommonPrefix(extensionNode.Key, path); + var pathRemain = path[prefix.Length..]; + var keyRemain = extensionNode.Key.AsSpan(prefix.Length); + var son = new BranchNode(); + MPTNode grandSon1 = HashNode.EmptyNode; + MPTNode grandSon2 = HashNode.EmptyNode; + + Put(ref grandSon1, keyRemain[1..], extensionNode.Next); + son.Children[keyRemain[0]] = grandSon1; + + if (pathRemain.IsEmpty) + { + Put(ref grandSon2, pathRemain, val); + son.Children[BranchNode.ChildCount - 1] = grandSon2; + } + else + { + Put(ref grandSon2, pathRemain[1..], val); + son.Children[pathRemain[0]] = grandSon2; + } + PutToStore(son); + if (prefix.Length > 0) + { + var exNode = new ExtensionNode() + { + Key = prefix.ToArray(), + Next = son, + }; + PutToStore(exNode); + node = exNode; + } + else + { + node = son; + } + return true; + } + case BranchNode branchNode: + { + bool result; + if (path.IsEmpty) + { + result = Put(ref branchNode.Children[BranchNode.ChildCount - 1], path, val); + } + else + { + result = Put(ref branchNode.Children[path[0]], path[1..], val); + } + if (result) + { + branchNode.SetDirty(); + PutToStore(branchNode); + } + return result; + } + case HashNode hashNode: + { + MPTNode newNode; + if (hashNode.IsEmpty) + { + if (path.IsEmpty) + { + newNode = val; + } + else + { + newNode = new ExtensionNode() + { + Key = path.ToArray(), + Next = val, + }; + PutToStore(newNode); + } + node = newNode; + if (val is LeafNode) PutToStore(val); + return true; + } + newNode = Resolve(hashNode); + if (newNode is null) return false; + node = newNode; + return Put(ref node, path, val); + } + default: + return false; + } + } + } +} diff --git a/src/StateService/MPT/MPTTrie.cs b/src/StateService/MPT/MPTTrie.cs new file mode 100644 index 000000000..c77bc746e --- /dev/null +++ b/src/StateService/MPT/MPTTrie.cs @@ -0,0 +1,58 @@ +using Neo.IO; +using Neo.Persistence; +using System; + +namespace Neo.Plugins.MPT +{ + public partial class MPTTrie + where TKey : notnull, ISerializable, new() + where TValue : class, ISerializable, new() + { + private const byte Prefix = 0xf0; + + private readonly ISnapshot store; + private MPTNode root; + + public MPTNode Root => root; + + public MPTTrie(ISnapshot store, UInt256 root) + { + this.store = store ?? throw new ArgumentNullException(); + this.root = root is null ? HashNode.EmptyNode : new HashNode(root); + } + + private MPTNode Resolve(HashNode n) + { + var data = store.TryGet(Prefix, n.Hash.ToArray()); + return MPTNode.Decode(data); + } + + private static byte[] ToNibbles(ReadOnlySpan path) + { + var result = new byte[path.Length * 2]; + for (int i = 0; i < path.Length; i++) + { + result[i * 2] = (byte)(path[i] >> 4); + result[i * 2 + 1] = (byte)(path[i] & 0x0F); + } + return result; + } + + private static byte[] FromNibbles(ReadOnlySpan path) + { + if (path.Length % 2 != 0) throw new FormatException($"MPTTrie.FromNibbles invalid path."); + var key = new byte[path.Length / 2]; + for (int i = 0; i < key.Length; i++) + { + key[i] = (byte)(path[i * 2] << 4); + key[i] |= path[i * 2 + 1]; + } + return key; + } + + private void PutToStore(MPTNode node) + { + store.Put(Prefix, node.Hash.ToArray(), node.Encode()); + } + } +} diff --git a/src/StateService/StateService.csproj b/src/StateService/StateService.csproj new file mode 100644 index 000000000..24ea71663 --- /dev/null +++ b/src/StateService/StateService.csproj @@ -0,0 +1,9 @@ + + + + Neo.Plugins.StateService + Neo.Plugins + true + + + diff --git a/tests/Neo.Plugins.StateService.Tests/MPT/UT_MPTNode.cs b/tests/Neo.Plugins.StateService.Tests/MPT/UT_MPTNode.cs new file mode 100644 index 000000000..a26325470 --- /dev/null +++ b/tests/Neo.Plugins.StateService.Tests/MPT/UT_MPTNode.cs @@ -0,0 +1,30 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.MPT; +using System.Text; + +namespace Neo.Plugins.StateService.Tests.MPT +{ + [TestClass] + public class UT_MPTNode + { + [TestMethod] + public void TestDecode() + { + var n = new LeafNode + { + Value = Encoding.ASCII.GetBytes("hello") + }; + var code = n.Encode(); + var m = MPTNode.Decode(code); + Assert.IsInstanceOfType(m, n.GetType()); + } + + [TestMethod] + public void TestHashNode() + { + var hn = new HashNode(null); + var data = hn.Encode(); + Assert.AreEqual("0200", data.ToHexString()); + } + } +} diff --git a/tests/Neo.Plugins.StateService.Tests/MPT/UT_MPTTrie.cs b/tests/Neo.Plugins.StateService.Tests/MPT/UT_MPTTrie.cs new file mode 100644 index 000000000..31ec30b5d --- /dev/null +++ b/tests/Neo.Plugins.StateService.Tests/MPT/UT_MPTTrie.cs @@ -0,0 +1,320 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.MPT; +using Neo.IO; +using Neo.Persistence; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Neo.Plugins.StateService.Tests.MPT +{ + public class TestKey : ISerializable + { + private byte[] key; + + public int Size => key.Length; + + public TestKey() + { + this.key = Array.Empty(); + } + + public TestKey(byte[] key) + { + this.key = key; + } + public void Serialize(BinaryWriter writer) + { + writer.Write(key); + } + + public void Deserialize(BinaryReader reader) + { + key = reader.ReadBytes((int)(reader.BaseStream.Length - reader.BaseStream.Position)); + } + + public override string ToString() + { + return key.ToHexString(); + } + + public static implicit operator TestKey(byte[] key) + { + return new TestKey(key); + } + } + + public class TestValue : ISerializable + { + private byte[] value; + + public int Size => value.Length; + + public TestValue() + { + this.value = Array.Empty(); + } + + public TestValue(byte[] value) + { + this.value = value; + } + + public void Serialize(BinaryWriter writer) + { + writer.Write(value); + } + + public void Deserialize(BinaryReader reader) + { + value = reader.ReadBytes((int)(reader.BaseStream.Length - reader.BaseStream.Position)); + } + + public override string ToString() + { + return value.ToHexString(); + } + + public static implicit operator TestValue(byte[] value) + { + return new TestValue(value); + } + } + + [TestClass] + public class UT_MPTTrie + { + private MPTNode root; + private IStore mptdb; + + private void PutToStore(MPTNode node) + { + mptdb.Put(0xf0, node.Hash.ToArray(), node.Encode()); + } + + [TestInitialize] + public void TestInit() + { + var b = new BranchNode(); + var r = new ExtensionNode { Key = "0a0c".HexToBytes(), Next = b }; + var v1 = new LeafNode { Value = "abcd".HexToBytes() }; + var v2 = new LeafNode { Value = "2222".HexToBytes() }; + var v3 = new LeafNode { Value = Encoding.ASCII.GetBytes("hello") }; + var h1 = new HashNode(v3.Hash); + var l1 = new ExtensionNode { Key = new byte[] { 0x01 }, Next = v1 }; + var l2 = new ExtensionNode { Key = new byte[] { 0x09 }, Next = v2 }; + var l3 = new ExtensionNode { Key = "0e".HexToBytes(), Next = h1 }; + b.Children[0] = l1; + b.Children[9] = l2; + b.Children[10] = l3; + this.root = r; + this.mptdb = new MemoryStore(); + PutToStore(r); + PutToStore(b); + PutToStore(l1); + PutToStore(l2); + PutToStore(l3); + PutToStore(v1); + PutToStore(v2); + PutToStore(v3); + } + + [TestMethod] + public void TestTryGet() + { + var mpt = new MPTTrie(mptdb.GetSnapshot(), root.Hash); + Assert.AreEqual("abcd", mpt["ac01".HexToBytes()].ToString()); + Assert.AreEqual("2222", mpt["ac99".HexToBytes()].ToString()); + Assert.IsNull(mpt["ab99".HexToBytes()]); + Assert.IsNull(mpt["ac39".HexToBytes()]); + Assert.IsNull(mpt["ac02".HexToBytes()]); + Assert.IsNull(mpt["ac9910".HexToBytes()]); + } + + [TestMethod] + public void TestTryGetResolve() + { + var mpt = new MPTTrie(mptdb.GetSnapshot(), root.Hash); + Assert.AreEqual(Encoding.ASCII.GetBytes("hello").ToHexString(), mpt["acae".HexToBytes()].ToString()); + } + + [TestMethod] + public void TestTryPut() + { + var store = new MemoryStore(); + var mpt = new MPTTrie(store.GetSnapshot(), null); + var result = mpt.Put("ac01".HexToBytes(), "abcd".HexToBytes()); + Assert.IsTrue(result); + result = mpt.Put("ac99".HexToBytes(), "2222".HexToBytes()); + Assert.IsTrue(result); + result = mpt.Put("acae".HexToBytes(), Encoding.ASCII.GetBytes("hello")); + Assert.IsTrue(result); + Assert.AreEqual(root.Hash.ToString(), mpt.Root.Hash.ToString()); + } + + [TestMethod] + public void TestTryDelete() + { + var b = new BranchNode(); + var r = new ExtensionNode { Key = "0a0c".HexToBytes(), Next = b }; + var v1 = new LeafNode { Value = "abcd".HexToBytes() }; + var v2 = new LeafNode { Value = "2222".HexToBytes() }; + var r1 = new ExtensionNode { Key = "0a0c0001".HexToBytes(), Next = v1 }; + var l1 = new ExtensionNode { Key = new byte[] { 0x01 }, Next = v1 }; + var l2 = new ExtensionNode { Key = new byte[] { 0x09 }, Next = v2 }; + b.Children[0] = l1; + b.Children[9] = l2; + + Assert.AreEqual("0xdea3ab46e9461e885ed7091c1e533e0a8030b248d39cbc638962394eaca0fbb3", r1.Hash.ToString()); + Assert.AreEqual("0x93e8e1ffe2f83dd92fca67330e273bcc811bf64b8f8d9d1b25d5e7366b47d60d", r.Hash.ToString()); + + var mpt = new MPTTrie(mptdb.GetSnapshot(), root.Hash); + Assert.IsNotNull(mpt["ac99".HexToBytes()]); + bool result = mpt.Delete("ac99".HexToBytes()); + Assert.IsTrue(result); + result = mpt.Delete("acae".HexToBytes()); + Assert.IsTrue(result); + Assert.AreEqual("0xdea3ab46e9461e885ed7091c1e533e0a8030b248d39cbc638962394eaca0fbb3", mpt.Root.Hash.ToString()); + } + + [TestMethod] + public void TestDeleteSameValue() + { + var store = new MemoryStore(); + var snapshot = store.GetSnapshot(); + var mpt = new MPTTrie(snapshot, null); + Assert.IsTrue(mpt.Put("ac01".HexToBytes(), "abcd".HexToBytes())); + Assert.IsTrue(mpt.Put("ac02".HexToBytes(), "abcd".HexToBytes())); + Assert.IsNotNull(mpt["ac01".HexToBytes()]); + Assert.IsNotNull(mpt["ac02".HexToBytes()]); + mpt.Delete("ac01".HexToBytes()); + Assert.IsNotNull(mpt["ac02".HexToBytes()]); + snapshot.Commit(); + + var mpt0 = new MPTTrie(store.GetSnapshot(), mpt.Root.Hash); + Assert.IsNotNull(mpt0["ac02".HexToBytes()]); + } + + [TestMethod] + public void TestBranchNodeRemainValue() + { + var store = new MemoryStore(); + var mpt = new MPTTrie(store.GetSnapshot(), null); + Assert.IsTrue(mpt.Put("ac11".HexToBytes(), "ac11".HexToBytes())); + Assert.IsTrue(mpt.Put("ac22".HexToBytes(), "ac22".HexToBytes())); + Assert.IsTrue(mpt.Put("ac".HexToBytes(), "ac".HexToBytes())); + Assert.IsTrue(mpt.Delete("ac11".HexToBytes())); + mpt.Delete("ac22".HexToBytes()); + Assert.IsNotNull(mpt["ac".HexToBytes()]); + } + + [TestMethod] + public void TestGetProof() + { + var b = new BranchNode(); + var r = new ExtensionNode { Key = "0a0c".HexToBytes(), Next = b }; + var v1 = new LeafNode { Value = "abcd".HexToBytes() }; + var v2 = new LeafNode { Value = "2222".HexToBytes() }; + var v3 = new LeafNode { Value = Encoding.ASCII.GetBytes("hello") }; + var h1 = new HashNode(v3.Hash); + var l1 = new ExtensionNode { Key = new byte[] { 0x01 }, Next = v1 }; + var l2 = new ExtensionNode { Key = new byte[] { 0x09 }, Next = v2 }; + var l3 = new ExtensionNode { Key = "0e".HexToBytes(), Next = h1 }; + b.Children[0] = l1; + b.Children[9] = l2; + b.Children[10] = l3; + + var mpt = new MPTTrie(mptdb.GetSnapshot(), root.Hash); + Assert.AreEqual(r.Hash.ToString(), mpt.Root.Hash.ToString()); + HashSet proof = mpt.GetProof("ac01".HexToBytes()); + Assert.AreEqual(4, proof.Count); + Assert.IsTrue(proof.Contains(b.Encode())); + Assert.IsTrue(proof.Contains(r.Encode())); + Assert.IsTrue(proof.Contains(l1.Encode())); + Assert.IsTrue(proof.Contains(v1.Encode())); + + proof = mpt.GetProof(Array.Empty()); + Assert.IsNull(proof); + } + + [TestMethod] + public void TestVerifyProof() + { + var mpt = new MPTTrie(mptdb.GetSnapshot(), root.Hash); + HashSet proof = mpt.GetProof("ac01".HexToBytes()); + TestValue value = MPTTrie.VerifyProof(root.Hash, "ac01".HexToBytes(), proof); + Assert.IsNotNull(value); + Assert.AreEqual(value.ToString(), "abcd"); + } + + [TestMethod] + public void TestAddLongerKey() + { + var store = new MemoryStore(); + var snapshot = store.GetSnapshot(); + var mpt = new MPTTrie(snapshot, null); + var result = mpt.Put(new byte[] { 0xab }, new byte[] { 0x01 }); + Assert.IsTrue(result); + result = mpt.Put(new byte[] { 0xab, 0xcd }, new byte[] { 0x02 }); + Assert.IsTrue(result); + } + + [TestMethod] + public void TestSplitKey() + { + var store = new MemoryStore(); + var snapshot = store.GetSnapshot(); + var mpt1 = new MPTTrie(snapshot, null); + Assert.IsTrue(mpt1.Put(new byte[] { 0xab, 0xcd }, new byte[] { 0x01 })); + Assert.IsTrue(mpt1.Put(new byte[] { 0xab }, new byte[] { 0x02 })); + HashSet set1 = mpt1.GetProof(new byte[] { 0xab, 0xcd }); + Assert.AreEqual(4, set1.Count); + var mpt2 = new MPTTrie(snapshot, null); + Assert.IsTrue(mpt2.Put(new byte[] { 0xab }, new byte[] { 0x02 })); + Assert.IsTrue(mpt2.Put(new byte[] { 0xab, 0xcd }, new byte[] { 0x01 })); + HashSet set2 = mpt2.GetProof(new byte[] { 0xab, 0xcd }); + Assert.AreEqual(4, set2.Count); + Assert.AreEqual(mpt1.Root.Hash, mpt2.Root.Hash); + } + + [TestMethod] + public void TestFind() + { + var store = new MemoryStore(); + var snapshot = store.GetSnapshot(); + var mpt1 = new MPTTrie(snapshot, null); + var results = mpt1.Find(ReadOnlySpan.Empty).ToArray(); + Assert.AreEqual(0, results.Count()); + var mpt2 = new MPTTrie(snapshot, null); + Assert.IsTrue(mpt2.Put(new byte[] { 0xab, 0xcd, 0xef }, new byte[] { 0x01 })); + Assert.IsTrue(mpt2.Put(new byte[] { 0xab, 0xcd, 0xe1 }, new byte[] { 0x02 })); + Assert.IsTrue(mpt2.Put(new byte[] { 0xab }, new byte[] { 0x03 })); + results = mpt2.Find(ReadOnlySpan.Empty).ToArray(); + Assert.AreEqual(3, results.Count()); + results = mpt2.Find(new byte[] { 0xab }).ToArray(); + Assert.AreEqual(3, results.Count()); + results = mpt2.Find(new byte[] { 0xab, 0xcd }).ToArray(); + Assert.AreEqual(2, results.Count()); + results = mpt2.Find(new byte[] { 0xac }).ToArray(); + Assert.AreEqual(0, results.Count()); + } + + [TestMethod] + public void TestFindLeadNode() + { + // r.Key = 0x0a0c + // b.Key = 0x00 + // l1.Key = 0x01 + var mpt = new MPTTrie(mptdb.GetSnapshot(), root.Hash); + var prefix = new byte[] { 0xac, 0x01 }; // = FromNibbles(path = { 0x0a, 0x0c, 0x00, 0x01 }); + var results = mpt.Find(prefix).ToArray(); + Assert.AreEqual(1, results.Count()); + + prefix = new byte[] { 0xac }; // = FromNibbles(path = { 0x0a, 0x0c }); + results = mpt.Find(prefix).ToArray(); + Assert.AreEqual(3, results.Count()); + } + } +} diff --git a/tests/Neo.Plugins.StateService.Tests/Neo.Plugins.StateService.Tests.csproj b/tests/Neo.Plugins.StateService.Tests/Neo.Plugins.StateService.Tests.csproj new file mode 100644 index 000000000..8733ce647 --- /dev/null +++ b/tests/Neo.Plugins.StateService.Tests/Neo.Plugins.StateService.Tests.csproj @@ -0,0 +1,16 @@ + + + + netcoreapp3.0 + Neo.Plugins.StateService.Tests + false + + + + + + + + + + From f4d9b2387f84ff7d9d1cbbacf34e7581cf495682 Mon Sep 17 00:00:00 2001 From: Owen Zhang <38493437+superboyiii@users.noreply.github.com> Date: Fri, 27 Nov 2020 17:36:27 +0800 Subject: [PATCH 3/5] Unify GetUnclaimedGas and GetWalletUnclaimedGas with decimal (#413) * Unify GetUnclaimedGas and GetWalletUnclaimedGas with decimal * fix --- src/RpcClient/Models/RpcUnclaimedGas.cs | 6 +++--- src/RpcClient/RpcClient.cs | 4 ++-- src/RpcServer/RpcServer.SmartContract.cs | 2 +- src/RpcServer/RpcServer.Wallet.cs | 2 +- tests/Neo.Network.RPC.Tests/RpcTestCases.json | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/RpcClient/Models/RpcUnclaimedGas.cs b/src/RpcClient/Models/RpcUnclaimedGas.cs index 93e2c72fc..4ec14f5d7 100644 --- a/src/RpcClient/Models/RpcUnclaimedGas.cs +++ b/src/RpcClient/Models/RpcUnclaimedGas.cs @@ -1,11 +1,11 @@ using Neo.IO.Json; -using System.Numerics; +using Neo.SmartContract.Native; namespace Neo.Network.RPC.Models { public class RpcUnclaimedGas { - public BigInteger Unclaimed { get; set; } + public BigDecimal Unclaimed { get; set; } public string Address { get; set; } @@ -21,7 +21,7 @@ public static RpcUnclaimedGas FromJson(JObject json) { return new RpcUnclaimedGas { - Unclaimed = BigInteger.Parse(json["unclaimed"].AsString()), + Unclaimed = BigDecimal.Parse(json["unclaimed"].AsString(), NativeContract.GAS.Decimals), Address = json["address"].AsString() }; } diff --git a/src/RpcClient/RpcClient.cs b/src/RpcClient/RpcClient.cs index b5d2cf430..431e08988 100644 --- a/src/RpcClient/RpcClient.cs +++ b/src/RpcClient/RpcClient.cs @@ -460,10 +460,10 @@ public async Task GetWalletBalanceAsync(string assetId) /// /// Gets the amount of unclaimed GAS in the wallet. /// - public async Task GetWalletUnclaimedGasAsync() + public async Task GetWalletUnclaimedGasAsync() { var result = await RpcSendAsync(GetRpcName()).ConfigureAwait(false); - return BigInteger.Parse(result.AsString()); + return BigDecimal.Parse(result.AsString(), SmartContract.Native.NativeContract.GAS.Decimals); } /// diff --git a/src/RpcServer/RpcServer.SmartContract.cs b/src/RpcServer/RpcServer.SmartContract.cs index a48ede69a..99dff93bf 100644 --- a/src/RpcServer/RpcServer.SmartContract.cs +++ b/src/RpcServer/RpcServer.SmartContract.cs @@ -148,7 +148,7 @@ protected virtual JObject GetUnclaimedGas(JArray _params) if (script_hash == null) throw new RpcException(-100, "Invalid address"); SnapshotView snapshot = Blockchain.Singleton.GetSnapshot(); - json["unclaimed"] = NativeContract.NEO.UnclaimedGas(snapshot, script_hash, snapshot.Height + 1).ToString(); + json["unclaimed"] = new BigDecimal(NativeContract.NEO.UnclaimedGas(snapshot, script_hash, snapshot.Height + 1), NativeContract.GAS.Decimals).ToString(); json["address"] = script_hash.ToAddress(); return json; } diff --git a/src/RpcServer/RpcServer.Wallet.cs b/src/RpcServer/RpcServer.Wallet.cs index 214df1bee..6ec21fb56 100644 --- a/src/RpcServer/RpcServer.Wallet.cs +++ b/src/RpcServer/RpcServer.Wallet.cs @@ -94,7 +94,7 @@ protected virtual JObject GetWalletUnclaimedGas(JArray _params) { gas += NativeContract.NEO.UnclaimedGas(snapshot, account, snapshot.Height + 1); } - return gas.ToString(); + return new BigDecimal(gas, NativeContract.GAS.Decimals).ToString(); } [RpcMethod] diff --git a/tests/Neo.Network.RPC.Tests/RpcTestCases.json b/tests/Neo.Network.RPC.Tests/RpcTestCases.json index 8833fed6b..7b4c1c33d 100644 --- a/tests/Neo.Network.RPC.Tests/RpcTestCases.json +++ b/tests/Neo.Network.RPC.Tests/RpcTestCases.json @@ -834,7 +834,7 @@ "jsonrpc": "2.0", "id": 1, "result": { - "unclaimed": "735870007400", + "unclaimed": "7358.700074", "address": "NPvKVTGZapmFWABLsyvfreuqn73jCjJtN1" } } @@ -994,7 +994,7 @@ "Response": { "jsonrpc": "2.0", "id": 1, - "result": "735870007400" + "result": "7358.700074" } }, { From 32f4eecd6c0c5e287f13e708a1735f3eedada571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=BF=97=E5=90=8C?= Date: Wed, 2 Dec 2020 02:40:09 -0600 Subject: [PATCH 4/5] Fixed UT (Neo CI01089) (#416) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [RpcServer] Querying contracts by ID/name (#378) * fixed-bug-1021 * Update src/RpcServer/RpcServer.SmartContract.cs * 😂 * draft * draft * update * fixed bug with decimal of GAS consumed in invokefunction/invokescript * remove modify of invokescript * Querying contracts by ID/name, server side * update * Enable using native.name for search Enable using native.name for search * Using keyword instead of addressOrScriptHash * revert * _initialize * split * update * Update exception message in ApplicationLog * Update src/ApplicationLogs/LogReader.cs Co-authored-by: Luchuan * update * a * More fix * fix * Fixed UT * Simplify Code * Simplify Code 2 * Update RpcServer * update Co-authored-by: Shargon Co-authored-by: Owen Zhang <38493437+superboyiii@users.noreply.github.com> Co-authored-by: superboyiii <573504781@qq.com> Co-authored-by: Vitor Nazário Coelho Co-authored-by: Luchuan * MPT in StateService from core (#410) * Unify GetUnclaimedGas and GetWalletUnclaimedGas with decimal (#413) * Unify GetUnclaimedGas and GetWalletUnclaimedGas with decimal * fix * Fixed UT (Neo CI01089) Co-authored-by: Shargon Co-authored-by: Owen Zhang <38493437+superboyiii@users.noreply.github.com> Co-authored-by: superboyiii <573504781@qq.com> Co-authored-by: Vitor Nazário Coelho Co-authored-by: Luchuan Co-authored-by: ZhangTao --- src/Directory.Build.props | 2 +- src/RpcClient/Models/RpcContractState.cs | 2 + src/RpcClient/RpcClient.cs | 1 + tests/Neo.Network.RPC.Tests/RpcTestCases.json | 117 ++++++++---------- .../UT_ContractClient.cs | 2 - 5 files changed, 54 insertions(+), 70 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 44d252790..ab7337427 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -15,7 +15,7 @@ - + \ No newline at end of file diff --git a/src/RpcClient/Models/RpcContractState.cs b/src/RpcClient/Models/RpcContractState.cs index 041cd97f8..802710d0d 100644 --- a/src/RpcClient/Models/RpcContractState.cs +++ b/src/RpcClient/Models/RpcContractState.cs @@ -1,3 +1,4 @@ +using Neo; using Neo.IO.Json; using Neo.Ledger; using Neo.SmartContract.Manifest; @@ -19,6 +20,7 @@ public static RpcContractState FromJson(JObject json) ContractState = new ContractState { Id = (int)json["id"].AsNumber(), + Hash = UInt160.Parse(json["hash"].AsString()), Script = Convert.FromBase64String(json["script"].AsString()), Manifest = ContractManifest.FromJson(json["manifest"]) } diff --git a/src/RpcClient/RpcClient.cs b/src/RpcClient/RpcClient.cs index 431e08988..b9c184137 100644 --- a/src/RpcClient/RpcClient.cs +++ b/src/RpcClient/RpcClient.cs @@ -201,6 +201,7 @@ public static ContractState ContractStateFromJson(JObject json) return new ContractState { Id = (int)json["id"].AsNumber(), + Hash = UInt160.Parse(json["hash"].AsString()), Script = Convert.FromBase64String(json["script"].AsString()), Manifest = ContractManifest.FromJson(json["manifest"]) }; diff --git a/tests/Neo.Network.RPC.Tests/RpcTestCases.json b/tests/Neo.Network.RPC.Tests/RpcTestCases.json index 7b4c1c33d..eae9639f9 100644 --- a/tests/Neo.Network.RPC.Tests/RpcTestCases.json +++ b/tests/Neo.Network.RPC.Tests/RpcTestCases.json @@ -318,109 +318,87 @@ "Request": { "jsonrpc": "2.0", "method": "getcontractstate", - "params": [ "0x806b7fa0db3b46d6c42e1e1b0a7fd50db9d4a9b0" ], + "params": [ "0x36a019d836d964c438c573f78badf79b9e7eebdd" ], "id": 1 }, "Response": { "jsonrpc": "2.0", "id": 1, "result": { - "id": 0, - "hash": "0x806b7fa0db3b46d6c42e1e1b0a7fd50db9d4a9b0", - "script": "VgIMFGklqlVHEkOanGE7oRTvo/rCPdvKYAwDKiwKYVcDAiFB6X04oAwBINshmlCaULQlIwAAACEMFGklqlVHEkOanGE7oRTvo/rCPdvKIUH4J+yMQCFB6X04oAwBQNshmlCaULQlKQEAAHgMCWJhbGFuY2VPZpcnDwAAAHkQziE1WgEAAEB4DAhkZWNpbWFsc5cnDAAAACE1sQEAAEB4DAZkZXBsb3mXJwwAAAAhNbQBAABAeAwEbmFtZZcnDAAAACE1OQIAAEB4DAZzeW1ib2yXJwwAAAAhNTsCAABAeAwSc3VwcG9ydGVkU3RhbmRhcmRzlycMAAAAITUfAgAAQHgMC3RvdGFsU3VwcGx5lycMAAAAITUmAgAAQHgMCHRyYW5zZmVylyclAAAAecoTsyUHAAAAEEB5EM55Ec5weRLOcWhpIVM1EgIAAEB4DAdkZXN0cm95lycMAAAAITUzAAAAQHgMB21pZ3JhdGWXJyAAAAB5yhKzJQcAAAAQQHkQznkRznJqIVA1EAAAAEAQQCFBxp8d8BFAVwACIQwUaSWqVUcSQ5qcYTuhFO+j+sI928ohQfgn7IwlBwAAABBAeMonDAAAAHnKJQcAAAAQQHh5IVBBMcYzHRFAVwEBeMoMARTbIbMlPAAAAAwyVGhlIHBhcmFtZXRlciBhY2NvdW50IFNIT1VMRCBiZSAyMC1ieXRlIGFkZHJlc3Nlcy4hRTohQZv2Z854IVBBkl3oMRBwJQcAAABoQCFBm/ZnznghUEGSXegx2yFAGEBXAQF4IUGpxUtBcGgnCQAAAGgSzkARQCFBm/ZnzgwLdG90YWxTdXBwbHkhUEGSXegxRSFBm/ZnziEMFGklqlVHEkOanGE7oRTvo/rCPdvKIQwDKiwKIVNB5j8YhCFBm/ZnzgwLdG90YWxTdXBwbHkhDAMqLAohU0HmPxiEIQshDBRpJapVRxJDmpxhO6EU76P6wj3byiEMAyosCiFTDAhUcmFuc2ZlchTAQZUBb2ERQAwVTmVwNSBOZW8zIFRlbXBsYXRlIDEgQAwDTk5EQBPDShAMBU5FUC010EoRDAVORVAtN9BKEgwGTkVQLTEw0EAhQZv2Z84MC3RvdGFsU3VwcGx5IVBBkl3oMdshQFcCA3oQticHAAAAEEB4IUH4J+yMJQcAAAAQQHnKDAEU2yGzJQcAAAAQQCFBm/ZnznghUEGSXegx2yFwaHq1JwcAAAAQQHh5mlCaULQlBwAAABFAaHqzJxgAAAAhQZv2Z854IVBBL1jF7SMWAAAAIUGb9mfOeGh6nyFTQeY/GIQhQZv2Z855IVBBkl3oMXFpJ0QAAAAMB2lmIHBhc3MhQc/nR5YhQZv2Z855adshep4hU0HmPxiEDBBTdG9yYWdlLnB1dCBwYXNzIUHP50eWIzsAAAAMCWVsc2UgcGFzcyFBz+dHliFBm/Znznl6IVNB5j8YhAwOYW1vdW50IC0+IHBhc3MhQc/nR5YheHl6IVMMCFRyYW5zZmVyFMBBlQFvYRFA", + "id": -2, + "updatecounter": 0, + "hash": "0x36a019d836d964c438c573f78badf79b9e7eebdd", + "script": "DANHQVNBa2d4Cw==", "manifest": { + "name": "GAS", "groups": [], - "features": { - "storage": true, - "payable": true - }, "supportedstandards": [ - "NEP-5" + "NEP-17" ], "abi": { - "hash": "0x806b7fa0db3b46d6c42e1e1b0a7fd50db9d4a9b0", "methods": [ { - "name": "main", - "parameters": [ - { - "name": "method", - "type": "String" - }, - { - "name": "args", - "type": "Array" - } - ], + "name": "onPersist", + "parameters": [], "offset": 0, - "returntype": "ByteArray" + "returntype": "Void" }, { - "name": "Destroy", + "name": "totalSupply", "parameters": [], - "offset": 400, - "returntype": "Boolean" + "offset": 0, + "returntype": "Integer" }, { - "name": "Migrate", + "name": "balanceOf", "parameters": [ { - "name": "script", + "name": "account", "type": "ByteArray" - }, - { - "name": "manifest", - "type": "String" } ], - "offset": 408, - "returntype": "Boolean" + "offset": 0, + "returntype": "Integer" }, { - "name": "BalanceOf", + "name": "transfer", "parameters": [ { - "name": "account", + "name": "from", "type": "ByteArray" + }, + { + "name": "to", + "type": "ByteArray" + }, + { + "name": "amount", + "type": "Integer" + }, + { + "name": "data", + "type": "Any" } ], - "offset": 474, - "returntype": "Integer" - }, - { - "name": "Decimals", - "parameters": [], - "offset": 585, - "returntype": "Integer" - }, - { - "name": "Deploy", - "parameters": [], - "offset": 610, + "offset": 0, "returntype": "Boolean" }, { - "name": "Name", + "name": "postPersist", "parameters": [], - "offset": 763, - "returntype": "String" + "offset": 0, + "returntype": "Void" }, { - "name": "Symbol", + "name": "symbol", "parameters": [], - "offset": 787, + "offset": 0, "returntype": "String" }, { - "name": "SupportedStandards", + "name": "decimals", "parameters": [], - "offset": 793, - "returntype": "Array" - }, - { - "name": "TotalSupply", - "parameters": [], - "offset": 827, + "offset": 0, "returntype": "Integer" } ], @@ -429,15 +407,15 @@ "name": "Transfer", "parameters": [ { - "name": "arg1", - "type": "ByteArray" + "name": "from", + "type": "Hash160" }, { - "name": "arg2", - "type": "ByteArray" + "name": "to", + "type": "Hash160" }, { - "name": "arg3", + "name": "amount", "type": "Integer" } ] @@ -451,7 +429,12 @@ } ], "trusts": [], - "safemethods": [], + "safemethods": [ + "totalSupply", + "balanceOf", + "symbol", + "decimals" + ], "extra": null } } diff --git a/tests/Neo.Network.RPC.Tests/UT_ContractClient.cs b/tests/Neo.Network.RPC.Tests/UT_ContractClient.cs index 2a120fad4..a442fdd74 100644 --- a/tests/Neo.Network.RPC.Tests/UT_ContractClient.cs +++ b/tests/Neo.Network.RPC.Tests/UT_ContractClient.cs @@ -45,7 +45,6 @@ public async Task TestDeployContract() Permissions = new[] { ContractPermission.DefaultPermission }, Abi = new ContractAbi() { - Hash = new byte[1].ToScriptHash(), Events = new ContractEventDescriptor[0], Methods = new ContractMethodDescriptor[0] }, @@ -55,7 +54,6 @@ public async Task TestDeployContract() SupportedStandards = new string[] { "NEP-10" }, Extra = null, }; - manifest.Features = ContractFeatures.HasStorage | ContractFeatures.Payable; using (ScriptBuilder sb = new ScriptBuilder()) { sb.EmitSysCall(ApplicationEngine.System_Contract_Create, new byte[1], manifest.ToString()); From c995e65164ae10a303ec7515af5a0e93dc493be2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=BF=97=E5=90=8C?= Date: Mon, 7 Dec 2020 02:57:14 -0600 Subject: [PATCH 5/5] Nep17 (#412) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [RpcServer] Querying contracts by ID/name (#378) * fixed-bug-1021 * Update src/RpcServer/RpcServer.SmartContract.cs * 😂 * draft * draft * update * fixed bug with decimal of GAS consumed in invokefunction/invokescript * remove modify of invokescript * Querying contracts by ID/name, server side * update * Enable using native.name for search Enable using native.name for search * Using keyword instead of addressOrScriptHash * revert * _initialize * split * update * Update exception message in ApplicationLog * Update src/ApplicationLogs/LogReader.cs Co-authored-by: Luchuan * update * a * More fix * fix * Fixed UT * Simplify Code * Simplify Code 2 * Update RpcServer * update Co-authored-by: Shargon Co-authored-by: Owen Zhang <38493437+superboyiii@users.noreply.github.com> Co-authored-by: superboyiii <573504781@qq.com> Co-authored-by: Vitor Nazário Coelho Co-authored-by: Luchuan * String Substitution * Modify filename and name methods * update * update * Update src/RpcClient/Models/RpcNep5TokenInfo.cs Co-authored-by: Vitor Nazário Coelho * Update src/RpcClient/Nep17API.cs Co-authored-by: Vitor Nazário Coelho * MPT in StateService from core (#410) * Unify GetUnclaimedGas and GetWalletUnclaimedGas with decimal (#413) * Unify GetUnclaimedGas and GetWalletUnclaimedGas with decimal * fix * Fixed UT (Neo CI01089) * Update Nep17API.cs * Update README.md Co-authored-by: Owen Zhang <38493437+superboyiii@users.noreply.github.com> * update filename * Fix UT * Format * fix * update * update UT * Add Unit Tests * update Co-authored-by: Shargon Co-authored-by: Owen Zhang <38493437+superboyiii@users.noreply.github.com> Co-authored-by: superboyiii <573504781@qq.com> Co-authored-by: Vitor Nazário Coelho Co-authored-by: Luchuan Co-authored-by: ZhangTao --- README.md | 6 +- neo-modules.sln | 2 +- src/Directory.Build.props | 2 +- ...RpcNep5Balances.cs => RpcNep17Balances.cs} | 18 ++--- ...cNep5TokenInfo.cs => RpcNep17TokenInfo.cs} | 2 +- ...cNep5Transfers.cs => RpcNep17Transfers.cs} | 20 ++--- src/RpcClient/{Nep5API.cs => Nep17API.cs} | 57 ++++++------- src/RpcClient/RpcClient.cs | 20 ++--- src/RpcClient/TransactionManager.cs | 2 +- src/RpcClient/WalletAPI.cs | 34 ++++---- .../DbCache.cs | 0 .../Helper.cs | 0 .../Nep17Balance.cs} | 8 +- .../Nep17BalanceKey.cs} | 12 +-- .../Nep17Transfer.cs} | 8 +- .../Nep17TransferKey.cs} | 12 +-- .../RpcNep17Tracker.cs} | 80 +++++++++---------- .../RpcNep17Tracker.csproj} | 4 +- .../RpcNep17Tracker}/config.json | 4 +- tests/Neo.Network.RPC.Tests/RpcTestCases.json | 14 ++-- .../{UT_Nep5API.cs => UT_Nep17API.cs} | 56 ++++++------- tests/Neo.Network.RPC.Tests/UT_RpcClient.cs | 16 ++-- tests/Neo.Network.RPC.Tests/UT_RpcModels.cs | 12 +-- tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs | 16 ++++ 24 files changed, 208 insertions(+), 197 deletions(-) rename src/RpcClient/Models/{RpcNep5Balances.cs => RpcNep17Balances.cs} (75%) rename src/RpcClient/Models/{RpcNep5TokenInfo.cs => RpcNep17TokenInfo.cs} (87%) rename src/RpcClient/Models/{RpcNep5Transfers.cs => RpcNep17Transfers.cs} (81%) rename src/RpcClient/{Nep5API.cs => Nep17API.cs} (76%) rename src/{RpcNep5Tracker => RpcNep17Tracker}/DbCache.cs (100%) rename src/{RpcNep5Tracker => RpcNep17Tracker}/Helper.cs (100%) rename src/{RpcNep5Tracker/Nep5Balance.cs => RpcNep17Tracker/Nep17Balance.cs} (80%) rename src/{RpcNep5Tracker/Nep5BalanceKey.cs => RpcNep17Tracker/Nep17BalanceKey.cs} (80%) rename src/{RpcNep5Tracker/Nep5Transfer.cs => RpcNep17Tracker/Nep17Transfer.cs} (84%) rename src/{RpcNep5Tracker/Nep5TransferKey.cs => RpcNep17Tracker/Nep17TransferKey.cs} (87%) rename src/{RpcNep5Tracker/RpcNep5Tracker.cs => RpcNep17Tracker/RpcNep17Tracker.cs} (75%) rename src/{RpcNep5Tracker/RpcNep5Tracker.csproj => RpcNep17Tracker/RpcNep17Tracker.csproj} (82%) rename src/{RpcNep5Tracker/RpcNep5Tracker => RpcNep17Tracker/RpcNep17Tracker}/config.json (76%) rename tests/Neo.Network.RPC.Tests/{UT_Nep5API.cs => UT_Nep17API.cs} (67%) diff --git a/README.md b/README.md index 4712b5be2..95f0e3526 100644 --- a/README.md +++ b/README.md @@ -65,12 +65,12 @@ You can also use `RocksDBStore` in the NEO system by modifying the default stora ### RpcServer Plugin for hosting a RpcServer on the neo-node, being able to disable specific calls. -### RpcNep5Tracker -Plugin that enables NEP5 tracking using LevelDB. +### RpcNep17Tracker +Plugin that enables NEP17 tracking using LevelDB. This module works in conjunction with RpcServer, otherwise, just local storage (on leveldb) would be created. ## C# SDK ### RpcClient The RpcClient Project is an individual SDK that is used to interact with NEO blockchain through NEO RPC methods for development using. The main functions include RPC calling, Transaction making, Contract deployment & calling, and Asset transfering. -It needs a NEO node with the `RpcServer` plugin as a provider. And the provider needs more plugins like `RpcNep5Tracker` and `ApplicationLogs` if you want to call RPC methods supplied by the plugins. +It needs a NEO node with the `RpcServer` plugin as a provider. And the provider needs more plugins like `RpcNep17Tracker` and `ApplicationLogs` if you want to call RPC methods supplied by the plugins. diff --git a/neo-modules.sln b/neo-modules.sln index 24f303eb4..18f557324 100644 --- a/neo-modules.sln +++ b/neo-modules.sln @@ -10,7 +10,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationLogs", "src\Appl EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StatesDumper", "src\StatesDumper\StatesDumper.csproj", "{86531DB1-A231-46C4-823F-BE60972F7523}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RpcNep5Tracker", "src\RpcNep5Tracker\RpcNep5Tracker.csproj", "{BBE8AC15-12DF-4AF0-ABC1-F1557EB5DC8E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RpcNep17Tracker", "src\RpcNep17Tracker\RpcNep17Tracker.csproj", "{BBE8AC15-12DF-4AF0-ABC1-F1557EB5DC8E}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LevelDBStore", "src\LevelDBStore\LevelDBStore.csproj", "{C66214CD-0B97-4EA5-B7A2-164F54346F19}" EndProject diff --git a/src/Directory.Build.props b/src/Directory.Build.props index ab7337427..b7a7f73f8 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -15,7 +15,7 @@ - + \ No newline at end of file diff --git a/src/RpcClient/Models/RpcNep5Balances.cs b/src/RpcClient/Models/RpcNep17Balances.cs similarity index 75% rename from src/RpcClient/Models/RpcNep5Balances.cs rename to src/RpcClient/Models/RpcNep17Balances.cs index 4cdc1ecc7..dfd35b939 100644 --- a/src/RpcClient/Models/RpcNep5Balances.cs +++ b/src/RpcClient/Models/RpcNep17Balances.cs @@ -6,11 +6,11 @@ namespace Neo.Network.RPC.Models { - public class RpcNep5Balances + public class RpcNep17Balances { public UInt160 UserScriptHash { get; set; } - public List Balances { get; set; } + public List Balances { get; set; } public JObject ToJson() { @@ -20,18 +20,18 @@ public JObject ToJson() return json; } - public static RpcNep5Balances FromJson(JObject json) + public static RpcNep17Balances FromJson(JObject json) { - RpcNep5Balances nep5Balance = new RpcNep5Balances + RpcNep17Balances nep17Balance = new RpcNep17Balances { - Balances = ((JArray)json["balance"]).Select(p => RpcNep5Balance.FromJson(p)).ToList(), + Balances = ((JArray)json["balance"]).Select(p => RpcNep17Balance.FromJson(p)).ToList(), UserScriptHash = json["address"].ToScriptHash() }; - return nep5Balance; + return nep17Balance; } } - public class RpcNep5Balance + public class RpcNep17Balance { public UInt160 AssetHash { get; set; } @@ -48,9 +48,9 @@ public JObject ToJson() return json; } - public static RpcNep5Balance FromJson(JObject json) + public static RpcNep17Balance FromJson(JObject json) { - RpcNep5Balance balance = new RpcNep5Balance + RpcNep17Balance balance = new RpcNep17Balance { AssetHash = json["assethash"].ToScriptHash(), Amount = BigInteger.Parse(json["amount"].AsString()), diff --git a/src/RpcClient/Models/RpcNep5TokenInfo.cs b/src/RpcClient/Models/RpcNep17TokenInfo.cs similarity index 87% rename from src/RpcClient/Models/RpcNep5TokenInfo.cs rename to src/RpcClient/Models/RpcNep17TokenInfo.cs index cb609b8a9..d28873943 100644 --- a/src/RpcClient/Models/RpcNep5TokenInfo.cs +++ b/src/RpcClient/Models/RpcNep17TokenInfo.cs @@ -2,7 +2,7 @@ namespace Neo.Network.RPC.Models { - public class RpcNep5TokenInfo + public class RpcNep17TokenInfo { public string Name { get; set; } diff --git a/src/RpcClient/Models/RpcNep5Transfers.cs b/src/RpcClient/Models/RpcNep17Transfers.cs similarity index 81% rename from src/RpcClient/Models/RpcNep5Transfers.cs rename to src/RpcClient/Models/RpcNep17Transfers.cs index d23ca7a41..bc433ede0 100644 --- a/src/RpcClient/Models/RpcNep5Transfers.cs +++ b/src/RpcClient/Models/RpcNep17Transfers.cs @@ -7,13 +7,13 @@ namespace Neo.Network.RPC.Models { - public class RpcNep5Transfers + public class RpcNep17Transfers { public UInt160 UserScriptHash { get; set; } - public List Sent { get; set; } + public List Sent { get; set; } - public List Received { get; set; } + public List Received { get; set; } public JObject ToJson() { @@ -24,19 +24,19 @@ public JObject ToJson() return json; } - public static RpcNep5Transfers FromJson(JObject json) + public static RpcNep17Transfers FromJson(JObject json) { - RpcNep5Transfers transfers = new RpcNep5Transfers + RpcNep17Transfers transfers = new RpcNep17Transfers { - Sent = ((JArray)json["sent"]).Select(p => RpcNep5Transfer.FromJson(p)).ToList(), - Received = ((JArray)json["received"]).Select(p => RpcNep5Transfer.FromJson(p)).ToList(), + Sent = ((JArray)json["sent"]).Select(p => RpcNep17Transfer.FromJson(p)).ToList(), + Received = ((JArray)json["received"]).Select(p => RpcNep17Transfer.FromJson(p)).ToList(), UserScriptHash = json["address"].ToScriptHash() }; return transfers; } } - public class RpcNep5Transfer + public class RpcNep17Transfer { public ulong TimestampMS { get; set; } @@ -65,9 +65,9 @@ public JObject ToJson() return json; } - public static RpcNep5Transfer FromJson(JObject json) + public static RpcNep17Transfer FromJson(JObject json) { - return new RpcNep5Transfer + return new RpcNep17Transfer { TimestampMS = (ulong)json["timestamp"].AsNumber(), AssetHash = json["assethash"].ToScriptHash(), diff --git a/src/RpcClient/Nep5API.cs b/src/RpcClient/Nep17API.cs similarity index 76% rename from src/RpcClient/Nep5API.cs rename to src/RpcClient/Nep17API.cs index 59cadd67e..50650ade6 100644 --- a/src/RpcClient/Nep5API.cs +++ b/src/RpcClient/Nep17API.cs @@ -13,18 +13,18 @@ namespace Neo.Network.RPC { /// - /// Call NEP5 methods with RPC API + /// Call NEP17 methods with RPC API /// - public class Nep5API : ContractClient + public class Nep17API : ContractClient { /// - /// Nep5API Constructor + /// Nep17API Constructor /// /// the RPC client to call NEO RPC methods - public Nep5API(RpcClient rpcClient) : base(rpcClient) { } + public Nep17API(RpcClient rpcClient) : base(rpcClient) { } /// - /// Get balance of NEP5 token + /// Get balance of NEP17 token /// /// contract script hash /// account script hash @@ -37,18 +37,7 @@ public async Task BalanceOfAsync(UInt160 scriptHash, UInt160 account } /// - /// Get name of NEP5 token - /// - /// contract script hash - /// - public async Task NameAsync(UInt160 scriptHash) - { - var result = await TestInvokeAsync(scriptHash, "name").ConfigureAwait(false); - return result.Stack.Single().GetString(); - } - - /// - /// Get symbol of NEP5 token + /// Get symbol of NEP17 token /// /// contract script hash /// @@ -59,7 +48,7 @@ public async Task SymbolAsync(UInt160 scriptHash) } /// - /// Get decimals of NEP5 token + /// Get decimals of NEP17 token /// /// contract script hash /// @@ -70,7 +59,7 @@ public async Task DecimalsAsync(UInt160 scriptHash) } /// - /// Get total supply of NEP5 token + /// Get total supply of NEP17 token /// /// contract script hash /// @@ -85,40 +74,43 @@ public async Task TotalSupplyAsync(UInt160 scriptHash) /// /// contract script hash /// - public async Task GetTokenInfoAsync(UInt160 scriptHash) + public async Task GetTokenInfoAsync(UInt160 scriptHash) { byte[] script = Concat( - scriptHash.MakeScript("name"), scriptHash.MakeScript("symbol"), scriptHash.MakeScript("decimals"), scriptHash.MakeScript("totalSupply")); + var contractState = await rpcClient.GetContractStateAsync(scriptHash.ToString()).ConfigureAwait(false); + var name = contractState.Manifest.Name; + var result = await rpcClient.InvokeScriptAsync(script).ConfigureAwait(false); var stack = result.Stack; - return new RpcNep5TokenInfo + return new RpcNep17TokenInfo { - Name = stack[0].GetString(), - Symbol = stack[1].GetString(), - Decimals = (byte)stack[2].GetInteger(), - TotalSupply = stack[3].GetInteger() + Name = name, + Symbol = stack[0].GetString(), + Decimals = (byte)stack[1].GetInteger(), + TotalSupply = stack[2].GetInteger() }; } /// - /// Create NEP5 token transfer transaction + /// Create NEP17 token transfer transaction /// /// contract script hash /// from KeyPair /// to account script hash /// transfer amount + /// onPayment data /// - public async Task CreateTransferTxAsync(UInt160 scriptHash, KeyPair fromKey, UInt160 to, BigInteger amount) + public async Task CreateTransferTxAsync(UInt160 scriptHash, KeyPair fromKey, UInt160 to, BigInteger amount, object data = null) { var sender = Contract.CreateSignatureRedeemScript(fromKey.PublicKey).ToScriptHash(); Signer[] signers = new[] { new Signer { Scopes = WitnessScope.CalledByEntry, Account = sender } }; - byte[] script = scriptHash.MakeScript("transfer", sender, to, amount); + byte[] script = data is null ? scriptHash.MakeScript("transfer", sender, to, amount) : scriptHash.MakeScript("transfer", sender, to, amount, data); TransactionManagerFactory factory = new TransactionManagerFactory(rpcClient, magic); TransactionManager manager = await factory.MakeTransactionAsync(script, signers).ConfigureAwait(false); @@ -128,7 +120,7 @@ public async Task CreateTransferTxAsync(UInt160 scriptHash, KeyPair } /// - /// Create NEP5 token transfer transaction from multi-sig account + /// Create NEP17 token transfer transaction from multi-sig account /// /// contract script hash /// multi-sig min signature count @@ -136,15 +128,16 @@ public async Task CreateTransferTxAsync(UInt160 scriptHash, KeyPair /// sign keys /// to account /// transfer amount + /// onPayment data /// - public async Task CreateTransferTxAsync(UInt160 scriptHash, int m, ECPoint[] pubKeys, KeyPair[] fromKeys, UInt160 to, BigInteger amount) + public async Task CreateTransferTxAsync(UInt160 scriptHash, int m, ECPoint[] pubKeys, KeyPair[] fromKeys, UInt160 to, BigInteger amount, object data = null) { if (m > fromKeys.Length) throw new ArgumentException($"Need at least {m} KeyPairs for signing!"); var sender = Contract.CreateMultiSigContract(m, pubKeys).ScriptHash; Signer[] signers = new[] { new Signer { Scopes = WitnessScope.CalledByEntry, Account = sender } }; - byte[] script = scriptHash.MakeScript("transfer", sender, to, amount); + byte[] script = data is null ? scriptHash.MakeScript("transfer", sender, to, amount) : scriptHash.MakeScript("transfer", sender, to, amount, data); TransactionManagerFactory factory = new TransactionManagerFactory(rpcClient, magic); TransactionManager manager = await factory.MakeTransactionAsync(script, signers).ConfigureAwait(false); diff --git a/src/RpcClient/RpcClient.cs b/src/RpcClient/RpcClient.cs index b9c184137..8c0ea27c1 100644 --- a/src/RpcClient/RpcClient.cs +++ b/src/RpcClient/RpcClient.cs @@ -447,14 +447,14 @@ public async Task GetNewAddressAsync() /// /// Returns the balance of the corresponding asset in the wallet, based on the specified asset Id. - /// This method applies to assets that conform to NEP-5 standards. + /// This method applies to assets that conform to NEP-17 standards. /// /// new address as string public async Task GetWalletBalanceAsync(string assetId) { var result = await RpcSendAsync(GetRpcName(), assetId).ConfigureAwait(false); BigInteger balance = BigInteger.Parse(result["balance"].AsString()); - byte decimals = await new Nep5API(this).DecimalsAsync(UInt160.Parse(assetId.AsScriptHash())).ConfigureAwait(false); + byte decimals = await new Nep17API(this).DecimalsAsync(UInt160.Parse(assetId.AsScriptHash())).ConfigureAwait(false); return new BigDecimal(balance, decimals); } @@ -556,30 +556,30 @@ public async Task GetApplicationLogAsync(string txHash, Trigg } /// - /// Returns all the NEP-5 transaction information occurred in the specified address. - /// This method is provided by the plugin RpcNep5Tracker. + /// Returns all the NEP-17 transaction information occurred in the specified address. + /// This method is provided by the plugin RpcNep17Tracker. /// /// The address to query the transaction information. /// The start block Timestamp, default to seven days before UtcNow /// The end block Timestamp, default to UtcNow - public async Task GetNep5TransfersAsync(string address, ulong? startTimestamp = default, ulong? endTimestamp = default) + public async Task GetNep17TransfersAsync(string address, ulong? startTimestamp = default, ulong? endTimestamp = default) { startTimestamp ??= 0; endTimestamp ??= DateTime.UtcNow.ToTimestampMS(); var result = await RpcSendAsync(GetRpcName(), address.AsScriptHash(), startTimestamp, endTimestamp) .ConfigureAwait(false); - return RpcNep5Transfers.FromJson(result); + return RpcNep17Transfers.FromJson(result); } /// - /// Returns the balance of all NEP-5 assets in the specified address. - /// This method is provided by the plugin RpcNep5Tracker. + /// Returns the balance of all NEP-17 assets in the specified address. + /// This method is provided by the plugin RpcNep17Tracker. /// - public async Task GetNep5BalancesAsync(string address) + public async Task GetNep17BalancesAsync(string address) { var result = await RpcSendAsync(GetRpcName(), address.AsScriptHash()) .ConfigureAwait(false); - return RpcNep5Balances.FromJson(result); + return RpcNep17Balances.FromJson(result); } #endregion Plugins diff --git a/src/RpcClient/TransactionManager.cs b/src/RpcClient/TransactionManager.cs index 067fc2ff6..c301fea59 100644 --- a/src/RpcClient/TransactionManager.cs +++ b/src/RpcClient/TransactionManager.cs @@ -166,7 +166,7 @@ public async Task SignAsync() Tx.NetworkFee = await rpcClient.CalculateNetworkFeeAsync(Tx).ConfigureAwait(false); Tx.Witnesses = null; - var gasBalance = await new Nep5API(rpcClient).BalanceOfAsync(NativeContract.GAS.Hash, Tx.Sender).ConfigureAwait(false); + var gasBalance = await new Nep17API(rpcClient).BalanceOfAsync(NativeContract.GAS.Hash, Tx.Sender).ConfigureAwait(false); if (gasBalance < Tx.SystemFee + Tx.NetworkFee) throw new InvalidOperationException($"Insufficient GAS in address: {Tx.Sender.ToAddress()}"); diff --git a/src/RpcClient/WalletAPI.cs b/src/RpcClient/WalletAPI.cs index b4cef3c55..aba15cf91 100644 --- a/src/RpcClient/WalletAPI.cs +++ b/src/RpcClient/WalletAPI.cs @@ -19,7 +19,7 @@ namespace Neo.Network.RPC public class WalletAPI { private readonly RpcClient rpcClient; - private readonly Nep5API nep5API; + private readonly Nep17API nep17API; /// /// WalletAPI Constructor @@ -28,7 +28,7 @@ public class WalletAPI public WalletAPI(RpcClient rpc) { rpcClient = rpc; - nep5API = new Nep5API(rpc); + nep17API = new Nep17API(rpc); } /// @@ -52,7 +52,7 @@ public async Task GetUnclaimedGasAsync(UInt160 account) { UInt160 scriptHash = NativeContract.NEO.Hash; var blockCount = await rpcClient.GetBlockCountAsync().ConfigureAwait(false); - var result = await nep5API.TestInvokeAsync(scriptHash, "unclaimedGas", account, blockCount - 1).ConfigureAwait(false); + var result = await nep17API.TestInvokeAsync(scriptHash, "unclaimedGas", account, blockCount - 1).ConfigureAwait(false); BigInteger balance = result.Stack.Single().GetInteger(); return ((decimal)balance) / (long)NativeContract.GAS.Factor; } @@ -92,7 +92,7 @@ public Task GetTokenBalanceAsync(string tokenHash, string account) { UInt160 scriptHash = Utility.GetScriptHash(tokenHash); UInt160 accountHash = Utility.GetScriptHash(account); - return nep5API.BalanceOfAsync(scriptHash, accountHash); + return nep17API.BalanceOfAsync(scriptHash, accountHash); } /// @@ -117,49 +117,49 @@ public Task ClaimGasAsync(string key) public async Task ClaimGasAsync(KeyPair keyPair) { UInt160 toHash = Contract.CreateSignatureRedeemScript(keyPair.PublicKey).ToScriptHash(); - BigInteger balance = await nep5API.BalanceOfAsync(NativeContract.NEO.Hash, toHash).ConfigureAwait(false); - Transaction transaction = await nep5API.CreateTransferTxAsync(NativeContract.NEO.Hash, keyPair, toHash, balance).ConfigureAwait(false); + BigInteger balance = await nep17API.BalanceOfAsync(NativeContract.NEO.Hash, toHash).ConfigureAwait(false); + Transaction transaction = await nep17API.CreateTransferTxAsync(NativeContract.NEO.Hash, keyPair, toHash, balance).ConfigureAwait(false); await rpcClient.SendRawTransactionAsync(transaction).ConfigureAwait(false); return transaction; } /// - /// Transfer NEP5 token balance, with common data types + /// Transfer NEP17 token balance, with common data types /// - /// nep5 token script hash, Example: scripthash ("0xb0a31817c80ad5f87b6ed390ecb3f9d312f7ceb8") + /// nep17 token script hash, Example: scripthash ("0xb0a31817c80ad5f87b6ed390ecb3f9d312f7ceb8") /// wif or private key /// Example: WIF ("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p"), PrivateKey ("450d6c2a04b5b470339a745427bae6828400cf048400837d73c415063835e005") /// address or account script hash /// token amount /// - public async Task TransferAsync(string tokenHash, string fromKey, string toAddress, decimal amount) + public async Task TransferAsync(string tokenHash, string fromKey, string toAddress, decimal amount, object data = null) { UInt160 scriptHash = Utility.GetScriptHash(tokenHash); - var decimals = await nep5API.DecimalsAsync(scriptHash).ConfigureAwait(false); + var decimals = await nep17API.DecimalsAsync(scriptHash).ConfigureAwait(false); KeyPair from = Utility.GetKeyPair(fromKey); UInt160 to = Utility.GetScriptHash(toAddress); BigInteger amountInteger = amount.ToBigInteger(decimals); - return await TransferAsync(scriptHash, from, to, amountInteger).ConfigureAwait(false); + return await TransferAsync(scriptHash, from, to, amountInteger, data).ConfigureAwait(false); } /// - /// Transfer NEP5 token from single-sig account + /// Transfer NEP17 token from single-sig account /// /// contract script hash /// from KeyPair /// to account script hash /// transfer amount /// - public async Task TransferAsync(UInt160 scriptHash, KeyPair from, UInt160 to, BigInteger amountInteger) + public async Task TransferAsync(UInt160 scriptHash, KeyPair from, UInt160 to, BigInteger amountInteger, object data = null) { - Transaction transaction = await nep5API.CreateTransferTxAsync(scriptHash, from, to, amountInteger).ConfigureAwait(false); + Transaction transaction = await nep17API.CreateTransferTxAsync(scriptHash, from, to, amountInteger, data).ConfigureAwait(false); await rpcClient.SendRawTransactionAsync(transaction).ConfigureAwait(false); return transaction; } /// - /// Transfer NEP5 token from multi-sig account + /// Transfer NEP17 token from multi-sig account /// /// contract script hash /// multi-sig min signature count @@ -168,9 +168,9 @@ public async Task TransferAsync(UInt160 scriptHash, KeyPair from, U /// to account /// transfer amount /// - public async Task TransferAsync(UInt160 scriptHash, int m, ECPoint[] pubKeys, KeyPair[] keys, UInt160 to, BigInteger amountInteger) + public async Task TransferAsync(UInt160 scriptHash, int m, ECPoint[] pubKeys, KeyPair[] keys, UInt160 to, BigInteger amountInteger, object data = null) { - Transaction transaction = await nep5API.CreateTransferTxAsync(scriptHash, m, pubKeys, keys, to, amountInteger).ConfigureAwait(false); + Transaction transaction = await nep17API.CreateTransferTxAsync(scriptHash, m, pubKeys, keys, to, amountInteger, data).ConfigureAwait(false); await rpcClient.SendRawTransactionAsync(transaction).ConfigureAwait(false); return transaction; } diff --git a/src/RpcNep5Tracker/DbCache.cs b/src/RpcNep17Tracker/DbCache.cs similarity index 100% rename from src/RpcNep5Tracker/DbCache.cs rename to src/RpcNep17Tracker/DbCache.cs diff --git a/src/RpcNep5Tracker/Helper.cs b/src/RpcNep17Tracker/Helper.cs similarity index 100% rename from src/RpcNep5Tracker/Helper.cs rename to src/RpcNep17Tracker/Helper.cs diff --git a/src/RpcNep5Tracker/Nep5Balance.cs b/src/RpcNep17Tracker/Nep17Balance.cs similarity index 80% rename from src/RpcNep5Tracker/Nep5Balance.cs rename to src/RpcNep17Tracker/Nep17Balance.cs index c848879f5..9dc5e3d67 100644 --- a/src/RpcNep5Tracker/Nep5Balance.cs +++ b/src/RpcNep17Tracker/Nep17Balance.cs @@ -4,7 +4,7 @@ namespace Neo.Plugins { - public class Nep5Balance : ICloneable, ISerializable + public class Nep17Balance : ICloneable, ISerializable { public BigInteger Balance; public uint LastUpdatedBlock; @@ -23,16 +23,16 @@ void ISerializable.Deserialize(BinaryReader reader) LastUpdatedBlock = reader.ReadUInt32(); } - Nep5Balance ICloneable.Clone() + Nep17Balance ICloneable.Clone() { - return new Nep5Balance + return new Nep17Balance { Balance = Balance, LastUpdatedBlock = LastUpdatedBlock }; } - public void FromReplica(Nep5Balance replica) + public void FromReplica(Nep17Balance replica) { Balance = replica.Balance; LastUpdatedBlock = replica.LastUpdatedBlock; diff --git a/src/RpcNep5Tracker/Nep5BalanceKey.cs b/src/RpcNep17Tracker/Nep17BalanceKey.cs similarity index 80% rename from src/RpcNep5Tracker/Nep5BalanceKey.cs rename to src/RpcNep17Tracker/Nep17BalanceKey.cs index ec1fc218d..0d5e5267a 100644 --- a/src/RpcNep5Tracker/Nep5BalanceKey.cs +++ b/src/RpcNep17Tracker/Nep17BalanceKey.cs @@ -4,18 +4,18 @@ namespace Neo.Plugins { - public class Nep5BalanceKey : IComparable, IEquatable, ISerializable + public class Nep17BalanceKey : IComparable, IEquatable, ISerializable { public readonly UInt160 UserScriptHash; public readonly UInt160 AssetScriptHash; public int Size => 20 + 20; - public Nep5BalanceKey() : this(new UInt160(), new UInt160()) + public Nep17BalanceKey() : this(new UInt160(), new UInt160()) { } - public Nep5BalanceKey(UInt160 userScriptHash, UInt160 assetScriptHash) + public Nep17BalanceKey(UInt160 userScriptHash, UInt160 assetScriptHash) { if (userScriptHash == null || assetScriptHash == null) throw new ArgumentNullException(); @@ -23,7 +23,7 @@ public Nep5BalanceKey(UInt160 userScriptHash, UInt160 assetScriptHash) AssetScriptHash = assetScriptHash; } - public int CompareTo(Nep5BalanceKey other) + public int CompareTo(Nep17BalanceKey other) { if (other is null) return 1; if (ReferenceEquals(this, other)) return 0; @@ -32,7 +32,7 @@ public int CompareTo(Nep5BalanceKey other) return AssetScriptHash.CompareTo(other.AssetScriptHash); } - public bool Equals(Nep5BalanceKey other) + public bool Equals(Nep17BalanceKey other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; @@ -41,7 +41,7 @@ public bool Equals(Nep5BalanceKey other) public override bool Equals(Object other) { - return other is Nep5BalanceKey otherKey && Equals(otherKey); + return other is Nep17BalanceKey otherKey && Equals(otherKey); } public override int GetHashCode() diff --git a/src/RpcNep5Tracker/Nep5Transfer.cs b/src/RpcNep17Tracker/Nep17Transfer.cs similarity index 84% rename from src/RpcNep5Tracker/Nep5Transfer.cs rename to src/RpcNep17Tracker/Nep17Transfer.cs index 84c71f1d7..8a2f33561 100644 --- a/src/RpcNep5Tracker/Nep5Transfer.cs +++ b/src/RpcNep17Tracker/Nep17Transfer.cs @@ -4,7 +4,7 @@ namespace Neo.Plugins { - public class Nep5Transfer : ICloneable, ISerializable + public class Nep17Transfer : ICloneable, ISerializable { public UInt160 UserScriptHash; public uint BlockIndex; @@ -29,9 +29,9 @@ void ISerializable.Deserialize(BinaryReader reader) Amount = new BigInteger(reader.ReadVarBytes(512)); } - Nep5Transfer ICloneable.Clone() + Nep17Transfer ICloneable.Clone() { - return new Nep5Transfer + return new Nep17Transfer { UserScriptHash = UserScriptHash, BlockIndex = BlockIndex, @@ -40,7 +40,7 @@ Nep5Transfer ICloneable.Clone() }; } - void ICloneable.FromReplica(Nep5Transfer replica) + void ICloneable.FromReplica(Nep17Transfer replica) { UserScriptHash = replica.UserScriptHash; BlockIndex = replica.BlockIndex; diff --git a/src/RpcNep5Tracker/Nep5TransferKey.cs b/src/RpcNep17Tracker/Nep17TransferKey.cs similarity index 87% rename from src/RpcNep5Tracker/Nep5TransferKey.cs rename to src/RpcNep17Tracker/Nep17TransferKey.cs index 69e7fc735..128a7bef1 100644 --- a/src/RpcNep5Tracker/Nep5TransferKey.cs +++ b/src/RpcNep17Tracker/Nep17TransferKey.cs @@ -4,7 +4,7 @@ namespace Neo.Plugins { - public class Nep5TransferKey : IComparable, IEquatable, ISerializable + public class Nep17TransferKey : IComparable, IEquatable, ISerializable { public readonly UInt160 UserScriptHash; public ulong TimestampMS { get; private set; } @@ -17,11 +17,11 @@ public class Nep5TransferKey : IComparable, IEquatable _balances; - private DataCache _transfersSent; - private DataCache _transfersReceived; + private DataCache _balances; + private DataCache _transfersSent; + private DataCache _transfersReceived; private WriteBatch _writeBatch; private bool _shouldTrackHistory; private bool _recordNullAddressHistory; private uint _maxResults; private Snapshot _levelDbSnapshot; - public override string Description => "Enquiries NEP-5 balances and transaction history of accounts through RPC"; + public override string Description => "Enquiries NEP-17 balances and transaction history of accounts through RPC"; - public RpcNep5Tracker() + public RpcNep17Tracker() { RpcServerPlugin.RegisterMethods(this); } @@ -44,7 +44,7 @@ protected override void Configure() { if (_db == null) { - var dbPath = GetConfiguration().GetSection("DBPath").Value ?? "Nep5BalanceData"; + var dbPath = GetConfiguration().GetSection("DBPath").Value ?? "Nep17BalanceData"; _db = DB.Open(GetFullPath(dbPath), new Options { CreateIfMissing = true }); } _shouldTrackHistory = (GetConfiguration().GetSection("TrackHistory").Value ?? true.ToString()) != false.ToString(); @@ -58,13 +58,13 @@ private void ResetBatch() _levelDbSnapshot?.Dispose(); _levelDbSnapshot = _db.GetSnapshot(); ReadOptions dbOptions = new ReadOptions { FillCache = false, Snapshot = _levelDbSnapshot }; - _balances = new DbCache(_db, dbOptions, _writeBatch, Nep5BalancePrefix); + _balances = new DbCache(_db, dbOptions, _writeBatch, Nep17BalancePrefix); if (_shouldTrackHistory) { _transfersSent = - new DbCache(_db, dbOptions, _writeBatch, Nep5TransferSentPrefix); + new DbCache(_db, dbOptions, _writeBatch, Nep17TransferSentPrefix); _transfersReceived = - new DbCache(_db, dbOptions, _writeBatch, Nep5TransferReceivedPrefix); + new DbCache(_db, dbOptions, _writeBatch, Nep17TransferReceivedPrefix); } } @@ -76,8 +76,8 @@ private void RecordTransferHistory(StoreView snapshot, UInt160 scriptHash, UInt1 if (_recordNullAddressHistory || from != UInt160.Zero) { - _transfersSent.Add(new Nep5TransferKey(from, header.Timestamp, scriptHash, transferIndex), - new Nep5Transfer + _transfersSent.Add(new Nep17TransferKey(from, header.Timestamp, scriptHash, transferIndex), + new Nep17Transfer { Amount = amount, UserScriptHash = to, @@ -88,8 +88,8 @@ private void RecordTransferHistory(StoreView snapshot, UInt160 scriptHash, UInt1 if (_recordNullAddressHistory || to != UInt160.Zero) { - _transfersReceived.Add(new Nep5TransferKey(to, header.Timestamp, scriptHash, transferIndex), - new Nep5Transfer + _transfersReceived.Add(new Nep17TransferKey(to, header.Timestamp, scriptHash, transferIndex), + new Nep17Transfer { Amount = amount, UserScriptHash = from, @@ -102,7 +102,7 @@ private void RecordTransferHistory(StoreView snapshot, UInt160 scriptHash, UInt1 private void HandleNotification(StoreView snapshot, IVerifiable scriptContainer, UInt160 scriptHash, string eventName, VM.Types.Array stateItems, - Dictionary nep5BalancesChanged, ref ushort transferIndex) + Dictionary nep17BalancesChanged, ref ushort transferIndex) { if (stateItems.Count == 0) return; if (eventName != "Transfer") return; @@ -129,15 +129,15 @@ private void HandleNotification(StoreView snapshot, IVerifiable scriptContainer, if (fromBytes != null) { from = new UInt160(fromBytes); - var fromKey = new Nep5BalanceKey(from, scriptHash); - if (!nep5BalancesChanged.ContainsKey(fromKey)) nep5BalancesChanged.Add(fromKey, new Nep5Balance()); + var fromKey = new Nep17BalanceKey(from, scriptHash); + if (!nep17BalancesChanged.ContainsKey(fromKey)) nep17BalancesChanged.Add(fromKey, new Nep17Balance()); } if (toBytes != null) { to = new UInt160(toBytes); - var toKey = new Nep5BalanceKey(to, scriptHash); - if (!nep5BalancesChanged.ContainsKey(toKey)) nep5BalancesChanged.Add(toKey, new Nep5Balance()); + var toKey = new Nep17BalanceKey(to, scriptHash); + if (!nep17BalancesChanged.ContainsKey(toKey)) nep17BalancesChanged.Add(toKey, new Nep17Balance()); } if (scriptContainer is Transaction transaction) { @@ -149,7 +149,7 @@ public void OnPersist(StoreView snapshot, IReadOnlyList nep5BalancesChanged = new Dictionary(); + Dictionary nep17BalancesChanged = new Dictionary(); ushort transferIndex = 0; foreach (Blockchain.ApplicationExecuted appExecuted in applicationExecutedList) @@ -161,18 +161,18 @@ public void OnPersist(StoreView snapshot, IReadOnlyList nep5BalancePair.Value); - if (itemToChange != nep5BalancePair.Value) - itemToChange.FromReplica(nep5BalancePair.Value); + var itemToChange = _balances.GetAndChange(nep17BalancePair.Key, () => nep17BalancePair.Value); + if (itemToChange != nep17BalancePair.Value) + itemToChange.FromReplica(nep17BalancePair.Value); } } @@ -223,7 +223,7 @@ private void AddTransfers(byte dbPrefix, UInt160 userScriptHash, ulong startTime Array.Reverse(endTimeBytes); } - var transferPairs = _db.FindRange( + var transferPairs = _db.FindRange( prefix.Concat(startTimeBytes).ToArray(), prefix.Concat(endTimeBytes).ToArray()); @@ -250,7 +250,7 @@ private UInt160 GetScriptHashFromParam(string addressOrScriptHash) } [RpcMethod] - public JObject GetNep5Transfers(JArray _params) + public JObject GetNep17Transfers(JArray _params) { if (!_shouldTrackHistory) throw new RpcException(-32601, "Method not found"); UInt160 userScriptHash = GetScriptHashFromParam(_params[0].AsString()); @@ -267,13 +267,13 @@ public JObject GetNep5Transfers(JArray _params) JArray transfersReceived = new JArray(); json["received"] = transfersReceived; json["address"] = userScriptHash.ToAddress(); - AddTransfers(Nep5TransferSentPrefix, userScriptHash, startTime, endTime, transfersSent); - AddTransfers(Nep5TransferReceivedPrefix, userScriptHash, startTime, endTime, transfersReceived); + AddTransfers(Nep17TransferSentPrefix, userScriptHash, startTime, endTime, transfersSent); + AddTransfers(Nep17TransferReceivedPrefix, userScriptHash, startTime, endTime, transfersReceived); return json; } [RpcMethod] - public JObject GetNep5Balances(JArray _params) + public JObject GetNep17Balances(JArray _params) { UInt160 userScriptHash = GetScriptHashFromParam(_params[0].AsString()); @@ -281,7 +281,7 @@ public JObject GetNep5Balances(JArray _params) JArray balances = new JArray(); json["balance"] = balances; json["address"] = userScriptHash.ToAddress(); - var dbCache = new DbCache(_db, null, null, Nep5BalancePrefix); + var dbCache = new DbCache(_db, null, null, Nep17BalancePrefix); byte[] prefix = userScriptHash.ToArray(); foreach (var (key, value) in dbCache.Find(prefix)) { diff --git a/src/RpcNep5Tracker/RpcNep5Tracker.csproj b/src/RpcNep17Tracker/RpcNep17Tracker.csproj similarity index 82% rename from src/RpcNep5Tracker/RpcNep5Tracker.csproj rename to src/RpcNep17Tracker/RpcNep17Tracker.csproj index 403f4a12a..bb3b93e44 100644 --- a/src/RpcNep5Tracker/RpcNep5Tracker.csproj +++ b/src/RpcNep17Tracker/RpcNep17Tracker.csproj @@ -1,10 +1,10 @@ - Neo.Plugins.RpcNep5Tracker + Neo.Plugins.RpcNep17Tracker Neo.Plugins - + PreserveNewest PreserveNewest diff --git a/src/RpcNep5Tracker/RpcNep5Tracker/config.json b/src/RpcNep17Tracker/RpcNep17Tracker/config.json similarity index 76% rename from src/RpcNep5Tracker/RpcNep5Tracker/config.json rename to src/RpcNep17Tracker/RpcNep17Tracker/config.json index 13e79b805..af5e20247 100644 --- a/src/RpcNep5Tracker/RpcNep5Tracker/config.json +++ b/src/RpcNep17Tracker/RpcNep17Tracker/config.json @@ -1,6 +1,6 @@ -{ +{ "PluginConfiguration": { - "DBPath": "Nep5BalanceData", + "DBPath": "Nep17BalanceData", "TrackHistory" : true, "RecordNullAddressHistory": false, "MaxResults" : 1000 diff --git a/tests/Neo.Network.RPC.Tests/RpcTestCases.json b/tests/Neo.Network.RPC.Tests/RpcTestCases.json index eae9639f9..42ac24a45 100644 --- a/tests/Neo.Network.RPC.Tests/RpcTestCases.json +++ b/tests/Neo.Network.RPC.Tests/RpcTestCases.json @@ -850,7 +850,7 @@ ] }, { - "name": "RpcNep5Tracker", + "name": "RpcNep17Tracker", "version": "3.0.0.0", "interfaces": [ "IPersistencePlugin" @@ -1340,10 +1340,10 @@ } }, { - "Name": "getnep5transfersasync", + "Name": "getnep17transfersasync", "Request": { "jsonrpc": "2.0", - "method": "getnep5transfers", + "method": "getnep17transfers", "params": [ "NVVwFw6XyhtRCFQ8SpUTMdPyYt4Vd9A1XQ", 0, 1868595301000 ], "id": 1 }, @@ -1387,10 +1387,10 @@ } }, { - "Name": "getnep5transfersasync_with_null_transferaddress", + "Name": "getnep17transfersasync_with_null_transferaddress", "Request": { "jsonrpc": "2.0", - "method": "getnep5transfers", + "method": "getnep17transfers", "params": [ "Ncb7jVsYWBt1q5T5k3ZTP8bn5eK4DuanLd", 0, 1868595301000 ], "id": 1 }, @@ -1434,10 +1434,10 @@ } }, { - "Name": "getnep5balancesasync", + "Name": "getnep17balancesasync", "Request": { "jsonrpc": "2.0", - "method": "getnep5balances", + "method": "getnep17balances", "params": [ "NVVwFw6XyhtRCFQ8SpUTMdPyYt4Vd9A1XQ" ], "id": 1 }, diff --git a/tests/Neo.Network.RPC.Tests/UT_Nep5API.cs b/tests/Neo.Network.RPC.Tests/UT_Nep17API.cs similarity index 67% rename from tests/Neo.Network.RPC.Tests/UT_Nep5API.cs rename to tests/Neo.Network.RPC.Tests/UT_Nep17API.cs index 12f0142c9..b35a2bfc9 100644 --- a/tests/Neo.Network.RPC.Tests/UT_Nep5API.cs +++ b/tests/Neo.Network.RPC.Tests/UT_Nep17API.cs @@ -1,5 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Neo.IO.Json; using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.VM; @@ -11,12 +12,12 @@ namespace Neo.Network.RPC.Tests { [TestClass] - public class UT_Nep5API + public class UT_Nep17API { Mock rpcClientMock; KeyPair keyPair1; UInt160 sender; - Nep5API nep5API; + Nep17API nep17API; [TestInitialize] public void TestSetup() @@ -24,7 +25,7 @@ public void TestSetup() keyPair1 = new KeyPair(Wallet.GetPrivateKeyFromWIF("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p")); sender = Contract.CreateSignatureRedeemScript(keyPair1.PublicKey).ToScriptHash(); rpcClientMock = UT_TransactionManager.MockRpcClient(sender, new byte[0]); - nep5API = new Nep5API(rpcClientMock.Object); + nep17API = new Nep17API(rpcClientMock.Object); } [TestMethod] @@ -33,27 +34,17 @@ public async Task TestBalanceOf() byte[] testScript = NativeContract.GAS.Hash.MakeScript("balanceOf", UInt160.Zero); UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(10000) }); - var balance = await nep5API.BalanceOfAsync(NativeContract.GAS.Hash, UInt160.Zero); + var balance = await nep17API.BalanceOfAsync(NativeContract.GAS.Hash, UInt160.Zero); Assert.AreEqual(10000, (int)balance); } - [TestMethod] - public async Task TestGetName() - { - byte[] testScript = NativeContract.GAS.Hash.MakeScript("name"); - UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.String, Value = NativeContract.GAS.Name }); - - var result = await nep5API.NameAsync(NativeContract.GAS.Hash); - Assert.AreEqual(NativeContract.GAS.Name, result); - } - [TestMethod] public async Task TestGetSymbol() { byte[] testScript = NativeContract.GAS.Hash.MakeScript("symbol"); UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.String, Value = NativeContract.GAS.Symbol }); - var result = await nep5API.SymbolAsync(NativeContract.GAS.Hash); + var result = await nep17API.SymbolAsync(NativeContract.GAS.Hash); Assert.AreEqual(NativeContract.GAS.Symbol, result); } @@ -63,7 +54,7 @@ public async Task TestGetDecimals() byte[] testScript = NativeContract.GAS.Hash.MakeScript("decimals"); UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(NativeContract.GAS.Decimals) }); - var result = await nep5API.DecimalsAsync(NativeContract.GAS.Hash); + var result = await nep17API.DecimalsAsync(NativeContract.GAS.Hash); Assert.AreEqual(NativeContract.GAS.Decimals, result); } @@ -73,7 +64,7 @@ public async Task TestGetTotalSupply() byte[] testScript = NativeContract.GAS.Hash.MakeScript("totalSupply"); UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_00000000) }); - var result = await nep5API.TotalSupplyAsync(NativeContract.GAS.Hash); + var result = await nep17API.TotalSupplyAsync(NativeContract.GAS.Hash); Assert.AreEqual(1_00000000, (int)result); } @@ -81,22 +72,28 @@ public async Task TestGetTotalSupply() public async Task TestGetTokenInfo() { UInt160 scriptHash = NativeContract.GAS.Hash; - byte[] testScript = scriptHash.MakeScript("name") - .Concat(scriptHash.MakeScript("symbol")) + byte[] testScript = scriptHash.MakeScript("symbol") .Concat(scriptHash.MakeScript("decimals")) .Concat(scriptHash.MakeScript("totalSupply")) - .ToArray(); ; + .ToArray(); UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, - new ContractParameter { Type = ContractParameterType.String, Value = NativeContract.GAS.Name }, new ContractParameter { Type = ContractParameterType.String, Value = NativeContract.GAS.Symbol }, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(NativeContract.GAS.Decimals) }, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_00000000) }); - var result = await nep5API.GetTokenInfoAsync(NativeContract.GAS.Hash); - Assert.AreEqual(NativeContract.GAS.Name, result.Name); - Assert.AreEqual(NativeContract.GAS.Symbol, result.Symbol); - Assert.AreEqual(8, (int)result.Decimals); - Assert.AreEqual(1_00000000, (int)result.TotalSupply); + var tests = TestUtils.RpcTestCases.Where(p => p.Name == "getcontractstateasync"); + foreach (var test in tests) + { + rpcClientMock.Setup(p => p.RpcSendAsync("getcontractstate", It.Is(u => true))) + .ReturnsAsync(test.Response.Result) + .Verifiable(); + + var result = await nep17API.GetTokenInfoAsync(NativeContract.GAS.Hash); + Assert.AreEqual(NativeContract.GAS.Symbol, result.Symbol); + Assert.AreEqual(8, (int)result.Decimals); + Assert.AreEqual(1_00000000, (int)result.TotalSupply); + Assert.AreEqual("GAS", result.Name); + } } [TestMethod] @@ -105,7 +102,12 @@ public async Task TestTransfer() byte[] testScript = NativeContract.GAS.Hash.MakeScript("transfer", sender, UInt160.Zero, new BigInteger(1_00000000)); UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter()); - var result = await nep5API.CreateTransferTxAsync(NativeContract.GAS.Hash, keyPair1, UInt160.Zero, new BigInteger(1_00000000)); + var result = await nep17API.CreateTransferTxAsync(NativeContract.GAS.Hash, keyPair1, UInt160.Zero, new BigInteger(1_00000000)); + + testScript = NativeContract.GAS.Hash.MakeScript("transfer", sender, UInt160.Zero, new BigInteger(1_00000000), string.Empty); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter()); + + result = await nep17API.CreateTransferTxAsync(NativeContract.GAS.Hash, keyPair1, UInt160.Zero, new BigInteger(1_00000000), string.Empty); Assert.IsNotNull(result); } } diff --git a/tests/Neo.Network.RPC.Tests/UT_RpcClient.cs b/tests/Neo.Network.RPC.Tests/UT_RpcClient.cs index b558ef201..69ba8caaf 100644 --- a/tests/Neo.Network.RPC.Tests/UT_RpcClient.cs +++ b/tests/Neo.Network.RPC.Tests/UT_RpcClient.cs @@ -431,21 +431,21 @@ public async Task GetApplicationLogTest_TriggerType() } [TestMethod()] - public async Task GetNep5TransfersTest() + public async Task GetNep17TransfersTest() { - var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetNep5TransfersAsync).ToLower()); - var result = await rpc.GetNep5TransfersAsync(test.Request.Params[0].AsString(), (ulong)test.Request.Params[1].AsNumber(), (ulong)test.Request.Params[2].AsNumber()); + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetNep17TransfersAsync).ToLower()); + var result = await rpc.GetNep17TransfersAsync(test.Request.Params[0].AsString(), (ulong)test.Request.Params[1].AsNumber(), (ulong)test.Request.Params[2].AsNumber()); Assert.AreEqual(test.Response.Result.ToString(), result.ToJson().ToString()); - test = TestUtils.RpcTestCases.Find(p => p.Name == (nameof(rpc.GetNep5TransfersAsync).ToLower() + "_with_null_transferaddress")); - result = await rpc.GetNep5TransfersAsync(test.Request.Params[0].AsString(), (ulong)test.Request.Params[1].AsNumber(), (ulong)test.Request.Params[2].AsNumber()); + test = TestUtils.RpcTestCases.Find(p => p.Name == (nameof(rpc.GetNep17TransfersAsync).ToLower() + "_with_null_transferaddress")); + result = await rpc.GetNep17TransfersAsync(test.Request.Params[0].AsString(), (ulong)test.Request.Params[1].AsNumber(), (ulong)test.Request.Params[2].AsNumber()); Assert.AreEqual(test.Response.Result.ToString(), result.ToJson().ToString()); } [TestMethod()] - public async Task GetNep5BalancesTest() + public async Task GetNep17BalancesTest() { - var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetNep5BalancesAsync).ToLower()); - var result = await rpc.GetNep5BalancesAsync(test.Request.Params[0].AsString()); + var test = TestUtils.RpcTestCases.Find(p => p.Name == nameof(rpc.GetNep17BalancesAsync).ToLower()); + var result = await rpc.GetNep17BalancesAsync(test.Request.Params[0].AsString()); Assert.AreEqual(test.Response.Result.ToString(), result.ToJson().ToString()); } diff --git a/tests/Neo.Network.RPC.Tests/UT_RpcModels.cs b/tests/Neo.Network.RPC.Tests/UT_RpcModels.cs index 04613dbc1..18b55c603 100644 --- a/tests/Neo.Network.RPC.Tests/UT_RpcModels.cs +++ b/tests/Neo.Network.RPC.Tests/UT_RpcModels.cs @@ -57,18 +57,18 @@ public void TestRpcInvokeResult() } [TestMethod()] - public void TestRpcNep5Balances() + public void TestRpcNep17Balances() { - JObject json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.GetNep5BalancesAsync).ToLower()).Response.Result; - var item = RpcNep5Balances.FromJson(json); + JObject json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.GetNep17BalancesAsync).ToLower()).Response.Result; + var item = RpcNep17Balances.FromJson(json); Assert.AreEqual(json.ToString(), item.ToJson().ToString()); } [TestMethod()] - public void TestRpcNep5Transfers() + public void TestRpcNep17Transfers() { - JObject json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.GetNep5TransfersAsync).ToLower()).Response.Result; - var item = RpcNep5Transfers.FromJson(json); + JObject json = TestUtils.RpcTestCases.Find(p => p.Name == nameof(RpcClient.GetNep17TransfersAsync).ToLower()).Response.Result; + var item = RpcNep17Transfers.FromJson(json); Assert.AreEqual(json.ToString(), item.ToJson().ToString()); } diff --git a/tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs b/tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs index ebfbcb51b..721b53fef 100644 --- a/tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs +++ b/tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs @@ -127,6 +127,22 @@ public async Task TestTransferfromMultiSigAccount() var tranaction = await walletAPI.TransferAsync(NativeContract.GAS.Hash, 1, new[] { keyPair1.PublicKey }, new[] { keyPair1 }, UInt160.Zero, NativeContract.GAS.Factor * 100); Assert.AreEqual(testScript.ToHexString(), tranaction.Script.ToHexString()); + + try + { + tranaction = await walletAPI.TransferAsync(NativeContract.GAS.Hash, 2, new[] { keyPair1.PublicKey }, new[] { keyPair1 }, UInt160.Zero, NativeContract.GAS.Factor * 100); + Assert.Fail(); + } + catch (System.Exception e) + { + Assert.AreEqual(e.Message, $"Need at least 2 KeyPairs for signing!"); + } + + testScript = NativeContract.GAS.Hash.MakeScript("transfer", multiSender, UInt160.Zero, NativeContract.GAS.Factor * 100, string.Empty); + UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_10000000) }); + + tranaction = await walletAPI.TransferAsync(NativeContract.GAS.Hash, 1, new[] { keyPair1.PublicKey }, new[] { keyPair1 }, UInt160.Zero, NativeContract.GAS.Factor * 100, string.Empty); + Assert.AreEqual(testScript.ToHexString(), tranaction.Script.ToHexString()); } [TestMethod]