diff --git a/src/ApplicationLogs/LogReader.cs b/src/ApplicationLogs/LogReader.cs index 5c42d6a0d..73e4b6adf 100644 --- a/src/ApplicationLogs/LogReader.cs +++ b/src/ApplicationLogs/LogReader.cs @@ -59,10 +59,8 @@ protected override void OnSystemLoaded(NeoSystem system) [RpcMethod] public JToken GetApplicationLog(JArray _params) { - UInt256 hash = UInt256.Parse(_params[0].AsString()); - byte[] value = _db.TryGet(hash.ToArray()); - if (value is null) - throw new RpcException(-100, "Unknown transaction/blockhash"); + UInt256 hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid transaction hash: {_params[0]}")); + byte[] value = _db.TryGet(hash.ToArray()).NotNull_Or(RpcError.UnknownScriptContainer); JObject raw = (JObject)JToken.Parse(Neo.Utility.StrictUTF8.GetString(value)); //Additional optional "trigger" parameter to getapplicationlog for clients to be able to get just one execution result for a block. diff --git a/src/OracleService/OracleService.cs b/src/OracleService/OracleService.cs index 8959b028c..528485ccc 100644 --- a/src/OracleService/OracleService.cs +++ b/src/OracleService/OracleService.cs @@ -220,24 +220,22 @@ private async void OnTimer(object state) [RpcMethod] public JObject SubmitOracleResponse(JArray _params) { - if (status != OracleStatus.Running) throw new InvalidOperationException(); + status.Equals(OracleStatus.Running).True_Or(RpcError.OracleDisabled); ECPoint oraclePub = ECPoint.DecodePoint(Convert.FromBase64String(_params[0].AsString()), ECCurve.Secp256r1); - ulong requestId = (ulong)_params[1].AsNumber(); - byte[] txSign = Convert.FromBase64String(_params[2].AsString()); - byte[] msgSign = Convert.FromBase64String(_params[3].AsString()); + ulong requestId = Result.Ok_Or(() => (ulong)_params[1].AsNumber(), RpcError.InvalidParams.WithData($"Invalid requestId: {_params[1]}")); + byte[] txSign = Result.Ok_Or(() => Convert.FromBase64String(_params[2].AsString()), RpcError.InvalidParams.WithData($"Invalid txSign: {_params[2]}")); + byte[] msgSign = Result.Ok_Or(() => Convert.FromBase64String(_params[3].AsString()), RpcError.InvalidParams.WithData($"Invalid msgSign: {_params[3]}")); - if (finishedCache.ContainsKey(requestId)) throw new RpcException(-100, "Request has already finished"); + finishedCache.ContainsKey(requestId).False_Or(RpcError.OracleRequestFinished); using (var snapshot = System.GetSnapshot()) { uint height = NativeContract.Ledger.CurrentIndex(snapshot) + 1; var oracles = NativeContract.RoleManagement.GetDesignatedByRole(snapshot, Role.Oracle, height); - if (!oracles.Any(p => p.Equals(oraclePub))) throw new RpcException(-100, $"{oraclePub} isn't an oracle node"); - if (NativeContract.Oracle.GetRequest(snapshot, requestId) is null) - throw new RpcException(-100, "Request is not found"); + oracles.Any(p => p.Equals(oraclePub)).True_Or(RpcErrorFactory.OracleNotDesignatedNode(oraclePub)); + NativeContract.Oracle.GetRequest(snapshot, requestId).NotNull_Or(RpcError.OracleRequestNotFound); var data = Neo.Helper.Concat(oraclePub.ToArray(), BitConverter.GetBytes(requestId), txSign); - if (!Crypto.VerifySignature(data, msgSign, oraclePub)) throw new RpcException(-100, "Invalid sign"); - + Crypto.VerifySignature(data, msgSign, oraclePub).True_Or(RpcErrorFactory.InvalidSignature($"Invalid oracle response transaction signature from '{oraclePub}'.")); AddResponseTxSign(snapshot, requestId, oraclePub, txSign); } return new JObject(); @@ -497,7 +495,7 @@ private void AddResponseTxSign(DataCache snapshot, ulong requestId, ECPoint orac else if (Crypto.VerifySignature(task.BackupTx.GetSignData(System.Settings.Network), sign, oraclePub)) task.BackupSigns.TryAdd(oraclePub, sign); else - throw new RpcException(-100, "Invalid response transaction sign"); + throw new RpcException(RpcErrorFactory.InvalidSignature($"Invalid oracle response transaction signature from '{oraclePub}'.")); if (CheckTxSign(snapshot, task.Tx, task.Signs) || CheckTxSign(snapshot, task.BackupTx, task.BackupSigns)) { diff --git a/src/RpcServer/Result.cs b/src/RpcServer/Result.cs new file mode 100644 index 000000000..0ead2482c --- /dev/null +++ b/src/RpcServer/Result.cs @@ -0,0 +1,116 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Result.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 +{ + public static class Result + { + /// + /// Checks the execution result of a function and throws an exception if it is null or throw an exception. + /// + /// The function to execute + /// The rpc error + /// Append extra base exception message + /// The return type + /// The execution result + /// The Rpc exception + public static T Ok_Or(this Func function, RpcError err, bool withData = false) + { + try + { + var result = function(); + if (result == null) throw new RpcException(err); + return result; + } + catch (Exception ex) + { + if (withData) + throw new RpcException(err.WithData(ex.GetBaseException().Message)); + throw new RpcException(err); + } + } + + /// + /// Checks the execution result and throws an exception if it is null. + /// + /// The execution result + /// The rpc error + /// The return type + /// The execution result + /// The Rpc exception + public static T NotNull_Or(this T result, RpcError err) + { + if (result == null) throw new RpcException(err); + return result; + } + + /// + /// The execution result is true or throws an exception or null. + /// + /// The function to execute + /// the rpc exception code + /// the execution result + /// The rpc exception + public static bool True_Or(Func function, RpcError err) + { + try + { + var result = function(); + if (!result.Equals(true)) throw new RpcException(err); + return result; + } + catch + { + throw new RpcException(err); + } + } + + /// + /// Checks if the execution result is true or throws an exception. + /// + /// the execution result + /// the rpc exception code + /// the execution result + /// The rpc exception + public static bool True_Or(this bool result, RpcError err) + { + if (!result.Equals(true)) throw new RpcException(err); + return result; + } + + /// + /// Checks if the execution result is false or throws an exception. + /// + /// the execution result + /// the rpc exception code + /// the execution result + /// The rpc exception + public static bool False_Or(this bool result, RpcError err) + { + if (!result.Equals(false)) throw new RpcException(err); + return result; + } + + /// + /// Check if the execution result is null or throws an exception. + /// + /// The execution result + /// the rpc error + /// The execution result type + /// The execution result + /// the rpc exception + public static void Null_Or(this T result, RpcError err) + { + if (result != null) throw new RpcException(err); + } + } +} diff --git a/src/RpcServer/RpcError.cs b/src/RpcServer/RpcError.cs new file mode 100644 index 000000000..713054018 --- /dev/null +++ b/src/RpcServer/RpcError.cs @@ -0,0 +1,103 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcError.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.Json; + +namespace Neo.Plugins +{ + public class RpcError + { + #region Default Values + + // https://www.jsonrpc.org/specification + // | code | message | meaning | + // |--------------------|-----------------|-----------------------------------------------------------------------------------| + // | -32700 | Parse error | Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text. | + // | -32600 | Invalid request | The JSON sent is not a valid Request object. | + // | -32601 | Method not found| The method does not exist / is not available. | + // | -32602 | Invalid params | Invalid method parameter(s). | + // | -32603 | Internal error | Internal JSON-RPC error. | + // | -32000 to -32099 | Server error | Reserved for implementation-defined server-errors. | + public static readonly RpcError InvalidRequest = new(-32600, "Invalid request"); + public static readonly RpcError MethodNotFound = new(-32601, "Method not found"); + public static readonly RpcError InvalidParams = new(-32602, "Invalid params"); + public static readonly RpcError InternalServerError = new(-32603, "Internal server RpcError"); + public static readonly RpcError BadRequest = new(-32700, "Bad request"); + + // https://github.com/neo-project/proposals/pull/156/files + public static readonly RpcError UnknownBlock = new(-101, "Unknown block"); + public static readonly RpcError UnknownContract = new(-102, "Unknown contract"); + public static readonly RpcError UnknownTransaction = new(-103, "Unknown transaction"); + public static readonly RpcError UnknownStorageItem = new(-104, "Unknown storage item"); + public static readonly RpcError UnknownScriptContainer = new(-105, "Unknown script container"); + public static readonly RpcError UnknownStateRoot = new(-106, "Unknown state root"); + public static readonly RpcError UnknownSession = new(-107, "Unknown session"); + public static readonly RpcError UnknownIterator = new(-108, "Unknown iterator"); + public static readonly RpcError UnknownHeight = new(-109, "Unknown height"); + + public static readonly RpcError InsufficientFundsWallet = new(-300, "Insufficient funds in wallet"); + public static readonly RpcError WalletFeeLimit = new(-301, "Wallet fee limit exceeded", "The necessary fee is more than the Max_fee, this transaction is failed. Please increase your Max_fee value."); + public static readonly RpcError NoOpenedWallet = new(-302, "No opened wallet"); + public static readonly RpcError WalletNotFound = new(-303, "Wallet not found"); + public static readonly RpcError WalletNotSupported = new(-304, "Wallet not supported"); + + public static readonly RpcError VerificationFailed = new(-500, "Inventory verification failed"); + public static readonly RpcError AlreadyExists = new(-501, "Inventory already exists"); + public static readonly RpcError MempoolCapReached = new(-502, "Memory pool capacity reached"); + public static readonly RpcError AlreadyInPool = new(-503, "Already in pool"); + public static readonly RpcError InsufficientNetworkFee = new(-504, "Insufficient network fee"); + public static readonly RpcError PolicyFailed = new(-505, "Policy check failed"); + public static readonly RpcError InvalidScript = new(-509, "Invalid transaction script"); + public static readonly RpcError InvalidAttribute = new(-507, "Invalid transaction attribute"); + public static readonly RpcError InvalidSignature = new(-508, "Invalid signature"); + public static readonly RpcError InvalidSize = new(-509, "Invalid inventory size"); + public static readonly RpcError ExpiredTransaction = new(-510, "Expired transaction"); + public static readonly RpcError InsufficientFunds = new(-511, "Insufficient funds for fee"); + public static readonly RpcError InvalidContractVerification = new(-512, "Invalid contract verification function"); + + public static readonly RpcError AccessDenied = new(-600, "Access denied"); + public static readonly RpcError SessionsDisabled = new(-601, "State iterator sessions disabled"); + public static readonly RpcError OracleDisabled = new(-602, "Oracle service disabled"); + public static readonly RpcError OracleRequestFinished = new(-603, "Oracle request already finished"); + public static readonly RpcError OracleRequestNotFound = new(-604, "Oracle request not found"); + public static readonly RpcError OracleNotDesignatedNode = new(-605, "Not a designated oracle node"); + public static readonly RpcError UnsupportedState = new(-606, "Old state not supported"); + public static readonly RpcError InvalidProof = new(-607, "Invalid state proof"); + public static readonly RpcError ExecutionFailed = new(-608, "Contract execution failed"); + + #endregion + + public int Code { get; set; } + public string Message { get; set; } + public string Data { get; set; } + + public RpcError(int code, string message, string data = null) + { + Code = code; + Message = message; + Data = data; + } + + public override string ToString() => string.IsNullOrEmpty(Data) ? $"{Message} ({Code})" : $"{Message} ({Code}) - {Data}"; + + public JToken ToJson() + { + JObject json = new(); + json["code"] = Code; + json["message"] = ErrorMessage; + if (!string.IsNullOrEmpty(Data)) + json["data"] = Data; + return json; + } + + public string ErrorMessage => string.IsNullOrEmpty(Data) ? $"{Message}" : $"{Message} - {Data}"; + } +} diff --git a/src/RpcServer/RpcErrorFactory.cs b/src/RpcServer/RpcErrorFactory.cs new file mode 100644 index 000000000..328ba02f6 --- /dev/null +++ b/src/RpcServer/RpcErrorFactory.cs @@ -0,0 +1,43 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcErrorFactory.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.Cryptography.ECC; + +namespace Neo.Plugins +{ + public static class RpcErrorFactory + { + public static RpcError WithData(this RpcError error, string data = null) + { + return new RpcError(error.Code, error.Message, data); + } + + public static RpcError NewCustomError(int code, string message, string data = null) + { + return new RpcError(code, message, data); + } + + #region Require data + + public static RpcError MethodNotFound(string method) => RpcError.MethodNotFound.WithData($"The method '{method}' doesn't exists."); + public static RpcError AlreadyExists(string data) => RpcError.AlreadyExists.WithData(data); + public static RpcError InvalidParams(string data) => RpcError.InvalidParams.WithData(data); + public static RpcError BadRequest(string data) => RpcError.BadRequest.WithData(data); + public static RpcError InsufficientFundsWallet(string data) => RpcError.InsufficientFundsWallet.WithData(data); + public static RpcError VerificationFailed(string data) => RpcError.VerificationFailed.WithData(data); + public static RpcError InvalidContractVerification(UInt160 contractHash) => RpcError.InvalidContractVerification.WithData($"The smart contract {contractHash} haven't got verify method."); + public static RpcError InvalidContractVerification(string data) => RpcError.InvalidContractVerification.WithData(data); + public static RpcError InvalidSignature(string data) => RpcError.InvalidSignature.WithData(data); + public static RpcError OracleNotDesignatedNode(ECPoint oraclePub) => RpcError.OracleNotDesignatedNode.WithData($"{oraclePub} isn't an oracle node."); + + #endregion + } +} diff --git a/src/RpcServer/RpcException.cs b/src/RpcServer/RpcException.cs index 827dc6210..5c7b5675a 100644 --- a/src/RpcServer/RpcException.cs +++ b/src/RpcServer/RpcException.cs @@ -15,9 +15,9 @@ namespace Neo.Plugins { public class RpcException : Exception { - public RpcException(int code, string message) : base(message) + public RpcException(RpcError error) : base(error.ErrorMessage) { - HResult = code; + HResult = error.Code; } } } diff --git a/src/RpcServer/RpcServer.Blockchain.cs b/src/RpcServer/RpcServer.Blockchain.cs index 4dced4e59..e381a19ef 100644 --- a/src/RpcServer/RpcServer.Blockchain.cs +++ b/src/RpcServer/RpcServer.Blockchain.cs @@ -33,7 +33,7 @@ protected virtual JToken GetBestBlockHash(JArray _params) [RpcMethod] protected virtual JToken GetBlock(JArray _params) { - JToken key = _params[0]; + 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.GetSnapshot(); Block block; @@ -47,8 +47,7 @@ protected virtual JToken GetBlock(JArray _params) UInt256 hash = UInt256.Parse(key.AsString()); block = NativeContract.Ledger.GetBlock(snapshot, hash); } - if (block == null) - throw new RpcException(-100, "Unknown block"); + block.NotNull_Or(RpcError.UnknownBlock); if (verbose) { JObject json = Utility.BlockToJson(block, system.Settings); @@ -76,13 +75,13 @@ protected virtual JToken GetBlockCount(JArray _params) [RpcMethod] protected virtual JToken GetBlockHash(JArray _params) { - uint height = uint.Parse(_params[0].AsString()); + 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)) { return NativeContract.Ledger.GetBlockHash(snapshot, height).ToString(); } - throw new RpcException(-100, "Invalid Height"); + throw new RpcException(RpcError.UnknownHeight); } [RpcMethod] @@ -95,16 +94,13 @@ protected virtual JToken GetBlockHeader(JArray _params) if (key is JNumber) { uint height = uint.Parse(key.AsString()); - header = NativeContract.Ledger.GetHeader(snapshot, height); + header = NativeContract.Ledger.GetHeader(snapshot, height).NotNull_Or(RpcError.UnknownBlock); } else { UInt256 hash = UInt256.Parse(key.AsString()); - header = NativeContract.Ledger.GetHeader(snapshot, hash); + header = NativeContract.Ledger.GetHeader(snapshot, hash).NotNull_Or(RpcError.UnknownBlock); } - if (header == null) - throw new RpcException(-100, "Unknown block"); - if (verbose) { JObject json = header.ToJson(system.Settings); @@ -124,13 +120,13 @@ protected virtual JToken GetContractState(JArray _params) if (int.TryParse(_params[0].AsString(), out int contractId)) { var contracts = NativeContract.ContractManagement.GetContractById(system.StoreView, contractId); - return contracts?.ToJson() ?? throw new RpcException(-100, "Unknown contract"); + return contracts?.ToJson().NotNull_Or(RpcError.UnknownContract); } else { UInt160 script_hash = ToScriptHash(_params[0].AsString()); ContractState contract = NativeContract.ContractManagement.GetContract(system.StoreView, script_hash); - return contract?.ToJson() ?? throw new RpcException(-100, "Unknown contract"); + return contract?.ToJson().NotNull_Or(RpcError.UnknownContract); } } @@ -165,14 +161,14 @@ protected virtual JToken GetRawMemPool(JArray _params) [RpcMethod] protected virtual JToken GetRawTransaction(JArray _params) { - UInt256 hash = UInt256.Parse(_params[0].AsString()); + 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) return Convert.ToBase64String(tx.ToArray()); var snapshot = system.StoreView; TransactionState state = NativeContract.Ledger.GetTransactionState(snapshot, hash); tx ??= state?.Transaction; - if (tx is null) throw new RpcException(-100, "Unknown transaction"); + tx.NotNull_Or(RpcError.UnknownTransaction); if (!verbose) return Convert.ToBase64String(tx.ToArray()); JObject json = Utility.TransactionToJson(tx, system.Settings); if (state is not null) @@ -192,8 +188,7 @@ protected virtual JToken GetStorage(JArray _params) if (!int.TryParse(_params[0].AsString(), out int id)) { UInt160 hash = UInt160.Parse(_params[0].AsString()); - ContractState contract = NativeContract.ContractManagement.GetContract(snapshot, hash); - if (contract is null) throw new RpcException(-100, "Unknown contract"); + ContractState contract = NativeContract.ContractManagement.GetContract(snapshot, hash).NotNull_Or(RpcError.UnknownContract); id = contract.Id; } byte[] key = Convert.FromBase64String(_params[1].AsString()); @@ -201,8 +196,7 @@ protected virtual JToken GetStorage(JArray _params) { Id = id, Key = key - }); - if (item is null) throw new RpcException(-100, "Unknown storage"); + }).NotNull_Or(RpcError.UnknownStorageItem); return Convert.ToBase64String(item.Value.Span); } @@ -213,8 +207,7 @@ protected virtual JToken FindStorage(JArray _params) if (!int.TryParse(_params[0].AsString(), out int id)) { UInt160 hash = UInt160.Parse(_params[0].AsString()); - ContractState contract = NativeContract.ContractManagement.GetContract(snapshot, hash); - if (contract is null) throw new RpcException(-100, "Unknown contract"); + ContractState contract = NativeContract.ContractManagement.GetContract(snapshot, hash).NotNull_Or(RpcError.UnknownContract); id = contract.Id; } @@ -259,10 +252,10 @@ protected virtual JToken FindStorage(JArray _params) [RpcMethod] protected virtual JToken GetTransactionHeight(JArray _params) { - UInt256 hash = UInt256.Parse(_params[0].AsString()); + 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(-100, "Unknown transaction"); + throw new RpcException(RpcError.UnknownTransaction); } [RpcMethod] @@ -288,15 +281,24 @@ protected virtual JToken GetCandidates(JArray _params) { script = sb.EmitDynamicCall(NativeContract.NEO.Hash, "getCandidates", null).ToArray(); } - using ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, settings: system.Settings, gas: settings.MaxGasInvoke); + StackItem[] resultstack; + try + { + using ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, settings: system.Settings, gas: settings.MaxGasInvoke); + resultstack = engine.ResultStack.ToArray(); + } + catch + { + throw new RpcException(RpcError.InternalServerError.WithData("Can't get candidates.")); + } + JObject json = new(); try { - var resultstack = engine.ResultStack.ToArray(); if (resultstack.Length > 0) { JArray jArray = new(); - var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, system.Settings.ValidatorsCount); + var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, system.Settings.ValidatorsCount) ?? throw new RpcException(RpcError.InternalServerError.WithData("Can't get next block validators.")); foreach (var item in resultstack) { @@ -314,10 +316,11 @@ protected virtual JToken GetCandidates(JArray _params) } } } - catch (InvalidOperationException) + catch { - json["exception"] = "Invalid result."; + throw new RpcException(RpcError.InternalServerError.WithData("Can't get next block validators")); } + return json; } diff --git a/src/RpcServer/RpcServer.Node.cs b/src/RpcServer/RpcServer.Node.cs index 1b08e62db..c45510080 100644 --- a/src/RpcServer/RpcServer.Node.cs +++ b/src/RpcServer/RpcServer.Node.cs @@ -53,15 +53,59 @@ protected virtual JToken GetPeers(JArray _params) private static JObject GetRelayResult(VerifyResult reason, UInt256 hash) { - if (reason == VerifyResult.Succeed) - { - var ret = new JObject(); - ret["hash"] = hash.ToString(); - return ret; - } - else + + switch (reason) { - throw new RpcException(-500, reason.ToString()); + case VerifyResult.Succeed: + { + var ret = new JObject(); + ret["hash"] = hash.ToString(); + return ret; + } + case VerifyResult.AlreadyExists: + { + throw new RpcException(RpcError.AlreadyExists.WithData(reason.ToString())); + } + case VerifyResult.AlreadyInPool: + { + throw new RpcException(RpcError.AlreadyInPool.WithData(reason.ToString())); + } + case VerifyResult.OutOfMemory: + { + throw new RpcException(RpcError.MempoolCapReached.WithData(reason.ToString())); + } + case VerifyResult.InvalidScript: + { + throw new RpcException(RpcError.InvalidScript.WithData(reason.ToString())); + } + case VerifyResult.InvalidAttribute: + { + throw new RpcException(RpcError.InvalidAttribute.WithData(reason.ToString())); + } + case VerifyResult.InvalidSignature: + { + throw new RpcException(RpcError.InvalidSignature.WithData(reason.ToString())); + } + case VerifyResult.OverSize: + { + throw new RpcException(RpcError.InvalidSize.WithData(reason.ToString())); + } + case VerifyResult.Expired: + { + throw new RpcException(RpcError.ExpiredTransaction.WithData(reason.ToString())); + } + case VerifyResult.InsufficientFunds: + { + throw new RpcException(RpcError.InsufficientFunds.WithData(reason.ToString())); + } + case VerifyResult.PolicyFail: + { + throw new RpcException(RpcError.PolicyFailed.WithData(reason.ToString())); + } + default: + { + throw new RpcException(RpcError.VerificationFailed.WithData(reason.ToString())); + } } } @@ -108,7 +152,7 @@ private static string StripPrefix(string s, string prefix) [RpcMethod] protected virtual JToken SendRawTransaction(JArray _params) { - Transaction tx = Convert.FromBase64String(_params[0].AsString()).AsSerializable(); + Transaction tx = Result.Ok_Or(() => Convert.FromBase64String(_params[0].AsString()).AsSerializable(), RpcError.InvalidParams.WithData($"Invalid Transaction Format: {_params[0]}")); RelayResult reason = system.Blockchain.Ask(tx).Result; return GetRelayResult(reason.Result, tx.Hash); } @@ -116,7 +160,7 @@ protected virtual JToken SendRawTransaction(JArray _params) [RpcMethod] protected virtual JToken SubmitBlock(JArray _params) { - Block block = Convert.FromBase64String(_params[0].AsString()).AsSerializable(); + Block block = Result.Ok_Or(() => Convert.FromBase64String(_params[0].AsString()).AsSerializable(), RpcError.InvalidParams.WithData($"Invalid Block Format: {_params[0]}")); RelayResult reason = system.Blockchain.Ask(block).Result; return GetRelayResult(reason.Result, block.Hash); } diff --git a/src/RpcServer/RpcServer.SmartContract.cs b/src/RpcServer/RpcServer.SmartContract.cs index 6ccf10965..0fe6fc0c1 100644 --- a/src/RpcServer/RpcServer.SmartContract.cs +++ b/src/RpcServer/RpcServer.SmartContract.cs @@ -174,7 +174,7 @@ private static Signer[] SignersFromJson(JArray _params, ProtocolSettings setting { if (_params.Count > Transaction.MaxTransactionAttributes) { - throw new RpcException(-100, "Max allowed witness exceeded."); + throw new RpcException(RpcError.InvalidParams.WithData("Max allowed witness exceeded.")); } var ret = _params.Select(u => new Signer @@ -197,7 +197,7 @@ private static Witness[] WitnessesFromJson(JArray _params) { if (_params.Count > Transaction.MaxTransactionAttributes) { - throw new RpcException(-100, "Max allowed witness exceeded."); + throw new RpcException(RpcError.InvalidParams.WithData("Max allowed witness exceeded.")); } return _params.Select(u => new @@ -214,8 +214,8 @@ private static Witness[] WitnessesFromJson(JArray _params) [RpcMethod] protected virtual JToken InvokeFunction(JArray _params) { - UInt160 script_hash = UInt160.Parse(_params[0].AsString()); - string operation = _params[1].AsString(); + UInt160 script_hash = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid script hash {nameof(script_hash)}")); + string operation = Result.Ok_Or(() => _params[1].AsString(), RpcError.InvalidParams); ContractParameter[] args = _params.Count >= 3 ? ((JArray)_params[2]).Select(p => ContractParameter.FromJson((JObject)p)).ToArray() : System.Array.Empty(); Signer[] signers = _params.Count >= 4 ? SignersFromJson((JArray)_params[3], system.Settings) : null; Witness[] witnesses = _params.Count >= 4 ? WitnessesFromJson((JArray)_params[3]) : null; @@ -232,7 +232,7 @@ protected virtual JToken InvokeFunction(JArray _params) [RpcMethod] protected virtual JToken InvokeScript(JArray _params) { - byte[] script = Convert.FromBase64String(_params[0].AsString()); + byte[] script = Result.Ok_Or(() => Convert.FromBase64String(_params[0].AsString()), RpcError.InvalidParams); Signer[] signers = _params.Count >= 2 ? SignersFromJson((JArray)_params[1], system.Settings) : null; Witness[] witnesses = _params.Count >= 2 ? WitnessesFromJson((JArray)_params[1]) : null; bool useDiagnostic = _params.Count >= 3 && _params[2].GetBoolean(); @@ -242,18 +242,18 @@ protected virtual JToken InvokeScript(JArray _params) [RpcMethod] protected virtual JToken TraverseIterator(JArray _params) { - Guid sid = Guid.Parse(_params[0].GetString()); - Guid iid = Guid.Parse(_params[1].GetString()); + settings.SessionEnabled.True_Or(RpcError.SessionsDisabled); + Guid sid = Result.Ok_Or(() => Guid.Parse(_params[0].GetString()), RpcError.InvalidParams.WithData($"Invalid session id {nameof(sid)}")); + Guid iid = Result.Ok_Or(() => Guid.Parse(_params[1].GetString()), RpcError.InvalidParams.WithData($"Invliad iterator id {nameof(iid)}")); int count = _params[2].GetInt32(); - if (count > settings.MaxIteratorResultItems) - throw new ArgumentOutOfRangeException(nameof(count)); + Result.True_Or(() => count > settings.MaxIteratorResultItems, RpcError.InvalidParams.WithData($"Invalid iterator items count {nameof(count)}")); Session session; lock (sessions) { - session = sessions[sid]; + session = Result.Ok_Or(() => sessions[sid], RpcError.UnknownSession); session.ResetExpiration(); } - IIterator iterator = session.Iterators[iid]; + IIterator iterator = Result.Ok_Or(() => session.Iterators[iid], RpcError.UnknownIterator); JArray json = new(); while (count-- > 0 && iterator.Next()) json.Add(iterator.Value(null).ToJson()); @@ -263,11 +263,15 @@ protected virtual JToken TraverseIterator(JArray _params) [RpcMethod] protected virtual JToken TerminateSession(JArray _params) { - Guid sid = Guid.Parse(_params[0].GetString()); - Session session; + settings.SessionEnabled.True_Or(RpcError.SessionsDisabled); + Guid sid = Result.Ok_Or(() => Guid.Parse(_params[0].GetString()), RpcError.InvalidParams.WithData("Invalid session id")); + + Session session = null; bool result; lock (sessions) - result = sessions.Remove(sid, out session); + { + result = Result.Ok_Or(() => sessions.Remove(sid, out session), RpcError.UnknownSession); + } if (result) session.Dispose(); return result; } @@ -275,19 +279,10 @@ protected virtual JToken TerminateSession(JArray _params) [RpcMethod] protected virtual JToken GetUnclaimedGas(JArray _params) { - string address = _params[0].AsString(); + string address = Result.Ok_Or(() => _params[0].AsString(), RpcError.InvalidParams.WithData($"Invalid address {nameof(address)}")); JObject json = new(); - UInt160 script_hash; - try - { - script_hash = AddressToScriptHash(address, system.Settings.AddressVersion); - } - catch - { - script_hash = null; - } - if (script_hash == null) - throw new RpcException(-100, "Invalid address"); + UInt160 script_hash = Result.Ok_Or(() => AddressToScriptHash(address, system.Settings.AddressVersion), RpcError.InvalidParams); + var snapshot = system.StoreView; json["unclaimed"] = NativeContract.NEO.UnclaimedGas(snapshot, script_hash, NativeContract.Ledger.CurrentIndex(snapshot) + 1).ToString(); json["address"] = script_hash.ToAddress(system.Settings.AddressVersion); diff --git a/src/RpcServer/RpcServer.Utilities.cs b/src/RpcServer/RpcServer.Utilities.cs index 28da86829..f410e01b7 100644 --- a/src/RpcServer/RpcServer.Utilities.cs +++ b/src/RpcServer/RpcServer.Utilities.cs @@ -36,7 +36,7 @@ protected virtual JToken ListPlugins(JArray _params) [RpcMethod] protected virtual JToken ValidateAddress(JArray _params) { - string address = _params[0].AsString(); + string address = Result.Ok_Or(() => _params[0].AsString(), RpcError.InvalidParams.WithData($"Invlid address format: {_params[0]}")); JObject json = new(); UInt160 scriptHash; try diff --git a/src/RpcServer/RpcServer.Wallet.cs b/src/RpcServer/RpcServer.Wallet.cs index 92d630061..cc552a58a 100644 --- a/src/RpcServer/RpcServer.Wallet.cs +++ b/src/RpcServer/RpcServer.Wallet.cs @@ -52,8 +52,7 @@ private class DummyWallet : Wallet private void CheckWallet() { - if (wallet is null) - throw new RpcException(-400, "Access denied"); + wallet.NotNull_Or(RpcError.NoOpenedWallet); } [RpcMethod] @@ -156,9 +155,8 @@ protected virtual JToken OpenWallet(JArray _params) { string path = _params[0].AsString(); string password = _params[1].AsString(); - if (!File.Exists(path)) throw new FileNotFoundException(); - wallet = Wallet.Open(path, password, system.Settings) - ?? throw new NotSupportedException(); + File.Exists(path).True_Or(RpcError.WalletNotFound); + wallet = Wallet.Open(path, password, system.Settings).NotNull_Or(RpcError.WalletNotSupported); return true; } @@ -200,8 +198,7 @@ protected virtual JToken SendFrom(JArray _params) using var snapshot = system.GetSnapshot(); AssetDescriptor descriptor = new(snapshot, system.Settings, assetId); BigDecimal amount = new(BigInteger.Parse(_params[3].AsString()), descriptor.Decimals); - if (amount.Sign <= 0) - throw new RpcException(-32602, "Invalid params"); + (amount.Sign > 0).True_Or(RpcErrorFactory.InvalidParams("Amount can't be negative.")); Signer[] signers = _params.Count >= 5 ? ((JArray)_params[4]).Select(p => new Signer() { Account = AddressToScriptHash(p.AsString(), system.Settings.AddressVersion), Scopes = WitnessScope.CalledByEntry }).ToArray() : null; Transaction tx = wallet.MakeTransaction(snapshot, new[] @@ -212,9 +209,7 @@ protected virtual JToken SendFrom(JArray _params) Value = amount, ScriptHash = to } - }, from, signers); - if (tx == null) - throw new RpcException(-300, "Insufficient funds"); + }, from, signers).NotNull_Or(RpcError.InsufficientFunds); ContractParametersContext transContext = new(snapshot, tx, settings.Network); wallet.Sign(transContext); @@ -227,8 +222,7 @@ protected virtual JToken SendFrom(JArray _params) if (tx.NetworkFee < calFee) tx.NetworkFee = calFee; } - if (tx.NetworkFee > settings.MaxFee) - throw new RpcException(-301, "The necessary fee is more than the Max_fee, this transaction is failed. Please increase your Max_fee value."); + (tx.NetworkFee <= settings.MaxFee).True_Or(RpcError.WalletFeeLimit); return SignAndRelay(snapshot, tx); } @@ -243,9 +237,8 @@ protected virtual JToken SendMany(JArray _params) from = AddressToScriptHash(_params[0].AsString(), system.Settings.AddressVersion); to_start = 1; } - JArray to = (JArray)_params[to_start]; - if (to.Count == 0) - throw new RpcException(-32602, "Invalid params"); + JArray to = Result.Ok_Or(() => (JArray)_params[to_start], RpcError.InvalidParams.WithData($"Invalid 'to' parameter: {_params[to_start]}")); + (to.Count != 0).True_Or(RpcErrorFactory.InvalidParams("Argument 'to' can't be empty.")); Signer[] signers = _params.Count >= to_start + 2 ? ((JArray)_params[to_start + 1]).Select(p => new Signer() { Account = AddressToScriptHash(p.AsString(), system.Settings.AddressVersion), Scopes = WitnessScope.CalledByEntry }).ToArray() : null; TransferOutput[] outputs = new TransferOutput[to.Count]; @@ -260,12 +253,9 @@ protected virtual JToken SendMany(JArray _params) Value = new BigDecimal(BigInteger.Parse(to[i]["value"].AsString()), descriptor.Decimals), ScriptHash = AddressToScriptHash(to[i]["address"].AsString(), system.Settings.AddressVersion) }; - if (outputs[i].Value.Sign <= 0) - throw new RpcException(-32602, "Invalid params"); + (outputs[i].Value.Sign > 0).True_Or(RpcErrorFactory.InvalidParams($"Amount of '{asset_id}' can't be negative.")); } - Transaction tx = wallet.MakeTransaction(snapshot, outputs, from, signers); - if (tx == null) - throw new RpcException(-300, "Insufficient funds"); + Transaction tx = wallet.MakeTransaction(snapshot, outputs, from, signers).NotNull_Or(RpcError.InsufficientFunds); ContractParametersContext transContext = new(snapshot, tx, settings.Network); wallet.Sign(transContext); @@ -278,8 +268,7 @@ protected virtual JToken SendMany(JArray _params) if (tx.NetworkFee < calFee) tx.NetworkFee = calFee; } - if (tx.NetworkFee > settings.MaxFee) - throw new RpcException(-301, "The necessary fee is more than the Max_fee, this transaction is failed. Please increase your Max_fee value."); + (tx.NetworkFee <= settings.MaxFee).True_Or(RpcError.WalletFeeLimit); return SignAndRelay(snapshot, tx); } @@ -287,13 +276,12 @@ protected virtual JToken SendMany(JArray _params) protected virtual JToken SendToAddress(JArray _params) { CheckWallet(); - UInt160 assetId = UInt160.Parse(_params[0].AsString()); + UInt160 assetId = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid asset hash: {_params[0]}")); UInt160 to = AddressToScriptHash(_params[1].AsString(), system.Settings.AddressVersion); using var snapshot = system.GetSnapshot(); AssetDescriptor descriptor = new(snapshot, system.Settings, assetId); BigDecimal amount = new(BigInteger.Parse(_params[2].AsString()), descriptor.Decimals); - if (amount.Sign <= 0) - throw new RpcException(-32602, "Invalid params"); + (amount.Sign > 0).True_Or(RpcError.InvalidParams); Transaction tx = wallet.MakeTransaction(snapshot, new[] { new TransferOutput @@ -302,9 +290,7 @@ protected virtual JToken SendToAddress(JArray _params) Value = amount, ScriptHash = to } - }); - if (tx == null) - throw new RpcException(-300, "Insufficient funds"); + }).NotNull_Or(RpcError.InsufficientFunds); ContractParametersContext transContext = new(snapshot, tx, settings.Network); wallet.Sign(transContext); @@ -317,8 +303,7 @@ protected virtual JToken SendToAddress(JArray _params) if (tx.NetworkFee < calFee) tx.NetworkFee = calFee; } - if (tx.NetworkFee > settings.MaxFee) - throw new RpcException(-301, "The necessary fee is more than the Max_fee, this transaction is failed. Please increase your Max_fee value."); + (tx.NetworkFee <= settings.MaxFee).True_Or(RpcError.WalletFeeLimit); return SignAndRelay(snapshot, tx); } @@ -326,20 +311,12 @@ protected virtual JToken SendToAddress(JArray _params) protected virtual JToken CancelTransaction(JArray _params) { CheckWallet(); - var txid = UInt256.Parse(_params[0].AsString()); - TransactionState state = NativeContract.Ledger.GetTransactionState(system.StoreView, txid); - if (state != null) - { - throw new RpcException(32700, "This tx is already confirmed, can't be cancelled."); - } + var txid = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid txid: {_params[0]}")); + NativeContract.Ledger.GetTransactionState(system.StoreView, txid).Null_Or(RpcErrorFactory.AlreadyExists("This tx is already confirmed, can't be cancelled.")); var conflict = new TransactionAttribute[] { new Conflicts() { Hash = txid } }; Signer[] signers = _params.Count >= 2 ? ((JArray)_params[1]).Select(j => new Signer() { Account = AddressToScriptHash(j.AsString(), system.Settings.AddressVersion), Scopes = WitnessScope.None }).ToArray() : Array.Empty(); - if (!signers.Any()) - { - throw new RpcException(32701, "no signers"); - } - + (!signers.Any()).True_Or(RpcErrorFactory.BadRequest("No signer.")); Transaction tx = new Transaction { Signers = signers, @@ -347,14 +324,7 @@ protected virtual JToken CancelTransaction(JArray _params) Witnesses = Array.Empty(), }; - try - { - tx = wallet.MakeTransaction(system.StoreView, new[] { (byte)OpCode.RET }, signers[0].Account, signers, conflict); - } - catch (InvalidOperationException e) - { - throw new RpcException(-500, GetExceptionMessage(e)); - } + tx = Result.Ok_Or(() => wallet.MakeTransaction(system.StoreView, new[] { (byte)OpCode.RET }, signers[0].Account, signers, conflict), RpcError.InsufficientFunds, true); if (system.MemPool.TryGetValue(txid, out Transaction conflictTx)) { @@ -364,10 +334,8 @@ protected virtual JToken CancelTransaction(JArray _params) { var extraFee = _params[2].AsString(); AssetDescriptor descriptor = new(system.StoreView, system.Settings, NativeContract.GAS.Hash); - if (!BigDecimal.TryParse(extraFee, descriptor.Decimals, out BigDecimal decimalExtraFee) || decimalExtraFee.Sign <= 0) - { - throw new RpcException(32702, "Incorrect Amount Format"); - } + (BigDecimal.TryParse(extraFee, descriptor.Decimals, out BigDecimal decimalExtraFee) && decimalExtraFee.Sign > 0).True_Or(RpcErrorFactory.InvalidParams("Incorrect amount format.")); + tx.NetworkFee += (long)decimalExtraFee.Value; }; return SignAndRelay(system.StoreView, tx); @@ -376,7 +344,7 @@ protected virtual JToken CancelTransaction(JArray _params) [RpcMethod] protected virtual JToken InvokeContractVerify(JArray _params) { - UInt160 script_hash = UInt160.Parse(_params[0].AsString()); + UInt160 script_hash = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid script hash: {_params[0]}")); ContractParameter[] args = _params.Count >= 2 ? ((JArray)_params[1]).Select(p => ContractParameter.FromJson((JObject)p)).ToArray() : Array.Empty(); Signer[] signers = _params.Count >= 3 ? SignersFromJson((JArray)_params[2], system.Settings) : null; Witness[] witnesses = _params.Count >= 3 ? WitnessesFromJson((JArray)_params[2]) : null; @@ -386,17 +354,9 @@ protected virtual JToken InvokeContractVerify(JArray _params) private JObject GetVerificationResult(UInt160 scriptHash, ContractParameter[] args, Signer[] signers = null, Witness[] witnesses = null) { using var snapshot = system.GetSnapshot(); - var contract = NativeContract.ContractManagement.GetContract(snapshot, scriptHash); - if (contract is null) - { - throw new RpcException(-100, "Unknown contract"); - } - var md = contract.Manifest.Abi.GetMethod("verify", -1); - if (md is null) - throw new RpcException(-101, $"The smart contract {contract.Hash} haven't got verify method."); - if (md.ReturnType != ContractParameterType.Boolean) - throw new RpcException(-102, "The verify method doesn't return boolean value."); - + var contract = NativeContract.ContractManagement.GetContract(snapshot, scriptHash).NotNull_Or(RpcError.UnknownContract); + var md = contract.Manifest.Abi.GetMethod("verify", -1).NotNull_Or(RpcErrorFactory.InvalidContractVerification(contract.Hash)); + (md.ReturnType == ContractParameterType.Boolean).True_Or(RpcErrorFactory.InvalidContractVerification("The verify method doesn't return boolean value.")); Transaction tx = new() { Signers = signers ?? new Signer[] { new() { Account = scriptHash } }, diff --git a/src/RpcServer/RpcServer.cs b/src/RpcServer/RpcServer.cs index 8023622f5..f77c2d1e3 100644 --- a/src/RpcServer/RpcServer.cs +++ b/src/RpcServer/RpcServer.cs @@ -80,14 +80,10 @@ private bool CheckAuth(HttpContext context) return authvalues[0] == settings.RpcUser && authvalues[1] == settings.RpcPass; } - private static JObject CreateErrorResponse(JToken id, int code, string message, JToken data = null) + private static JObject CreateErrorResponse(JToken id, RpcError rpcError) { JObject response = CreateResponse(id); - response["error"] = new JObject(); - response["error"]["code"] = code; - response["error"]["message"] = message; - if (data != null) - response["error"]["data"] = data; + response["error"] = rpcError.ToJson(); return response; } @@ -238,13 +234,13 @@ public async Task ProcessAsync(HttpContext context) JToken response; if (request == null) { - response = CreateErrorResponse(null, -32700, "Parse error"); + response = CreateErrorResponse(null, RpcError.BadRequest); } else if (request is JArray array) { if (array.Count == 0) { - response = CreateErrorResponse(request["id"], -32600, "Invalid Request"); + response = CreateErrorResponse(request["id"], RpcError.InvalidRequest); } else { @@ -268,16 +264,14 @@ private async Task ProcessRequestAsync(HttpContext context, JObject req JToken @params = request["params"] ?? new JArray(); if (!request.ContainsProperty("method") || @params is not JArray) { - return CreateErrorResponse(request["id"], -32600, "Invalid Request"); + return CreateErrorResponse(request["id"], RpcError.InvalidRequest); } JObject response = CreateResponse(request["id"]); try { string method = request["method"].AsString(); - if (!CheckAuth(context) || settings.DisabledMethods.Contains(method)) - throw new RpcException(-400, "Access denied"); - if (!methods.TryGetValue(method, out var func)) - throw new RpcException(-32601, "Method not found"); + (CheckAuth(context) && !settings.DisabledMethods.Contains(method)).True_Or(RpcError.AccessDenied); + methods.TryGetValue(method, out var func).True_Or(RpcErrorFactory.MethodNotFound(method)); response["result"] = func((JArray)@params) switch { JToken result => result, @@ -286,20 +280,20 @@ private async Task ProcessRequestAsync(HttpContext context, JObject req }; return response; } - catch (FormatException) + catch (FormatException ex) { - return CreateErrorResponse(request["id"], -32602, "Invalid params"); + return CreateErrorResponse(request["id"], RpcError.InvalidParams.WithData(ex.Message)); } - catch (IndexOutOfRangeException) + catch (IndexOutOfRangeException ex) { - return CreateErrorResponse(request["id"], -32602, "Invalid params"); + return CreateErrorResponse(request["id"], RpcError.InvalidParams.WithData(ex.Message)); } catch (Exception ex) { #if DEBUG - return CreateErrorResponse(request["id"], ex.HResult, ex.Message, ex.StackTrace); + return CreateErrorResponse(request["id"], RpcErrorFactory.NewCustomError(ex.HResult, ex.Message, ex.StackTrace)); #else - return CreateErrorResponse(request["id"], ex.HResult, ex.Message); + return CreateErrorResponse(request["id"], RpcErrorFactory.NewCustomError(ex.HResult, ex.Message)); #endif } } diff --git a/src/StateService/StatePlugin.cs b/src/StateService/StatePlugin.cs index b760eed79..dda1e1aff 100644 --- a/src/StateService/StatePlugin.cs +++ b/src/StateService/StatePlugin.cs @@ -179,13 +179,10 @@ private void OnVerifyProof(UInt256 root_hash, string proof) [RpcMethod] public JToken GetStateRoot(JArray _params) { - uint index = uint.Parse(_params[0].AsString()); + uint index = Result.Ok_Or(() => uint.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid state root index: {_params[0]}")); using var snapshot = StateStore.Singleton.GetSnapshot(); - StateRoot state_root = snapshot.GetStateRoot(index); - if (state_root is null) - throw new RpcException(-100, "Unknown state root"); - else - return state_root.ToJson(); + StateRoot state_root = snapshot.GetStateRoot(index).NotNull_Or(RpcError.UnknownStateRoot); + return state_root.ToJson(); } private string GetProof(Trie trie, int contract_id, byte[] key) @@ -200,9 +197,7 @@ private string GetProof(Trie trie, int contract_id, byte[] key) private string GetProof(Trie trie, StorageKey skey) { - var result = trie.TryGetProof(skey.ToArray(), out var proof); - if (!result) throw new KeyNotFoundException(); - + trie.TryGetProof(skey.ToArray(), out var proof).True_Or(RpcError.UnknownStorageItem); using MemoryStream ms = new(); using BinaryWriter writer = new(ms, Utility.StrictUTF8); @@ -219,23 +214,19 @@ private string GetProof(Trie trie, StorageKey skey) private string GetProof(UInt256 root_hash, UInt160 script_hash, byte[] key) { - if (!Settings.Default.FullState && StateStore.Singleton.CurrentLocalRootHash != root_hash) - { - throw new RpcException(-100, "Old state not supported"); - } + (!Settings.Default.FullState && StateStore.Singleton.CurrentLocalRootHash != root_hash).False_Or(RpcError.UnsupportedState); using var store = StateStore.Singleton.GetStoreSnapshot(); var trie = new Trie(store, root_hash); - var contract = GetHistoricalContractState(trie, script_hash); - if (contract is null) throw new RpcException(-100, "Unknown contract"); + var contract = GetHistoricalContractState(trie, script_hash).NotNull_Or(RpcError.UnknownContract); return GetProof(trie, contract.Id, key); } [RpcMethod] public JToken GetProof(JArray _params) { - UInt256 root_hash = UInt256.Parse(_params[0].AsString()); - UInt160 script_hash = UInt160.Parse(_params[1].AsString()); - byte[] key = Convert.FromBase64String(_params[2].AsString()); + UInt256 root_hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid root hash: {_params[0]}")); + UInt160 script_hash = Result.Ok_Or(() => UInt160.Parse(_params[1].AsString()), RpcError.InvalidParams.WithData($"Invalid script hash: {_params[1]}")); + byte[] key = Result.Ok_Or(() => Convert.FromBase64String(_params[2].AsString()), RpcError.InvalidParams.WithData($"Invalid key: {_params[2]}")); return GetProof(root_hash, script_hash, key); } @@ -253,16 +244,15 @@ private string VerifyProof(UInt256 root_hash, byte[] proof) proofs.Add(reader.ReadVarBytes()); } - var value = Trie.VerifyProof(root_hash, key, proofs); - if (value is null) throw new RpcException(-100, "Verification failed"); + var value = Trie.VerifyProof(root_hash, key, proofs).NotNull_Or(RpcError.InvalidProof); return Convert.ToBase64String(value); } [RpcMethod] public JToken VerifyProof(JArray _params) { - UInt256 root_hash = UInt256.Parse(_params[0].AsString()); - byte[] proof_bytes = Convert.FromBase64String(_params[1].AsString()); + UInt256 root_hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid root hash: {_params[0]}")); + byte[] proof_bytes = Result.Ok_Or(() => Convert.FromBase64String(_params[1].AsString()), RpcError.InvalidParams.WithData($"Invalid proof: {_params[1]}")); return VerifyProof(root_hash, proof_bytes); } @@ -294,23 +284,21 @@ private StorageKey ParseStorageKey(byte[] data) [RpcMethod] public JToken FindStates(JArray _params) { - var root_hash = UInt256.Parse(_params[0].AsString()); - if (!Settings.Default.FullState && StateStore.Singleton.CurrentLocalRootHash != root_hash) - throw new RpcException(-100, "Old state not supported"); - var script_hash = UInt160.Parse(_params[1].AsString()); - var prefix = Convert.FromBase64String(_params[2].AsString()); + var root_hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid root hash: {_params[0]}")); + (!Settings.Default.FullState && StateStore.Singleton.CurrentLocalRootHash != root_hash).False_Or(RpcError.UnsupportedState); + var script_hash = Result.Ok_Or(() => UInt160.Parse(_params[1].AsString()), RpcError.InvalidParams.WithData($"Invalid script hash: {_params[1]}")); + var prefix = Result.Ok_Or(() => Convert.FromBase64String(_params[2].AsString()), RpcError.InvalidParams.WithData($"Invalid prefix: {_params[2]}")); byte[] key = Array.Empty(); if (3 < _params.Count) - key = Convert.FromBase64String(_params[3].AsString()); + key = Result.Ok_Or(() => Convert.FromBase64String(_params[3].AsString()), RpcError.InvalidParams.WithData($"Invalid key: {_params[3]}")); int count = Settings.Default.MaxFindResultItems; if (4 < _params.Count) - count = int.Parse(_params[4].AsString()); + count = Result.Ok_Or(() => int.Parse(_params[4].AsString()), RpcError.InvalidParams.WithData($"Invalid count: {_params[4]}")); if (Settings.Default.MaxFindResultItems < count) count = Settings.Default.MaxFindResultItems; using var store = StateStore.Singleton.GetStoreSnapshot(); var trie = new Trie(store, root_hash); - var contract = GetHistoricalContractState(trie, script_hash); - if (contract is null) throw new RpcException(-100, "Unknown contract"); + var contract = GetHistoricalContractState(trie, script_hash).NotNull_Or(RpcError.UnknownContract); StorageKey pkey = new() { Id = contract.Id, @@ -352,16 +340,14 @@ public JToken FindStates(JArray _params) [RpcMethod] public JToken GetState(JArray _params) { - var root_hash = UInt256.Parse(_params[0].AsString()); - if (!Settings.Default.FullState && StateStore.Singleton.CurrentLocalRootHash != root_hash) - throw new RpcException(-100, "Old state not supported"); - var script_hash = UInt160.Parse(_params[1].AsString()); - var key = Convert.FromBase64String(_params[2].AsString()); + var root_hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid root hash: {_params[0]}")); + (!Settings.Default.FullState && StateStore.Singleton.CurrentLocalRootHash != root_hash).False_Or(RpcError.UnsupportedState); + var script_hash = Result.Ok_Or(() => UInt160.Parse(_params[1].AsString()), RpcError.InvalidParams.WithData($"Invalid script hash: {_params[1]}")); + var key = Result.Ok_Or(() => Convert.FromBase64String(_params[2].AsString()), RpcError.InvalidParams.WithData($"Invalid key: {_params[2]}")); using var store = StateStore.Singleton.GetStoreSnapshot(); var trie = new Trie(store, root_hash); - var contract = GetHistoricalContractState(trie, script_hash); - if (contract is null) throw new RpcException(-100, "Unknown contract"); + var contract = GetHistoricalContractState(trie, script_hash).NotNull_Or(RpcError.UnknownContract); StorageKey skey = new() { Id = contract.Id, diff --git a/src/TokensTracker/Trackers/NEP-11/Nep11Tracker.cs b/src/TokensTracker/Trackers/NEP-11/Nep11Tracker.cs index bc80d77aa..35a579eb7 100644 --- a/src/TokensTracker/Trackers/NEP-11/Nep11Tracker.cs +++ b/src/TokensTracker/Trackers/NEP-11/Nep11Tracker.cs @@ -195,14 +195,13 @@ private void RecordTransferHistoryNep11(UInt160 contractHash, UInt160 from, UInt [RpcMethod] public JToken GetNep11Transfers(JArray _params) { - if (!_shouldTrackHistory) throw new RpcException(-32601, "Method not found"); + _shouldTrackHistory.True_Or(RpcError.MethodNotFound); UInt160 userScriptHash = GetScriptHashFromParam(_params[0].AsString()); // If start time not present, default to 1 week of history. ulong startTime = _params.Count > 1 ? (ulong)_params[1].AsNumber() : (DateTime.UtcNow - TimeSpan.FromDays(7)).ToTimestampMS(); ulong endTime = _params.Count > 2 ? (ulong)_params[2].AsNumber() : DateTime.UtcNow.ToTimestampMS(); - - if (endTime < startTime) throw new RpcException(-32602, "Invalid params"); + (endTime >= startTime).True_Or(RpcError.InvalidParams); JObject json = new(); json["address"] = userScriptHash.ToAddress(_neoSystem.Settings.AddressVersion); diff --git a/src/TokensTracker/Trackers/NEP-17/Nep17Tracker.cs b/src/TokensTracker/Trackers/NEP-17/Nep17Tracker.cs index 989bc6a41..8ea2efa6a 100644 --- a/src/TokensTracker/Trackers/NEP-17/Nep17Tracker.cs +++ b/src/TokensTracker/Trackers/NEP-17/Nep17Tracker.cs @@ -73,7 +73,7 @@ public override void OnPersist(NeoSystem system, Block block, DataCache snapshot } } - //update nep17 balance + //update nep17 balance foreach (var balanceChangeRecord in balanceChangeRecords) { try @@ -144,14 +144,14 @@ private void SaveNep17Balance(BalanceChangeRecord balanceChanged, DataCache snap [RpcMethod] public JToken GetNep17Transfers(JArray _params) { - if (!_shouldTrackHistory) throw new RpcException(-32601, "Method not found"); + _shouldTrackHistory.True_Or(RpcError.MethodNotFound); UInt160 userScriptHash = GetScriptHashFromParam(_params[0].AsString()); // If start time not present, default to 1 week of history. ulong startTime = _params.Count > 1 ? (ulong)_params[1].AsNumber() : (DateTime.UtcNow - TimeSpan.FromDays(7)).ToTimestampMS(); ulong endTime = _params.Count > 2 ? (ulong)_params[2].AsNumber() : DateTime.UtcNow.ToTimestampMS(); - if (endTime < startTime) throw new RpcException(-32602, "Invalid params"); + (endTime >= startTime).True_Or(RpcError.InvalidParams); JObject json = new(); json["address"] = userScriptHash.ToAddress(_neoSystem.Settings.AddressVersion); diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcError.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcError.cs new file mode 100644 index 000000000..d3c43a545 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcError.cs @@ -0,0 +1,48 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RpcError.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 System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Neo.Plugins.RpcServer.Tests +{ + [TestClass] + public class UT_RpcError + { + [TestMethod] + public void AllDifferent() + { + HashSet codes = new(); + + foreach (RpcError error in typeof(RpcError) + .GetFields(BindingFlags.Static | BindingFlags.Public) + .Where(u => u.DeclaringType == typeof(RpcError)) + .Select(u => u.GetValue(null)) + .Cast()) + { + Assert.IsTrue(codes.Add(error.ToString())); + + if (error.Code == RpcError.WalletFeeLimit.Code) + Assert.IsNotNull(error.Data); + else + Assert.IsNull(error.Data); + } + } + + [TestMethod] + public void TestJson() + { + Assert.AreEqual("{\"code\":-600,\"message\":\"Access denied\"}", RpcError.AccessDenied.ToJson().ToString(false)); + } + } +}