From 863f36f1e911ea130d2c6d783d0bafdf69fa7b5e Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 5 Aug 2024 12:57:38 +0800 Subject: [PATCH 01/16] rpc parameter parse --- .../RpcServer/JsonPropertyNameAttribute.cs | 20 ++++ src/Plugins/RpcServer/RpcServer.cs | 99 ++++++++++++++++++- src/Plugins/RpcServer/RpcServer.csproj | 4 + 3 files changed, 120 insertions(+), 3 deletions(-) create mode 100644 src/Plugins/RpcServer/JsonPropertyNameAttribute.cs diff --git a/src/Plugins/RpcServer/JsonPropertyNameAttribute.cs b/src/Plugins/RpcServer/JsonPropertyNameAttribute.cs new file mode 100644 index 0000000000..e4d3da3696 --- /dev/null +++ b/src/Plugins/RpcServer/JsonPropertyNameAttribute.cs @@ -0,0 +1,20 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JsonPropertyNameAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.Plugins.RpcServer; + +[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] +public class JsonPropertyNameAttribute(string name) : Attribute +{ + public string Name { get; } = name; +} diff --git a/src/Plugins/RpcServer/RpcServer.cs b/src/Plugins/RpcServer/RpcServer.cs index c53f17b860..28636f109d 100644 --- a/src/Plugins/RpcServer/RpcServer.cs +++ b/src/Plugins/RpcServer/RpcServer.cs @@ -261,17 +261,70 @@ public async Task ProcessAsync(HttpContext context) private async Task ProcessRequestAsync(HttpContext context, JObject request) { if (!request.ContainsProperty("id")) return null; - JToken @params = request["params"] ?? new JArray(); + var @params = request["params"] ?? new JArray(); if (!request.ContainsProperty("method") || @params is not JArray) { return CreateErrorResponse(request["id"], RpcError.InvalidRequest); } - JObject response = CreateResponse(request["id"]); + + var parameters = (JArray)@params; + var pa = parameters[0]; + + var response = CreateResponse(request["id"]); try { - string method = request["method"].AsString(); + var method = request["method"].AsString(); (CheckAuth(context) && !settings.DisabledMethods.Contains(method)).True_Or(RpcError.AccessDenied); methods.TryGetValue(method, out var func).True_Or(RpcErrorFactory.MethodNotFound(method)); + var paramInfos = func.Method.GetParameters(); + var args = new object[paramInfos.Length]; + + for (var i = 0; i < paramInfos.Length; i++) + { + var param = paramInfos[i]; + + + if (parameters.Count > i && parameters[i] != null && parameters[i].Type != JTokenType.Null) + { + try + { + args[i] = ConvertParameter(parameters[i], param.ParameterType); + } + catch (Exception e) + { + // 如果转换失败,检查参数是否可选或有默认值 + if (param.IsOptional) + { + args[i] = param.DefaultValue; + } + else + { + // 如果参数不是可选的,且转换失败,则抛出异常 + throw new ArgumentException($"Invalid value for parameter '{param.Name}'", e); + } + } + } + else + { + // 如果参数未提供 + if (param.IsOptional) + { + // 如果参数是可选的,使用默认值 + args[i] = param.DefaultValue; + } + else if (param.ParameterType.IsValueType && Nullable.GetUnderlyingType(param.ParameterType) == null) + { + // 如果参数是非可空值类型,且未提供值,抛出异常 + throw new ArgumentException($"Required parameter '{param.Name}' is missing"); + } + else + { + // 对于引用类型或可空值类型,设置为 null + args[i] = null; + } + } + } + response["result"] = func((JArray)@params) switch { JToken result => result, @@ -308,5 +361,45 @@ public void RegisterMethods(object handler) methods[name] = method.CreateDelegate>(handler); } } + + private string GetJsonPropertyName(ParameterInfo param) + { + var attr = param.GetCustomAttribute(); + return attr != null ? attr.Name : param.Name; + } + + + private object ConvertParameter(JToken token, Type targetType) + { + if (targetType == typeof(string)) + { + return token.ToString(); + } + else if (targetType == typeof(int)) + { + return token.Value(); + } + else if (targetType == typeof(long)) + { + return token.Value(); + } + else if (targetType == typeof(double)) + { + return token.Value(); + } + else if (targetType == typeof(bool)) + { + return token.Value(); + } + else if (targetType == typeof(UInt160)) + { + return UInt160.Parse(token.ToString()); + } + // 添加其他类型的转换... + + // 如果是复杂类型,可以使用 JSON 反序列化 + return token.ToObject(targetType); + } + } } diff --git a/src/Plugins/RpcServer/RpcServer.csproj b/src/Plugins/RpcServer/RpcServer.csproj index c5b72e7a61..0721fddabb 100644 --- a/src/Plugins/RpcServer/RpcServer.csproj +++ b/src/Plugins/RpcServer/RpcServer.csproj @@ -20,4 +20,8 @@ + + + + From 4956166deb2a4ca6ea1ca7f379d5958364b9b922 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 12 Aug 2024 00:43:29 +0800 Subject: [PATCH 02/16] update blockchain related apis. --- .../RpcServer/Model/BlockHashOrIndex.cs | 45 ++++ .../RpcServer/Model/ContractHashOrId.cs | 44 ++++ .../RpcServer/RpcMethodWithParamsAttribute.cs | 21 ++ src/Plugins/RpcServer/RpcServer.Blockchain.cs | 189 +++++++--------- src/Plugins/RpcServer/RpcServer.cs | 202 +++++++++++------ src/Plugins/RpcServer/RpcServer.csproj | 4 - .../UT_RpcServer.Blockchain.cs | 214 +++--------------- 7 files changed, 356 insertions(+), 363 deletions(-) create mode 100644 src/Plugins/RpcServer/Model/BlockHashOrIndex.cs create mode 100644 src/Plugins/RpcServer/Model/ContractHashOrId.cs create mode 100644 src/Plugins/RpcServer/RpcMethodWithParamsAttribute.cs diff --git a/src/Plugins/RpcServer/Model/BlockHashOrIndex.cs b/src/Plugins/RpcServer/Model/BlockHashOrIndex.cs new file mode 100644 index 0000000000..ce148a56e2 --- /dev/null +++ b/src/Plugins/RpcServer/Model/BlockHashOrIndex.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BlockHashOrIndex.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Plugins.RpcServer.Model; + +public class BlockHashOrIndex +{ + + private readonly object _value; + + public BlockHashOrIndex(uint index) + { + _value = index; + } + + public BlockHashOrIndex(UInt256 hash) + { + _value = hash; + } + + public bool IsIndex => _value is uint; + public bool IsHash => _value is UInt256; + + public uint AsIndex() + { + if (_value is uint intValue) + return intValue; + throw new RpcException(RpcError.InvalidParams.WithData($"Value {_value} is not a valid block index")); + } + + public UInt256 AsHash() + { + if (_value is UInt256 hash) + return hash; + throw new RpcException(RpcError.InvalidParams.WithData($"Value {_value} is not a valid block hash")); + } +} diff --git a/src/Plugins/RpcServer/Model/ContractHashOrId.cs b/src/Plugins/RpcServer/Model/ContractHashOrId.cs new file mode 100644 index 0000000000..22ea5b8c8f --- /dev/null +++ b/src/Plugins/RpcServer/Model/ContractHashOrId.cs @@ -0,0 +1,44 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractHashOrId.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Plugins.RpcServer.Model; + +public class ContractHashOrId +{ + private readonly object _value; + + public ContractHashOrId(int id) + { + _value = id; + } + + public ContractHashOrId(UInt160 hash) + { + _value = hash; + } + + public bool IsId => _value is int; + public bool IsHash => _value is UInt160; + + public int AsId() + { + if (_value is int intValue) + return intValue; + throw new RpcException(RpcError.InvalidParams.WithData($"Value {_value} is not a valid contract id")); + } + + public UInt160 AsHash() + { + if (_value is UInt160 hash) + return hash; + throw new RpcException(RpcError.InvalidParams.WithData($"Value {_value} is not a valid contract hash")); + } +} diff --git a/src/Plugins/RpcServer/RpcMethodWithParamsAttribute.cs b/src/Plugins/RpcServer/RpcMethodWithParamsAttribute.cs new file mode 100644 index 0000000000..fe044e8af7 --- /dev/null +++ b/src/Plugins/RpcServer/RpcMethodWithParamsAttribute.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcMethodWithParamsAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Org.BouncyCastle.Asn1; +using System; + +namespace Neo.Plugins.RpcServer; + +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] +public class RpcMethodWithParamsAttribute : Attribute +{ + public string Name { get; set; } +} diff --git a/src/Plugins/RpcServer/RpcServer.Blockchain.cs b/src/Plugins/RpcServer/RpcServer.Blockchain.cs index 8f73fa3c78..7b68da0163 100644 --- a/src/Plugins/RpcServer/RpcServer.Blockchain.cs +++ b/src/Plugins/RpcServer/RpcServer.Blockchain.cs @@ -13,6 +13,7 @@ using Neo.IO; using Neo.Json; using Neo.Network.P2P.Payloads; +using Neo.Plugins.RpcServer.Model; using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.VM; @@ -28,10 +29,9 @@ partial class RpcServer /// /// Gets the hash of the best (most recent) block. /// - /// An empty array; no parameters are required. /// The hash of the best block as a . - [RpcMethod] - protected internal virtual JToken GetBestBlockHash(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetBestBlockHash() { return NativeContract.Ledger.CurrentHash(system.StoreView).ToString(); } @@ -39,29 +39,17 @@ protected internal virtual JToken GetBestBlockHash(JArray _params) /// /// Gets a block by its hash or index. /// - /// - /// An array containing the block hash or index as the first element, - /// and an optional boolean indicating whether to return verbose information. - /// + /// The block hash or index. + /// Optional, the default value is false. /// The block data as a . If the second item of _params is true, then /// block data is json format, otherwise, the return type is Base64-encoded byte array. - [RpcMethod] - protected internal virtual JToken GetBlock(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetBlock(BlockHashOrIndex blockHashOrIndex, bool verbose = false) { - JToken key = Result.Ok_Or(() => _params[0], RpcError.InvalidParams.WithData($"Invalid Block Hash or Index: {_params[0]}")); - bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); + // JToken key = Result.Ok_Or(() => _params[0], RpcError.InvalidParams.WithData($"Invalid Block Hash or Index: {_params[0]}")); + // bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); using var snapshot = system.GetSnapshotCache(); - Block block; - if (key is JNumber) - { - uint index = uint.Parse(key.AsString()); - block = NativeContract.Ledger.GetBlock(snapshot, index); - } - else - { - UInt256 hash = Result.Ok_Or(() => UInt256.Parse(key.AsString()), RpcError.InvalidParams.WithData($"Invalid block hash {_params[0]}")); - block = NativeContract.Ledger.GetBlock(snapshot, hash); - } + var block = blockHashOrIndex.IsIndex ? NativeContract.Ledger.GetBlock(snapshot, blockHashOrIndex.AsIndex()) : NativeContract.Ledger.GetBlock(snapshot, blockHashOrIndex.AsHash()); block.NotNull_Or(RpcError.UnknownBlock); if (verbose) { @@ -78,10 +66,9 @@ protected internal virtual JToken GetBlock(JArray _params) /// /// Gets the number of block headers in the blockchain. /// - /// An empty array; no parameters are required. /// The count of block headers as a . - [RpcMethod] - internal virtual JToken GetBlockHeaderCount(JArray _params) + [RpcMethodWithParams] + internal virtual JToken GetBlockHeaderCount() { return (system.HeaderCache.Last?.Index ?? NativeContract.Ledger.CurrentIndex(system.StoreView)) + 1; } @@ -89,10 +76,9 @@ internal virtual JToken GetBlockHeaderCount(JArray _params) /// /// Gets the number of blocks in the blockchain. /// - /// An empty array; no parameters are required. /// The count of blocks as a . - [RpcMethod] - protected internal virtual JToken GetBlockCount(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetBlockCount() { return NativeContract.Ledger.CurrentIndex(system.StoreView) + 1; } @@ -100,12 +86,11 @@ protected internal virtual JToken GetBlockCount(JArray _params) /// /// Gets the hash of the block at the specified height. /// - /// An array containing the block height as the first element. + /// Block index (block height) /// The hash of the block at the specified height as a . - [RpcMethod] - protected internal virtual JToken GetBlockHash(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetBlockHash(uint height) { - uint height = Result.Ok_Or(() => uint.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Height: {_params[0]}")); var snapshot = system.StoreView; if (height <= NativeContract.Ledger.CurrentIndex(snapshot)) { @@ -117,27 +102,26 @@ protected internal virtual JToken GetBlockHash(JArray _params) /// /// Gets a block header by its hash or index. /// - /// - /// An array containing the block header hash or index as the first element, - /// and an optional boolean indicating whether to return verbose information. - /// + /// The block script hash or index (i.e. block height=number of blocks - 1). + /// Optional, the default value is false. + /// + /// When verbose is false, serialized information of the block is returned in a hexadecimal string. + /// If you need the detailed information, use the SDK for deserialization. + /// When verbose is true or 1, detailed information of the block is returned in Json format. + /// /// The block header data as a . In json format if the second item of _params is true, otherwise Base64-encoded byte array. - [RpcMethod] - protected internal virtual JToken GetBlockHeader(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetBlockHeader(BlockHashOrIndex blockHashOrIndex, bool verbose = false) { - JToken key = _params[0]; - bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); var snapshot = system.StoreView; Header header; - if (key is JNumber) + if (blockHashOrIndex.IsIndex) { - uint height = uint.Parse(key.AsString()); - header = NativeContract.Ledger.GetHeader(snapshot, height).NotNull_Or(RpcError.UnknownBlock); + header = NativeContract.Ledger.GetHeader(snapshot, blockHashOrIndex.AsIndex()).NotNull_Or(RpcError.UnknownBlock); } else { - UInt256 hash = Result.Ok_Or(() => UInt256.Parse(key.AsString()), RpcError.InvalidParams.WithData($"Invalid block hash {_params[0]}")); - header = NativeContract.Ledger.GetHeader(snapshot, hash).NotNull_Or(RpcError.UnknownBlock); + header = NativeContract.Ledger.GetHeader(snapshot, blockHashOrIndex.AsHash()).NotNull_Or(RpcError.UnknownBlock); } if (verbose) { @@ -155,19 +139,18 @@ protected internal virtual JToken GetBlockHeader(JArray _params) /// /// Gets the state of a contract by its ID or script hash or (only for native contracts) by case-insensitive name. /// - /// An array containing the contract ID or script hash or case-insensitive native contract name as the first element. + /// Contract script hash or the native contract id. /// The contract state in json format as a . - [RpcMethod] - protected internal virtual JToken GetContractState(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetContractState(ContractHashOrId contractHashOrId) { - if (int.TryParse(_params[0].AsString(), out int contractId)) + if (contractHashOrId.IsId) { - var contractState = NativeContract.ContractManagement.GetContractById(system.StoreView, contractId); + var contractState = NativeContract.ContractManagement.GetContractById(system.StoreView, contractHashOrId.AsId()); return contractState.NotNull_Or(RpcError.UnknownContract).ToJson(); } - var scriptHash = Result.Ok_Or(() => ToScriptHash(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid contract hash {_params[0]}")); - var contract = NativeContract.ContractManagement.GetContract(system.StoreView, scriptHash); + var contract = NativeContract.ContractManagement.GetContract(system.StoreView, contractHashOrId.AsHash()); return contract.NotNull_Or(RpcError.UnknownContract).ToJson(); } @@ -185,12 +168,11 @@ private static UInt160 ToScriptHash(string keyword) /// /// Gets the current memory pool transactions. /// - /// An array containing an optional boolean indicating whether to include unverified transactions. + /// Optional, the default value is false. /// The memory pool transactions in json format as a . - [RpcMethod] - protected internal virtual JToken GetRawMemPool(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetRawMemPool(bool shouldGetUnverified = false) { - bool shouldGetUnverified = _params.Count >= 1 && _params[0].AsBoolean(); if (!shouldGetUnverified) return new JArray(system.MemPool.GetVerifiedTransactions().Select(p => (JToken)p.Hash.ToString())); @@ -207,27 +189,23 @@ protected internal virtual JToken GetRawMemPool(JArray _params) /// /// Gets a transaction by its hash. /// - /// - /// An array containing the transaction hash as the first element, - /// and an optional boolean indicating whether to return verbose information. - /// - /// The transaction data as a . In json format if the second item of _params is true, otherwise base64string. - [RpcMethod] - protected internal virtual JToken GetRawTransaction(JArray _params) + /// The transaction hash. + /// Optional, the default value is false. + /// The transaction data as a . In json format if verbose is true, otherwise base64string. + [RpcMethodWithParams] + protected internal virtual JToken GetRawTransaction(UInt256 hash, bool verbose = false) { - UInt256 hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Transaction Hash: {_params[0]}")); - bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); - if (system.MemPool.TryGetValue(hash, out Transaction tx) && !verbose) + if (system.MemPool.TryGetValue(hash, out var tx) && !verbose) return Convert.ToBase64String(tx.ToArray()); var snapshot = system.StoreView; - TransactionState state = NativeContract.Ledger.GetTransactionState(snapshot, hash); + var state = NativeContract.Ledger.GetTransactionState(snapshot, hash); tx ??= state?.Transaction; tx.NotNull_Or(RpcError.UnknownTransaction); if (!verbose) return Convert.ToBase64String(tx.ToArray()); - JObject json = Utility.TransactionToJson(tx, system.Settings); + var json = Utility.TransactionToJson(tx, system.Settings); if (state is not null) { - TrimmedBlock block = NativeContract.Ledger.GetTrimmedBlock(snapshot, NativeContract.Ledger.GetBlockHash(snapshot, state.BlockIndex)); + var block = NativeContract.Ledger.GetTrimmedBlock(snapshot, NativeContract.Ledger.GetBlockHash(snapshot, state.BlockIndex)); json["blockhash"] = block.Hash.ToString(); json["confirmations"] = NativeContract.Ledger.CurrentIndex(snapshot) - block.Index + 1; json["blocktime"] = block.Header.Timestamp; @@ -238,23 +216,26 @@ protected internal virtual JToken GetRawTransaction(JArray _params) /// /// Gets the storage item by contract ID or script hash and key. /// - /// - /// An array containing the contract ID or script hash as the first element, - /// and the storage key as the second element. - /// + /// The contract ID or script hash. + /// The Base64-encoded storage key. /// The storage item as a . - [RpcMethod] - protected internal virtual JToken GetStorage(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetStorage(ContractHashOrId contractHashOrId, string base64Key) { using var snapshot = system.GetSnapshotCache(); - if (!int.TryParse(_params[0].AsString(), out int id)) + int id; + if (contractHashOrId.IsHash) { - UInt160 hash = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid contract hash {_params[0]}")); - ContractState contract = NativeContract.ContractManagement.GetContract(snapshot, hash).NotNull_Or(RpcError.UnknownContract); + var hash = contractHashOrId.AsHash(); + var contract = NativeContract.ContractManagement.GetContract(snapshot, hash).NotNull_Or(RpcError.UnknownContract); id = contract.Id; } - byte[] key = Convert.FromBase64String(_params[1].AsString()); - StorageItem item = snapshot.TryGet(new StorageKey + else + { + id = contractHashOrId.AsId(); + } + var key = Convert.FromBase64String(base64Key); + var item = snapshot.TryGet(new StorageKey { Id = id, Key = key @@ -271,25 +252,24 @@ protected internal virtual JToken GetStorage(JArray _params) /// and an optional start index as the third element. /// /// The found storage items as a . - [RpcMethod] - protected internal virtual JToken FindStorage(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken FindStorage(ContractHashOrId contractHashOrId, string base64KeyPrefix, int start = 0) { using var snapshot = system.GetSnapshotCache(); - if (!int.TryParse(_params[0].AsString(), out int id)) + int id; + if (contractHashOrId.IsHash) { - UInt160 hash = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid contract hash {_params[0]}")); - ContractState contract = NativeContract.ContractManagement.GetContract(snapshot, hash).NotNull_Or(RpcError.UnknownContract); + ContractState contract = NativeContract.ContractManagement.GetContract(snapshot, contractHashOrId.AsHash()).NotNull_Or(RpcError.UnknownContract); id = contract.Id; } - - byte[] prefix = Result.Ok_Or(() => Convert.FromBase64String(_params[1].AsString()), RpcError.InvalidParams.WithData($"Invalid Base64 string{_params[1]}")); - byte[] prefix_key = StorageKey.CreateSearchPrefix(id, prefix); - - if (!int.TryParse(_params[2].AsString(), out int start)) + else { - start = 0; + id = contractHashOrId.AsId(); } + byte[] prefix = Result.Ok_Or(() => Convert.FromBase64String(base64KeyPrefix), RpcError.InvalidParams.WithData($"Invalid Base64 string{base64KeyPrefix}")); + byte[] prefix_key = StorageKey.CreateSearchPrefix(id, prefix); + JObject json = new(); JArray jarr = new(); int pageSize = settings.FindStoragePageSize; @@ -323,12 +303,11 @@ protected internal virtual JToken FindStorage(JArray _params) /// /// Gets the height of a transaction by its hash. /// - /// An array containing the transaction hash as the first element. + /// The transaction hash. /// The height of the transaction as a . - [RpcMethod] - protected internal virtual JToken GetTransactionHeight(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetTransactionHeight(UInt256 hash) { - UInt256 hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Transaction Hash: {_params[0]}")); uint? height = NativeContract.Ledger.GetTransactionState(system.StoreView, hash)?.BlockIndex; if (height.HasValue) return height.Value; throw new RpcException(RpcError.UnknownTransaction); @@ -337,10 +316,9 @@ protected internal virtual JToken GetTransactionHeight(JArray _params) /// /// Gets the next block validators. /// - /// An empty array; no parameters are required. /// The next block validators as a . - [RpcMethod] - protected internal virtual JToken GetNextBlockValidators(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetNextBlockValidators() { using var snapshot = system.GetSnapshotCache(); var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, system.Settings.ValidatorsCount); @@ -356,10 +334,9 @@ protected internal virtual JToken GetNextBlockValidators(JArray _params) /// /// Gets the list of candidates for the next block validators. /// - /// An empty array; no parameters are required. /// The candidates public key list as a JToken. - [RpcMethod] - protected internal virtual JToken GetCandidates(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetCandidates() { using var snapshot = system.GetSnapshotCache(); byte[] script; @@ -413,10 +390,9 @@ protected internal virtual JToken GetCandidates(JArray _params) /// /// Gets the list of committee members. /// - /// An empty array; no parameters are required. /// The committee members publickeys as a . - [RpcMethod] - protected internal virtual JToken GetCommittee(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetCommittee() { return new JArray(NativeContract.NEO.GetCommittee(system.StoreView).Select(p => (JToken)p.ToString())); } @@ -424,10 +400,9 @@ protected internal virtual JToken GetCommittee(JArray _params) /// /// Gets the list of native contracts. /// - /// An empty array; no parameters are required. /// The native contract states as a . - [RpcMethod] - protected internal virtual JToken GetNativeContracts(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetNativeContracts() { return new JArray(NativeContract.Contracts.Select(p => NativeContract.ContractManagement.GetContract(system.StoreView, p.Hash).ToJson())); } diff --git a/src/Plugins/RpcServer/RpcServer.cs b/src/Plugins/RpcServer/RpcServer.cs index 28636f109d..bde8c45fa1 100644 --- a/src/Plugins/RpcServer/RpcServer.cs +++ b/src/Plugins/RpcServer/RpcServer.cs @@ -18,11 +18,14 @@ using Microsoft.Extensions.DependencyInjection; using Neo.Json; using Neo.Network.P2P; +using Neo.Plugins.RpcServer.Model; +using Neo.Wallets; using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; +using System.Linq.Expressions; using System.Net.Security; using System.Reflection; using System.Security.Cryptography.X509Certificates; @@ -36,6 +39,7 @@ public partial class RpcServer : IDisposable private const int MaxParamsDepth = 32; private readonly Dictionary> methods = new(); + private readonly Dictionary _methodsWithParams = new(); private IWebHost host; private RpcServerSettings settings; @@ -258,6 +262,7 @@ public async Task ProcessAsync(HttpContext context) await context.Response.WriteAsync(response.ToString(), Encoding.UTF8); } + private async Task ProcessRequestAsync(HttpContext context, JObject request) { if (!request.ContainsProperty("id")) return null; @@ -267,71 +272,77 @@ private async Task ProcessRequestAsync(HttpContext context, JObject req return CreateErrorResponse(request["id"], RpcError.InvalidRequest); } - var parameters = (JArray)@params; - var pa = parameters[0]; - + var jsonParameters = (JArray)@params; var response = CreateResponse(request["id"]); try { var method = request["method"].AsString(); (CheckAuth(context) && !settings.DisabledMethods.Contains(method)).True_Or(RpcError.AccessDenied); - methods.TryGetValue(method, out var func).True_Or(RpcErrorFactory.MethodNotFound(method)); - var paramInfos = func.Method.GetParameters(); - var args = new object[paramInfos.Length]; - for (var i = 0; i < paramInfos.Length; i++) + if (methods.TryGetValue(method, out var func)) { - var param = paramInfos[i]; + response["result"] = func(jsonParameters) switch + { + JToken result => result, + Task task => await task, + _ => throw new NotSupportedException() + }; + return response; + } + if (_methodsWithParams.TryGetValue(method, out var func2)) + { + var paramInfos = func2.Method.GetParameters(); + var args = new object[paramInfos.Length]; - if (parameters.Count > i && parameters[i] != null && parameters[i].Type != JTokenType.Null) + for (var i = 0; i < paramInfos.Length; i++) { - try + var param = paramInfos[i]; + if (jsonParameters.Count > i && jsonParameters[i] != null) { - args[i] = ConvertParameter(parameters[i], param.ParameterType); + try + { + args[i] = ConvertParameter(jsonParameters[i], param.ParameterType); + } + catch (Exception e) + { + if (param.IsOptional) + { + args[i] = param.DefaultValue; + } + else + { + throw new ArgumentException($"Invalid value for parameter '{param.Name}'", e); + } + } } - catch (Exception e) + else { - // 如果转换失败,检查参数是否可选或有默认值 if (param.IsOptional) { args[i] = param.DefaultValue; } + else if (param.ParameterType.IsValueType && Nullable.GetUnderlyingType(param.ParameterType) == null) + { + throw new ArgumentException($"Required parameter '{param.Name}' is missing"); + } else { - // 如果参数不是可选的,且转换失败,则抛出异常 - throw new ArgumentException($"Invalid value for parameter '{param.Name}'", e); + args[i] = null; } } } - else + + response["result"] = func2.DynamicInvoke(args) switch { - // 如果参数未提供 - if (param.IsOptional) - { - // 如果参数是可选的,使用默认值 - args[i] = param.DefaultValue; - } - else if (param.ParameterType.IsValueType && Nullable.GetUnderlyingType(param.ParameterType) == null) - { - // 如果参数是非可空值类型,且未提供值,抛出异常 - throw new ArgumentException($"Required parameter '{param.Name}' is missing"); - } - else - { - // 对于引用类型或可空值类型,设置为 null - args[i] = null; - } - } + JToken result => result, + Task task => await task, + _ => throw new NotSupportedException() + }; + return response; } - response["result"] = func((JArray)@params) switch - { - JToken result => result, - Task task => await task, - _ => throw new NotSupportedException() - }; - return response; + throw new RpcException(RpcError.MethodNotFound.WithData(method)); } catch (FormatException ex) { @@ -341,33 +352,42 @@ private async Task ProcessRequestAsync(HttpContext context, JObject req { return CreateErrorResponse(request["id"], RpcError.InvalidParams.WithData(ex.Message)); } - catch (Exception ex) + catch (Exception ex) when (ex is not RpcException) { #if DEBUG return CreateErrorResponse(request["id"], RpcErrorFactory.NewCustomError(ex.HResult, ex.Message, ex.StackTrace)); #else - return CreateErrorResponse(request["id"], RpcErrorFactory.NewCustomError(ex.HResult, ex.Message)); + return CreateErrorResponse(request["id"], RpcErrorFactory.NewCustomError(ex.HResult, ex.Message)); #endif } } public void RegisterMethods(object handler) { - foreach (MethodInfo method in handler.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) + foreach (var method in handler.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { - RpcMethodAttribute attribute = method.GetCustomAttribute(); - if (attribute is null) continue; - string name = string.IsNullOrEmpty(attribute.Name) ? method.Name.ToLowerInvariant() : attribute.Name; - methods[name] = method.CreateDelegate>(handler); - } - } + var attribute = method.GetCustomAttribute(); + var attributeWithParams = method.GetCustomAttribute(); + if (attribute is null && attributeWithParams is null) continue; + if (attribute is not null && attributeWithParams is not null) throw new InvalidOperationException("Method cannot have both RpcMethodAttribute and RpcMethodWithParamsAttribute"); - private string GetJsonPropertyName(ParameterInfo param) - { - var attr = param.GetCustomAttribute(); - return attr != null ? attr.Name : param.Name; - } + if (attribute is not null) + { + var name = string.IsNullOrEmpty(attribute.Name) ? method.Name.ToLowerInvariant() : attribute.Name; + methods[name] = method.CreateDelegate>(handler); + } + + if (attributeWithParams is not null) + { + var name = string.IsNullOrEmpty(attributeWithParams.Name) ? method.Name.ToLowerInvariant() : attributeWithParams.Name; + var parameters = method.GetParameters().Select(p => p.ParameterType).ToArray(); + var delegateType = Expression.GetDelegateType(parameters.Concat([method.ReturnType]).ToArray()); + + _methodsWithParams[name] = Delegate.CreateDelegate(delegateType, handler, method); + } + } + } private object ConvertParameter(JToken token, Type targetType) { @@ -375,31 +395,77 @@ private object ConvertParameter(JToken token, Type targetType) { return token.ToString(); } - else if (targetType == typeof(int)) + + if (targetType == typeof(int)) { - return token.Value(); + return token.GetInt32(); } - else if (targetType == typeof(long)) + + if (targetType == typeof(long) || targetType == typeof(uint)) { - return token.Value(); + return token.GetNumber(); } - else if (targetType == typeof(double)) + + if (targetType == typeof(double)) { - return token.Value(); + return token.GetNumber(); } - else if (targetType == typeof(bool)) + + if (targetType == typeof(bool)) { - return token.Value(); + return token.GetBoolean(); } - else if (targetType == typeof(UInt160)) + + if (targetType == typeof(UInt160)) { - return UInt160.Parse(token.ToString()); + var value = token.AsString(); + if (UInt160.TryParse(value, out var scriptHash)) + { + return scriptHash; + } + + return Result.Ok_Or(() => value.ToScriptHash(system.Settings.AddressVersion), + RpcError.InvalidParams.WithData($"Invalid UInt160 Format: {token}")); } - // 添加其他类型的转换... - // 如果是复杂类型,可以使用 JSON 反序列化 - return token.ToObject(targetType); - } + if (targetType == typeof(UInt256)) + { + return Result.Ok_Or(() => UInt256.Parse(token.AsString()), + RpcError.InvalidParams.WithData($"Invalid UInt256 Format: {token}")); + } + if (targetType == typeof(ContractHashOrId)) + { + var value = token.AsString(); + if (int.TryParse(value, out var id)) + { + return new ContractHashOrId(id); + } + + if (UInt160.TryParse(value, out var hash)) + { + return new ContractHashOrId(hash); + } + + throw new RpcException(RpcError.InvalidParams.WithData($"Invalid contract hash or id Format: {token}")); + } + + if (targetType == typeof(BlockHashOrIndex)) + { + var value = token.AsString(); + if (uint.TryParse(value, out var index)) + { + return new BlockHashOrIndex(index); + } + + if (UInt256.TryParse(value, out var hash)) + { + return new BlockHashOrIndex(hash); + } + + throw new RpcException(RpcError.InvalidParams.WithData($"Invalid block hash or index Format: {token}")); + } + return null; + } } } diff --git a/src/Plugins/RpcServer/RpcServer.csproj b/src/Plugins/RpcServer/RpcServer.csproj index 0721fddabb..c5b72e7a61 100644 --- a/src/Plugins/RpcServer/RpcServer.csproj +++ b/src/Plugins/RpcServer/RpcServer.csproj @@ -20,8 +20,4 @@ - - - - diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs index ab5693937f..655b072351 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs @@ -16,6 +16,7 @@ using Neo.Json; using Neo.Ledger; using Neo.Network.P2P.Payloads; +using Neo.Plugins.RpcServer.Model; using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.UnitTests; @@ -40,7 +41,7 @@ public void TestGetBestBlockHash() b.Index = 100; snapshot.Commit(); - var result = _rpcServer.GetBestBlockHash([]); + var result = _rpcServer.GetBestBlockHash(); // Assert Assert.AreEqual(expectedHash.ToString(), result.AsString()); } @@ -53,8 +54,7 @@ public void TestGetBlockByHash() TestUtils.BlocksAdd(snapshot, block.Hash, block); snapshot.Commit(); - var parameters = new JArray(block.Hash.ToString(), false); - var result = _rpcServer.GetBlock(parameters); + var result = _rpcServer.GetBlock(new BlockHashOrIndex(block.Hash), false); var blockArr = Convert.FromBase64String(result.AsString()); var block2 = blockArr.AsSerializable(); block2.Transactions.ForEach(tx => @@ -71,8 +71,7 @@ public void TestGetBlockByIndex() TestUtils.BlocksAdd(snapshot, block.Hash, block); snapshot.Commit(); - var parameters = new JArray(block.Index, false); - var result = _rpcServer.GetBlock(parameters); + var result = _rpcServer.GetBlock(new BlockHashOrIndex(block.Index), false); var blockArr = Convert.FromBase64String(result.AsString()); var block2 = blockArr.AsSerializable(); block2.Transactions.ForEach(tx => @@ -85,7 +84,7 @@ public void TestGetBlockByIndex() public void TestGetBlockCount() { var expectedCount = 1; - var result = _rpcServer.GetBlockCount(new JArray()); + var result = _rpcServer.GetBlockCount(); Assert.AreEqual(expectedCount, result.AsNumber()); } @@ -93,7 +92,7 @@ public void TestGetBlockCount() public void TestGetBlockHeaderCount() { var expectedCount = 1; - var result = _rpcServer.GetBlockHeaderCount(new JArray()); + var result = _rpcServer.GetBlockHeaderCount(); Assert.AreEqual(expectedCount, result.AsNumber()); } @@ -106,7 +105,7 @@ public void TestGetBlockHash() // snapshot.Commit(); var reason = _neoSystem.Blockchain.Ask(block).Result; var expectedHash = block.Hash.ToString(); - var result = _rpcServer.GetBlockHash(new JArray(block.Index)); + var result = _rpcServer.GetBlockHash(block.Index); Assert.AreEqual(expectedHash, result.AsString()); } @@ -117,8 +116,7 @@ public void TestGetBlockHeader() var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); TestUtils.BlocksAdd(snapshot, block.Hash, block); snapshot.Commit(); - var parameters = new JArray(block.Hash.ToString(), true); - var result = _rpcServer.GetBlockHeader(parameters); + var result = _rpcServer.GetBlockHeader(new BlockHashOrIndex(block.Hash), true); var header = block.Header.ToJson(_neoSystem.Settings); header["confirmations"] = NativeContract.Ledger.CurrentIndex(snapshot) - block.Index + 1; Assert.AreEqual(header.ToString(), result.ToString()); @@ -131,7 +129,7 @@ public void TestGetContractState() var contractState = TestUtils.GetContract(); snapshot.AddContract(contractState.Hash, contractState); snapshot.Commit(); - var result = _rpcServer.GetContractState(new JArray(contractState.Hash.ToString())); + var result = _rpcServer.GetContractState(new ContractHashOrId(contractState.Hash)); Assert.AreEqual(contractState.ToJson().ToString(), result.ToString()); } @@ -144,7 +142,7 @@ public void TestGetRawMemPool() snapshot.Commit(); _neoSystem.MemPool.TryAdd(tx, snapshot); - var result = _rpcServer.GetRawMemPool(new JArray()); + var result = _rpcServer.GetRawMemPool(); Assert.IsTrue(((JArray)result).Any(p => p.AsString() == tx.Hash.ToString())); } @@ -155,9 +153,8 @@ public void TestGetRawTransaction() var snapshot = _neoSystem.GetSnapshotCache(); var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount); _neoSystem.MemPool.TryAdd(tx, snapshot); - var parameters = new JArray(tx.Hash.ToString(), true); snapshot.Commit(); - var result = _rpcServer.GetRawTransaction(parameters); + var result = _rpcServer.GetRawTransaction(tx.Hash, true); var json = Utility.TransactionToJson(tx, _neoSystem.Settings); Assert.AreEqual(json.ToString(), result.ToString()); @@ -174,7 +171,7 @@ public void TestGetStorage() TestUtils.StorageItemAdd(snapshot, contractState.Id, key, value); snapshot.Commit(); - var result = _rpcServer.GetStorage(new JArray(contractState.Hash.ToString(), Convert.ToBase64String(key))); + var result = _rpcServer.GetStorage(new ContractHashOrId(contractState.Hash), Convert.ToBase64String(key)); Assert.AreEqual(Convert.ToBase64String(value), result.AsString()); } @@ -188,7 +185,7 @@ public void TestFindStorage() var value = new byte[] { 0x02 }; TestUtils.StorageItemAdd(snapshot, contractState.Id, key, value); snapshot.Commit(); - var result = _rpcServer.FindStorage(new JArray(contractState.Hash.ToString(), Convert.ToBase64String(key), 0)); + var result = _rpcServer.FindStorage(new ContractHashOrId(contractState.Hash), Convert.ToBase64String(key), 0); var json = new JObject(); var jarr = new JArray(); @@ -210,7 +207,7 @@ public void TestGetTransactionHeight() TestUtils.BlocksAdd(snapshot, block.Hash, block); snapshot.Commit(); var tx = block.Transactions[0]; - var result = _rpcServer.GetTransactionHeight(new JArray(tx.Hash.ToString())); + var result = _rpcServer.GetTransactionHeight(tx.Hash); Assert.AreEqual(block.Index, result.AsNumber()); } @@ -218,7 +215,7 @@ public void TestGetTransactionHeight() public void TestGetNextBlockValidators() { var snapshot = _neoSystem.GetSnapshotCache(); - var result = _rpcServer.GetNextBlockValidators(new JArray()); + var result = _rpcServer.GetNextBlockValidators(); var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, _neoSystem.Settings.ValidatorsCount); var expected = validators.Select(p => @@ -235,7 +232,7 @@ public void TestGetNextBlockValidators() public void TestGetCandidates() { var snapshot = _neoSystem.GetSnapshotCache(); - var result = _rpcServer.GetCandidates(new JArray()); + var result = _rpcServer.GetCandidates(); var json = new JArray(); var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, _neoSystem.Settings.ValidatorsCount); snapshot.Commit(); @@ -256,7 +253,7 @@ public void TestGetCandidates() public void TestGetCommittee() { var snapshot = _neoSystem.GetSnapshotCache(); - var result = _rpcServer.GetCommittee(new JArray()); + var result = _rpcServer.GetCommittee(); var committee = NativeContract.NEO.GetCommittee(snapshot); var expected = new JArray(committee.Select(p => (JToken)p.ToString())); Assert.AreEqual(expected.ToString(), result.ToString()); @@ -265,7 +262,7 @@ public void TestGetCommittee() [TestMethod] public void TestGetNativeContracts() { - var result = _rpcServer.GetNativeContracts(new JArray()); + var result = _rpcServer.GetNativeContracts(); var contracts = new JArray(NativeContract.Contracts.Select(p => NativeContract.ContractManagement.GetContract(_neoSystem.GetSnapshotCache(), p.Hash).ToJson())); Assert.AreEqual(contracts.ToString(), result.ToString()); } @@ -278,10 +275,9 @@ public void TestGetBlockByUnknownIndex() TestUtils.BlocksAdd(snapshot, block.Hash, block); snapshot.Commit(); - var parameters = new JArray(int.MaxValue, false); try { - _rpcServer.GetBlock(parameters); + _rpcServer.GetBlock(new BlockHashOrIndex(int.MaxValue), false); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -298,10 +294,9 @@ public void TestGetBlockByUnknownHash() TestUtils.BlocksAdd(snapshot, block.Hash, block); snapshot.Commit(); - var parameters = new JArray(TestUtils.RandomUInt256().ToString(), false); try { - _rpcServer.GetBlock(parameters); + _rpcServer.GetBlock(new BlockHashOrIndex(TestUtils.RandomUInt256()), false); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -318,10 +313,9 @@ public void TestGetBlockByUnKnownIndex() TestUtils.BlocksAdd(snapshot, block.Hash, block); snapshot.Commit(); - var parameters = new JArray(int.MaxValue, false); try { - _rpcServer.GetBlock(parameters); + _rpcServer.GetBlock(new BlockHashOrIndex(int.MaxValue), false); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -338,10 +332,9 @@ public void TestGetBlockByUnKnownHash() TestUtils.BlocksAdd(snapshot, block.Hash, block); snapshot.Commit(); - var parameters = new JArray(TestUtils.RandomUInt256().ToString(), false); try { - _rpcServer.GetBlock(parameters); + _rpcServer.GetBlock(new BlockHashOrIndex(TestUtils.RandomUInt256()), false); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -357,7 +350,7 @@ public void TestGetBlockHashInvalidIndex() var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); TestUtils.BlocksAdd(snapshot, block.Hash, block); snapshot.Commit(); - Assert.ThrowsException(() => _rpcServer.GetBlockHash(new JArray(block.Index + 1))); + Assert.ThrowsException(() => _rpcServer.GetBlockHash(block.Index + 1)); } [TestMethod] @@ -367,7 +360,7 @@ public void TestGetContractStateUnknownContract() var randomHash = TestUtils.RandomUInt160(); try { - _rpcServer.GetContractState(new JArray(randomHash.ToString())); + _rpcServer.GetContractState(new ContractHashOrId(randomHash)); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -384,7 +377,7 @@ public void TestGetStorageUnknownContract() var key = new byte[] { 0x01 }; try { - _rpcServer.GetStorage(new JArray(randomHash.ToString(), Convert.ToBase64String(key))); + _rpcServer.GetStorage(new ContractHashOrId(randomHash), Convert.ToBase64String(key)); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -404,7 +397,7 @@ public void TestGetStorageUnknownStorageItem() var key = new byte[] { 0x01 }; try { - _rpcServer.GetStorage(new JArray(contractState.Hash.ToString(), Convert.ToBase64String(key))); + _rpcServer.GetStorage(new ContractHashOrId(contractState.Hash), Convert.ToBase64String(key)); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -419,7 +412,7 @@ public void TestGetTransactionHeightUnknownTransaction() var randomHash = TestUtils.RandomUInt256(); try { - _rpcServer.GetTransactionHeight(new JArray(randomHash.ToString())); + _rpcServer.GetTransactionHeight(randomHash); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -434,7 +427,7 @@ public void TestGetRawTransactionUnknownTransaction() var randomHash = TestUtils.RandomUInt256(); try { - _rpcServer.GetRawTransaction(new JArray(randomHash.ToString(), true)); + _rpcServer.GetRawTransaction(randomHash, true); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -443,160 +436,13 @@ public void TestGetRawTransactionUnknownTransaction() } } - [TestMethod] - public void TestGetBlockInvalidParams() - { - try - { - _rpcServer.GetBlock(new JArray("invalid_hash", false)); - Assert.Fail("Expected RpcException was not thrown."); - } - catch (RpcException ex) - { - Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); - } - catch (FormatException) - { - } - catch - { - Assert.Fail("Unexpected exception"); - } - } - - [TestMethod] - public void TestGetBlockHashInvalidParams() - { - try - { - _rpcServer.GetBlockHash(new JArray("invalid_index")); - Assert.Fail("Expected RpcException was not thrown."); - } - catch (RpcException ex) - { - Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); - } - } - - [TestMethod] - public void TestGetBlockHeaderInvalidParams() - { - try - { - _rpcServer.GetBlockHeader(new JArray("invalid_hash", true)); - Assert.Fail("Expected RpcException was not thrown."); - } - catch (RpcException ex) - { - Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); - } - catch (FormatException) - { - } - catch - { - Assert.Fail("Unexpected exception"); - } - } - - [TestMethod] - public void TestGetContractStateInvalidParams() - { - try - { - _rpcServer.GetContractState(new JArray("invalid_hash")); - Assert.Fail("Expected RpcException was not thrown."); - } - catch (RpcException ex) - { - Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); - } - catch (FormatException) - { - } - catch - { - Assert.Fail("Unexpected exception"); - } - } - - [TestMethod] - public void TestGetStorageInvalidParams() - { - try - { - _rpcServer.GetStorage(new JArray("invalid_hash", "invalid_key")); - Assert.Fail("Expected RpcException was not thrown."); - } - catch (RpcException ex) - { - Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); - } - catch (FormatException) - { - } - catch - { - Assert.Fail("Unexpected exception"); - } - } - - [TestMethod] - public void TestFindStorageInvalidParams() - { - try - { - _rpcServer.FindStorage(new JArray("invalid_hash", "invalid_prefix", "invalid_start")); - Assert.Fail("Expected RpcException was not thrown."); - } - catch (RpcException ex) - { - Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); - } - catch (FormatException) - { - } - catch - { - Assert.Fail("Unexpected exception"); - } - } - - [TestMethod] - public void TestGetTransactionHeightInvalidParams() - { - try - { - _rpcServer.GetTransactionHeight(new JArray("invalid_hash")); - Assert.Fail("Expected RpcException was not thrown."); - } - catch (RpcException ex) - { - Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); - } - } - - [TestMethod] - public void TestGetRawTransactionInvalidParams() - { - try - { - _rpcServer.GetRawTransaction(new JArray("invalid_hash", true)); - Assert.Fail("Expected RpcException was not thrown."); - } - catch (RpcException ex) - { - Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); - } - } - [TestMethod] public void TestInternalServerError() { _memoryStore.Reset(); try { - _rpcServer.GetCandidates(new JArray()); + _rpcServer.GetCandidates(); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -610,7 +456,7 @@ public void TestUnknownHeight() { try { - _rpcServer.GetBlockHash(new JArray(int.MaxValue)); + _rpcServer.GetBlockHash(int.MaxValue); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) From 9221d08887b7c0dbcfe8d23bd3ab0d05d00976c4 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 12 Aug 2024 00:48:11 +0800 Subject: [PATCH 03/16] Update src/Plugins/RpcServer/RpcMethodWithParamsAttribute.cs --- src/Plugins/RpcServer/RpcMethodWithParamsAttribute.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Plugins/RpcServer/RpcMethodWithParamsAttribute.cs b/src/Plugins/RpcServer/RpcMethodWithParamsAttribute.cs index fe044e8af7..c53dd1fd85 100644 --- a/src/Plugins/RpcServer/RpcMethodWithParamsAttribute.cs +++ b/src/Plugins/RpcServer/RpcMethodWithParamsAttribute.cs @@ -9,7 +9,6 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -using Org.BouncyCastle.Asn1; using System; namespace Neo.Plugins.RpcServer; From 4261abeeccad335972e085dc5c358158f9b1072a Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 12 Aug 2024 00:48:29 +0800 Subject: [PATCH 04/16] Delete src/Plugins/RpcServer/JsonPropertyNameAttribute.cs --- .../RpcServer/JsonPropertyNameAttribute.cs | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 src/Plugins/RpcServer/JsonPropertyNameAttribute.cs diff --git a/src/Plugins/RpcServer/JsonPropertyNameAttribute.cs b/src/Plugins/RpcServer/JsonPropertyNameAttribute.cs deleted file mode 100644 index e4d3da3696..0000000000 --- a/src/Plugins/RpcServer/JsonPropertyNameAttribute.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (C) 2015-2024 The Neo Project. -// -// JsonPropertyNameAttribute.cs file belongs to the neo project and is free -// software distributed under the MIT software license, see the -// accompanying file LICENSE in the main directory of the -// repository or http://www.opensource.org/licenses/mit-license.php -// for more details. -// -// Redistribution and use in source and binary forms with or without -// modifications are permitted. - -using System; - -namespace Neo.Plugins.RpcServer; - -[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] -public class JsonPropertyNameAttribute(string name) : Attribute -{ - public string Name { get; } = name; -} From e18b24b3906b7a9c221eec5c26baec2236570116 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Mon, 12 Aug 2024 11:33:50 +0800 Subject: [PATCH 05/16] udpate contract model --- ...tHashOrId.cs => ContractNameOrHashOrId.cs} | 20 ++++++++--- src/Plugins/RpcServer/RpcServer.Blockchain.cs | 33 ++++++++++--------- src/Plugins/RpcServer/RpcServer.cs | 11 +++++-- .../UT_RpcServer.Blockchain.cs | 12 +++---- 4 files changed, 47 insertions(+), 29 deletions(-) rename src/Plugins/RpcServer/Model/{ContractHashOrId.cs => ContractNameOrHashOrId.cs} (65%) diff --git a/src/Plugins/RpcServer/Model/ContractHashOrId.cs b/src/Plugins/RpcServer/Model/ContractNameOrHashOrId.cs similarity index 65% rename from src/Plugins/RpcServer/Model/ContractHashOrId.cs rename to src/Plugins/RpcServer/Model/ContractNameOrHashOrId.cs index 22ea5b8c8f..467ba556a6 100644 --- a/src/Plugins/RpcServer/Model/ContractHashOrId.cs +++ b/src/Plugins/RpcServer/Model/ContractNameOrHashOrId.cs @@ -1,6 +1,6 @@ // Copyright (C) 2015-2024 The Neo Project. // -// ContractHashOrId.cs file belongs to the neo project and is free +// ContractNameOrHashOrId.cs file belongs to the neo project and is free // software distributed under the MIT software license, see the // accompanying file LICENSE in the main directory of the // repository or http://www.opensource.org/licenses/mit-license.php @@ -11,22 +11,28 @@ namespace Neo.Plugins.RpcServer.Model; -public class ContractHashOrId +public class ContractNameOrHashOrId { private readonly object _value; - public ContractHashOrId(int id) + public ContractNameOrHashOrId(int id) { _value = id; } - public ContractHashOrId(UInt160 hash) + public ContractNameOrHashOrId(UInt160 hash) { _value = hash; } + public ContractNameOrHashOrId(string name) + { + _value = name; + } + public bool IsId => _value is int; public bool IsHash => _value is UInt160; + public bool IsName => _value is string; public int AsId() { @@ -41,4 +47,10 @@ public UInt160 AsHash() return hash; throw new RpcException(RpcError.InvalidParams.WithData($"Value {_value} is not a valid contract hash")); } + public string AsName() + { + if (_value is string name) + return name; + throw new RpcException(RpcError.InvalidParams.WithData($"Value {_value} is not a valid contract name")); + } } diff --git a/src/Plugins/RpcServer/RpcServer.Blockchain.cs b/src/Plugins/RpcServer/RpcServer.Blockchain.cs index 7b68da0163..81a06cfc47 100644 --- a/src/Plugins/RpcServer/RpcServer.Blockchain.cs +++ b/src/Plugins/RpcServer/RpcServer.Blockchain.cs @@ -18,6 +18,7 @@ using Neo.SmartContract.Native; using Neo.VM; using Neo.VM.Types; +using Neo.Wallets; using System; using System.Collections.Generic; using System.Linq; @@ -46,8 +47,7 @@ protected internal virtual JToken GetBestBlockHash() [RpcMethodWithParams] protected internal virtual JToken GetBlock(BlockHashOrIndex blockHashOrIndex, bool verbose = false) { - // JToken key = Result.Ok_Or(() => _params[0], RpcError.InvalidParams.WithData($"Invalid Block Hash or Index: {_params[0]}")); - // bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); + using var snapshot = system.GetSnapshotCache(); var block = blockHashOrIndex.IsIndex ? NativeContract.Ledger.GetBlock(snapshot, blockHashOrIndex.AsIndex()) : NativeContract.Ledger.GetBlock(snapshot, blockHashOrIndex.AsHash()); block.NotNull_Or(RpcError.UnknownBlock); @@ -139,18 +139,19 @@ protected internal virtual JToken GetBlockHeader(BlockHashOrIndex blockHashOrInd /// /// Gets the state of a contract by its ID or script hash or (only for native contracts) by case-insensitive name. /// - /// Contract script hash or the native contract id. + /// Contract name or script hash or the native contract id. /// The contract state in json format as a . [RpcMethodWithParams] - protected internal virtual JToken GetContractState(ContractHashOrId contractHashOrId) + protected internal virtual JToken GetContractState(ContractNameOrHashOrId contractNameOrHashOrId) { - if (contractHashOrId.IsId) + if (contractNameOrHashOrId.IsId) { - var contractState = NativeContract.ContractManagement.GetContractById(system.StoreView, contractHashOrId.AsId()); + var contractState = NativeContract.ContractManagement.GetContractById(system.StoreView, contractNameOrHashOrId.AsId()); return contractState.NotNull_Or(RpcError.UnknownContract).ToJson(); } - var contract = NativeContract.ContractManagement.GetContract(system.StoreView, contractHashOrId.AsHash()); + var hash = contractNameOrHashOrId.IsName ? ToScriptHash(contractNameOrHashOrId.AsName()) : contractNameOrHashOrId.AsHash(); + var contract = NativeContract.ContractManagement.GetContract(system.StoreView, hash); return contract.NotNull_Or(RpcError.UnknownContract).ToJson(); } @@ -216,23 +217,23 @@ protected internal virtual JToken GetRawTransaction(UInt256 hash, bool verbose = /// /// Gets the storage item by contract ID or script hash and key. /// - /// The contract ID or script hash. + /// The contract ID or script hash. /// The Base64-encoded storage key. /// The storage item as a . [RpcMethodWithParams] - protected internal virtual JToken GetStorage(ContractHashOrId contractHashOrId, string base64Key) + protected internal virtual JToken GetStorage(ContractNameOrHashOrId contractNameOrHashOrId, string base64Key) { using var snapshot = system.GetSnapshotCache(); int id; - if (contractHashOrId.IsHash) + if (contractNameOrHashOrId.IsHash) { - var hash = contractHashOrId.AsHash(); + var hash = contractNameOrHashOrId.AsHash(); var contract = NativeContract.ContractManagement.GetContract(snapshot, hash).NotNull_Or(RpcError.UnknownContract); id = contract.Id; } else { - id = contractHashOrId.AsId(); + id = contractNameOrHashOrId.AsId(); } var key = Convert.FromBase64String(base64Key); var item = snapshot.TryGet(new StorageKey @@ -253,18 +254,18 @@ protected internal virtual JToken GetStorage(ContractHashOrId contractHashOrId, /// /// The found storage items as a . [RpcMethodWithParams] - protected internal virtual JToken FindStorage(ContractHashOrId contractHashOrId, string base64KeyPrefix, int start = 0) + protected internal virtual JToken FindStorage(ContractNameOrHashOrId contractNameOrHashOrId, string base64KeyPrefix, int start = 0) { using var snapshot = system.GetSnapshotCache(); int id; - if (contractHashOrId.IsHash) + if (contractNameOrHashOrId.IsHash) { - ContractState contract = NativeContract.ContractManagement.GetContract(snapshot, contractHashOrId.AsHash()).NotNull_Or(RpcError.UnknownContract); + ContractState contract = NativeContract.ContractManagement.GetContract(snapshot, contractNameOrHashOrId.AsHash()).NotNull_Or(RpcError.UnknownContract); id = contract.Id; } else { - id = contractHashOrId.AsId(); + id = contractNameOrHashOrId.AsId(); } byte[] prefix = Result.Ok_Or(() => Convert.FromBase64String(base64KeyPrefix), RpcError.InvalidParams.WithData($"Invalid Base64 string{base64KeyPrefix}")); diff --git a/src/Plugins/RpcServer/RpcServer.cs b/src/Plugins/RpcServer/RpcServer.cs index bde8c45fa1..54c84ea7e0 100644 --- a/src/Plugins/RpcServer/RpcServer.cs +++ b/src/Plugins/RpcServer/RpcServer.cs @@ -434,17 +434,22 @@ private object ConvertParameter(JToken token, Type targetType) RpcError.InvalidParams.WithData($"Invalid UInt256 Format: {token}")); } - if (targetType == typeof(ContractHashOrId)) + if (targetType == typeof(ContractNameOrHashOrId)) { var value = token.AsString(); if (int.TryParse(value, out var id)) { - return new ContractHashOrId(id); + return new ContractNameOrHashOrId(id); } if (UInt160.TryParse(value, out var hash)) { - return new ContractHashOrId(hash); + return new ContractNameOrHashOrId(hash); + } + + if (value.Length > 0) + { + return new ContractNameOrHashOrId(value); } throw new RpcException(RpcError.InvalidParams.WithData($"Invalid contract hash or id Format: {token}")); diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs index 655b072351..2fbb2f6f3f 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs @@ -129,7 +129,7 @@ public void TestGetContractState() var contractState = TestUtils.GetContract(); snapshot.AddContract(contractState.Hash, contractState); snapshot.Commit(); - var result = _rpcServer.GetContractState(new ContractHashOrId(contractState.Hash)); + var result = _rpcServer.GetContractState(new ContractNameOrHashOrId(contractState.Hash)); Assert.AreEqual(contractState.ToJson().ToString(), result.ToString()); } @@ -171,7 +171,7 @@ public void TestGetStorage() TestUtils.StorageItemAdd(snapshot, contractState.Id, key, value); snapshot.Commit(); - var result = _rpcServer.GetStorage(new ContractHashOrId(contractState.Hash), Convert.ToBase64String(key)); + var result = _rpcServer.GetStorage(new ContractNameOrHashOrId(contractState.Hash), Convert.ToBase64String(key)); Assert.AreEqual(Convert.ToBase64String(value), result.AsString()); } @@ -185,7 +185,7 @@ public void TestFindStorage() var value = new byte[] { 0x02 }; TestUtils.StorageItemAdd(snapshot, contractState.Id, key, value); snapshot.Commit(); - var result = _rpcServer.FindStorage(new ContractHashOrId(contractState.Hash), Convert.ToBase64String(key), 0); + var result = _rpcServer.FindStorage(new ContractNameOrHashOrId(contractState.Hash), Convert.ToBase64String(key), 0); var json = new JObject(); var jarr = new JArray(); @@ -360,7 +360,7 @@ public void TestGetContractStateUnknownContract() var randomHash = TestUtils.RandomUInt160(); try { - _rpcServer.GetContractState(new ContractHashOrId(randomHash)); + _rpcServer.GetContractState(new ContractNameOrHashOrId(randomHash)); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -377,7 +377,7 @@ public void TestGetStorageUnknownContract() var key = new byte[] { 0x01 }; try { - _rpcServer.GetStorage(new ContractHashOrId(randomHash), Convert.ToBase64String(key)); + _rpcServer.GetStorage(new ContractNameOrHashOrId(randomHash), Convert.ToBase64String(key)); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -397,7 +397,7 @@ public void TestGetStorageUnknownStorageItem() var key = new byte[] { 0x01 }; try { - _rpcServer.GetStorage(new ContractHashOrId(contractState.Hash), Convert.ToBase64String(key)); + _rpcServer.GetStorage(new ContractNameOrHashOrId(contractState.Hash), Convert.ToBase64String(key)); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) From d0a663eb79b7ce5e36834206f2f1c87a59f3f246 Mon Sep 17 00:00:00 2001 From: Shargon Date: Mon, 12 Aug 2024 08:05:09 +0200 Subject: [PATCH 06/16] Update src/Plugins/RpcServer/Model/BlockHashOrIndex.cs --- src/Plugins/RpcServer/Model/BlockHashOrIndex.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Plugins/RpcServer/Model/BlockHashOrIndex.cs b/src/Plugins/RpcServer/Model/BlockHashOrIndex.cs index ce148a56e2..cc39167e59 100644 --- a/src/Plugins/RpcServer/Model/BlockHashOrIndex.cs +++ b/src/Plugins/RpcServer/Model/BlockHashOrIndex.cs @@ -13,7 +13,6 @@ namespace Neo.Plugins.RpcServer.Model; public class BlockHashOrIndex { - private readonly object _value; public BlockHashOrIndex(uint index) From 265674c2f5fc380d21dd92f00605885db82898cd Mon Sep 17 00:00:00 2001 From: Shargon Date: Mon, 12 Aug 2024 08:08:31 +0200 Subject: [PATCH 07/16] Apply suggestions from code review Remove comments --- src/Plugins/RpcServer/RpcServer.Blockchain.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Plugins/RpcServer/RpcServer.Blockchain.cs b/src/Plugins/RpcServer/RpcServer.Blockchain.cs index 7b68da0163..955ef1d5ca 100644 --- a/src/Plugins/RpcServer/RpcServer.Blockchain.cs +++ b/src/Plugins/RpcServer/RpcServer.Blockchain.cs @@ -46,8 +46,6 @@ protected internal virtual JToken GetBestBlockHash() [RpcMethodWithParams] protected internal virtual JToken GetBlock(BlockHashOrIndex blockHashOrIndex, bool verbose = false) { - // JToken key = Result.Ok_Or(() => _params[0], RpcError.InvalidParams.WithData($"Invalid Block Hash or Index: {_params[0]}")); - // bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); using var snapshot = system.GetSnapshotCache(); var block = blockHashOrIndex.IsIndex ? NativeContract.Ledger.GetBlock(snapshot, blockHashOrIndex.AsIndex()) : NativeContract.Ledger.GetBlock(snapshot, blockHashOrIndex.AsHash()); block.NotNull_Or(RpcError.UnknownBlock); From c256b6e8ec6d9ced3c3810f73471dc0646e9185c Mon Sep 17 00:00:00 2001 From: Shargon Date: Mon, 12 Aug 2024 08:11:03 +0200 Subject: [PATCH 08/16] Update src/Plugins/RpcServer/RpcServer.cs --- src/Plugins/RpcServer/RpcServer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Plugins/RpcServer/RpcServer.cs b/src/Plugins/RpcServer/RpcServer.cs index bde8c45fa1..387a06df1d 100644 --- a/src/Plugins/RpcServer/RpcServer.cs +++ b/src/Plugins/RpcServer/RpcServer.cs @@ -262,7 +262,6 @@ public async Task ProcessAsync(HttpContext context) await context.Response.WriteAsync(response.ToString(), Encoding.UTF8); } - private async Task ProcessRequestAsync(HttpContext context, JObject request) { if (!request.ContainsProperty("id")) return null; From a1fcf5e779305acac890d179260c5f7b871584ae Mon Sep 17 00:00:00 2001 From: Jimmy Date: Wed, 14 Aug 2024 11:06:24 +0800 Subject: [PATCH 09/16] fix warnings --- src/Plugins/RpcServer/Model/BlockHashOrIndex.cs | 2 +- src/Plugins/RpcServer/Model/ContractNameOrHashOrId.cs | 2 +- src/Plugins/RpcServer/RpcServer.Blockchain.cs | 8 +++----- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/Plugins/RpcServer/Model/BlockHashOrIndex.cs b/src/Plugins/RpcServer/Model/BlockHashOrIndex.cs index 1eca914f1f..fe35d9c3e5 100644 --- a/src/Plugins/RpcServer/Model/BlockHashOrIndex.cs +++ b/src/Plugins/RpcServer/Model/BlockHashOrIndex.cs @@ -29,7 +29,7 @@ public BlockHashOrIndex(UInt256 hash) public bool IsIndex => _value is uint; - public static bool TryParse(string value, [NotNullWhen(true)] out BlockHashOrIndex? blockHashOrIndex) + public static bool TryParse(string value, [NotNullWhen(true)] out BlockHashOrIndex blockHashOrIndex) { if (uint.TryParse(value, out var index)) { diff --git a/src/Plugins/RpcServer/Model/ContractNameOrHashOrId.cs b/src/Plugins/RpcServer/Model/ContractNameOrHashOrId.cs index e9f011fb14..1f821854c2 100644 --- a/src/Plugins/RpcServer/Model/ContractNameOrHashOrId.cs +++ b/src/Plugins/RpcServer/Model/ContractNameOrHashOrId.cs @@ -36,7 +36,7 @@ public ContractNameOrHashOrId(string name) public bool IsHash => _value is UInt160; public bool IsName => _value is string; - public static bool TryParse(string value, [NotNullWhen(true)] out ContractNameOrHashOrId? contractNameOrHashOrId) + public static bool TryParse(string value, [NotNullWhen(true)] out ContractNameOrHashOrId contractNameOrHashOrId) { if (int.TryParse(value, out var id)) { diff --git a/src/Plugins/RpcServer/RpcServer.Blockchain.cs b/src/Plugins/RpcServer/RpcServer.Blockchain.cs index a3699b8084..446f1b4f16 100644 --- a/src/Plugins/RpcServer/RpcServer.Blockchain.cs +++ b/src/Plugins/RpcServer/RpcServer.Blockchain.cs @@ -245,11 +245,9 @@ protected internal virtual JToken GetStorage(ContractNameOrHashOrId contractName /// /// Finds storage items by contract ID or script hash and prefix. /// - /// - /// An array containing the contract ID or script hash as the first element, - /// the Base64-encoded storage key prefix as the second element, - /// and an optional start index as the third element. - /// + /// The contract ID (int) or script hash (UInt160). + /// The Base64-encoded storage key prefix. + /// The start index. /// The found storage items as a . [RpcMethodWithParams] protected internal virtual JToken FindStorage(ContractNameOrHashOrId contractNameOrHashOrId, string base64KeyPrefix, int start = 0) From 2c2e19331b63a5108b7d2b8b913bcade3d812ab1 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Wed, 14 Aug 2024 15:05:24 +0800 Subject: [PATCH 10/16] ensure it can load both true/false and 1/0 --- src/Plugins/RpcServer/RpcServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugins/RpcServer/RpcServer.cs b/src/Plugins/RpcServer/RpcServer.cs index 84f3160c69..0f83996e8a 100644 --- a/src/Plugins/RpcServer/RpcServer.cs +++ b/src/Plugins/RpcServer/RpcServer.cs @@ -407,7 +407,7 @@ private object ConvertParameter(JToken token, Type targetType) if (targetType == typeof(bool)) { - return token.GetBoolean(); + return token.AsBoolean(); } if (targetType == typeof(UInt160)) From f726436d2d60a34f26ac733976fd8662238c4c90 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Wed, 14 Aug 2024 22:44:27 +0800 Subject: [PATCH 11/16] optimize the pr and add unit tests --- src/Plugins/RpcServer/ParameterConverter.cs | 140 +++++++++++++ src/Plugins/RpcServer/RpcServer.cs | 85 +------- .../UT_Parameters.cs | 195 ++++++++++++++++++ 3 files changed, 344 insertions(+), 76 deletions(-) create mode 100644 src/Plugins/RpcServer/ParameterConverter.cs create mode 100644 tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs diff --git a/src/Plugins/RpcServer/ParameterConverter.cs b/src/Plugins/RpcServer/ParameterConverter.cs new file mode 100644 index 0000000000..f1bf269cb8 --- /dev/null +++ b/src/Plugins/RpcServer/ParameterConverter.cs @@ -0,0 +1,140 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ParameterConverter.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Plugins.RpcServer.Model; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using JToken = Neo.Json.JToken; + +namespace Neo.Plugins.RpcServer; + +public static class ParameterConverter +{ + private static readonly Dictionary> _conversionStrategies; + + static ParameterConverter() + { + _conversionStrategies = new Dictionary> + { + { typeof(string), token => Result.Ok_Or(token.AsString, CreateInvalidParamError(token)) }, + { typeof(byte), ConvertNumeric }, + { typeof(sbyte), ConvertNumeric }, + { typeof(short), ConvertNumeric }, + { typeof(ushort), ConvertNumeric }, + { typeof(int), ConvertNumeric }, + { typeof(uint), ConvertNumeric }, + { typeof(long), ConvertNumeric }, + { typeof(ulong), ConvertNumeric }, + { typeof(double), token => Result.Ok_Or(token.AsNumber, CreateInvalidParamError(token)) }, + { typeof(bool), token => Result.Ok_Or(token.AsBoolean, CreateInvalidParamError(token)) }, + { typeof(UInt256), ConvertUInt256 }, + { typeof(ContractNameOrHashOrId), ConvertContractNameOrHashOrId }, + { typeof(BlockHashOrIndex), ConvertBlockHashOrIndex } + }; + } + + internal static object ConvertParameter(JToken token, Type targetType) + { + if (_conversionStrategies.TryGetValue(targetType, out var conversionStrategy)) + return conversionStrategy(token); + throw new RpcException(RpcError.InvalidParams.WithData($"Unsupported parameter type: {targetType}")); + } + + private static object ConvertNumeric(JToken token) where T : struct + { + if (TryConvertDoubleToNumericType(token, out var result)) + { + return result; + } + + throw new RpcException(CreateInvalidParamError(token)); + } + + private static bool TryConvertDoubleToNumericType(JToken token, out T result) where T : struct + { + result = default; + try + { + var value = token.AsNumber(); + var minValue = Convert.ToDouble(typeof(T).GetField("MinValue").GetValue(null)); + var maxValue = Convert.ToDouble(typeof(T).GetField("MaxValue").GetValue(null)); + + if (value < minValue || value > maxValue) + { + return false; + } + + if (!typeof(T).IsFloatingPoint() && Math.Floor(value) != value) + { + return false; + } + + result = (T)Convert.ChangeType(value, typeof(T)); + return true; + } + catch + { + return false; + } + } + + internal static object ConvertUInt160(JToken token, byte addressVersion) + { + var value = token.AsString(); + if (UInt160.TryParse(value, out var scriptHash)) + { + return scriptHash; + } + return Result.Ok_Or(() => value.ToScriptHash(addressVersion), + RpcError.InvalidParams.WithData($"Invalid UInt160 Format: {token}")); + } + + private static object ConvertUInt256(JToken token) + { + if (UInt256.TryParse(token.AsString(), out var hash)) + { + return hash; + } + throw new RpcException(RpcError.InvalidParams.WithData($"Invalid UInt256 Format: {token}")); + } + + private static object ConvertContractNameOrHashOrId(JToken token) + { + if (ContractNameOrHashOrId.TryParse(token.AsString(), out var contractNameOrHashOrId)) + { + return contractNameOrHashOrId; + } + throw new RpcException(RpcError.InvalidParams.WithData($"Invalid contract hash or id Format: {token}")); + } + + private static object ConvertBlockHashOrIndex(JToken token) + { + if (BlockHashOrIndex.TryParse(token.AsString(), out var blockHashOrIndex)) + { + return blockHashOrIndex; + } + throw new RpcException(RpcError.InvalidParams.WithData($"Invalid block hash or index Format: {token}")); + } + + private static RpcError CreateInvalidParamError(JToken token) + { + return RpcError.InvalidParams.WithData($"Invalid {typeof(T)} value: {token}"); + } +} + +public static class TypeExtensions +{ + public static bool IsFloatingPoint(this Type type) + { + return type == typeof(float) || type == typeof(double) || type == typeof(decimal); + } +} diff --git a/src/Plugins/RpcServer/RpcServer.cs b/src/Plugins/RpcServer/RpcServer.cs index 0f83996e8a..450139d10f 100644 --- a/src/Plugins/RpcServer/RpcServer.cs +++ b/src/Plugins/RpcServer/RpcServer.cs @@ -301,9 +301,16 @@ private async Task ProcessRequestAsync(HttpContext context, JObject req { try { - args[i] = ConvertParameter(jsonParameters[i], param.ParameterType); + if (param.ParameterType == typeof(UInt160)) + { + args[i] = ParameterConverter.ConvertUInt160(jsonParameters[i], system.Settings.AddressVersion); + } + else + { + args[i] = ParameterConverter.ConvertParameter(jsonParameters[i], param.ParameterType); + } } - catch (Exception e) + catch (Exception e) when (e is not RpcException) { throw new ArgumentException($"Invalid value for parameter '{param.Name}'", e); } @@ -380,79 +387,5 @@ public void RegisterMethods(object handler) } } } - - private object ConvertParameter(JToken token, Type targetType) - { - - - if (targetType == typeof(string)) - { - return token.ToString(); - } - - if (targetType == typeof(int)) - { - return token.GetInt32(); - } - - if (targetType == typeof(long) || targetType == typeof(uint)) - { - return token.GetNumber(); - } - - if (targetType == typeof(double)) - { - return token.GetNumber(); - } - - if (targetType == typeof(bool)) - { - return token.AsBoolean(); - } - - if (targetType == typeof(UInt160)) - { - var value = token.AsString(); - if (UInt160.TryParse(value, out var scriptHash)) - { - return scriptHash; - } - - return Result.Ok_Or(() => value.ToScriptHash(system.Settings.AddressVersion), - RpcError.InvalidParams.WithData($"Invalid UInt160 Format: {token}")); - } - - if (targetType == typeof(UInt256)) - { - - if (UInt256.TryParse(token.AsString(), out var hash)) - { - return hash; - } - - throw new RpcException(RpcError.InvalidParams.WithData($"Invalid UInt256 Format: {token}")); - } - - if (targetType == typeof(ContractNameOrHashOrId)) - { - if (ContractNameOrHashOrId.TryParse(token.AsString(), out var contractNameOrHashOrId)) - { - return contractNameOrHashOrId; - } - - throw new RpcException(RpcError.InvalidParams.WithData($"Invalid contract hash or id Format: {token}")); - } - - if (targetType == typeof(BlockHashOrIndex)) - { - if (BlockHashOrIndex.TryParse(token.AsString(), out var blockHashOrIndex)) - { - return blockHashOrIndex; - } - - throw new RpcException(RpcError.InvalidParams.WithData($"Invalid block hash or index Format: {token}")); - } - return null; - } } } diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs new file mode 100644 index 0000000000..d4c56e747f --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs @@ -0,0 +1,195 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Parameters.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Json; +using Neo.Plugins.RpcServer.Model; +using Neo.UnitTests; +using Neo.Wallets; + +namespace Neo.Plugins.RpcServer.Tests; + +// ConvertParameter + +[TestClass] +public class UT_Parameters +{ + private NeoSystem _neoSystem; + + [TestInitialize] + public void TestSetup() + { + var _neoSystem = new NeoSystem(TestProtocolSettings.Default, new TestBlockchain.StoreProvider()); + } + + [TestMethod] + public void TestTryParse_ContractNameOrHashOrId() + { + Assert.IsTrue(ContractNameOrHashOrId.TryParse("1", out var contractNameOrHashOrId)); + Assert.IsTrue(contractNameOrHashOrId.IsId); + Assert.IsTrue(ContractNameOrHashOrId.TryParse("0x1234567890abcdef1234567890abcdef12345678", out contractNameOrHashOrId)); + Assert.IsTrue(contractNameOrHashOrId.IsHash); + Assert.IsTrue(ContractNameOrHashOrId.TryParse("test", out contractNameOrHashOrId)); + Assert.IsTrue(contractNameOrHashOrId.IsName); + Assert.IsFalse(ContractNameOrHashOrId.TryParse("", out _)); + + JToken token = 1; + Assert.AreEqual(1, ((ContractNameOrHashOrId)ParameterConverter.ConvertParameter(token, typeof(ContractNameOrHashOrId))).AsId()); + + JToken token2 = "1"; + Assert.AreEqual(1, ((ContractNameOrHashOrId)ParameterConverter.ConvertParameter(token2, typeof(ContractNameOrHashOrId))).AsId()); + + JToken token3 = "0x1234567890abcdef1234567890abcdef12345678"; + Assert.AreEqual(UInt160.Parse("0x1234567890abcdef1234567890abcdef12345678"), ((ContractNameOrHashOrId)ParameterConverter.ConvertParameter(token3, typeof(ContractNameOrHashOrId))).AsHash()); + + JToken token4 = "0xabc"; + Assert.ThrowsException(() => ((ContractNameOrHashOrId)ParameterConverter.ConvertParameter(token4, typeof(ContractNameOrHashOrId))).AsHash()); + + + } + + [TestMethod] + public void TestTryParse_BlockHashOrIndex() + { + Assert.IsTrue(BlockHashOrIndex.TryParse("1", out var blockHashOrIndex)); + Assert.IsTrue(blockHashOrIndex.IsIndex); + Assert.AreEqual(1u, blockHashOrIndex.AsIndex()); + Assert.IsTrue(BlockHashOrIndex.TryParse("0x761a9bb72ca2a63984db0cc43f943a2a25e464f62d1a91114c2b6fbbfd24b51d", out blockHashOrIndex)); + Assert.AreEqual(UInt256.Parse("0x761a9bb72ca2a63984db0cc43f943a2a25e464f62d1a91114c2b6fbbfd24b51d"), blockHashOrIndex.AsHash()); + Assert.IsFalse(BlockHashOrIndex.TryParse("", out _)); + + JToken token = 1; + Assert.AreEqual(1u, ((BlockHashOrIndex)ParameterConverter.ConvertParameter(token, typeof(BlockHashOrIndex))).AsIndex()); + + JToken token2 = -1; + Assert.ThrowsException(() => ((BlockHashOrIndex)ParameterConverter.ConvertParameter(token2, typeof(BlockHashOrIndex))).AsIndex()); + + JToken token3 = "1"; + Assert.AreEqual(1u, ((BlockHashOrIndex)ParameterConverter.ConvertParameter(token3, typeof(BlockHashOrIndex))).AsIndex()); + + JToken token4 = "-1"; + Assert.ThrowsException(() => ((BlockHashOrIndex)ParameterConverter.ConvertParameter(token4, typeof(BlockHashOrIndex))).AsIndex()); + + JToken token5 = "0x761a9bb72ca2a63984db0cc43f943a2a25e464f62d1a91114c2b6fbbfd24b51d"; + Assert.AreEqual(UInt256.Parse("0x761a9bb72ca2a63984db0cc43f943a2a25e464f62d1a91114c2b6fbbfd24b51d"), ((BlockHashOrIndex)ParameterConverter.ConvertParameter(token5, typeof(BlockHashOrIndex))).AsHash()); + + JToken token6 = "761a9bb72ca2a63984db0cc43f943a2a25e464f62d1a91114c2b6fbbfd24b51d"; + Assert.AreEqual(UInt256.Parse("0x761a9bb72ca2a63984db0cc43f943a2a25e464f62d1a91114c2b6fbbfd24b51d"), ((BlockHashOrIndex)ParameterConverter.ConvertParameter(token6, typeof(BlockHashOrIndex))).AsHash()); + + JToken token7 = "0xabc"; + Assert.ThrowsException(() => ((BlockHashOrIndex)ParameterConverter.ConvertParameter(token7, typeof(BlockHashOrIndex))).AsHash()); + } + + [TestMethod] + public void TestUInt160() + { + JToken token = "0x1234567890abcdef1234567890abcdef12345678"; + Assert.AreEqual(UInt160.Parse("0x1234567890abcdef1234567890abcdef12345678"), ParameterConverter.ConvertUInt160(token, TestProtocolSettings.Default.AddressVersion)); + + JToken token2 = "0xabc"; + Assert.ThrowsException(() => ParameterConverter.ConvertUInt160(token2, TestProtocolSettings.Default.AddressVersion)); + + JToken token3 = "NdtB8RXRmJ7Nhw1FPTm7E6HoDZGnDw37nf"; + Assert.AreEqual("NdtB8RXRmJ7Nhw1FPTm7E6HoDZGnDw37nf".ToScriptHash(TestProtocolSettings.Default.AddressVersion), ParameterConverter.ConvertUInt160(token3, TestProtocolSettings.Default.AddressVersion)); + } + + [TestMethod] + public void TestUInt256() + { + JToken token = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; + Assert.AreEqual(UInt256.Parse("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), ParameterConverter.ConvertParameter(token, typeof(UInt256))); + + JToken token2 = "0xabc"; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(UInt256))); + } + + [TestMethod] + public void TestInteger() + { + JToken token = 1; + Assert.AreEqual(1, ParameterConverter.ConvertParameter(token, typeof(int))); + Assert.AreEqual((long)1, ParameterConverter.ConvertParameter(token, typeof(long))); + Assert.AreEqual((uint)1, ParameterConverter.ConvertParameter(token, typeof(uint))); + Assert.AreEqual((ulong)1, ParameterConverter.ConvertParameter(token, typeof(ulong))); + Assert.AreEqual((short)1, ParameterConverter.ConvertParameter(token, typeof(short))); + Assert.AreEqual((ushort)1, ParameterConverter.ConvertParameter(token, typeof(ushort))); + Assert.AreEqual((byte)1, ParameterConverter.ConvertParameter(token, typeof(byte))); + Assert.AreEqual((sbyte)1, ParameterConverter.ConvertParameter(token, typeof(sbyte))); + + JToken token2 = 1.1; + + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(int))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(long))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(uint))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(ulong))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(short))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(ushort))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(byte))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(sbyte))); + + JToken token3 = "1"; + + Assert.AreEqual((int)1, ParameterConverter.ConvertParameter(token3, typeof(int))); + Assert.AreEqual((long)1, ParameterConverter.ConvertParameter(token3, typeof(long))); + Assert.AreEqual((uint)1, ParameterConverter.ConvertParameter(token3, typeof(uint))); + Assert.AreEqual((ulong)1, ParameterConverter.ConvertParameter(token3, typeof(ulong))); + Assert.AreEqual((short)1, ParameterConverter.ConvertParameter(token3, typeof(short))); + Assert.AreEqual((ushort)1, ParameterConverter.ConvertParameter(token3, typeof(ushort))); + Assert.AreEqual((byte)1, ParameterConverter.ConvertParameter(token3, typeof(byte))); + Assert.AreEqual((sbyte)1, ParameterConverter.ConvertParameter(token3, typeof(sbyte))); + + JToken token4 = "1.1"; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token4, typeof(int))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token4, typeof(long))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token4, typeof(uint))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token4, typeof(ulong))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token4, typeof(short))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token4, typeof(ushort))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token4, typeof(byte))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token4, typeof(sbyte))); + + JToken token5 = "abc"; + + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token5, typeof(int))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token5, typeof(long))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token5, typeof(uint))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token5, typeof(ulong))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token5, typeof(short))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token5, typeof(ushort))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token5, typeof(byte))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token5, typeof(sbyte))); + + JToken token6 = -1; + + Assert.AreEqual(-1, ParameterConverter.ConvertParameter(token6, typeof(int))); + Assert.AreEqual((long)-1, ParameterConverter.ConvertParameter(token6, typeof(long))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token6, typeof(uint))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token6, typeof(ulong))); + Assert.AreEqual((short)-1, ParameterConverter.ConvertParameter(token6, typeof(short))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token6, typeof(ushort))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token6, typeof(byte))); + Assert.AreEqual((sbyte)-1, ParameterConverter.ConvertParameter(token6, typeof(sbyte))); + } + + [TestMethod] + public void TestBoolean() + { + JToken token = true; + Assert.AreEqual(true, ParameterConverter.ConvertParameter(token, typeof(bool))); + JToken token2 = false; + Assert.AreEqual(false, ParameterConverter.ConvertParameter(token2, typeof(bool))); + JToken token6 = 1; + Assert.AreEqual(true, ParameterConverter.ConvertParameter(token6, typeof(bool))); + JToken token7 = 0; + Assert.AreEqual(false, ParameterConverter.ConvertParameter(token7, typeof(bool))); + } + +} From 30d3b55c4434320a7173b2d70ed68ad33d45c13a Mon Sep 17 00:00:00 2001 From: Jimmy Date: Thu, 15 Aug 2024 01:46:01 +0800 Subject: [PATCH 12/16] add more tests and check the safe max value and safe min value --- src/Plugins/RpcServer/ParameterConverter.cs | 17 +- src/Plugins/RpcServer/RpcServer.cs | 2 - .../UT_Parameters.cs | 231 ++++++++++++++++++ 3 files changed, 244 insertions(+), 6 deletions(-) diff --git a/src/Plugins/RpcServer/ParameterConverter.cs b/src/Plugins/RpcServer/ParameterConverter.cs index f1bf269cb8..7d9b754f7c 100644 --- a/src/Plugins/RpcServer/ParameterConverter.cs +++ b/src/Plugins/RpcServer/ParameterConverter.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Json; using Neo.Plugins.RpcServer.Model; using Neo.Wallets; using System; @@ -19,11 +20,11 @@ namespace Neo.Plugins.RpcServer; public static class ParameterConverter { - private static readonly Dictionary> _conversionStrategies; + private static readonly Dictionary> s_conversionStrategies; static ParameterConverter() { - _conversionStrategies = new Dictionary> + s_conversionStrategies = new Dictionary> { { typeof(string), token => Result.Ok_Or(token.AsString, CreateInvalidParamError(token)) }, { typeof(byte), ConvertNumeric }, @@ -44,7 +45,7 @@ static ParameterConverter() internal static object ConvertParameter(JToken token, Type targetType) { - if (_conversionStrategies.TryGetValue(targetType, out var conversionStrategy)) + if (s_conversionStrategies.TryGetValue(targetType, out var conversionStrategy)) return conversionStrategy(token); throw new RpcException(RpcError.InvalidParams.WithData($"Unsupported parameter type: {targetType}")); } @@ -73,7 +74,7 @@ private static bool TryConvertDoubleToNumericType(JToken token, out T result) return false; } - if (!typeof(T).IsFloatingPoint() && Math.Floor(value) != value) + if (!typeof(T).IsFloatingPoint() && !IsValidInteger(value)) { return false; } @@ -87,6 +88,14 @@ private static bool TryConvertDoubleToNumericType(JToken token, out T result) } } + private static bool IsValidInteger(double value) + { + // Integer values are safe if they are within the range of MIN_SAFE_INTEGER and MAX_SAFE_INTEGER + if (value < JNumber.MIN_SAFE_INTEGER || value > JNumber.MAX_SAFE_INTEGER) + return false; + return Math.Abs(value % 1) <= double.Epsilon; + } + internal static object ConvertUInt160(JToken token, byte addressVersion) { var value = token.AsString(); diff --git a/src/Plugins/RpcServer/RpcServer.cs b/src/Plugins/RpcServer/RpcServer.cs index 450139d10f..c90bbad539 100644 --- a/src/Plugins/RpcServer/RpcServer.cs +++ b/src/Plugins/RpcServer/RpcServer.cs @@ -18,8 +18,6 @@ using Microsoft.Extensions.DependencyInjection; using Neo.Json; using Neo.Network.P2P; -using Neo.Plugins.RpcServer.Model; -using Neo.Wallets; using System; using System.Collections.Generic; using System.IO; diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs index d4c56e747f..377ee693a6 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs @@ -14,6 +14,7 @@ using Neo.Plugins.RpcServer.Model; using Neo.UnitTests; using Neo.Wallets; +using System; namespace Neo.Plugins.RpcServer.Tests; @@ -192,4 +193,234 @@ public void TestBoolean() Assert.AreEqual(false, ParameterConverter.ConvertParameter(token7, typeof(bool))); } + [TestMethod] + public void TestNumericTypeConversions() + { + // Test integer conversions + TestIntegerConversions(); + + // Test byte conversions + TestByteConversions(); + + // Test sbyte conversions + TestSByteConversions(); + + // Test short conversions + TestShortConversions(); + + // Test ushort conversions + TestUShortConversions(); + + // Test uint conversions + TestUIntConversions(); + + // Test long conversions + TestLongConversions(); + + // Test ulong conversions + TestULongConversions(); + } + + private void TestIntegerConversions() + { + // Test max value + JToken maxToken = int.MaxValue; + Assert.AreEqual(int.MaxValue, ParameterConverter.ConvertParameter(maxToken, typeof(int))); + + // Test min value + JToken minToken = int.MinValue; + Assert.AreEqual(int.MinValue, ParameterConverter.ConvertParameter(minToken, typeof(int))); + + // Test overflow + JToken overflowToken = (long)int.MaxValue + 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(overflowToken, typeof(int))); + + // Test underflow + JToken underflowToken = (long)int.MinValue - 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(underflowToken, typeof(int))); + } + + private void TestByteConversions() + { + // Test max value + JToken maxToken = byte.MaxValue; + Assert.AreEqual(byte.MaxValue, ParameterConverter.ConvertParameter(maxToken, typeof(byte))); + + // Test min value + JToken minToken = byte.MinValue; + Assert.AreEqual(byte.MinValue, ParameterConverter.ConvertParameter(minToken, typeof(byte))); + + // Test overflow + JToken overflowToken = (int)byte.MaxValue + 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(overflowToken, typeof(byte))); + + // Test underflow + JToken underflowToken = -1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(underflowToken, typeof(byte))); + } + + private void TestSByteConversions() + { + // Test max value + JToken maxToken = sbyte.MaxValue; + Assert.AreEqual(sbyte.MaxValue, ParameterConverter.ConvertParameter(maxToken, typeof(sbyte))); + + // Test min value + JToken minToken = sbyte.MinValue; + Assert.AreEqual(sbyte.MinValue, ParameterConverter.ConvertParameter(minToken, typeof(sbyte))); + + // Test overflow + JToken overflowToken = (int)sbyte.MaxValue + 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(overflowToken, typeof(sbyte))); + + // Test underflow + JToken underflowToken = (int)sbyte.MinValue - 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(underflowToken, typeof(sbyte))); + } + + private void TestShortConversions() + { + // Test max value + JToken maxToken = short.MaxValue; + Assert.AreEqual(short.MaxValue, ParameterConverter.ConvertParameter(maxToken, typeof(short))); + + // Test min value + JToken minToken = short.MinValue; + Assert.AreEqual(short.MinValue, ParameterConverter.ConvertParameter(minToken, typeof(short))); + + // Test overflow + JToken overflowToken = (int)short.MaxValue + 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(overflowToken, typeof(short))); + + // Test underflow + JToken underflowToken = (int)short.MinValue - 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(underflowToken, typeof(short))); + } + + private void TestUShortConversions() + { + // Test max value + JToken maxToken = ushort.MaxValue; + Assert.AreEqual(ushort.MaxValue, ParameterConverter.ConvertParameter(maxToken, typeof(ushort))); + + // Test min value + JToken minToken = ushort.MinValue; + Assert.AreEqual(ushort.MinValue, ParameterConverter.ConvertParameter(minToken, typeof(ushort))); + + // Test overflow + JToken overflowToken = (int)ushort.MaxValue + 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(overflowToken, typeof(ushort))); + + // Test underflow + JToken underflowToken = -1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(underflowToken, typeof(ushort))); + } + + private void TestUIntConversions() + { + // Test max value + JToken maxToken = uint.MaxValue; + Assert.AreEqual(uint.MaxValue, ParameterConverter.ConvertParameter(maxToken, typeof(uint))); + + // Test min value + JToken minToken = uint.MinValue; + Assert.AreEqual(uint.MinValue, ParameterConverter.ConvertParameter(minToken, typeof(uint))); + + // Test overflow + JToken overflowToken = (ulong)uint.MaxValue + 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(overflowToken, typeof(uint))); + + // Test underflow + JToken underflowToken = -1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(underflowToken, typeof(uint))); + } + + private void TestLongConversions() + { + // Test max value + JToken maxToken = JNumber.MAX_SAFE_INTEGER; + Assert.AreEqual(JNumber.MAX_SAFE_INTEGER, ParameterConverter.ConvertParameter(maxToken, typeof(long))); + + // Test min value + JToken minToken = JNumber.MIN_SAFE_INTEGER; + Assert.AreEqual(JNumber.MIN_SAFE_INTEGER, ParameterConverter.ConvertParameter(minToken, typeof(long))); + + // Test overflow + JToken overflowToken = $"{JNumber.MAX_SAFE_INTEGER}0"; // This will be parsed as a string, causing overflow + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(overflowToken, typeof(long))); + + // Test underflow + JToken underflowToken = $"-{JNumber.MIN_SAFE_INTEGER}0"; // This will be parsed as a string, causing underflow + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(underflowToken, typeof(long))); + } + + private void TestULongConversions() + { + // Test max value + JToken maxToken = JNumber.MAX_SAFE_INTEGER; + Assert.AreEqual((ulong)JNumber.MAX_SAFE_INTEGER, ParameterConverter.ConvertParameter(maxToken, typeof(ulong))); + + // Test min value + JToken minToken = ulong.MinValue; + Assert.AreEqual(ulong.MinValue, ParameterConverter.ConvertParameter(minToken, typeof(ulong))); + + // Test overflow + JToken overflowToken = $"{JNumber.MAX_SAFE_INTEGER}0"; // This will be parsed as a string, causing overflow + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(overflowToken, typeof(ulong))); + + // Test underflow + JToken underflowToken = -1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(underflowToken, typeof(ulong))); + } + + [TestMethod] + public void TestAdditionalEdgeCases() + { + // Test conversion of fractional values slightly less than integers + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(0.9999999999999, typeof(int))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(-0.0000000000001, typeof(int))); + + // Test conversion of very large double values to integer types + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(double.MaxValue, typeof(long))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(double.MinValue, typeof(long))); + + // Test conversion of NaN and Infinity + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(double.NaN, typeof(int))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(double.PositiveInfinity, typeof(long))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(double.NegativeInfinity, typeof(ulong))); + + // Test conversion of string representations of numbers + Assert.AreEqual(int.MaxValue, ParameterConverter.ConvertParameter(int.MaxValue.ToString(), typeof(int))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(long.MinValue.ToString(), typeof(long))); + + // Test conversion of hexadecimal string representations + Assert.ThrowsException(() => ParameterConverter.ConvertParameter("0xFF", typeof(int))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter("0x100", typeof(byte))); + + // Test conversion of whitespace-padded strings + Assert.AreEqual(42, ParameterConverter.ConvertParameter(" 42 ", typeof(int))); + Assert.AreEqual(42, ParameterConverter.ConvertParameter(" 42.0 ", typeof(int))); + + // Test conversion of empty or null values + Assert.AreEqual(0, ParameterConverter.ConvertParameter("", typeof(int))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(JToken.Null, typeof(int))); + + // Test conversion to non-numeric types + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(42, typeof(DateTime))); + + // Test conversion of values just outside the safe integer range for long and ulong + Assert.ThrowsException(() => ParameterConverter.ConvertParameter((double)long.MaxValue, typeof(long))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter((double)ulong.MaxValue, typeof(ulong))); + + // Test conversion of scientific notation + Assert.AreEqual(1000000, ParameterConverter.ConvertParameter("1e6", typeof(int))); + Assert.AreEqual(150, ParameterConverter.ConvertParameter("1.5e2", typeof(int))); + + // Test conversion of boolean values to numeric types + Assert.AreEqual(1, ParameterConverter.ConvertParameter(true, typeof(int))); + Assert.AreEqual(0, ParameterConverter.ConvertParameter(false, typeof(int))); + + // Test conversion of Unicode numeric characters + Assert.ThrowsException(() => ParameterConverter.ConvertParameter("1234", typeof(int))); + } } From 4d27e49b31a6458c0a6b70ba6451d8f16728c850 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Thu, 15 Aug 2024 10:02:52 +0800 Subject: [PATCH 13/16] remove format --- tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs index 377ee693a6..8fe9fd6f66 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs @@ -18,8 +18,6 @@ namespace Neo.Plugins.RpcServer.Tests; -// ConvertParameter - [TestClass] public class UT_Parameters { @@ -53,8 +51,6 @@ public void TestTryParse_ContractNameOrHashOrId() JToken token4 = "0xabc"; Assert.ThrowsException(() => ((ContractNameOrHashOrId)ParameterConverter.ConvertParameter(token4, typeof(ContractNameOrHashOrId))).AsHash()); - - } [TestMethod] From b3613f58d4eca75a756b9e03504b693b145c9bb2 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Thu, 15 Aug 2024 10:43:04 +0800 Subject: [PATCH 14/16] remove unused --- tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs index 8fe9fd6f66..3c3d8685b6 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs @@ -21,14 +21,7 @@ namespace Neo.Plugins.RpcServer.Tests; [TestClass] public class UT_Parameters { - private NeoSystem _neoSystem; - - [TestInitialize] - public void TestSetup() - { - var _neoSystem = new NeoSystem(TestProtocolSettings.Default, new TestBlockchain.StoreProvider()); - } - + [TestMethod] public void TestTryParse_ContractNameOrHashOrId() { From 29cf41a9ef94fdd38949ad87a853345e34d8b133 Mon Sep 17 00:00:00 2001 From: Jimmy Date: Thu, 15 Aug 2024 10:48:35 +0800 Subject: [PATCH 15/16] format --- tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs index 3c3d8685b6..32c47205a3 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs @@ -21,7 +21,7 @@ namespace Neo.Plugins.RpcServer.Tests; [TestClass] public class UT_Parameters { - + [TestMethod] public void TestTryParse_ContractNameOrHashOrId() { From 9115032d95a151ca96100a6dd11ee9b011060da5 Mon Sep 17 00:00:00 2001 From: Shargon Date: Sat, 17 Aug 2024 11:52:59 +1200 Subject: [PATCH 16/16] Apply suggestions from code review --- src/Plugins/RpcServer/Model/ContractNameOrHashOrId.cs | 1 + tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugins/RpcServer/Model/ContractNameOrHashOrId.cs b/src/Plugins/RpcServer/Model/ContractNameOrHashOrId.cs index 1f821854c2..f1b7c2c92e 100644 --- a/src/Plugins/RpcServer/Model/ContractNameOrHashOrId.cs +++ b/src/Plugins/RpcServer/Model/ContractNameOrHashOrId.cs @@ -71,6 +71,7 @@ public UInt160 AsHash() return hash; throw new RpcException(RpcError.InvalidParams.WithData($"Value {_value} is not a valid contract hash")); } + public string AsName() { if (_value is string name) diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs index 32c47205a3..a87f1c7961 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs @@ -21,7 +21,6 @@ namespace Neo.Plugins.RpcServer.Tests; [TestClass] public class UT_Parameters { - [TestMethod] public void TestTryParse_ContractNameOrHashOrId() {