Skip to content

Commit

Permalink
[Neo Plugin RPCServer] Rpc parameters. Part I (#3457)
Browse files Browse the repository at this point in the history
* rpc parameter parse

* update blockchain related apis.

* Update src/Plugins/RpcServer/RpcMethodWithParamsAttribute.cs

* Delete src/Plugins/RpcServer/JsonPropertyNameAttribute.cs

* udpate contract model

* Update src/Plugins/RpcServer/Model/BlockHashOrIndex.cs

* Apply suggestions from code review

Remove comments

* Update src/Plugins/RpcServer/RpcServer.cs

* fix warnings

* ensure it can load both true/false and 1/0

* optimize the pr and add unit tests

* add more tests and check the safe max value and safe min value

* remove format

* remove unused

* format

* Apply suggestions from code review

---------

Co-authored-by: Shargon <[email protected]>
Co-authored-by: NGD Admin <[email protected]>
  • Loading branch information
3 people authored Aug 23, 2024
1 parent 5fb2262 commit 71da68d
Show file tree
Hide file tree
Showing 8 changed files with 933 additions and 314 deletions.
61 changes: 61 additions & 0 deletions src/Plugins/RpcServer/Model/BlockHashOrIndex.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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.

using System.Diagnostics.CodeAnalysis;

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 static bool TryParse(string value, [NotNullWhen(true)] out BlockHashOrIndex blockHashOrIndex)
{
if (uint.TryParse(value, out var index))
{
blockHashOrIndex = new BlockHashOrIndex(index);
return true;
}
if (UInt256.TryParse(value, out var hash))
{
blockHashOrIndex = new BlockHashOrIndex(hash);
return true;
}
blockHashOrIndex = null;
return false;
}

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"));
}
}
81 changes: 81 additions & 0 deletions src/Plugins/RpcServer/Model/ContractNameOrHashOrId.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (C) 2015-2024 The Neo Project.
//
// 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
// for more details.
//
// Redistribution and use in source and binary forms with or without
// modifications are permitted.

using System.Diagnostics.CodeAnalysis;

namespace Neo.Plugins.RpcServer.Model;

public class ContractNameOrHashOrId
{
private readonly object _value;

public ContractNameOrHashOrId(int id)
{
_value = id;
}

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 static bool TryParse(string value, [NotNullWhen(true)] out ContractNameOrHashOrId contractNameOrHashOrId)
{
if (int.TryParse(value, out var id))
{
contractNameOrHashOrId = new ContractNameOrHashOrId(id);
return true;
}
if (UInt160.TryParse(value, out var hash))
{
contractNameOrHashOrId = new ContractNameOrHashOrId(hash);
return true;
}

if (value.Length > 0)
{
contractNameOrHashOrId = new ContractNameOrHashOrId(value);
return true;
}
contractNameOrHashOrId = null;
return false;
}

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

public string AsName()
{
if (_value is string name)
return name;
throw new RpcException(RpcError.InvalidParams.WithData($"Value {_value} is not a valid contract name"));
}
}
149 changes: 149 additions & 0 deletions src/Plugins/RpcServer/ParameterConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// 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.Json;
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<Type, Func<JToken, object>> s_conversionStrategies;

static ParameterConverter()
{
s_conversionStrategies = new Dictionary<Type, Func<JToken, object>>
{
{ typeof(string), token => Result.Ok_Or(token.AsString, CreateInvalidParamError<string>(token)) },
{ typeof(byte), ConvertNumeric<byte> },
{ typeof(sbyte), ConvertNumeric<sbyte> },
{ typeof(short), ConvertNumeric<short> },
{ typeof(ushort), ConvertNumeric<ushort> },
{ typeof(int), ConvertNumeric<int> },
{ typeof(uint), ConvertNumeric<uint> },
{ typeof(long), ConvertNumeric<long> },
{ typeof(ulong), ConvertNumeric<ulong> },
{ typeof(double), token => Result.Ok_Or(token.AsNumber, CreateInvalidParamError<double>(token)) },
{ typeof(bool), token => Result.Ok_Or(token.AsBoolean, CreateInvalidParamError<bool>(token)) },
{ typeof(UInt256), ConvertUInt256 },
{ typeof(ContractNameOrHashOrId), ConvertContractNameOrHashOrId },
{ typeof(BlockHashOrIndex), ConvertBlockHashOrIndex }
};
}

internal static object ConvertParameter(JToken token, Type targetType)
{
if (s_conversionStrategies.TryGetValue(targetType, out var conversionStrategy))
return conversionStrategy(token);
throw new RpcException(RpcError.InvalidParams.WithData($"Unsupported parameter type: {targetType}"));
}

private static object ConvertNumeric<T>(JToken token) where T : struct
{
if (TryConvertDoubleToNumericType<T>(token, out var result))
{
return result;
}

throw new RpcException(CreateInvalidParamError<T>(token));
}

private static bool TryConvertDoubleToNumericType<T>(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() && !IsValidInteger(value))
{
return false;
}

result = (T)Convert.ChangeType(value, typeof(T));
return true;
}
catch
{
return false;
}
}

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();
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<T>(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);
}
}
20 changes: 20 additions & 0 deletions src/Plugins/RpcServer/RpcMethodWithParamsAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// 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 System;

namespace Neo.Plugins.RpcServer;

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class RpcMethodWithParamsAttribute : Attribute
{
public string Name { get; set; }
}
Loading

0 comments on commit 71da68d

Please sign in to comment.