Skip to content

Commit

Permalink
Implement RPC errors proposal #156 (#815)
Browse files Browse the repository at this point in the history
* implemente RPC errors proposal #156

* fix debug

* Apply suggestions from code review

Remove comments

* fix typo

* Format code

* one class per file

* code format

* Clean code

* fix error

* Unify namespace

* Update .editorconfig

* adopt old namespace style

* format

* Fix .editorconfig

* Remove .editorconfig

Create a new PR for 

[*.cs]
csharp_style_namespace_declarations = file_scoped:warning
dotnet_diagnostic.IDE0161.severity = warning

* Shargon's review

* Optimize code reusing static objects

* Some uts

* Fix ut

* Remove cast

* Remove static

* Comment not used

* Update src/StateService/StatePlugin.cs

Co-authored-by: Anna Shaleva <[email protected]>

* Some Anna's feedback

* fix ut

* More info

* Add . at the end to all of them

* update error code

* add 600 error code and add extra parameter checks. update exception to rpcexception

* optimize code and fix ut

* use result to replace try-catch

* update result with data parameter

* address more anna's comments

* fix oracle exception

* fix GetRelayResult

* fix iterator with result

* Update src/RpcServer/RpcError.cs

* Update src/RpcServer/RpcServer.Node.cs

* Update src/RpcServer/Result.cs

Co-authored-by: Shargon <[email protected]>

* add more result checking function and simplify the process of checking parameters.

* add more params check

* update exception

* all good now

* runs dotnet format. and fix errors

* update Can't get next block validators

---------

Co-authored-by: Shargon <[email protected]>
Co-authored-by: Anna Shaleva <[email protected]>
  • Loading branch information
3 people committed Mar 18, 2024
1 parent cd603d5 commit 253a77e
Show file tree
Hide file tree
Showing 16 changed files with 496 additions and 209 deletions.
6 changes: 2 additions & 4 deletions src/ApplicationLogs/LogReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
20 changes: 9 additions & 11 deletions src/OracleService/OracleService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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))
{
Expand Down
116 changes: 116 additions & 0 deletions src/RpcServer/Result.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Checks the execution result of a function and throws an exception if it is null or throw an exception.
/// </summary>
/// <param name="function">The function to execute</param>
/// <param name="err">The rpc error</param>
/// <param name="withData">Append extra base exception message</param>
/// <typeparam name="T">The return type</typeparam>
/// <returns>The execution result</returns>
/// <exception cref="RpcException">The Rpc exception</exception>
public static T Ok_Or<T>(this Func<T> 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);
}
}

/// <summary>
/// Checks the execution result and throws an exception if it is null.
/// </summary>
/// <param name="result">The execution result</param>
/// <param name="err">The rpc error</param>
/// <typeparam name="T">The return type</typeparam>
/// <returns>The execution result</returns>
/// <exception cref="RpcException">The Rpc exception</exception>
public static T NotNull_Or<T>(this T result, RpcError err)
{
if (result == null) throw new RpcException(err);
return result;
}

/// <summary>
/// The execution result is true or throws an exception or null.
/// </summary>
/// <param name="function">The function to execute</param>
/// <param name="err">the rpc exception code</param>
/// <returns>the execution result</returns>
/// <exception cref="RpcException">The rpc exception</exception>
public static bool True_Or(Func<bool> function, RpcError err)
{
try
{
var result = function();
if (!result.Equals(true)) throw new RpcException(err);
return result;
}
catch
{
throw new RpcException(err);
}
}

/// <summary>
/// Checks if the execution result is true or throws an exception.
/// </summary>
/// <param name="result">the execution result</param>
/// <param name="err">the rpc exception code</param>
/// <returns>the execution result</returns>
/// <exception cref="RpcException">The rpc exception</exception>
public static bool True_Or(this bool result, RpcError err)
{
if (!result.Equals(true)) throw new RpcException(err);
return result;
}

/// <summary>
/// Checks if the execution result is false or throws an exception.
/// </summary>
/// <param name="result">the execution result</param>
/// <param name="err">the rpc exception code</param>
/// <returns>the execution result</returns>
/// <exception cref="RpcException">The rpc exception</exception>
public static bool False_Or(this bool result, RpcError err)
{
if (!result.Equals(false)) throw new RpcException(err);
return result;
}

/// <summary>
/// Check if the execution result is null or throws an exception.
/// </summary>
/// <param name="result">The execution result</param>
/// <param name="err">the rpc error</param>
/// <typeparam name="T">The execution result type</typeparam>
/// <returns>The execution result</returns>
/// <exception cref="RpcException">the rpc exception</exception>
public static void Null_Or<T>(this T result, RpcError err)
{
if (result != null) throw new RpcException(err);
}
}
}
103 changes: 103 additions & 0 deletions src/RpcServer/RpcError.cs
Original file line number Diff line number Diff line change
@@ -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}";
}
}
43 changes: 43 additions & 0 deletions src/RpcServer/RpcErrorFactory.cs
Original file line number Diff line number Diff line change
@@ -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
}
}
4 changes: 2 additions & 2 deletions src/RpcServer/RpcException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
Loading

0 comments on commit 253a77e

Please sign in to comment.