diff --git a/docs/RestServer/Addons.md b/docs/RestServer/Addons.md new file mode 100644 index 0000000000..b71c8a6d95 --- /dev/null +++ b/docs/RestServer/Addons.md @@ -0,0 +1,108 @@ +## RestServer Plugin +In this section of you will learn how to make a `neo-cli` plugin that integrates with `RestServer` +plugin. Lets take a look at [Example Plugin](/examples/RestServerPlugin). + +- No reference to `RestServer` is required. +- Requires DotNet 7.0 + +## Folder Structure +```bash +Project +├── Controllers +│ └── ExampleController.cs +├── ExamplePlugin.cs +├── ExamplePlugin.csproj +├── Exceptions +│ └── CustomException.cs +└── Models + └── ErrorModel.cs +``` +The only thing that is important here is the `controllers` folder. This folder is required for the `RestServer` +plugin to register the controllers in its web server. This location is where you put all your controllers. + +## Controllers +The `controller` class is the same as ASP.Net Core's. Controllers must have their attribute set +as `[ApiController]` and inherent from `ControllerBase`. + +## Swagger Controller +A `Swagger` controller uses special attributes that are set on your controller's class. + +**Controller Class Attributes** +- `[Produces(MediaTypeNames.Application.Json)]` (_Required_) +- `[Consumes(MediaTypeNames.Application.Json)]` (_Required_) +- `[ApiExplorerSettings(GroupName = "v1")]` + - **GroupName** - _is which version of the API you are targeting._ +- `[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ErrorModel))]` (_Required_) + - **Type** - _Must have a base class of [error](#error-class)._ + +## Error Class +Needs to be the same as `RestServer` of else there will be some inconsistencies +with end users not knowing which type to use. This class can be `public` or `internal`. +Properties `Code`, `Name` and `Message` values can be whatever you desire. + +**Model** +```csharp +public class ErrorModel +{ + public int Code { get; set; }; + public string Name { get; set; }; + public string Message { get; set; }; +} +``` + +## Controller Actions +Controller actions need to have special attributes as well as code comments. + +- `[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(string))]` + +HTTP status code `200 (OK)` is required with return type defined. You can use more than one attribute. One per HTTP status code. + +### Action Example +```csharp +[HttpGet("contracts/{hash:required}/sayHello", Name = "GetSayHello")] +[ProducesResponseType(StatusCodes.Status204NoContent)] +[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(string))] +public IActionResult GetSayHello( + [FromRoute(Name = "hash")] + UInt160 scripthash) +{ + if (scripthash == UInt160.Zero) + return NoContent(); + return Ok($"Hello, {scripthash}"); +} +``` +Notice that the _above_ example also returns with HTTP status code of `204 No Content`. +This action `route` also extends the `contracts` API. Adding method `sayHello`. Routes +can be what you like as well. But if you want to extend on any existing controller you +must use existing routes paths. + +### Path(s) +- `/api/v1/contracts/` +- `/api/v1/ledger/` +- `/api/v1/node/` +- `/api/v1/tokens` +- `/api/v1/Utils/` + +### Excluded Path(s) +- `/api/v1/wallet/` + +_for security reasons_. + +### Code Comments for Swagger +```csharp +/// +/// +/// +/// +/// +/// Successful +/// An error occurred. See Response for details. +``` + +Also note that you need to have `GenerateDocumentationFile` enabled in your +`.csproj` file. The `xml` file that is generated; in our case would be `RestServerPlugin.xml`. +This file gets put in same directory `Plugins/RestServerPlugin/` which is in the root of `neo-node` +executable folder. Where you will see `neo-cli.exe`. + +File `RestServerPlugin.xml` will get added to `Swagger` automatically by the `RestServer` +plugin. diff --git a/docs/RestServer/ConfigFile.md b/docs/RestServer/ConfigFile.md new file mode 100644 index 0000000000..1c9df0e1ad --- /dev/null +++ b/docs/RestServer/ConfigFile.md @@ -0,0 +1,54 @@ +## Table + +| Name | Type | Description | +| :--- | :---: | :--- | +|**Network**|_uint32_|_Network you would like the `RestServer` to be enabled on._| +|**BindAddress**|_string_|_Ip address of the interface you want to bind too._| +|**Port**|_uint32_|_Port number to bind too._| +|**KeepAliveTimeout**|_uint32_|_Time to keep the request alive, in seconds._| +|**SslCertFile**|_string_|_Is the path and file name of a certificate file, relative to the directory that contains the node's executable files._| +|**SslCertPassword**|_string_|_Is the password required to access the `X.509` certificate data._| +|**TrustedAuthorities**|_StringArray_|_Tumbprints of the of the last certificate authority in the chain._| +|**EnableBasicAuthentication**|_boolean_|_enables basic authentication._| +|**RestUser**|_string_|_Basic authentication's `username`._| +|**RestPass**|_string_|_Basic authentication's `password`._| +|**EnableCors**|_boolean_|_Enables Cross-origin resource sharing (`CORS`). Note by default it enables `*` any origin._| +|**AllowOrigins**|_StringArray_|_A list of the origins to allow. Note needs to add origins for basic auth to work with `CORS`._| +|**DisableControllers**|_StringArray_|_A list of `controllers` to be disabled. Requires restart of the node, if changed._| +|**EnableCompression**|_boolean_|_Enables `GZip` data compression._| +|**CompressionLevel**|_enum_|_Compression level. Values can be `Fastest`, `Optimal`, `NoCompression` or `SmallestSize`_| +|**EnableForwardedHeaders**|_boolean_|_Enables response/request headers for proxy forwarding. (data center usage)_| +|**EnableSwagger**|_boolean_|_Enables `Swagger` with `Swagger UI` for the rest services._| +|**MaxPageSize**|_uint32_|_Max page size for searches on `Ledger`/`Contracts` route._| +|**MaxConcurrentConnections**|_int64_|_Max allow concurrent HTTP connections._| +|**MaxInvokeGas**|_int64_|_Max gas to be invoked on the `Neo` virtual machine._| + +## Default "Config.json" file +```json +{ + "PluginConfiguration": { + "Network": 860833102, + "BindAddress": "127.0.0.1", + "Port": 10339, + "KeepAliveTimeout": 120, + "SslCertFile": "", + "SslCertPassword": "", + "TrustedAuthorities": [], + "EnableBasicAuthentication": false, + "RestUser": "", + "RestPass": "", + "EnableCors": true, + "AllowOrigins": [], + "DisableControllers": [ "WalletController" ], + "EnableCompression": true, + "CompressionLevel": "SmallestSize", + "EnableForwardedHeaders": false, + "EnableSwagger": true, + "MaxPageSize": 50, + "MaxConcurrentConnections": 40, + "MaxTransactionFee": 10000000, + "MaxInvokeGas": 20000000, + "WalletSessionTimeout": 120 + } +} +``` diff --git a/docs/RestServer/README.md b/docs/RestServer/README.md new file mode 100644 index 0000000000..9063babae4 --- /dev/null +++ b/docs/RestServer/README.md @@ -0,0 +1,144 @@ +## RestServer +In this section you will learn about `RestServer` plugin and how it works. + +See [config.json](ConfigFile.md) for information about the configurations. + +## Dependencies +- **Microsoft.AspNetCore.JsonPatch.dll** `Required` +- **Microsoft.AspNetCore.Mvc.NewtonsoftJson.dll** `Required` +- **Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer.dll** `Required` +- **Microsoft.AspNetCore.Mvc.Versioning.dll** `Required` +- **Microsoft.OpenApi.dll** `Required` +- **Newtonsoft.Json.Bson.dll** `Required` +- **Newtonsoft.Json.dll** `Required` +- **System.ServiceProcess.ServiceController.dll** `Required` +- **Microsoft.AspNetCore.Mvc.Versioning.dll** `Required` +- **Microsoft.AspNetCore.Mvc.Versioning.dll** `Required` +- **Microsoft.AspNetCore.Mvc.Versioning.dll** `Required` +- **Microsoft.OpenApi.dll** `Swagger` +- **Swashbuckle.AspNetCore.Swagger.dll** `Swagger` +- **Swashbuckle.AspNetCore.SwaggerGen.dll** `Swagger` +- **Swashbuckle.AspNetCore.SwaggerUI.dll** `Swagger` +- **Swashbuckle.AspNetCore.Newtonsoft.dll** `Swagger` +- **RestServer.xml** `Swagger UI` + +These files go in the same directory as the `RestServer.dll`. In neo-cli +`plugins/RestServer/` folder. + +## Response Headers +| Name | Value(s) | Description | +| :---: | --- | :--- | +|**server**|_neo-cli/3.6.0 RestServer/3.6.0_|_`neo-cli` and `RestServer` version._| + +Custom headers can be added by [Neo RestServer Plugins](Addons.md). + +## JSON Serializer +`RestServer` uses custom NewtonSoft JSON Converters to serialize controller action +responses and `route` parameters. + +**One Way Binding** - `Write` only. +- `Neo.SmartContract.ContractState` +- `Neo.SmartContract.NefFile` +- `Neo.SmartContract.MethodToken` +- `Neo.SmartContract.Native.TrimmedBlock` +- `Neo.SmartContract.Manifest.ContractAbi` +- `Neo.SmartContract.Manifest.ContractGroup` +- `Neo.SmartContract.Manifest.ContractManifest` +- `Neo.SmartContract.Manifest.ContractPermission` +- `Neo.SmartContract.Manifest.ContractPermissionDescriptor` +- `Neo.Network.P2P.Payloads.Block` +- `Neo.Network.P2P.Payloads.Header` +- `Neo.Network.P2P.Payloads.Signer` +- `Neo.Network.P2P.Payloads.TransactionAttribute` +- `Neo.Network.P2P.Payloads.Transaction` +- `Neo.Network.P2P.Payloads.Witness` + +**Two Way Binding** - `Read` & `Write` +- `System.Guid` +- `System.ReadOnlyMemory` +- `Neo.BigDecimal` +- `Neo.UInt160` +- `Neo.UInt256` +- `Neo.Cryptography.ECC.ECPoint` +- `Neo.VM.Types.Array` +- `Neo.VM.Types.Boolean` +- `Neo.VM.Types.Buffer` +- `Neo.VM.Types.ByteString` +- `Neo.VM.Types.Integer` +- `Neo.VM.Types.InteropInterface` +- `Neo.VM.Types.Null` +- `Neo.VM.Types.Map` +- `Neo.VM.Types.Pointer` +- `Neo.VM.Types.StackItem` +- `Neo.VM.Types.Struct` + +## Remote Endpoints +Parametes `{hash}` can be any Neo N3 address or scripthash; `{address}` can be any Neo N3 address **only**; `{number}` and `{index}` can be any _**uint32**_. + +**Parameter Examples** +- `{hash}` - _0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5_ **or** _NiHURyS83nX2mpxtA7xq84cGxVbHojj5Wc_ +- `{address}` - _NiHURyS83nX2mpxtA7xq84cGxVbHojj5Wc_ +- `{number}` - _1_ +- `{index}` - _2500000_ + +**Paths** +- Utils + - `[GET]` `/api/v1/utils/{hash}/address` + - `[GET]` `/api/v1/utils/{address}/scripthash` + - `[GET]` `/api/v1/utils/{hash}/{address}/validate` +- Node + - `[GET]` `/api/v1/node/peers` + - `[GET]` `/api/v1/node/plugins` + - `[GET]` `/api/v1/node/settings` +- Ledger + - `[GET]` `/api/v1/ledger/neo/accounts` + - `[GET]` `/api/v1/ledger/gas/accounts` + - `[GET]` `/api/v1/ledger/blocks?page={number}&size={number}` + - `[GET]` `/api/v1/ledger/blocks/height` + - `[GET]` `/api/v1/ledger/blocks/{index}` + - `[GET]` `/api/v1/ledger/blocks/{index}/header` + - `[GET]` `/api/v1/ledger/blocks/{index}/witness` + - `[GET]` `/api/v1/ledger/blocks/{index}/transactions?page={number}&size={number}` + - `[GET]` `/api/v1/ledger/transactions/{hash}` + - `[GET]` `/api/v1/ledger/transactions/{hash}/witnesses` + - `[GET]` `/api/v1/ledger/transactions/{hash}/signers` + - `[GET]` `/api/v1/ledger/transactions/{hash}/atributes` + - `[GET]` `/api/v1/ledger/memorypool?page={number}&size={number}` + - `[GET]` `/api/v1/ledger/memorypool/verified?page={number}&size={number}` + - `[GET]` `/api/v1/ledger/memorypool/unverified?page={number}&size={number}` + - `[GET]` `/api/v1/ledger/memorypool/count` +- Tokens + - `[GET]` `/api/v1/tokens/balanceof/{address}` + - NFTs + - `[GET]` `/api/v1/tokens/nep-11?page={number}&size={number}` + - `[GET]` `/api/v1/tokens/nep-11/count` + - `[GET]` `/api/v1/tokens/nep-11/{hash}/balanceof/{address}` + - NEP-17 + - `[GET]` `/api/v1/tokens/nep-17?page={number}&size={number}` + - `[GET]` `/api/v1/tokens/nep-17/count` + - `[GET]` `/api/v1/tokens/nep-17/{hash}/balanceof/{address}` +- Contracts + - `[GET]` `/api/v1/contracts?page={number}&size={number}` + - `[GET]` `/api/v1/contracts/count` + - `[GET]` `/api/v1/contracts/{hash}` + - `[GET]` `/api/v1/contracts/{hash}/abi` + - `[GET]` `/api/v1/contracts/{hash}/manifest` + - `[GET]` `/api/v1/contracts/{hash}/nef` + - `[GET]` `/api/v1/contracts/{hash}/storage` +- Wallet + - `[POST]` `/api/v1/wallet/open` + - `[POST]` `/api/v1/wallet/create` + - `[POST]` `/api/v1/wallet/{session}/address/create` + - `[GET]` `/api/v1/wallet/{session}/address/list` + - `[GET]` `/api/v1/wallet/{session}/asset/list` + - `[GET]` `/api/v1/wallet/{session}/balance/list` + - `[POST]` `/api/v1/wallet/{session}/changepassword` + - `[GET]` `/api/v1/wallet/{session}/close` + - `[GET]` `/api/v1/wallet/{session}/delete/{address}` + - `[GET]` `/api/v1/wallet/{session}/export/{address}` + - `[GET]` `/api/v1/wallet/{session}/export` + - `[GET]` `/api/v1/wallet/{session}/gas/unclaimed` + - `[GET]` `/api/v1/wallet/{session}/key/list` + - `[POST]` `/api/v1/wallet/{session}/import` + - `[POST]` `/api/v1/wallet/{session}/import/multisigaddress` + - `[POST]` `/api/v1/wallet/{session}/transfer` diff --git a/neo.sln b/neo.sln index 5e7f8c7dda..6b69c0587c 100644 --- a/neo.sln +++ b/neo.sln @@ -78,9 +78,20 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TokensTracker", "src\Plugin EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RpcClient", "src\Plugins\RpcClient\RpcClient.csproj", "{185ADAFC-BFC6-413D-BC2E-97F9FB0A8AF0}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{B753DC70-E313-4B73-B8F0-45D54126DE92}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "rest-server", "rest-server", "{40AB8866-F8A1-4DD4-868D-35F2B06ADEAB}" + ProjectSection(SolutionItems) = preProject + docs\RestServer\Addons.md = docs\RestServer\Addons.md + docs\RestServer\ConfigFile.md = docs\RestServer\ConfigFile.md + docs\RestServer\README.md = docs\RestServer\README.md + EndProjectSection +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Plugins.ApplicationLogs.Tests", "tests\Neo.Plugins.ApplicationLogs.Tests\Neo.Plugins.ApplicationLogs.Tests.csproj", "{8C866DC8-2E55-4399-9563-2F47FD4602EC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Extensions.Tests", "tests\Neo.Extensions.Tests\Neo.Extensions.Tests.csproj", "{77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Extensions.Tests", "tests\Neo.Extensions.Tests\Neo.Extensions.Tests.csproj", "{77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RestServer", "src\Plugins\RestServer\RestServer.csproj", "{50447098-054A-45EC-9881-BFB78B484D30}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -228,6 +239,10 @@ Global {77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F}.Debug|Any CPU.Build.0 = Debug|Any CPU {77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F}.Release|Any CPU.ActiveCfg = Release|Any CPU {77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F}.Release|Any CPU.Build.0 = Release|Any CPU + {50447098-054A-45EC-9881-BFB78B484D30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50447098-054A-45EC-9881-BFB78B484D30}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50447098-054A-45EC-9881-BFB78B484D30}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50447098-054A-45EC-9881-BFB78B484D30}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -267,8 +282,10 @@ Global {FF76D8A4-356B-461A-8471-BC1B83E57BBC} = {C2DC830A-327A-42A7-807D-295216D30DBB} {5E4947F3-05D3-4806-B0F3-30DAC71B5986} = {C2DC830A-327A-42A7-807D-295216D30DBB} {185ADAFC-BFC6-413D-BC2E-97F9FB0A8AF0} = {C2DC830A-327A-42A7-807D-295216D30DBB} + {40AB8866-F8A1-4DD4-868D-35F2B06ADEAB} = {B753DC70-E313-4B73-B8F0-45D54126DE92} {8C866DC8-2E55-4399-9563-2F47FD4602EC} = {7F257712-D033-47FF-B439-9D4320D06599} {77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F} = {EDE05FA8-8E73-4924-BC63-DD117127EEE1} + {50447098-054A-45EC-9881-BFB78B484D30} = {C2DC830A-327A-42A7-807D-295216D30DBB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BCBA19D9-F868-4C6D-8061-A2B91E06E3EC} diff --git a/src/Plugins/RestServer/Authentication/BasicAuthenticationHandler.cs b/src/Plugins/RestServer/Authentication/BasicAuthenticationHandler.cs new file mode 100644 index 0000000000..900ba9e77e --- /dev/null +++ b/src/Plugins/RestServer/Authentication/BasicAuthenticationHandler.cs @@ -0,0 +1,64 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BasicAuthenticationHandler.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.AspNetCore.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Neo.Plugins.RestServer; +using System; +using System.Net.Http.Headers; +using System.Security.Claims; +using System.Text; +using System.Text.Encodings.Web; +using System.Threading.Tasks; + +namespace RestServer.Authentication +{ + internal class BasicAuthenticationHandler : AuthenticationHandler + { + public BasicAuthenticationHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder) : base(options, logger, encoder) + { + } + + protected override Task HandleAuthenticateAsync() + { + var authHeader = Request.Headers.Authorization; + if (string.IsNullOrEmpty(authHeader) == false && AuthenticationHeaderValue.TryParse(authHeader, out var authValue)) + { + if (authValue.Scheme.Equals("basic", StringComparison.OrdinalIgnoreCase) && authValue.Parameter != null) + { + try + { + var decodedParams = Encoding.UTF8.GetString(Convert.FromBase64String(authValue.Parameter)); + var creds = decodedParams.Split(':', 2); + + if (creds.Length == 2 && creds[0] == RestServerSettings.Current.RestUser && creds[1] == RestServerSettings.Current.RestPass) + { + var claims = new[] { new Claim(ClaimTypes.NameIdentifier, creds[0]) }; + var identity = new ClaimsIdentity(claims, Scheme.Name); + var principal = new ClaimsPrincipal(identity); + var ticket = new AuthenticationTicket(principal, Scheme.Name); + + return Task.FromResult(AuthenticateResult.Success(ticket)); + } + } + catch (FormatException) + { + } + } + } + return Task.FromResult(AuthenticateResult.Fail("Authentication Failed!!!")); + } + } +} diff --git a/src/Plugins/RestServer/Binder/UInt160Binder.cs b/src/Plugins/RestServer/Binder/UInt160Binder.cs new file mode 100644 index 0000000000..33f25eafee --- /dev/null +++ b/src/Plugins/RestServer/Binder/UInt160Binder.cs @@ -0,0 +1,49 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UInt160Binder.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.AspNetCore.Mvc.ModelBinding; +using System; +using System.Threading.Tasks; + +namespace Neo.Plugins.RestServer.Binder +{ + internal class UInt160Binder : IModelBinder + { + public Task BindModelAsync(ModelBindingContext bindingContext) + { + _ = bindingContext ?? throw new ArgumentNullException(nameof(bindingContext)); + + if (bindingContext.BindingSource == BindingSource.Path || + bindingContext.BindingSource == BindingSource.Query) + { + var modelName = bindingContext.ModelName; + + // Try to fetch the value of the argument by name + var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName); + + if (valueProviderResult == ValueProviderResult.None) + return Task.CompletedTask; + + bindingContext.ModelState.SetModelValue(modelName, valueProviderResult); + + var value = valueProviderResult.FirstValue; + + // Check if the argument value is null or empty + if (string.IsNullOrEmpty(value)) + return Task.CompletedTask; + + var model = RestServerUtility.ConvertToScriptHash(value, RestServerPlugin.NeoSystem!.Settings); + bindingContext.Result = ModelBindingResult.Success(model); + } + return Task.CompletedTask; + } + } +} diff --git a/src/Plugins/RestServer/Binder/UInt160BinderProvider.cs b/src/Plugins/RestServer/Binder/UInt160BinderProvider.cs new file mode 100644 index 0000000000..9070167b4a --- /dev/null +++ b/src/Plugins/RestServer/Binder/UInt160BinderProvider.cs @@ -0,0 +1,35 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UInt160BinderProvider.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.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; +using System; + +namespace Neo.Plugins.RestServer.Binder +{ + internal class NeoBinderProvider : IModelBinderProvider + { + public IModelBinder? GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.Metadata.ModelType == typeof(UInt160)) + { + return new BinderTypeModelBinder(typeof(UInt160Binder)); + } + + return null; + } + } +} diff --git a/src/Plugins/RestServer/Controllers/v1/ContractsController.cs b/src/Plugins/RestServer/Controllers/v1/ContractsController.cs new file mode 100644 index 0000000000..4d61840d16 --- /dev/null +++ b/src/Plugins/RestServer/Controllers/v1/ContractsController.cs @@ -0,0 +1,221 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractsController.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.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Neo.Plugins.RestServer.Exceptions; +using Neo.Plugins.RestServer.Extensions; +using Neo.Plugins.RestServer.Helpers; +using Neo.Plugins.RestServer.Models; +using Neo.Plugins.RestServer.Models.Contract; +using Neo.Plugins.RestServer.Models.Error; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using Neo.SmartContract.Native; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Mime; + +namespace Neo.Plugins.RestServer.Controllers.v1 +{ + [Route("/api/v{version:apiVersion}/contracts")] + [Produces(MediaTypeNames.Application.Json)] + [Consumes(MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ErrorModel))] + [ApiVersion("1.0")] + [ApiController] + public class ContractsController : ControllerBase + { + private readonly NeoSystem _neoSystem; + + public ContractsController() + { + _neoSystem = RestServerPlugin.NeoSystem ?? throw new NodeNetworkException(); + } + + /// + /// Get all the smart contracts from the blockchain. + /// + /// Page + /// Page Size + /// An array of Contract object. + /// No more pages. + /// Successful + /// An error occurred. See Response for details. + [HttpGet(Name = "GetContracts")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ContractState[]))] + public IActionResult Get( + [FromQuery(Name = "page")] + int skip = 1, + [FromQuery(Name = "size")] + int take = 50) + { + if (skip < 1 || take < 1 || take > RestServerSettings.Current.MaxPageSize) + throw new InvalidParameterRangeException(); + var contracts = NativeContract.ContractManagement.ListContracts(_neoSystem.StoreView); + if (contracts.Any() == false) + return NoContent(); + var contractRequestList = contracts.OrderBy(o => o.Id).Skip((skip - 1) * take).Take(take); + if (contractRequestList.Any() == false) + return NoContent(); + return Ok(contractRequestList); + } + + /// + /// Gets count of total smart contracts on blockchain. + /// + /// Count Object + /// Successful + /// An error occurred. See Response for details. + [HttpGet("count", Name = "GetContractCount")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(CountModel))] + public IActionResult GetCount() + { + var contracts = NativeContract.ContractManagement.ListContracts(_neoSystem.StoreView); + return Ok(new CountModel() { Count = contracts.Count() }); + } + + /// + /// Get a smart contract's storage. + /// + /// ScriptHash + /// An array of the Key (Base64) Value (Base64) Pairs objects. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("{hash:required}/storage", Name = "GetContractStorage")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(KeyValuePair, ReadOnlyMemory>[]))] + public IActionResult GetContractStorage( + [FromRoute(Name = "hash")] + UInt160 scriptHash) + { + if (NativeContract.IsNative(scriptHash)) + return NoContent(); + var contract = NativeContract.ContractManagement.GetContract(_neoSystem.StoreView, scriptHash); + if (contract == null) + throw new ContractNotFoundException(scriptHash); + var contractStorage = contract.GetStorage(_neoSystem.StoreView); + return Ok(contractStorage.Select(s => new KeyValuePair, ReadOnlyMemory>(s.key.Key, s.value.Value))); + } + + /// + /// Get a smart contract. + /// + /// ScriptHash + /// Contract Object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("{hash:required}", Name = "GetContract")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ContractState))] + public IActionResult GetByScriptHash( + [FromRoute(Name = "hash")] + UInt160 scriptHash) + { + var contracts = NativeContract.ContractManagement.GetContract(_neoSystem.StoreView, scriptHash); + if (contracts == null) + throw new ContractNotFoundException(scriptHash); + return Ok(contracts); + } + + /// + /// Get abi of a smart contract. + /// + /// ScriptHash + /// Contract Abi Object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("{hash:required}/abi", Name = "GetContractAbi")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ContractAbi))] + public IActionResult GetContractAbi( + [FromRoute(Name = "hash")] + UInt160 scriptHash) + { + var contracts = NativeContract.ContractManagement.GetContract(_neoSystem.StoreView, scriptHash); + if (contracts == null) + throw new ContractNotFoundException(scriptHash); + return Ok(contracts.Manifest.Abi); + } + + /// + /// Get manifest of a smart contract. + /// + /// ScriptHash + /// Contract Manifest object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("{hash:required}/manifest", Name = "GetContractManifest")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ContractManifest))] + public IActionResult GetContractManifest( + [FromRoute(Name = "hash")] + UInt160 scriptHash) + { + var contracts = NativeContract.ContractManagement.GetContract(_neoSystem.StoreView, scriptHash); + if (contracts == null) + throw new ContractNotFoundException(scriptHash); + return Ok(contracts.Manifest); + } + + /// + /// Get nef of a smart contract. + /// + /// ScriptHash + /// Contract Nef object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("{hash:required}/nef", Name = "GetContractNefFile")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(NefFile))] + public IActionResult GetContractNef( + [FromRoute(Name = "hash")] + UInt160 scriptHash) + { + var contracts = NativeContract.ContractManagement.GetContract(_neoSystem.StoreView, scriptHash); + if (contracts == null) + throw new ContractNotFoundException(scriptHash); + return Ok(contracts.Nef); + } + + /// + /// Invoke a method as ReadOnly Flag on a smart contract. + /// + /// ScriptHash + /// method name + /// JArray of the contract parameters. + /// Execution Engine object. + /// Successful + /// An error occurred. See Response for details. + [HttpPost("{hash:required}/invoke", Name = "InvokeContractMethod")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ExecutionEngineModel))] + public IActionResult InvokeContract( + [FromRoute(Name = "hash")] + UInt160 scriptHash, + [FromQuery(Name = "method")] + string method, + [FromBody] + InvokeParams invokeParameters) + { + var contracts = NativeContract.ContractManagement.GetContract(_neoSystem.StoreView, scriptHash); + if (contracts == null) + throw new ContractNotFoundException(scriptHash); + if (string.IsNullOrEmpty(method)) + throw new QueryParameterNotFoundException(nameof(method)); + try + { + var engine = ScriptHelper.InvokeMethod(_neoSystem.Settings, _neoSystem.StoreView, contracts.Hash, method, invokeParameters.ContractParameters, invokeParameters.Signers, out var script); + return Ok(engine.ToModel()); + } + catch (Exception ex) + { + throw ex.InnerException ?? ex; + } + } + } +} diff --git a/src/Plugins/RestServer/Controllers/v1/LedgerController.cs b/src/Plugins/RestServer/Controllers/v1/LedgerController.cs new file mode 100644 index 0000000000..24de6a7b24 --- /dev/null +++ b/src/Plugins/RestServer/Controllers/v1/LedgerController.cs @@ -0,0 +1,390 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// LedgerController.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.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Neo.Network.P2P.Payloads; +using Neo.Plugins.RestServer.Exceptions; +using Neo.Plugins.RestServer.Extensions; +using Neo.Plugins.RestServer.Models.Blockchain; +using Neo.Plugins.RestServer.Models.Error; +using Neo.Plugins.RestServer.Models.Ledger; +using Neo.SmartContract.Native; +using System.Collections.Generic; +using System.Linq; +using System.Net.Mime; + +namespace Neo.Plugins.RestServer.Controllers.v1 +{ + [Route("/api/v{version:apiVersion}/ledger")] + [Produces(MediaTypeNames.Application.Json)] + [Consumes(MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ErrorModel))] + [ApiVersion("1.0")] + [ApiController] + public class LedgerController : ControllerBase + { + private readonly NeoSystem _neoSystem; + + public LedgerController() + { + _neoSystem = RestServerPlugin.NeoSystem ?? throw new NodeNetworkException(); + } + + #region Accounts + + /// + /// Gets all the accounts that hold gas on the blockchain. + /// + /// An array of account details object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("gas/accounts", Name = "GetGasAccounts")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AccountDetails[]))] + public IActionResult ShowGasAccounts() + { + var accounts = NativeContract.GAS.ListAccounts(_neoSystem.StoreView, _neoSystem.Settings); + return Ok(accounts.OrderByDescending(o => o.Balance)); + } + + /// + /// Get all the accounts that hold neo on the blockchain. + /// + /// An array of account details object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("neo/accounts", Name = "GetNeoAccounts")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(AccountDetails[]))] + public IActionResult ShowNeoAccounts() + { + var accounts = NativeContract.NEO.ListAccounts(_neoSystem.StoreView, _neoSystem.Settings); + return Ok(accounts.OrderByDescending(o => o.Balance)); + } + + #endregion + + #region Blocks + + /// + /// Get blocks from the blockchain. + /// + /// Page + /// Page Size + /// An array of Block Header Objects + /// No more pages. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("blocks", Name = "GetBlocks")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Header[]))] + public IActionResult GetBlocks( + [FromQuery(Name = "page")] + uint skip = 1, + [FromQuery(Name = "size")] + uint take = 50) + { + if (skip < 1 || take < 1 || take > RestServerSettings.Current.MaxPageSize) + throw new InvalidParameterRangeException(); + //var start = (skip - 1) * take + startIndex; + //var end = start + take; + var start = NativeContract.Ledger.CurrentIndex(_neoSystem.StoreView) - (skip - 1) * take; + var end = start - take; + var lstOfBlocks = new List
(); + for (var i = start; i > end; i--) + { + var block = NativeContract.Ledger.GetBlock(_neoSystem.StoreView, i); + if (block == null) + break; + lstOfBlocks.Add(block.Header); + } + if (lstOfBlocks.Count == 0) + return NoContent(); + return Ok(lstOfBlocks); + } + + /// + /// Gets the current block header of the connected node. + /// + /// Full Block Header Object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("blockheader/current", Name = "GetCurrnetBlockHeader")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Header))] + public IActionResult GetCurrentBlockHeader() + { + var currentIndex = NativeContract.Ledger.CurrentIndex(_neoSystem.StoreView); + var blockheader = NativeContract.Ledger.GetHeader(_neoSystem.StoreView, currentIndex); + return Ok(blockheader); + } + + /// + /// Gets a block by an its index. + /// + /// Block Index + /// Full Block Object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("blocks/{index:min(0)}", Name = "GetBlock")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Block))] + public IActionResult GetBlock( + [FromRoute(Name = "index")] + uint blockIndex) + { + var block = NativeContract.Ledger.GetBlock(_neoSystem.StoreView, blockIndex); + if (block == null) + throw new BlockNotFoundException(blockIndex); + return Ok(block); + } + + /// + /// Gets a block header by block index. + /// + /// Blocks index. + /// Block Header Object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("blocks/{index:min(0)}/header", Name = "GetBlockHeader")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Header))] + public IActionResult GetBlockHeader( + [FromRoute(Name = "index")] + uint blockIndex) + { + var block = NativeContract.Ledger.GetBlock(_neoSystem.StoreView, blockIndex); + if (block == null) + throw new BlockNotFoundException(blockIndex); + return Ok(block.Header); + } + + /// + /// Gets the witness of the block + /// + /// Block Index. + /// Witness Object + /// Successful + /// An error occurred. See Response for details. + [HttpGet("blocks/{index:min(0)}/witness", Name = "GetBlockWitness")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Witness))] + public IActionResult GetBlockWitness( + [FromRoute(Name = "index")] + uint blockIndex) + { + var block = NativeContract.Ledger.GetBlock(_neoSystem.StoreView, blockIndex); + if (block == null) + throw new BlockNotFoundException(blockIndex); + return Ok(block.Witness); + } + + /// + /// Gets the transactions of the block. + /// + /// Block Index. + /// Page + /// Page Size + /// An array of transaction object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("blocks/{index:min(0)}/transactions", Name = "GetBlockTransactions")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Transaction[]))] + public IActionResult GetBlockTransactions( + [FromRoute(Name = "index")] + uint blockIndex, + [FromQuery(Name = "page")] + int skip = 1, + [FromQuery(Name = "size")] + int take = 50) + { + if (skip < 1 || take < 1 || take > RestServerSettings.Current.MaxPageSize) + throw new InvalidParameterRangeException(); + var block = NativeContract.Ledger.GetBlock(_neoSystem.StoreView, blockIndex); + if (block == null) + throw new BlockNotFoundException(blockIndex); + if (block.Transactions == null || block.Transactions.Length == 0) + return NoContent(); + return Ok(block.Transactions.Skip((skip - 1) * take).Take(take)); + } + + #endregion + + #region Transactions + + /// + /// Gets a transaction + /// + /// Hash256 + /// Transaction object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("transactions/{hash:required}", Name = "GetTransaction")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Transaction))] + public IActionResult GetTransaction( + [FromRoute(Name = "hash")] + UInt256 hash) + { + if (NativeContract.Ledger.ContainsTransaction(_neoSystem.StoreView, hash) == false) + throw new TransactionNotFoundException(hash); + var txst = NativeContract.Ledger.GetTransaction(_neoSystem.StoreView, hash); + return Ok(txst); + } + + /// + /// Gets the witness of a transaction. + /// + /// Hash256 + /// An array of witness object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("transactions/{hash:required}/witnesses", Name = "GetTransactionWitnesses")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Witness[]))] + public IActionResult GetTransactionWitnesses( + [FromRoute( Name = "hash")] + UInt256 hash) + { + if (NativeContract.Ledger.ContainsTransaction(_neoSystem.StoreView, hash) == false) + throw new TransactionNotFoundException(hash); + var tx = NativeContract.Ledger.GetTransaction(_neoSystem.StoreView, hash); + return Ok(tx.Witnesses); + } + + /// + /// Gets the signers of a transaction. + /// + /// Hash256 + /// An array of Signer object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("transactions/{hash:required}/signers", Name = "GetTransactionSigners")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Signer[]))] + public IActionResult GetTransactionSigners( + [FromRoute( Name = "hash")] + UInt256 hash) + { + if (NativeContract.Ledger.ContainsTransaction(_neoSystem.StoreView, hash) == false) + throw new TransactionNotFoundException(hash); + var tx = NativeContract.Ledger.GetTransaction(_neoSystem.StoreView, hash); + return Ok(tx.Signers); + } + + /// + /// Gets the transaction attributes of a transaction. + /// + /// Hash256 + /// An array of the transaction attributes object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("transactions/{hash:required}/attributes", Name = "GetTransactionAttributes")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(TransactionAttribute[]))] + public IActionResult GetTransactionAttributes( + [FromRoute( Name = "hash")] + UInt256 hash) + { + if (NativeContract.Ledger.ContainsTransaction(_neoSystem.StoreView, hash) == false) + throw new TransactionNotFoundException(hash); + var tx = NativeContract.Ledger.GetTransaction(_neoSystem.StoreView, hash); + return Ok(tx.Attributes); + } + + #endregion + + #region Memory Pool + + /// + /// Gets memory pool. + /// + /// Page + /// Page Size. + /// An array of the Transaction object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("memorypool", Name = "GetMemoryPoolTransactions")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Transaction[]))] + public IActionResult GetMemoryPool( + [FromQuery(Name = "page")] + int skip = 1, + [FromQuery(Name = "size")] + int take = 50) + { + if (skip < 0 || take < 0 || take > RestServerSettings.Current.MaxPageSize) + throw new InvalidParameterRangeException(); + return Ok(_neoSystem.MemPool.Skip((skip - 1) * take).Take(take)); + } + + /// + /// Gets the count of the memory pool. + /// + /// Memory Pool Count Object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("memorypool/count", Name = "GetMemoryPoolCount")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(MemoryPoolCountModel))] + public IActionResult GetMemoryPoolCount() => + Ok(new MemoryPoolCountModel() + { + Count = _neoSystem.MemPool.Count, + UnVerifiedCount = _neoSystem.MemPool.UnVerifiedCount, + VerifiedCount = _neoSystem.MemPool.VerifiedCount, + }); + + /// + /// Gets verified memory pool. + /// + /// Page + /// Page Size. + /// An array of the Transaction object. + /// No more pages. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("memorypool/verified", Name = "GetMemoryPoolVeridiedTransactions")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Transaction[]))] + public IActionResult GetMemoryPoolVerified( + [FromQuery(Name = "page")] + int skip = 1, + [FromQuery(Name = "size")] + int take = 50) + { + if (skip < 0 || take < 0 || take > RestServerSettings.Current.MaxPageSize) + throw new InvalidParameterRangeException(); + if (_neoSystem.MemPool.Any() == false) + return NoContent(); + var vTx = _neoSystem.MemPool.GetVerifiedTransactions(); + return Ok(vTx.Skip((skip - 1) * take).Take(take)); + } + + /// + /// Gets unverified memory pool. + /// + /// Page + /// Page Size. + /// An array of the Transaction object. + /// No more pages. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("memorypool/unverified", Name = "GetMemoryPoolUnveridiedTransactions")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Transaction[]))] + public IActionResult GetMemoryPoolUnVerified( + [FromQuery(Name = "page")] + int skip = 1, + [FromQuery(Name = "size")] + int take = 50) + { + if (skip < 0 || take < 0 || take > RestServerSettings.Current.MaxPageSize) + throw new InvalidParameterRangeException(); + if (_neoSystem.MemPool.Any() == false) + return NoContent(); + _neoSystem.MemPool.GetVerifiedAndUnverifiedTransactions(out _, out var unVerifiedTransactions); + return Ok(unVerifiedTransactions.Skip((skip - 1) * take).Take(take)); + } + + #endregion + } +} diff --git a/src/Plugins/RestServer/Controllers/v1/NodeController.cs b/src/Plugins/RestServer/Controllers/v1/NodeController.cs new file mode 100644 index 0000000000..7d51c5a87b --- /dev/null +++ b/src/Plugins/RestServer/Controllers/v1/NodeController.cs @@ -0,0 +1,88 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// NodeController.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.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Neo.IO; +using Neo.Network.P2P; +using Neo.Plugins.RestServer.Extensions; +using Neo.Plugins.RestServer.Models.Error; +using Neo.Plugins.RestServer.Models.Node; +using System; +using System.Linq; +using System.Net.Mime; + +namespace Neo.Plugins.RestServer.Controllers.v1 +{ + [Route("/api/v{version:apiVersion}/node")] + [Produces(MediaTypeNames.Application.Json)] + [Consumes(MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ErrorModel))] + [ApiVersion("1.0")] + [ApiController] + public class NodeController : ControllerBase + { + private readonly LocalNode _neoLocalNode; + private readonly NeoSystem _neoSystem; + + public NodeController() + { + _neoLocalNode = RestServerPlugin.LocalNode ?? throw new InvalidOperationException(); + _neoSystem = RestServerPlugin.NeoSystem ?? throw new InvalidOperationException(); + } + + /// + /// Gets the connected remote nodes. + /// + /// An array of the Remote Node Objects. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("peers", Name = "GetNodeRemotePeers")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(RemoteNodeModel[]))] + public IActionResult GetPeers() + { + var rNodes = _neoLocalNode + .GetRemoteNodes() + .OrderByDescending(o => o.LastBlockIndex) + .ToArray(); + + return Ok(rNodes.Select(s => s.ToModel())); + } + + /// + /// Gets all the loaded plugins of the current connected node. + /// + /// An array of the Plugin objects. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("plugins", Name = "GetNodePlugins")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(PluginModel[]))] + public IActionResult GetPlugins() => + Ok(Plugin.Plugins.Select(s => + new PluginModel() + { + Name = s.Name, + Version = s.Version.ToString(3), + Description = s.Description, + })); + + /// + /// Gets the ProtocolSettings of the currently connected node. + /// + /// Protocol Settings Object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("settings", Name = "GetNodeProtocolSettings")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(ProtocolSettingsModel))] + public IActionResult GetSettings() => + Ok(_neoSystem.Settings.ToModel()); + } +} diff --git a/src/Plugins/RestServer/Controllers/v1/TokensController.cs b/src/Plugins/RestServer/Controllers/v1/TokensController.cs new file mode 100644 index 0000000000..78078b7cd3 --- /dev/null +++ b/src/Plugins/RestServer/Controllers/v1/TokensController.cs @@ -0,0 +1,306 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TokensController.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.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Neo.Plugins.RestServer.Exceptions; +using Neo.Plugins.RestServer.Extensions; +using Neo.Plugins.RestServer.Helpers; +using Neo.Plugins.RestServer.Models; +using Neo.Plugins.RestServer.Models.Error; +using Neo.Plugins.RestServer.Models.Token; +using Neo.Plugins.RestServer.Tokens; +using Neo.SmartContract.Native; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Mime; + +namespace Neo.Plugins.RestServer.Controllers.v1 +{ + [Route("/api/v{version:apiVersion}/tokens")] + [Produces(MediaTypeNames.Application.Json)] + [Consumes(MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ErrorModel))] + [ApiVersion("1.0")] + [ApiController] + public class TokensController : ControllerBase + { + private readonly NeoSystem _neoSystem; + + public TokensController() + { + _neoSystem = RestServerPlugin.NeoSystem ?? throw new NodeNetworkException(); + } + + #region NEP-17 + + /// + /// Gets all Nep-17 valid contracts from the blockchain. + /// + /// Page + /// Page Size + /// An array of the Nep-17 Token Object. + /// No more pages. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("nep-17", Name = "GetNep17Tokens")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(NEP17TokenModel[]))] + public IActionResult GetNEP17( + [FromQuery(Name = "page")] + int skip = 1, + [FromQuery(Name = "size")] + int take = 50) + { + if (skip < 1 || take < 1 || take > RestServerSettings.Current.MaxPageSize) + throw new InvalidParameterRangeException(); + var tokenList = NativeContract.ContractManagement.ListContracts(_neoSystem.StoreView); + var vaildContracts = tokenList + .Where(ContractHelper.IsNep17Supported) + .OrderBy(o => o.Manifest.Name) + .Skip((skip - 1) * take) + .Take(take); + if (vaildContracts.Any() == false) + return NoContent(); + var listResults = new List(); + foreach (var contract in vaildContracts) + { + try + { + var token = new NEP17Token(_neoSystem, contract.Hash); + listResults.Add(token.ToModel()); + } + catch + { + } + } + if (listResults.Any() == false) + return NoContent(); + return Ok(listResults); + } + + /// + /// The count of how many Nep-17 contracts are on the blockchain. + /// + /// Count Object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("nep-17/count", Name = "GetNep17TokenCount")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(CountModel))] + public IActionResult GetNEP17Count() + { + return Ok(new CountModel() + { + Count = NativeContract.ContractManagement.ListContracts(_neoSystem.StoreView).Count(ContractHelper.IsNep17Supported) + }); + } + + /// + /// Gets the balance of the Nep-17 contract by an address. + /// + /// Nep-17 ScriptHash + /// Neo Address ScriptHash + /// Token Balance Object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("nep-17/{scripthash:required}/balanceof/{address:required}", Name = "GetNep17TokenBalanceOf")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(TokenBalanceModel))] + public IActionResult GetNEP17( + [FromRoute(Name = "scripthash")] + UInt160 tokenAddessOrScripthash, + [FromRoute(Name = "address")] + UInt160 lookupAddressOrScripthash) + { + var contract = NativeContract.ContractManagement.GetContract(_neoSystem.StoreView, tokenAddessOrScripthash) ?? + throw new ContractNotFoundException(tokenAddessOrScripthash); + if (ContractHelper.IsNep17Supported(contract) == false) + throw new Nep17NotSupportedException(tokenAddessOrScripthash); + try + { + var token = new NEP17Token(_neoSystem, tokenAddessOrScripthash); + return Ok(new TokenBalanceModel() + { + Name = token.Name, + ScriptHash = token.ScriptHash, + Symbol = token.Symbol, + Decimals = token.Decimals, + Balance = token.BalanceOf(lookupAddressOrScripthash).Value, + TotalSupply = token.TotalSupply().Value, + }); + } + catch + { + throw new Nep17NotSupportedException(tokenAddessOrScripthash); + } + } + + #endregion + + #region NEP-11 + + /// + /// Gets all the Nep-11 valid contracts on from the blockchain. + /// + /// Page + /// Page Size + /// Nep-11 Token Object. + /// No more pages. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("nep-11", Name = "GetNep11Tokens")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(NEP11TokenModel[]))] + public IActionResult GetNEP11( + [FromQuery(Name = "page")] + int skip = 1, + [FromQuery(Name = "size")] + int take = 50) + { + if (skip < 1 || take < 1 || take > RestServerSettings.Current.MaxPageSize) + throw new InvalidParameterRangeException(); + var tokenList = NativeContract.ContractManagement.ListContracts(_neoSystem.StoreView); + var vaildContracts = tokenList + .Where(ContractHelper.IsNep11Supported) + .OrderBy(o => o.Manifest.Name) + .Skip((skip - 1) * take) + .Take(take); + if (vaildContracts.Any() == false) + return NoContent(); + var listResults = new List(); + foreach (var contract in vaildContracts) + { + try + { + var token = new NEP11Token(_neoSystem, contract.Hash); + listResults.Add(token.ToModel()); + } + catch + { + } + } + if (listResults.Any() == false) + return NoContent(); + return Ok(listResults); + } + + /// + /// The count of how many Nep-11 contracts are on the blockchain. + /// + /// Count Object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("nep-11/count", Name = "GetNep11TokenCount")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(CountModel))] + public IActionResult GetNEP11Count() + { + return Ok(new CountModel() { Count = NativeContract.ContractManagement.ListContracts(_neoSystem.StoreView).Count(ContractHelper.IsNep11Supported) }); + } + + /// + /// Gets the balance of the Nep-11 contract by an address. + /// + /// Nep-11 ScriptHash + /// Neo Address ScriptHash + /// Token Balance Object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("nep-11/{scripthash:required}/balanceof/{address:required}", Name = "GetNep11TokenBalanceOf")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(TokenBalanceModel))] + public IActionResult GetNEP11( + [FromRoute(Name = "scripthash")] + UInt160 sAddressHash, + [FromRoute(Name = "address")] + UInt160 addressHash) + { + var contract = NativeContract.ContractManagement.GetContract(_neoSystem.StoreView, sAddressHash) ?? + throw new ContractNotFoundException(sAddressHash); + if (ContractHelper.IsNep11Supported(contract) == false) + throw new Nep11NotSupportedException(sAddressHash); + try + { + var token = new NEP11Token(_neoSystem, sAddressHash); + return Ok(new TokenBalanceModel() + { + Name = token.Name, + ScriptHash = token.ScriptHash, + Symbol = token.Symbol, + Decimals = token.Decimals, + Balance = token.BalanceOf(addressHash).Value, + TotalSupply = token.TotalSupply().Value, + }); + } + catch + { + throw new Nep11NotSupportedException(sAddressHash); + } + } + + #endregion + + /// + /// Gets every single NEP17/NEP11 on the blockchain's balance by ScriptHash + /// + /// + /// Token Balance Object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("balanceof/{address:required}", Name = "GetAllTokensBalanceOf")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(TokenBalanceModel))] + public IActionResult GetBalances( + [FromRoute(Name = "address")] + UInt160 addressOrScripthash) + { + var tokenList = NativeContract.ContractManagement.ListContracts(_neoSystem.StoreView); + var validContracts = tokenList + .Where(w => ContractHelper.IsNep17Supported(w) || ContractHelper.IsNep11Supported(w)) + .OrderBy(o => o.Manifest.Name); + var listResults = new List(); + foreach (var contract in validContracts) + { + try + { + var token = new NEP17Token(_neoSystem, contract.Hash); + var balance = token.BalanceOf(addressOrScripthash).Value; + if (balance == 0) + continue; + listResults.Add(new() + { + Name = token.Name, + ScriptHash = token.ScriptHash, + Symbol = token.Symbol, + Decimals = token.Decimals, + Balance = balance, + TotalSupply = token.TotalSupply().Value, + }); + + var nft = new NEP11Token(_neoSystem, contract.Hash); + balance = nft.BalanceOf(addressOrScripthash).Value; + if (balance == 0) + continue; + listResults.Add(new() + { + Name = nft.Name, + ScriptHash = nft.ScriptHash, + Symbol = nft.Symbol, + Balance = balance, + Decimals = nft.Decimals, + TotalSupply = nft.TotalSupply().Value, + }); + } + catch (NotSupportedException) + { + } + } + return Ok(listResults); + } + } +} diff --git a/src/Plugins/RestServer/Controllers/v1/UtilsController.cs b/src/Plugins/RestServer/Controllers/v1/UtilsController.cs new file mode 100644 index 0000000000..88d03d0fca --- /dev/null +++ b/src/Plugins/RestServer/Controllers/v1/UtilsController.cs @@ -0,0 +1,108 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UtilsController.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.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Neo.Plugins.RestServer.Exceptions; +using Neo.Plugins.RestServer.Models.Error; +using Neo.Plugins.RestServer.Models.Utils; +using Neo.Wallets; +using System; +using System.Net.Mime; + +namespace Neo.Plugins.RestServer.Controllers.v1 +{ + [Route("/api/v{version:apiVersion}/utils")] + [Produces(MediaTypeNames.Application.Json)] + [Consumes(MediaTypeNames.Application.Json)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ErrorModel))] + [ApiVersion("1.0")] + [ApiController] + public class UtilsController : ControllerBase + { + private readonly NeoSystem _neoSystem; + + public UtilsController() + { + _neoSystem = RestServerPlugin.NeoSystem ?? throw new NodeNetworkException(); + } + + #region Validation + + /// + /// Converts script to Neo address. + /// + /// ScriptHash + /// Util Address Object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("{hash:required}/address", Name = "GetAddressByScripthash")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UtilsAddressModel))] + public IActionResult ScriptHashToWalletAddress( + [FromRoute(Name = "hash")] + UInt160 ScriptHash) + { + try + { + return Ok(new UtilsAddressModel() { Address = ScriptHash.ToAddress(_neoSystem.Settings.AddressVersion) }); + } + catch (FormatException) + { + throw new ScriptHashFormatException(); + } + } + + /// + /// Converts Neo address to ScriptHash + /// + /// Neo Address + /// Util ScriptHash Object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("{address:required}/scripthash", Name = "GetScripthashByAddress")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UtilsScriptHashModel))] + public IActionResult WalletAddressToScriptHash( + [FromRoute(Name = "address")] + string address) + { + try + { + return Ok(new UtilsScriptHashModel() { ScriptHash = address.ToScriptHash(_neoSystem.Settings.AddressVersion) }); + } + catch (FormatException) + { + throw new AddressFormatException(); + } + } + + /// + /// Get whether or not a Neo address or ScriptHash is valid. + /// + /// + /// Util Address Valid Object. + /// Successful + /// An error occurred. See Response for details. + [HttpGet("{address:required}/validate", Name = "IsValidAddressOrScriptHash")] + [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(UtilsAddressIsValidModel))] + public IActionResult ValidateAddress( + [FromRoute(Name = "address")] + string AddressOrScriptHash) + { + return Ok(new UtilsAddressIsValidModel() + { + Address = AddressOrScriptHash, + IsValid = RestServerUtility.TryConvertToScriptHash(AddressOrScriptHash, _neoSystem.Settings, out _), + }); + } + + #endregion + } +} diff --git a/src/Plugins/RestServer/Exceptions/AddressFormatException.cs b/src/Plugins/RestServer/Exceptions/AddressFormatException.cs new file mode 100644 index 0000000000..5c6001e0a9 --- /dev/null +++ b/src/Plugins/RestServer/Exceptions/AddressFormatException.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// AddressFormatException.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.RestServer.Exceptions +{ + internal class AddressFormatException : Exception + { + public AddressFormatException() : base() { } + public AddressFormatException(string message) : base(message) { } + } +} diff --git a/src/Plugins/RestServer/Exceptions/ApplicationEngineException.cs b/src/Plugins/RestServer/Exceptions/ApplicationEngineException.cs new file mode 100644 index 0000000000..aeb393eba6 --- /dev/null +++ b/src/Plugins/RestServer/Exceptions/ApplicationEngineException.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ApplicationEngineException.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.RestServer.Exceptions +{ + internal class ApplicationEngineException : Exception + { + public ApplicationEngineException() : base() { } + public ApplicationEngineException(string message) : base(message) { } + } +} diff --git a/src/Plugins/RestServer/Exceptions/BlockNotFoundException.cs b/src/Plugins/RestServer/Exceptions/BlockNotFoundException.cs new file mode 100644 index 0000000000..9e3623bdee --- /dev/null +++ b/src/Plugins/RestServer/Exceptions/BlockNotFoundException.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BlockNotFoundException.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.RestServer.Exceptions +{ + internal class BlockNotFoundException : Exception + { + public BlockNotFoundException() { } + public BlockNotFoundException(uint index) : base($"block '{index}' as not found.") { } + } +} diff --git a/src/Plugins/RestServer/Exceptions/ContractNotFoundException.cs b/src/Plugins/RestServer/Exceptions/ContractNotFoundException.cs new file mode 100644 index 0000000000..f6ef4fc97a --- /dev/null +++ b/src/Plugins/RestServer/Exceptions/ContractNotFoundException.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractNotFoundException.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.RestServer.Exceptions +{ + internal class ContractNotFoundException : Exception + { + public ContractNotFoundException() : base() { } + public ContractNotFoundException(UInt160 scriptHash) : base($"Contract '{scriptHash}' was not found.") { } + } +} diff --git a/src/Plugins/RestServer/Exceptions/InvalidParameterRangeException.cs b/src/Plugins/RestServer/Exceptions/InvalidParameterRangeException.cs new file mode 100644 index 0000000000..2da93e3099 --- /dev/null +++ b/src/Plugins/RestServer/Exceptions/InvalidParameterRangeException.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// InvalidParameterRangeException.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.RestServer.Exceptions +{ + internal class InvalidParameterRangeException : Exception + { + public InvalidParameterRangeException() : base() { } + public InvalidParameterRangeException(string message) : base(message) { } + } +} diff --git a/src/Plugins/RestServer/Exceptions/JsonPropertyNullOrEmptyException.cs b/src/Plugins/RestServer/Exceptions/JsonPropertyNullOrEmptyException.cs new file mode 100644 index 0000000000..9ef162ebf9 --- /dev/null +++ b/src/Plugins/RestServer/Exceptions/JsonPropertyNullOrEmptyException.cs @@ -0,0 +1,22 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JsonPropertyNullOrEmptyException.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.RestServer.Exceptions +{ + internal class JsonPropertyNullOrEmptyException : Exception + { + public JsonPropertyNullOrEmptyException() : base() { } + + public JsonPropertyNullOrEmptyException(string paramName) : base($"Value cannot be null or empty. (Parameter '{paramName}')") { } + } +} diff --git a/src/Plugins/RestServer/Exceptions/Nep11NotSupportedException.cs b/src/Plugins/RestServer/Exceptions/Nep11NotSupportedException.cs new file mode 100644 index 0000000000..3ea5bf3fba --- /dev/null +++ b/src/Plugins/RestServer/Exceptions/Nep11NotSupportedException.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Nep11NotSupportedException.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.RestServer.Exceptions +{ + internal class Nep11NotSupportedException : Exception + { + public Nep11NotSupportedException() { } + public Nep11NotSupportedException(UInt160 scriptHash) : base($"Contract '{scriptHash}' does not support NEP-11.") { } + } +} diff --git a/src/Plugins/RestServer/Exceptions/Nep17NotSupportedException.cs b/src/Plugins/RestServer/Exceptions/Nep17NotSupportedException.cs new file mode 100644 index 0000000000..04269c2f50 --- /dev/null +++ b/src/Plugins/RestServer/Exceptions/Nep17NotSupportedException.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Nep17NotSupportedException.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.RestServer.Exceptions +{ + internal class Nep17NotSupportedException : Exception + { + public Nep17NotSupportedException() { } + public Nep17NotSupportedException(UInt160 scriptHash) : base($"Contract '{scriptHash}' does not support NEP-17.") { } + } +} diff --git a/src/Plugins/RestServer/Exceptions/NodeException.cs b/src/Plugins/RestServer/Exceptions/NodeException.cs new file mode 100644 index 0000000000..1d81e40e97 --- /dev/null +++ b/src/Plugins/RestServer/Exceptions/NodeException.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// NodeException.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.RestServer.Exceptions +{ + internal class NodeException : Exception + { + public NodeException() : base() { } + public NodeException(string message) : base(message) { } + } +} diff --git a/src/Plugins/RestServer/Exceptions/NodeNetworkException.cs b/src/Plugins/RestServer/Exceptions/NodeNetworkException.cs new file mode 100644 index 0000000000..5fd045a3c9 --- /dev/null +++ b/src/Plugins/RestServer/Exceptions/NodeNetworkException.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// NodeNetworkException.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.RestServer.Exceptions +{ + internal class NodeNetworkException : Exception + { + public NodeNetworkException() : base("Network does not match config file's.") { } + public NodeNetworkException(string message) : base(message) { } + } +} diff --git a/src/Plugins/RestServer/Exceptions/QueryParameterNotFoundException.cs b/src/Plugins/RestServer/Exceptions/QueryParameterNotFoundException.cs new file mode 100644 index 0000000000..390b0032c9 --- /dev/null +++ b/src/Plugins/RestServer/Exceptions/QueryParameterNotFoundException.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// QueryParameterNotFoundException.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.RestServer.Exceptions +{ + internal class QueryParameterNotFoundException : Exception + { + public QueryParameterNotFoundException() { } + public QueryParameterNotFoundException(string parameterName) : base($"Query parameter '{parameterName}' was not found.") { } + } +} diff --git a/src/Plugins/RestServer/Exceptions/RestErrorCodes.cs b/src/Plugins/RestServer/Exceptions/RestErrorCodes.cs new file mode 100644 index 0000000000..d9a13d3dcf --- /dev/null +++ b/src/Plugins/RestServer/Exceptions/RestErrorCodes.cs @@ -0,0 +1,20 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RestErrorCodes.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.RestServer.Exceptions +{ + internal static class RestErrorCodes + { + //=========================Rest Codes========================= + public const int GenericException = 1000; + public const int ParameterFormatException = 1001; + } +} diff --git a/src/Plugins/RestServer/Exceptions/ScriptHashFormatException.cs b/src/Plugins/RestServer/Exceptions/ScriptHashFormatException.cs new file mode 100644 index 0000000000..7e3eda1f4b --- /dev/null +++ b/src/Plugins/RestServer/Exceptions/ScriptHashFormatException.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ScriptHashFormatException.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.RestServer.Exceptions +{ + internal class ScriptHashFormatException : Exception + { + public ScriptHashFormatException() : base() { } + public ScriptHashFormatException(string message) : base(message) { } + } +} diff --git a/src/Plugins/RestServer/Exceptions/TransactionNotFoundException.cs b/src/Plugins/RestServer/Exceptions/TransactionNotFoundException.cs new file mode 100644 index 0000000000..af67fb744b --- /dev/null +++ b/src/Plugins/RestServer/Exceptions/TransactionNotFoundException.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionNotFoundException.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.RestServer.Exceptions +{ + internal class TransactionNotFoundException : Exception + { + public TransactionNotFoundException() { } + public TransactionNotFoundException(UInt256 txhash) : base($"Transaction '{txhash}' was not found.") { } + } +} diff --git a/src/Plugins/RestServer/Exceptions/UInt256FormatException.cs b/src/Plugins/RestServer/Exceptions/UInt256FormatException.cs new file mode 100644 index 0000000000..bea2f43e46 --- /dev/null +++ b/src/Plugins/RestServer/Exceptions/UInt256FormatException.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UInt256FormatException.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.RestServer.Exceptions +{ + internal class UInt256FormatException : Exception + { + public UInt256FormatException() { } + public UInt256FormatException(string message) : base(message) { } + } +} diff --git a/src/Plugins/RestServer/Extensions/LedgerContractExtensions.cs b/src/Plugins/RestServer/Extensions/LedgerContractExtensions.cs new file mode 100644 index 0000000000..1c02e3f155 --- /dev/null +++ b/src/Plugins/RestServer/Extensions/LedgerContractExtensions.cs @@ -0,0 +1,145 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// LedgerContractExtensions.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.IO; +using Neo.Persistence; +using Neo.Plugins.RestServer.Models.Blockchain; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.Wallets; +using System; +using System.Collections.Generic; + +namespace Neo.Plugins.RestServer.Extensions +{ + internal static class LedgerContractExtensions + { + private const byte _prefix_block = 5; + private const byte _prefix_transaction = 11; + private const byte _prefix_account = 20; + //private const byte _prefix_totalsupply = 11; + + public static IEnumerable<(StorageKey key, StorageItem value)> GetStorageByPrefix(this ContractState contractState, DataCache snapshot, byte[] prefix) + { + ArgumentNullException.ThrowIfNull(nameof(contractState)); + ArgumentNullException.ThrowIfNull(nameof(snapshot)); + if (prefix?.Length == 0) + throw new ArgumentNullException(nameof(prefix)); + foreach (var (key, value) in snapshot.Find(StorageKey.CreateSearchPrefix(contractState.Id, prefix))) + yield return (key, value); + } + + public static StorageItem? GetStorageByKey(this ContractState contractState, DataCache snapshot, byte[] storageKey) + { + ArgumentNullException.ThrowIfNull(nameof(contractState)); + ArgumentNullException.ThrowIfNull(nameof(snapshot)); + if (storageKey?.Length == 0) + throw new ArgumentNullException(nameof(storageKey)); + foreach (var (key, value) in snapshot.Find(StorageKey.CreateSearchPrefix(contractState.Id, storageKey))) + if (key.Key.Span.SequenceEqual(storageKey)) + return value; + return default; + } + + public static IEnumerable<(StorageKey key, StorageItem value)> GetStorage(this ContractState contractState, DataCache snapshot) + { + ArgumentNullException.ThrowIfNull(nameof(contractState)); + ArgumentNullException.ThrowIfNull(nameof(snapshot)); + return ListContractStorage(snapshot, contractState.Id); + } + + public static IEnumerable<(StorageKey key, StorageItem value)> ListContractStorage(DataCache snapshot, int contractId) + { + ArgumentNullException.ThrowIfNull(nameof(snapshot)); + if (contractId < 0) + throw new ArgumentOutOfRangeException(nameof(contractId)); + foreach (var (key, value) in snapshot.Find(StorageKey.CreateSearchPrefix(contractId, ReadOnlySpan.Empty))) + yield return (key, value); + } + + public static IEnumerable ListBlocks(this LedgerContract ledger, DataCache snapshot) + { + ArgumentNullException.ThrowIfNull(nameof(snapshot)); + var kb = new KeyBuilder(ledger.Id, _prefix_block); + var prefixKey = kb.ToArray(); + foreach (var (key, value) in snapshot.Seek(prefixKey, SeekDirection.Forward)) + if (key.ToArray().AsSpan().StartsWith(prefixKey)) + yield return value.Value.AsSerializable(); + else + yield break; + } + + public static IEnumerable ListTransactions(this LedgerContract ledger, DataCache snapshot, uint page, uint pageSize) + { + ArgumentNullException.ThrowIfNull(nameof(snapshot)); + var kb = new KeyBuilder(ledger.Id, _prefix_transaction); + var prefixKey = kb.ToArray(); + uint index = 1; + foreach (var (key, value) in snapshot.Seek(prefixKey, SeekDirection.Forward)) + { + if (key.ToArray().AsSpan().StartsWith(prefixKey)) + { + if (index >= page && index < (pageSize + page)) + yield return value.GetInteroperable(); + index++; + } + else + yield break; + } + } + + public static IEnumerable ListAccounts(this GasToken gasToken, DataCache snapshot, ProtocolSettings protocolSettings) + { + ArgumentNullException.ThrowIfNull(nameof(snapshot)); + var kb = new KeyBuilder(gasToken.Id, _prefix_account); + var prefixKey = kb.ToArray(); + foreach (var (key, value) in snapshot.Seek(prefixKey, SeekDirection.Forward)) + { + if (key.ToArray().AsSpan().StartsWith(prefixKey)) + { + var addressHash = new UInt160(key.ToArray().AsSpan(5)); + yield return new AccountDetails() + { + ScriptHash = addressHash, + Address = addressHash.ToAddress(protocolSettings.AddressVersion), + Balance = value.GetInteroperable().Balance, + Decimals = NativeContract.GAS.Decimals + }; + } + else + yield break; + } + } + + public static IEnumerable ListAccounts(this NeoToken neoToken, DataCache snapshot, ProtocolSettings protocolSettings) + { + ArgumentNullException.ThrowIfNull(nameof(snapshot)); + var kb = new KeyBuilder(neoToken.Id, _prefix_account); + var prefixKey = kb.ToArray(); + foreach (var (key, value) in snapshot.Seek(prefixKey, SeekDirection.Forward)) + { + if (key.ToArray().AsSpan().StartsWith(prefixKey)) + { + var addressHash = new UInt160(key.ToArray().AsSpan(5)); + yield return new AccountDetails() + { + ScriptHash = addressHash, + Address = addressHash.ToAddress(protocolSettings.AddressVersion), + Balance = value.GetInteroperable().Balance, + Decimals = NativeContract.NEO.Decimals + }; + } + else + yield break; + } + } + } +} diff --git a/src/Plugins/RestServer/Extensions/ModelExtensions.cs b/src/Plugins/RestServer/Extensions/ModelExtensions.cs new file mode 100644 index 0000000000..610565cfd2 --- /dev/null +++ b/src/Plugins/RestServer/Extensions/ModelExtensions.cs @@ -0,0 +1,101 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ModelExtensions.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.Network.P2P; +using Neo.Plugins.RestServer.Models; +using Neo.Plugins.RestServer.Models.Error; +using Neo.Plugins.RestServer.Models.Node; +using Neo.Plugins.RestServer.Models.Token; +using Neo.Plugins.RestServer.Tokens; +using Neo.SmartContract; +using System; +using System.Linq; + +namespace Neo.Plugins.RestServer.Extensions +{ + internal static class ModelExtensions + { + public static ExecutionEngineModel ToModel(this ApplicationEngine ae) => + new() + { + GasConsumed = ae.FeeConsumed, + State = ae.State, + Notifications = ae.Notifications.Select(s => + new BlockchainEventModel() + { + ScriptHash = s.ScriptHash, + EventName = s.EventName, + State = [.. s.State], + }).ToArray(), + ResultStack = [.. ae.ResultStack], + FaultException = ae.FaultException == null ? + null : + new ErrorModel() + { + Code = ae.FaultException?.InnerException?.HResult ?? ae.FaultException?.HResult ?? -1, + Name = ae.FaultException?.InnerException?.GetType().Name ?? ae.FaultException?.GetType().Name ?? string.Empty, + Message = ae.FaultException?.InnerException?.Message ?? ae.FaultException?.Message ?? string.Empty, + }, + }; + + public static NEP17TokenModel ToModel(this NEP17Token token) => + new() + { + Name = token.Name, + Symbol = token.Symbol, + ScriptHash = token.ScriptHash, + Decimals = token.Decimals, + TotalSupply = token.TotalSupply().Value, + }; + + public static NEP11TokenModel ToModel(this NEP11Token nep11) => + new() + { + Name = nep11.Name, + ScriptHash = nep11.ScriptHash, + Symbol = nep11.Symbol, + Decimals = nep11.Decimals, + TotalSupply = nep11.TotalSupply().Value, + Tokens = nep11.Tokens().Select(s => new + { + Key = s, + Value = nep11.Properties(s), + }).ToDictionary(key => Convert.ToHexString(key.Key), value => value.Value), + }; + + public static ProtocolSettingsModel ToModel(this ProtocolSettings protocolSettings) => + new() + { + Network = protocolSettings.Network, + AddressVersion = protocolSettings.AddressVersion, + ValidatorsCount = protocolSettings.ValidatorsCount, + MillisecondsPerBlock = protocolSettings.MillisecondsPerBlock, + MaxValidUntilBlockIncrement = protocolSettings.MaxValidUntilBlockIncrement, + MaxTransactionsPerBlock = protocolSettings.MaxTransactionsPerBlock, + MemoryPoolMaxTransactions = protocolSettings.MemoryPoolMaxTransactions, + MaxTraceableBlocks = protocolSettings.MaxTraceableBlocks, + InitialGasDistribution = protocolSettings.InitialGasDistribution, + SeedList = protocolSettings.SeedList, + Hardforks = protocolSettings.Hardforks.ToDictionary(k => k.Key.ToString().Replace("HF_", string.Empty), v => v.Value), + StandbyValidators = protocolSettings.StandbyValidators, + StandbyCommittee = protocolSettings.StandbyCommittee, + }; + + public static RemoteNodeModel ToModel(this RemoteNode remoteNode) => + new() + { + RemoteAddress = remoteNode.Remote.Address.ToString(), + RemotePort = remoteNode.Remote.Port, + ListenTcpPort = remoteNode.ListenerTcpPort, + LastBlockIndex = remoteNode.LastBlockIndex, + }; + } +} diff --git a/src/Plugins/RestServer/Extensions/UInt160Extensions.cs b/src/Plugins/RestServer/Extensions/UInt160Extensions.cs new file mode 100644 index 0000000000..ecec27012a --- /dev/null +++ b/src/Plugins/RestServer/Extensions/UInt160Extensions.cs @@ -0,0 +1,28 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UInt160Extensions.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.RestServer.Helpers; +using Neo.SmartContract.Native; + +namespace Neo.Plugins.RestServer.Extensions +{ + internal static class UInt160Extensions + { + public static bool IsValidNep17(this UInt160 scriptHash) + { + var contractState = NativeContract.ContractManagement.GetContract(RestServerPlugin.NeoSystem!.StoreView, scriptHash); + return ContractHelper.IsNep17Supported(contractState); + } + + public static bool IsValidContract(this UInt160 scriptHash) => + NativeContract.ContractManagement.GetContract(RestServerPlugin.NeoSystem!.StoreView, scriptHash) != null; + } +} diff --git a/src/Plugins/RestServer/Helpers/ContractHelper.cs b/src/Plugins/RestServer/Helpers/ContractHelper.cs new file mode 100644 index 0000000000..be32f057c6 --- /dev/null +++ b/src/Plugins/RestServer/Helpers/ContractHelper.cs @@ -0,0 +1,181 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractHelper.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.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using Neo.SmartContract.Native; +using System; +using System.Linq; + +namespace Neo.Plugins.RestServer.Helpers +{ + public static class ContractHelper + { + public static ContractParameterDefinition[]? GetAbiEventParams(DataCache snapshot, UInt160 scriptHash, string eventName) + { + var contractState = NativeContract.ContractManagement.GetContract(snapshot, scriptHash); + if (contractState == null) + return []; + return contractState.Manifest.Abi.Events.SingleOrDefault(s => s.Name.Equals(eventName, StringComparison.OrdinalIgnoreCase))?.Parameters; + } + + public static bool IsNep17Supported(DataCache snapshot, UInt160 scriptHash) + { + var contractState = NativeContract.ContractManagement.GetContract(snapshot, scriptHash); + if (contractState == null) + return false; + return IsNep17Supported(contractState); + } + + public static bool IsNep11Supported(DataCache snapshot, UInt160 scriptHash) + { + var contractState = NativeContract.ContractManagement.GetContract(snapshot, scriptHash); + if (contractState == null) + return false; + return IsNep11Supported(contractState); + } + + public static bool IsNep17Supported(ContractState contractState) + { + var manifest = contractState.Manifest; + if (manifest.SupportedStandards.Any(a => a.Equals("NEP-17"))) + { + try + { + var symbolMethod = manifest.Abi.GetMethod("symbol", 0); + var decimalsMethod = manifest.Abi.GetMethod("decimals", 0); + var totalSupplyMethod = manifest.Abi.GetMethod("totalSupply", 0); + var balanceOfMethod = manifest.Abi.GetMethod("balanceOf", 1); + var transferMethod = manifest.Abi.GetMethod("transfer", 4); + + var symbolValid = symbolMethod.Safe == true && + symbolMethod.ReturnType == ContractParameterType.String; + var decimalsValid = decimalsMethod.Safe == true && + decimalsMethod.ReturnType == ContractParameterType.Integer; + var totalSupplyValid = totalSupplyMethod.Safe == true && + totalSupplyMethod.ReturnType == ContractParameterType.Integer; + var balanceOfValid = balanceOfMethod.Safe == true && + balanceOfMethod.ReturnType == ContractParameterType.Integer && + balanceOfMethod.Parameters[0].Type == ContractParameterType.Hash160; + var transferValid = transferMethod.Safe == false && + transferMethod.ReturnType == ContractParameterType.Boolean && + transferMethod.Parameters[0].Type == ContractParameterType.Hash160 && + transferMethod.Parameters[1].Type == ContractParameterType.Hash160 && + transferMethod.Parameters[2].Type == ContractParameterType.Integer && + transferMethod.Parameters[3].Type == ContractParameterType.Any; + var transferEvent = manifest.Abi.Events.Any(s => + s.Name == "Transfer" && + s.Parameters.Length == 3 && + s.Parameters[0].Type == ContractParameterType.Hash160 && + s.Parameters[1].Type == ContractParameterType.Hash160 && + s.Parameters[2].Type == ContractParameterType.Integer); + + return (symbolValid && + decimalsValid && + totalSupplyValid && + balanceOfValid && + transferValid && + transferEvent); + } + catch + { + return false; + } + } + return false; + } + + public static bool IsNep11Supported(ContractState contractState) + { + var manifest = contractState.Manifest; + if (manifest.SupportedStandards.Any(a => a.Equals("NEP-11"))) + { + try + { + var symbolMethod = manifest.Abi.GetMethod("symbol", 0); + var decimalsMethod = manifest.Abi.GetMethod("decimals", 0); + var totalSupplyMethod = manifest.Abi.GetMethod("totalSupply", 0); + var balanceOfMethod1 = manifest.Abi.GetMethod("balanceOf", 1); + var balanceOfMethod2 = manifest.Abi.GetMethod("balanceOf", 2); + var tokensOfMethod = manifest.Abi.GetMethod("tokensOf", 1); + var ownerOfMethod = manifest.Abi.GetMethod("ownerOf", 1); + var transferMethod1 = manifest.Abi.GetMethod("transfer", 3); + var transferMethod2 = manifest.Abi.GetMethod("transfer", 5); + + var symbolValid = symbolMethod.Safe == true && + symbolMethod.ReturnType == ContractParameterType.String; + var decimalsValid = decimalsMethod.Safe == true && + decimalsMethod.ReturnType == ContractParameterType.Integer; + var totalSupplyValid = totalSupplyMethod.Safe == true && + totalSupplyMethod.ReturnType == ContractParameterType.Integer; + var balanceOfValid1 = balanceOfMethod1.Safe == true && + balanceOfMethod1.ReturnType == ContractParameterType.Integer && + balanceOfMethod1.Parameters[0].Type == ContractParameterType.Hash160; + var balanceOfValid2 = balanceOfMethod2?.Safe == true && + balanceOfMethod2?.ReturnType == ContractParameterType.Integer && + balanceOfMethod2?.Parameters[0].Type == ContractParameterType.Hash160 && + balanceOfMethod2?.Parameters[0].Type == ContractParameterType.ByteArray; + var tokensOfValid = tokensOfMethod.Safe == true && + tokensOfMethod.ReturnType == ContractParameterType.InteropInterface && + tokensOfMethod.Parameters[0].Type == ContractParameterType.Hash160; + var ownerOfValid1 = ownerOfMethod.Safe == true && + ownerOfMethod.ReturnType == ContractParameterType.Hash160 && + ownerOfMethod.Parameters[0].Type == ContractParameterType.ByteArray; + var ownerOfValid2 = ownerOfMethod.Safe == true && + ownerOfMethod.ReturnType == ContractParameterType.InteropInterface && + ownerOfMethod.Parameters[0].Type == ContractParameterType.ByteArray; + var transferValid1 = transferMethod1.Safe == false && + transferMethod1.ReturnType == ContractParameterType.Boolean && + transferMethod1.Parameters[0].Type == ContractParameterType.Hash160 && + transferMethod1.Parameters[1].Type == ContractParameterType.ByteArray && + transferMethod1.Parameters[2].Type == ContractParameterType.Any; + var transferValid2 = transferMethod2?.Safe == false && + transferMethod2?.ReturnType == ContractParameterType.Boolean && + transferMethod2?.Parameters[0].Type == ContractParameterType.Hash160 && + transferMethod2?.Parameters[1].Type == ContractParameterType.Hash160 && + transferMethod2?.Parameters[2].Type == ContractParameterType.Integer && + transferMethod2?.Parameters[3].Type == ContractParameterType.ByteArray && + transferMethod2?.Parameters[4].Type == ContractParameterType.Any; + var transferEvent = manifest.Abi.Events.Any(a => + a.Name == "Transfer" && + a.Parameters.Length == 4 && + a.Parameters[0].Type == ContractParameterType.Hash160 && + a.Parameters[1].Type == ContractParameterType.Hash160 && + a.Parameters[2].Type == ContractParameterType.Integer && + a.Parameters[3].Type == ContractParameterType.ByteArray); + + return (symbolValid && + decimalsValid && + totalSupplyValid && + (balanceOfValid2 || balanceOfValid1) && + tokensOfValid && + (ownerOfValid2 || ownerOfValid1) && + (transferValid2 || transferValid1) && + transferEvent); + } + catch + { + return false; + } + } + return false; + } + + public static ContractMethodDescriptor? GetContractMethod(DataCache snapshot, UInt160 scriptHash, string method, int pCount) + { + var contractState = NativeContract.ContractManagement.GetContract(snapshot, scriptHash); + if (contractState == null) + return null; + return contractState.Manifest.Abi.GetMethod(method, pCount); + } + } +} diff --git a/src/Plugins/RestServer/Helpers/ScriptHelper.cs b/src/Plugins/RestServer/Helpers/ScriptHelper.cs new file mode 100644 index 0000000000..d592ef8708 --- /dev/null +++ b/src/Plugins/RestServer/Helpers/ScriptHelper.cs @@ -0,0 +1,72 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ScriptHelper.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.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.VM.Types; +using System; +using System.Linq; + +namespace Neo.Plugins.RestServer.Helpers +{ + internal static class ScriptHelper + { + public static bool InvokeMethod(ProtocolSettings protocolSettings, DataCache snapshot, UInt160 scriptHash, string method, out StackItem[] results, params object[] args) + { + using var scriptBuilder = new ScriptBuilder(); + scriptBuilder.EmitDynamicCall(scriptHash, method, CallFlags.ReadOnly, args); + byte[] script = scriptBuilder.ToArray(); + using var engine = ApplicationEngine.Run(script, snapshot, settings: protocolSettings, gas: RestServerSettings.Current.MaxGasInvoke); + results = engine.State == VMState.FAULT ? [] : [.. engine.ResultStack]; + return engine.State == VMState.HALT; + } + + public static ApplicationEngine InvokeMethod(ProtocolSettings protocolSettings, DataCache snapshot, UInt160 scriptHash, string method, ContractParameter[] args, Signer[]? signers, out byte[] script) + { + using var scriptBuilder = new ScriptBuilder(); + scriptBuilder.EmitDynamicCall(scriptHash, method, CallFlags.ReadOnly, args); + script = scriptBuilder.ToArray(); + var tx = signers == null ? null : new Transaction + { + Version = 0, + Nonce = (uint)Random.Shared.Next(), + ValidUntilBlock = NativeContract.Ledger.CurrentIndex(snapshot) + protocolSettings.MaxValidUntilBlockIncrement, + Signers = signers, + Attributes = [], + Script = script, + Witnesses = [.. signers.Select(s => new Witness())], + }; + using var engine = ApplicationEngine.Run(script, snapshot, settings: protocolSettings, gas: RestServerSettings.Current.MaxGasInvoke); + return engine; + } + + public static ApplicationEngine InvokeScript(ReadOnlyMemory script, Signer[]? signers = null, Witness[]? witnesses = null) + { + var neoSystem = RestServerPlugin.NeoSystem ?? throw new InvalidOperationException(); + + var snapshot = neoSystem.GetSnapshotCache(); + var tx = signers == null ? null : new Transaction + { + Version = 0, + Nonce = (uint)Random.Shared.Next(), + ValidUntilBlock = NativeContract.Ledger.CurrentIndex(snapshot) + neoSystem.Settings.MaxValidUntilBlockIncrement, + Signers = signers, + Attributes = [], + Script = script, + Witnesses = witnesses + }; + return ApplicationEngine.Run(script, snapshot, tx, settings: neoSystem.Settings, gas: RestServerSettings.Current.MaxGasInvoke); + } + } +} diff --git a/src/Plugins/RestServer/Middleware/RestServerMiddleware.cs b/src/Plugins/RestServer/Middleware/RestServerMiddleware.cs new file mode 100644 index 0000000000..860cc00496 --- /dev/null +++ b/src/Plugins/RestServer/Middleware/RestServerMiddleware.cs @@ -0,0 +1,73 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RestServerMiddleware.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.AspNetCore.Http; +using System.Reflection; +using System.Threading.Tasks; + +namespace Neo.Plugins.RestServer.Middleware +{ + internal class RestServerMiddleware + { + private readonly RequestDelegate _next; + + public RestServerMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task InvokeAsync(HttpContext context) + { + var request = context.Request; + var response = context.Response; + + SetServerInfomationHeader(response); + + await _next(context); + } + + public static void SetServerInfomationHeader(HttpResponse response) + { + var neoCliAsm = Assembly.GetEntryAssembly()?.GetName(); + var restServerAsm = Assembly.GetExecutingAssembly().GetName(); + + if (neoCliAsm?.Version is not null && restServerAsm.Version is not null) + { + if (restServerAsm.Version is not null) + { + response.Headers.Server = $"{neoCliAsm.Name}/{neoCliAsm.Version.ToString(3)} {restServerAsm.Name}/{restServerAsm.Version.ToString(3)}"; + } + else + { + response.Headers.Server = $"{neoCliAsm.Name}/{neoCliAsm.Version.ToString(3)} {restServerAsm.Name}"; + } + } + else + { + if (neoCliAsm is not null) + { + if (restServerAsm is not null) + { + response.Headers.Server = $"{neoCliAsm.Name} {restServerAsm.Name}"; + } + else + { + response.Headers.Server = $"{neoCliAsm.Name}"; + } + } + else + { + // Can't get the server name/version + } + } + } + } +} diff --git a/src/Plugins/RestServer/Models/Blockchain/AccountDetails.cs b/src/Plugins/RestServer/Models/Blockchain/AccountDetails.cs new file mode 100644 index 0000000000..ea0fc35da3 --- /dev/null +++ b/src/Plugins/RestServer/Models/Blockchain/AccountDetails.cs @@ -0,0 +1,43 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// AccountDetails.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.Numerics; + +namespace Neo.Plugins.RestServer.Models.Blockchain +{ + internal class AccountDetails + { + /// + /// Scripthash + /// + /// 0xed7cc6f5f2dd842d384f254bc0c2d58fb69a4761 + public UInt160 ScriptHash { get; set; } = UInt160.Zero; + + /// + /// Wallet address. + /// + /// NNLi44dJNXtDNSBkofB48aTVYtb1zZrNEs + + public string Address { get; set; } = string.Empty; + + /// + /// Balance of the account. + /// + /// 10000000 + public BigInteger Balance { get; set; } + + /// + /// Decimals of the token. + /// + /// 8 + public BigInteger Decimals { get; set; } + } +} diff --git a/src/Plugins/RestServer/Models/Contract/InvokeParams.cs b/src/Plugins/RestServer/Models/Contract/InvokeParams.cs new file mode 100644 index 0000000000..f455e19d7e --- /dev/null +++ b/src/Plugins/RestServer/Models/Contract/InvokeParams.cs @@ -0,0 +1,22 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// InvokeParams.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.Network.P2P.Payloads; +using Neo.SmartContract; + +namespace Neo.Plugins.RestServer.Models.Contract +{ + public class InvokeParams + { + public ContractParameter[] ContractParameters { get; set; } = []; + public Signer[] Signers { get; set; } = []; + } +} diff --git a/src/Plugins/RestServer/Models/CountModel.cs b/src/Plugins/RestServer/Models/CountModel.cs new file mode 100644 index 0000000000..8a5f5d925a --- /dev/null +++ b/src/Plugins/RestServer/Models/CountModel.cs @@ -0,0 +1,22 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// CountModel.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.RestServer.Models +{ + internal class CountModel + { + /// + /// The count of how many objects. + /// + /// 378 + public int Count { get; set; } + } +} diff --git a/src/Plugins/RestServer/Models/Error/ErrorModel.cs b/src/Plugins/RestServer/Models/Error/ErrorModel.cs new file mode 100644 index 0000000000..c741df9445 --- /dev/null +++ b/src/Plugins/RestServer/Models/Error/ErrorModel.cs @@ -0,0 +1,33 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ErrorModel.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.RestServer.Models.Error +{ + internal class ErrorModel + { + /// + /// Error's HResult Code. + /// + /// 1000 + public int Code { get; init; } = 1000; + /// + /// Error's name of the type. + /// + /// GeneralException + public string Name { get; init; } = "GeneralException"; + /// + /// Error's exception message. + /// + /// An error occurred. + /// Could be InnerException message as well, If exists. + public string Message { get; init; } = "An error occurred."; + } +} diff --git a/src/Plugins/RestServer/Models/Error/ParameterFormatExceptionModel.cs b/src/Plugins/RestServer/Models/Error/ParameterFormatExceptionModel.cs new file mode 100644 index 0000000000..2646ba780f --- /dev/null +++ b/src/Plugins/RestServer/Models/Error/ParameterFormatExceptionModel.cs @@ -0,0 +1,29 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ParameterFormatExceptionModel.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.RestServer.Exceptions; + +namespace Neo.Plugins.RestServer.Models.Error +{ + internal class ParameterFormatExceptionModel : ErrorModel + { + public ParameterFormatExceptionModel() + { + Code = RestErrorCodes.ParameterFormatException; + Name = nameof(RestErrorCodes.ParameterFormatException); + } + + public ParameterFormatExceptionModel(string message) : this() + { + Message = message; + } + } +} diff --git a/src/Plugins/RestServer/Models/ExecutionEngineModel.cs b/src/Plugins/RestServer/Models/ExecutionEngineModel.cs new file mode 100644 index 0000000000..e2668d68d8 --- /dev/null +++ b/src/Plugins/RestServer/Models/ExecutionEngineModel.cs @@ -0,0 +1,50 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ExecutionEngineModel.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.RestServer.Models.Error; +using Neo.SmartContract; +using Neo.VM; +using Neo.VM.Types; + +namespace Neo.Plugins.RestServer.Models +{ + internal class ExecutionEngineModel + { + public long GasConsumed { get; set; } = 0L; + public VMState State { get; set; } = VMState.NONE; + public BlockchainEventModel[] Notifications { get; set; } = System.Array.Empty(); + public StackItem[] ResultStack { get; set; } = System.Array.Empty(); + public ErrorModel? FaultException { get; set; } + } + + internal class BlockchainEventModel + { + public UInt160 ScriptHash { get; set; } = new(); + public string EventName { get; set; } = string.Empty; + public StackItem[] State { get; set; } = System.Array.Empty(); + + public static BlockchainEventModel Create(UInt160 scriptHash, string eventName, StackItem[] state) => + new() + { + ScriptHash = scriptHash, + EventName = eventName ?? string.Empty, + State = state, + }; + + public static BlockchainEventModel Create(NotifyEventArgs notifyEventArgs, StackItem[] state) => + new() + { + ScriptHash = notifyEventArgs.ScriptHash, + EventName = notifyEventArgs.EventName, + State = state, + }; + } +} diff --git a/src/Plugins/RestServer/Models/Ledger/MemoryPoolCountModel.cs b/src/Plugins/RestServer/Models/Ledger/MemoryPoolCountModel.cs new file mode 100644 index 0000000000..1edb3e6bf9 --- /dev/null +++ b/src/Plugins/RestServer/Models/Ledger/MemoryPoolCountModel.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MemoryPoolCountModel.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.RestServer.Models.Ledger +{ + internal class MemoryPoolCountModel + { + /// + /// Total count all transactions. + /// + /// 110 + public int Count { get; set; } + /// + /// Count of unverified transactions + /// + /// 10 + public int UnVerifiedCount { get; set; } + /// + /// Count of verified transactions. + /// + /// 100 + public int VerifiedCount { get; set; } + } +} diff --git a/src/Plugins/RestServer/Models/Node/PluginModel.cs b/src/Plugins/RestServer/Models/Node/PluginModel.cs new file mode 100644 index 0000000000..d93cd0202f --- /dev/null +++ b/src/Plugins/RestServer/Models/Node/PluginModel.cs @@ -0,0 +1,35 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// PluginModel.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.RestServer.Models.Node +{ + internal class PluginModel + { + /// + /// Name + /// + /// RestServer + public string Name { get; set; } = string.Empty; + + /// + /// Version + /// + /// 3.5.0 + + public string Version { get; set; } = string.Empty; + + /// + /// Description + /// + /// Enables REST Web Sevices for the node + public string Description { get; set; } = string.Empty; + } +} diff --git a/src/Plugins/RestServer/Models/Node/ProtocolSettingsModel.cs b/src/Plugins/RestServer/Models/Node/ProtocolSettingsModel.cs new file mode 100644 index 0000000000..e9535c3d6a --- /dev/null +++ b/src/Plugins/RestServer/Models/Node/ProtocolSettingsModel.cs @@ -0,0 +1,42 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ProtocolSettingsModel.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; +using System.Collections.Generic; + +namespace Neo.Plugins.RestServer.Models.Node +{ + internal class ProtocolSettingsModel + { + /// + /// Network + /// + /// 860833102 + public uint Network { get; set; } + + /// + /// AddressVersion + /// + /// 53 + public byte AddressVersion { get; set; } + public int ValidatorsCount { get; set; } + public uint MillisecondsPerBlock { get; set; } + public uint MaxValidUntilBlockIncrement { get; set; } + public uint MaxTransactionsPerBlock { get; set; } + public int MemoryPoolMaxTransactions { get; set; } + public uint MaxTraceableBlocks { get; set; } + public ulong InitialGasDistribution { get; set; } + public IReadOnlyCollection SeedList { get; set; } = new List().AsReadOnly(); + public IReadOnlyDictionary Hardforks { get; set; } = new Dictionary().AsReadOnly(); + public IReadOnlyList StandbyValidators { get; set; } = new List().AsReadOnly(); + public IReadOnlyList StandbyCommittee { get; set; } = new List().AsReadOnly(); + } +} diff --git a/src/Plugins/RestServer/Models/Node/RemoteNodeModel.cs b/src/Plugins/RestServer/Models/Node/RemoteNodeModel.cs new file mode 100644 index 0000000000..719d06284e --- /dev/null +++ b/src/Plugins/RestServer/Models/Node/RemoteNodeModel.cs @@ -0,0 +1,40 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RemoteNodeModel.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.RestServer.Models.Node +{ + public class RemoteNodeModel + { + /// + /// Remote peer's ip address. + /// + /// 10.0.0.100 + public string RemoteAddress { get; set; } = string.Empty; + + /// + /// Remote peer's port number. + /// + /// 20333 + public int RemotePort { get; set; } + + /// + /// Remote peer's listening tcp port. + /// + /// 20333 + public int ListenTcpPort { get; set; } + + /// + /// Remote peer's last synced block height. + /// + /// 2584158 + public uint LastBlockIndex { get; set; } + } +} diff --git a/src/Plugins/RestServer/Models/Token/NEP11TokenModel.cs b/src/Plugins/RestServer/Models/Token/NEP11TokenModel.cs new file mode 100644 index 0000000000..70ab7bb243 --- /dev/null +++ b/src/Plugins/RestServer/Models/Token/NEP11TokenModel.cs @@ -0,0 +1,22 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// NEP11TokenModel.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.VM.Types; +using System.Collections.Generic; + +namespace Neo.Plugins.RestServer.Models.Token +{ + internal class NEP11TokenModel : NEP17TokenModel + { + public IReadOnlyDictionary?> Tokens { get; set; } + = new Dictionary?>().AsReadOnly(); + } +} diff --git a/src/Plugins/RestServer/Models/Token/NEP17TokenModel.cs b/src/Plugins/RestServer/Models/Token/NEP17TokenModel.cs new file mode 100644 index 0000000000..b693e29774 --- /dev/null +++ b/src/Plugins/RestServer/Models/Token/NEP17TokenModel.cs @@ -0,0 +1,24 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// NEP17TokenModel.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.Numerics; + +namespace Neo.Plugins.RestServer.Models.Token +{ + internal class NEP17TokenModel + { + public string Name { get; set; } = string.Empty; + public UInt160 ScriptHash { get; set; } = UInt160.Zero; + public string Symbol { get; set; } = string.Empty; + public byte Decimals { get; set; } + public BigInteger TotalSupply { get; set; } + } +} diff --git a/src/Plugins/RestServer/Models/Token/TokenBalanceModel.cs b/src/Plugins/RestServer/Models/Token/TokenBalanceModel.cs new file mode 100644 index 0000000000..30a1de6e98 --- /dev/null +++ b/src/Plugins/RestServer/Models/Token/TokenBalanceModel.cs @@ -0,0 +1,25 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TokenBalanceModel.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.Numerics; + +namespace Neo.Plugins.RestServer.Models.Token +{ + public class TokenBalanceModel + { + public string Name { get; set; } = string.Empty; + public UInt160 ScriptHash { get; set; } = UInt160.Zero; + public string Symbol { get; set; } = string.Empty; + public byte Decimals { get; set; } + public BigInteger Balance { get; set; } + public BigInteger TotalSupply { get; set; } + } +} diff --git a/src/Plugins/RestServer/Models/Utils/UtilsAddressIsValidModel.cs b/src/Plugins/RestServer/Models/Utils/UtilsAddressIsValidModel.cs new file mode 100644 index 0000000000..780ce9c468 --- /dev/null +++ b/src/Plugins/RestServer/Models/Utils/UtilsAddressIsValidModel.cs @@ -0,0 +1,22 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UtilsAddressIsValidModel.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.RestServer.Models.Utils +{ + internal class UtilsAddressIsValidModel : UtilsAddressModel + { + /// + /// Indicates if address can be converted to ScriptHash or Neo Address. + /// + /// true + public bool IsValid { get; set; } + } +} diff --git a/src/Plugins/RestServer/Models/Utils/UtilsAddressModel.cs b/src/Plugins/RestServer/Models/Utils/UtilsAddressModel.cs new file mode 100644 index 0000000000..3454d25a1e --- /dev/null +++ b/src/Plugins/RestServer/Models/Utils/UtilsAddressModel.cs @@ -0,0 +1,22 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UtilsAddressModel.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.RestServer.Models.Utils +{ + internal class UtilsAddressModel + { + /// + /// Wallet address that was exported. + /// + /// NNLi44dJNXtDNSBkofB48aTVYtb1zZrNEs + public virtual string Address { get; set; } = string.Empty; + } +} diff --git a/src/Plugins/RestServer/Models/Utils/UtilsScriptHashModel.cs b/src/Plugins/RestServer/Models/Utils/UtilsScriptHashModel.cs new file mode 100644 index 0000000000..b1f30c0348 --- /dev/null +++ b/src/Plugins/RestServer/Models/Utils/UtilsScriptHashModel.cs @@ -0,0 +1,22 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UtilsScriptHashModel.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.RestServer.Models.Utils +{ + internal class UtilsScriptHashModel + { + /// + /// Scripthash of the wallet account exported. + /// + /// 0xed7cc6f5f2dd842d384f254bc0c2d58fb69a4761 + public UInt160 ScriptHash { get; set; } = UInt160.Zero; + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/BigDecimalJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/BigDecimalJsonConverter.cs new file mode 100644 index 0000000000..ef78dda5dc --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/BigDecimalJsonConverter.cs @@ -0,0 +1,66 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BigDecimalJsonConverter.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 Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Linq; +using System.Numerics; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class BigDecimalJsonConverter : JsonConverter + { + public override bool CanRead => true; + public override bool CanWrite => true; + + public override BigDecimal ReadJson(JsonReader reader, Type objectType, BigDecimal existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var token = JToken.ReadFrom(reader); + + switch (token.Type) + { + case JTokenType.Object: + { + var jobj = (JObject)token; + var valueProp = jobj.Properties().SingleOrDefault(p => p.Name.Equals("value", StringComparison.InvariantCultureIgnoreCase)); + var decimalsProp = jobj.Properties().SingleOrDefault(p => p.Name.Equals("decimals", StringComparison.InvariantCultureIgnoreCase)); + + if (valueProp != null && decimalsProp != null) + { + return new BigDecimal(valueProp.ToObject(), decimalsProp.ToObject()); + } + break; + } + case JTokenType.Float: + { + if (token is JValue jval && jval.Value is not null) + { + return new BigDecimal((decimal)jval.Value); + } + break; + } + } + + throw new FormatException(); + } + + public override void WriteJson(JsonWriter writer, BigDecimal value, JsonSerializer serializer) + { + var o = JToken.FromObject(new + { + value.Value, + value.Decimals, + }, serializer); + o.WriteTo(writer); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/BlockHeaderJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/BlockHeaderJsonConverter.cs new file mode 100644 index 0000000000..6efc1fbf63 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/BlockHeaderJsonConverter.cs @@ -0,0 +1,36 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BlockHeaderJsonConverter.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.Network.P2P.Payloads; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class BlockHeaderJsonConverter : JsonConverter
+ { + public override bool CanRead => false; + public override bool CanWrite => true; + + public override Header ReadJson(JsonReader reader, Type objectType, Header? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + public override void WriteJson(JsonWriter writer, Header? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.BlockHeaderToJToken(value, serializer); + j.WriteTo(writer); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/BlockJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/BlockJsonConverter.cs new file mode 100644 index 0000000000..aef729cef4 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/BlockJsonConverter.cs @@ -0,0 +1,34 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BlockJsonConverter.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.Network.P2P.Payloads; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; + +public class BlockJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override Block ReadJson(JsonReader reader, Type objectType, Block? existingValue, bool hasExistingValue, JsonSerializer serializer) => + throw new NotImplementedException(); + + public override void WriteJson(JsonWriter writer, Block? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.BlockToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/ContractAbiJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/ContractAbiJsonConverter.cs new file mode 100644 index 0000000000..c1ce53adc0 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/ContractAbiJsonConverter.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractAbiJsonConverter.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.SmartContract.Manifest; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; + +public class ContractAbiJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override ContractAbi ReadJson(JsonReader reader, Type objectType, ContractAbi? existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException(); + public override void WriteJson(JsonWriter writer, ContractAbi? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.ContractAbiToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/ContractEventDescriptorJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/ContractEventDescriptorJsonConverter.cs new file mode 100644 index 0000000000..e13f091aad --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/ContractEventDescriptorJsonConverter.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractEventDescriptorJsonConverter.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.SmartContract.Manifest; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; + +public class ContractEventDescriptorJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override ContractEventDescriptor ReadJson(JsonReader reader, Type objectType, ContractEventDescriptor? existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException(); + public override void WriteJson(JsonWriter writer, ContractEventDescriptor? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.ContractEventToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/ContractGroupJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/ContractGroupJsonConverter.cs new file mode 100644 index 0000000000..d988198c29 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/ContractGroupJsonConverter.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractGroupJsonConverter.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.SmartContract.Manifest; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; + +public class ContractGroupJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override ContractGroup ReadJson(JsonReader reader, Type objectType, ContractGroup? existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException(); + public override void WriteJson(JsonWriter writer, ContractGroup? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.ContractGroupToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/ContractInvokeParametersJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/ContractInvokeParametersJsonConverter.cs new file mode 100644 index 0000000000..e455cef718 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/ContractInvokeParametersJsonConverter.cs @@ -0,0 +1,35 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractInvokeParametersJsonConverter.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.RestServer.Models.Contract; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class ContractInvokeParametersJsonConverter : JsonConverter + { + public override bool CanRead => true; + public override bool CanWrite => false; + + public override InvokeParams ReadJson(JsonReader reader, Type objectType, InvokeParams? existingValue, bool hasExistingValue, global::Newtonsoft.Json.JsonSerializer serializer) + { + var token = JToken.ReadFrom(reader); + return RestServerUtility.ContractInvokeParametersFromJToken(token); + } + + public override void WriteJson(JsonWriter writer, InvokeParams? value, global::Newtonsoft.Json.JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/ContractJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/ContractJsonConverter.cs new file mode 100644 index 0000000000..ead8b5930c --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/ContractJsonConverter.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractJsonConverter.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.SmartContract; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; + +public class ContractJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override ContractState ReadJson(JsonReader reader, Type objectType, ContractState? existingValue, bool hasExistingValue, global::Newtonsoft.Json.JsonSerializer serializer) => throw new NotImplementedException(); + public override void WriteJson(JsonWriter writer, ContractState? value, global::Newtonsoft.Json.JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.ContractStateToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/ContractManifestJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/ContractManifestJsonConverter.cs new file mode 100644 index 0000000000..12a86bae48 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/ContractManifestJsonConverter.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractManifestJsonConverter.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.SmartContract.Manifest; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; + +public class ContractManifestJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override ContractManifest ReadJson(JsonReader reader, Type objectType, ContractManifest? existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException(); + public override void WriteJson(JsonWriter writer, ContractManifest? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.ContractManifestToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/ContractMethodJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/ContractMethodJsonConverter.cs new file mode 100644 index 0000000000..fe24c9d321 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/ContractMethodJsonConverter.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractMethodJsonConverter.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.SmartContract.Manifest; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; + +public class ContractMethodJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override ContractMethodDescriptor ReadJson(JsonReader reader, Type objectType, ContractMethodDescriptor? existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException(); + public override void WriteJson(JsonWriter writer, ContractMethodDescriptor? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.ContractMethodToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/ContractMethodParametersJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/ContractMethodParametersJsonConverter.cs new file mode 100644 index 0000000000..89994428c2 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/ContractMethodParametersJsonConverter.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractMethodParametersJsonConverter.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.SmartContract.Manifest; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; +public class ContractMethodParametersJsonConverter : JsonConverter +{ + public override bool CanRead => base.CanRead; + + public override bool CanWrite => base.CanWrite; + + public override ContractParameterDefinition ReadJson(JsonReader reader, Type objectType, ContractParameterDefinition? existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException(); + public override void WriteJson(JsonWriter writer, ContractParameterDefinition? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.ContractMethodParameterToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/ContractParameterDefinitionJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/ContractParameterDefinitionJsonConverter.cs new file mode 100644 index 0000000000..f90b483eb0 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/ContractParameterDefinitionJsonConverter.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractParameterDefinitionJsonConverter.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.SmartContract.Manifest; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; + +public class ContractParameterDefinitionJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override ContractParameterDefinition ReadJson(JsonReader reader, Type objectType, ContractParameterDefinition? existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException(); + public override void WriteJson(JsonWriter writer, ContractParameterDefinition? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.ContractParameterDefinitionToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/ContractParameterJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/ContractParameterJsonConverter.cs new file mode 100644 index 0000000000..1a6386d2a0 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/ContractParameterJsonConverter.cs @@ -0,0 +1,35 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractParameterJsonConverter.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.SmartContract; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class ContractParameterJsonConverter : JsonConverter + { + public override bool CanRead => true; + public override bool CanWrite => false; + + public override ContractParameter ReadJson(JsonReader reader, Type objectType, ContractParameter? existingValue, bool hasExistingValue, global::Newtonsoft.Json.JsonSerializer serializer) + { + var token = JToken.ReadFrom(reader); + return RestServerUtility.ContractParameterFromJToken(token); + } + + public override void WriteJson(JsonWriter writer, ContractParameter? value, global::Newtonsoft.Json.JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/ContractPermissionDescriptorJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/ContractPermissionDescriptorJsonConverter.cs new file mode 100644 index 0000000000..0922532f7f --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/ContractPermissionDescriptorJsonConverter.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractPermissionDescriptorJsonConverter.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.SmartContract.Manifest; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; + +public class ContractPermissionDescriptorJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override ContractPermissionDescriptor ReadJson(JsonReader reader, Type objectType, ContractPermissionDescriptor? existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException(); + public override void WriteJson(JsonWriter writer, ContractPermissionDescriptor? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.ContractPermissionDescriptorToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/ContractPermissionJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/ContractPermissionJsonConverter.cs new file mode 100644 index 0000000000..e5dc7c7d8c --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/ContractPermissionJsonConverter.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractPermissionJsonConverter.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.SmartContract.Manifest; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; + +internal class ContractPermissionJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override ContractPermission ReadJson(JsonReader reader, Type objectType, ContractPermission? existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException(); + public override void WriteJson(JsonWriter writer, ContractPermission? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.ContractPermissionToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/ECPointJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/ECPointJsonConverter.cs new file mode 100644 index 0000000000..6fb4abb9a6 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/ECPointJsonConverter.cs @@ -0,0 +1,41 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ECPointJsonConverter.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; +using Neo.Plugins.RestServer.Exceptions; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class ECPointJsonConverter : JsonConverter + { + public override ECPoint ReadJson(JsonReader reader, Type objectType, ECPoint? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var value = reader?.Value?.ToString(); + try + { + return ECPoint.Parse(value, ECCurve.Secp256r1); + } + catch (FormatException) + { + throw new UInt256FormatException($"'{value}' is invalid."); + } + } + + public override void WriteJson(JsonWriter writer, ECPoint? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + writer.WriteValue(value.ToString()); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/GuidJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/GuidJsonConverter.cs new file mode 100644 index 0000000000..36f244e610 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/GuidJsonConverter.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// GuidJsonConverter.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 Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + internal class GuidJsonConverter : JsonConverter + { + public override Guid ReadJson(JsonReader reader, Type objectType, Guid existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var value = reader.Value?.ToString(); + if (value is null) throw new ArgumentNullException(nameof(value)); + + return Guid.Parse(value); + } + + public override void WriteJson(JsonWriter writer, Guid value, JsonSerializer serializer) + { + writer.WriteValue(value.ToString("n")); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/InteropInterfaceJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/InteropInterfaceJsonConverter.cs new file mode 100644 index 0000000000..830f1e6e09 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/InteropInterfaceJsonConverter.cs @@ -0,0 +1,37 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// InteropInterfaceJsonConverter.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.VM.Types; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class InteropInterfaceJsonConverter : JsonConverter + { + public override InteropInterface ReadJson(JsonReader reader, Type objectType, InteropInterface? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var t = JToken.Load(reader); + if (RestServerUtility.StackItemFromJToken(t) is InteropInterface iface) return iface; + + throw new FormatException(); + } + + public override void WriteJson(JsonWriter writer, InteropInterface? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var t = RestServerUtility.StackItemToJToken(value, null, serializer); + t.WriteTo(writer); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/MethodTokenJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/MethodTokenJsonConverter.cs new file mode 100644 index 0000000000..a0acc4246a --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/MethodTokenJsonConverter.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MethodTokenJsonConverter.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.SmartContract; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; + +public class MethodTokenJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override MethodToken ReadJson(JsonReader reader, Type objectType, MethodToken? existingValue, bool hasExistingValue, global::Newtonsoft.Json.JsonSerializer serializer) => throw new NotImplementedException(); + public override void WriteJson(JsonWriter writer, MethodToken? value, global::Newtonsoft.Json.JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.MethodTokenToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/NefFileJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/NefFileJsonConverter.cs new file mode 100644 index 0000000000..bad159dc99 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/NefFileJsonConverter.cs @@ -0,0 +1,28 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// NefFileJsonConverter.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.SmartContract; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; + +public class NefFileJsonConverter : JsonConverter +{ + public override NefFile ReadJson(JsonReader reader, Type objectType, NefFile? existingValue, bool hasExistingValue, global::Newtonsoft.Json.JsonSerializer serializer) => throw new NotImplementedException(); + public override void WriteJson(JsonWriter writer, NefFile? value, global::Newtonsoft.Json.JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.ContractNefFileToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/ReadOnlyMemoryBytesJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/ReadOnlyMemoryBytesJsonConverter.cs new file mode 100644 index 0000000000..d75de27d96 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/ReadOnlyMemoryBytesJsonConverter.cs @@ -0,0 +1,34 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ReadOnlyMemoryBytesJsonConverter.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 Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class ReadOnlyMemoryBytesJsonConverter : JsonConverter> + { + public override ReadOnlyMemory ReadJson(JsonReader reader, Type objectType, ReadOnlyMemory existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var o = JToken.Load(reader); + var value = o.ToObject(); + if (value is null) throw new ArgumentNullException(nameof(value)); + + return Convert.FromBase64String(value); + } + + public override void WriteJson(JsonWriter writer, ReadOnlyMemory value, JsonSerializer serializer) + { + writer.WriteValue(Convert.ToBase64String(value.Span)); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/SignerJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/SignerJsonConverter.cs new file mode 100644 index 0000000000..c2d2c6e0d2 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/SignerJsonConverter.cs @@ -0,0 +1,34 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// SignerJsonConverter.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.Network.P2P.Payloads; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; + +public class SignerJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override Signer ReadJson(JsonReader reader, Type objectType, Signer? existingValue, bool hasExistingValue, JsonSerializer serializer) => + throw new NotImplementedException(); + + public override void WriteJson(JsonWriter writer, Signer? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.SignerToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/StackItemJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/StackItemJsonConverter.cs new file mode 100644 index 0000000000..4468468dd2 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/StackItemJsonConverter.cs @@ -0,0 +1,35 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// StackItemJsonConverter.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.VM.Types; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class StackItemJsonConverter : JsonConverter + { + public override StackItem ReadJson(JsonReader reader, Type objectType, StackItem? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var t = JObject.Load(reader); + return RestServerUtility.StackItemFromJToken(t); + } + + public override void WriteJson(JsonWriter writer, StackItem? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var t = RestServerUtility.StackItemToJToken(value, null, serializer); + t.WriteTo(writer); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/TransactionAttributeJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/TransactionAttributeJsonConverter.cs new file mode 100644 index 0000000000..7012c3fc93 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/TransactionAttributeJsonConverter.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionAttributeJsonConverter.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.Network.P2P.Payloads; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; + +public class TransactionAttributeJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override TransactionAttribute ReadJson(JsonReader reader, Type objectType, TransactionAttribute? existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException(); + public override void WriteJson(JsonWriter writer, TransactionAttribute? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.TransactionAttributeToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/TransactionJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/TransactionJsonConverter.cs new file mode 100644 index 0000000000..3813bdea78 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/TransactionJsonConverter.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionJsonConverter.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.Network.P2P.Payloads; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; + +public class TransactionJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override Transaction ReadJson(JsonReader reader, Type objectType, Transaction? existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException(); + public override void WriteJson(JsonWriter writer, Transaction? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.TransactionToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/UInt160JsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/UInt160JsonConverter.cs new file mode 100644 index 0000000000..496bb1a39f --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/UInt160JsonConverter.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UInt160JsonConverter.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.RestServer.Exceptions; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class UInt160JsonConverter : JsonConverter + { + public override bool CanRead => true; + public override bool CanWrite => true; + + public override UInt160 ReadJson(JsonReader reader, Type objectType, UInt160? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var value = reader.Value?.ToString(); + if (value is null) throw new ArgumentNullException(nameof(value)); + + try + { + return RestServerUtility.ConvertToScriptHash(value, RestServerPlugin.NeoSystem!.Settings); + } + catch (FormatException) + { + throw new ScriptHashFormatException($"'{value}' is invalid."); + } + } + + public override void WriteJson(JsonWriter writer, UInt160? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + writer.WriteValue(value.ToString()); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/UInt256JsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/UInt256JsonConverter.cs new file mode 100644 index 0000000000..fd4a8851d7 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/UInt256JsonConverter.cs @@ -0,0 +1,42 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UInt256JsonConverter.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.RestServer.Exceptions; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class UInt256JsonConverter : JsonConverter + { + public override UInt256 ReadJson(JsonReader reader, Type objectType, UInt256? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var value = reader.Value?.ToString(); + if (value is null) throw new ArgumentNullException(nameof(value)); + + try + { + return UInt256.Parse(value); + } + catch (FormatException) + { + throw new UInt256FormatException($"'{value}' is invalid."); + } + } + + public override void WriteJson(JsonWriter writer, UInt256? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + writer.WriteValue(value.ToString()); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/VmArrayJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/VmArrayJsonConverter.cs new file mode 100644 index 0000000000..2ff58c83aa --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/VmArrayJsonConverter.cs @@ -0,0 +1,37 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VmArrayJsonConverter.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 Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using Array = Neo.VM.Types.Array; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class VmArrayJsonConverter : JsonConverter + { + public override Array ReadJson(JsonReader reader, Type objectType, Array? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var t = JToken.Load(reader); + if (RestServerUtility.StackItemFromJToken(t) is Array a) return a; + + throw new FormatException(); + } + + public override void WriteJson(JsonWriter writer, Array? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var t = RestServerUtility.StackItemToJToken(value, null, serializer); + t.WriteTo(writer); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/VmBooleanJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/VmBooleanJsonConverter.cs new file mode 100644 index 0000000000..e3809e16af --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/VmBooleanJsonConverter.cs @@ -0,0 +1,37 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VmBooleanJsonConverter.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 Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using Boolean = Neo.VM.Types.Boolean; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class VmBooleanJsonConverter : JsonConverter + { + public override Boolean ReadJson(JsonReader reader, Type objectType, Boolean? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var t = JToken.ReadFrom(reader); + if (RestServerUtility.StackItemFromJToken(t) is Boolean b) return b; + + throw new FormatException(); + } + + public override void WriteJson(JsonWriter writer, Boolean? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var t = RestServerUtility.StackItemToJToken(value, null, serializer); + t.WriteTo(writer); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/VmBufferJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/VmBufferJsonConverter.cs new file mode 100644 index 0000000000..1441466c33 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/VmBufferJsonConverter.cs @@ -0,0 +1,37 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VmBufferJsonConverter.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 Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using Buffer = Neo.VM.Types.Buffer; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class VmBufferJsonConverter : JsonConverter + { + public override Buffer ReadJson(JsonReader reader, Type objectType, Buffer? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var t = JToken.ReadFrom(reader); + if (RestServerUtility.StackItemFromJToken(t) is Buffer b) return b; + + throw new FormatException(); + } + + public override void WriteJson(JsonWriter writer, Buffer? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var t = RestServerUtility.StackItemToJToken(value, null, serializer); + t.WriteTo(writer); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/VmByteStringJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/VmByteStringJsonConverter.cs new file mode 100644 index 0000000000..017af68e42 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/VmByteStringJsonConverter.cs @@ -0,0 +1,37 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VmByteStringJsonConverter.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.VM.Types; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class VmByteStringJsonConverter : JsonConverter + { + public override ByteString ReadJson(JsonReader reader, Type objectType, ByteString? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var t = JToken.ReadFrom(reader); + if (RestServerUtility.StackItemFromJToken(t) is ByteString bs) return bs; + + throw new FormatException(); + } + + public override void WriteJson(JsonWriter writer, ByteString? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var t = RestServerUtility.StackItemToJToken(value, null, serializer); + t.WriteTo(writer); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/VmIntegerJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/VmIntegerJsonConverter.cs new file mode 100644 index 0000000000..4fab789de6 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/VmIntegerJsonConverter.cs @@ -0,0 +1,37 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VmIntegerJsonConverter.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 Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using Integer = Neo.VM.Types.Integer; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class VmIntegerJsonConverter : JsonConverter + { + public override Integer ReadJson(JsonReader reader, Type objectType, Integer? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var t = JToken.ReadFrom(reader); + if (RestServerUtility.StackItemFromJToken(t) is Integer i) return i; + + throw new FormatException(); + } + + public override void WriteJson(JsonWriter writer, Integer? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var t = RestServerUtility.StackItemToJToken(value, null, serializer); + t.WriteTo(writer); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/VmMapJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/VmMapJsonConverter.cs new file mode 100644 index 0000000000..57595541f3 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/VmMapJsonConverter.cs @@ -0,0 +1,37 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VmMapJsonConverter.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.VM.Types; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class VmMapJsonConverter : JsonConverter + { + public override Map ReadJson(JsonReader reader, Type objectType, Map? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var t = JToken.Load(reader); + if (RestServerUtility.StackItemFromJToken(t) is Map map) return map; + + throw new FormatException(); + } + + public override void WriteJson(JsonWriter writer, Map? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var t = RestServerUtility.StackItemToJToken(value, null, serializer); + t.WriteTo(writer); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/VmNullJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/VmNullJsonConverter.cs new file mode 100644 index 0000000000..0726ed4a7a --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/VmNullJsonConverter.cs @@ -0,0 +1,37 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VmNullJsonConverter.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.VM.Types; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class VmNullJsonConverter : JsonConverter + { + public override Null ReadJson(JsonReader reader, Type objectType, Null? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var t = JToken.ReadFrom(reader); + if (RestServerUtility.StackItemFromJToken(t) is Null n) return n; + + throw new FormatException(); + } + + public override void WriteJson(JsonWriter writer, Null? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var t = RestServerUtility.StackItemToJToken(value, null, serializer); + t.WriteTo(writer); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/VmPointerJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/VmPointerJsonConverter.cs new file mode 100644 index 0000000000..980728ec00 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/VmPointerJsonConverter.cs @@ -0,0 +1,37 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VmPointerJsonConverter.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.VM.Types; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class VmPointerJsonConverter : JsonConverter + { + public override Pointer ReadJson(JsonReader reader, Type objectType, Pointer? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var t = JToken.ReadFrom(reader); + if (RestServerUtility.StackItemFromJToken(t) is Pointer p) return p; + + throw new FormatException(); + } + + public override void WriteJson(JsonWriter writer, Pointer? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var t = RestServerUtility.StackItemToJToken(value, null, serializer); + t.WriteTo(writer); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/VmStructJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/VmStructJsonConverter.cs new file mode 100644 index 0000000000..c2ed906223 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/VmStructJsonConverter.cs @@ -0,0 +1,37 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VmStructJsonConverter.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.VM.Types; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public class VmStructJsonConverter : JsonConverter + { + public override Struct ReadJson(JsonReader reader, Type objectType, Struct? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var t = JToken.Load(reader); + if (RestServerUtility.StackItemFromJToken(t) is Struct s) return s; + + throw new FormatException(); + } + + public override void WriteJson(JsonWriter writer, Struct? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var t = RestServerUtility.StackItemToJToken(value, null, serializer); + t.WriteTo(writer); + } + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/WitnessConditionJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/WitnessConditionJsonConverter.cs new file mode 100644 index 0000000000..67c59710c2 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/WitnessConditionJsonConverter.cs @@ -0,0 +1,105 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessConditionJsonConverter.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; +using Neo.Network.P2P.Payloads.Conditions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Linq; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json +{ + public sealed class WitnessConditionJsonConverter : JsonConverter + { + public override bool CanWrite => true; + public override bool CanRead => true; + + public override WitnessCondition ReadJson(JsonReader reader, Type objectType, WitnessCondition? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + var token = JToken.ReadFrom(reader); + if (token.Type == JTokenType.Object) + return FromJson((JObject)token); + throw new NotSupportedException($"{nameof(WitnessCondition)} Type({token.Type}) is not supported from JSON."); + } + + public override void WriteJson(JsonWriter writer, WitnessCondition? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.WitnessConditionToJToken(value, serializer); + j.WriteTo(writer); + } + + public static WitnessCondition FromJson(JObject o) + { + ArgumentNullException.ThrowIfNull(o, nameof(o)); + + var typeProp = o.Properties().Single(s => EqualsIgnoreCase(s.Name, "type")); + var typeValue = typeProp.Value(); + + try + { + if (typeValue is null) throw new ArgumentNullException(); + + var type = Enum.Parse(typeValue); + + switch (type) + { + case WitnessConditionType.Boolean: + var valueProp = o.Properties().Single(s => EqualsIgnoreCase(s.Name, "expression")); + return new BooleanCondition() { Expression = valueProp.Value() }; + case WitnessConditionType.Not: + valueProp = o.Properties().Single(s => EqualsIgnoreCase(s.Name, "expression")); + return new NotCondition() { Expression = FromJson((JObject)valueProp.Value) }; + case WitnessConditionType.And: + valueProp = o.Properties().Single(s => EqualsIgnoreCase(s.Name, "expressions")); + if (valueProp.Type == JTokenType.Array) + { + var array = (JArray)valueProp.Value; + return new AndCondition() { Expressions = array.Select(s => FromJson((JObject)s)).ToArray() }; + } + break; + case WitnessConditionType.Or: + valueProp = o.Properties().Single(s => EqualsIgnoreCase(s.Name, "expressions")); + if (valueProp.Type == JTokenType.Array) + { + var array = (JArray)valueProp.Value; + return new OrCondition() { Expressions = array.Select(s => FromJson((JObject)s)).ToArray() }; + } + break; + case WitnessConditionType.ScriptHash: + valueProp = o.Properties().Single(s => EqualsIgnoreCase(s.Name, "hash")); + return new ScriptHashCondition() { Hash = UInt160.Parse(valueProp.Value()) }; + case WitnessConditionType.Group: + valueProp = o.Properties().Single(s => EqualsIgnoreCase(s.Name, "group")); + return new GroupCondition() { Group = ECPoint.Parse(valueProp.Value(), ECCurve.Secp256r1) }; + case WitnessConditionType.CalledByEntry: + return new CalledByEntryCondition(); + case WitnessConditionType.CalledByContract: + valueProp = o.Properties().Single(s => EqualsIgnoreCase(s.Name, "hash")); + return new CalledByContractCondition() { Hash = UInt160.Parse(valueProp.Value()) }; + case WitnessConditionType.CalledByGroup: + valueProp = o.Properties().Single(s => EqualsIgnoreCase(s.Name, "group")); + return new CalledByGroupCondition() { Group = ECPoint.Parse(valueProp.Value(), ECCurve.Secp256r1) }; + } + } + catch (ArgumentNullException ex) + { + throw new NotSupportedException($"{ex.ParamName} is not supported from JSON."); + } + throw new NotSupportedException($"WitnessConditionType({typeValue}) is not supported from JSON."); + } + + private static bool EqualsIgnoreCase(string left, string right) => + left.Equals(right, StringComparison.InvariantCultureIgnoreCase); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/WitnessJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/WitnessJsonConverter.cs new file mode 100644 index 0000000000..4af6799e14 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/WitnessJsonConverter.cs @@ -0,0 +1,36 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessJsonConverter.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.Network.P2P.Payloads; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; + +public class WitnessJsonConverter : JsonConverter +{ + public override bool CanRead => false; + + public override bool CanWrite => true; + + public override Witness ReadJson(JsonReader reader, Type objectType, Witness? existingValue, bool hasExistingValue, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + public override void WriteJson(JsonWriter writer, Witness? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.WitnessToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Newtonsoft/Json/WitnessRuleJsonConverter.cs b/src/Plugins/RestServer/Newtonsoft/Json/WitnessRuleJsonConverter.cs new file mode 100644 index 0000000000..f71badde82 --- /dev/null +++ b/src/Plugins/RestServer/Newtonsoft/Json/WitnessRuleJsonConverter.cs @@ -0,0 +1,28 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessRuleJsonConverter.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.Network.P2P.Payloads; +using Newtonsoft.Json; +using System; + +namespace Neo.Plugins.RestServer.Newtonsoft.Json; + +public class WitnessRuleJsonConverter : JsonConverter +{ + public override WitnessRule ReadJson(JsonReader reader, Type objectType, WitnessRule? existingValue, bool hasExistingValue, JsonSerializer serializer) => throw new NotImplementedException(); + public override void WriteJson(JsonWriter writer, WitnessRule? value, JsonSerializer serializer) + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var j = RestServerUtility.WitnessRuleToJToken(value, serializer); + j.WriteTo(writer); + } +} diff --git a/src/Plugins/RestServer/Providers/BlackListControllerFeatureProvider.cs b/src/Plugins/RestServer/Providers/BlackListControllerFeatureProvider.cs new file mode 100644 index 0000000000..0f0713193d --- /dev/null +++ b/src/Plugins/RestServer/Providers/BlackListControllerFeatureProvider.cs @@ -0,0 +1,38 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BlackListControllerFeatureProvider.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.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; +using System; +using System.Linq; +using System.Reflection; + +namespace Neo.Plugins.RestServer.Providers +{ + internal class BlackListControllerFeatureProvider : ControllerFeatureProvider + { + private readonly RestServerSettings _settings; + + public BlackListControllerFeatureProvider() + { + _settings = RestServerSettings.Current; + } + + protected override bool IsController(TypeInfo typeInfo) + { + if (typeInfo.IsDefined(typeof(ApiControllerAttribute)) == false) // Rest API + return false; + if (_settings.DisableControllers.Any(a => a.Equals(typeInfo.Name, StringComparison.OrdinalIgnoreCase))) // BlackList + return false; + return base.IsController(typeInfo); // Default check + } + } +} diff --git a/src/Plugins/RestServer/RestServer.csproj b/src/Plugins/RestServer/RestServer.csproj new file mode 100644 index 0000000000..577a17923a --- /dev/null +++ b/src/Plugins/RestServer/RestServer.csproj @@ -0,0 +1,33 @@ + + + + net8.0 + Neo.Plugins.RestServer + Neo.Plugins.RestServer + enable + ../../../bin/$(PackageId) + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/src/Plugins/RestServer/RestServer.json b/src/Plugins/RestServer/RestServer.json new file mode 100644 index 0000000000..50cb29c845 --- /dev/null +++ b/src/Plugins/RestServer/RestServer.json @@ -0,0 +1,24 @@ +{ + "PluginConfiguration": { + "Network": 860833102, + "BindAddress": "127.0.0.1", + "Port": 10339, + "KeepAliveTimeout": 120, + "SslCertFile": "", + "SslCertPassword": "", + "TrustedAuthorities": [], // Example: "aec13cdd5ea6a3998aec14ac331ad96bedbb770f" (Thumbprint) + "EnableBasicAuthentication": false, + "RestUser": "", + "RestPass": "", + "EnableCors": true, + "AllowOrigins": [], // Example: "http://www.example.com" + "DisableControllers": [], + "EnableCompression": true, + "CompressionLevel": "SmallestSize", // "Fastest" or "Optimal" or "NoCompression" or "SmallestSize" + "EnableForwardedHeaders": false, + "EnableSwagger": true, + "MaxPageSize": 50, + "MaxConcurrentConnections": 40, + "MaxGasInvoke": 200000000 + } +} diff --git a/src/Plugins/RestServer/RestServerPlugin.cs b/src/Plugins/RestServer/RestServerPlugin.cs new file mode 100644 index 0000000000..e821f751c5 --- /dev/null +++ b/src/Plugins/RestServer/RestServerPlugin.cs @@ -0,0 +1,71 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RestServerPlugin.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 Akka.Actor; +using Neo.ConsoleService; +using Neo.Network.P2P; +using System; + +namespace Neo.Plugins.RestServer +{ + public partial class RestServerPlugin : Plugin + { + public override string Name => "RestServer"; + public override string Description => "Enables REST Web Sevices for the node"; + + public override string ConfigFile => System.IO.Path.Combine(RootPath, "RestServer.json"); + + #region Globals + + private RestServerSettings? _settings; + private RestWebServer? _server; + + #endregion + + #region Static Globals + + internal static NeoSystem? NeoSystem { get; private set; } + internal static LocalNode? LocalNode { get; private set; } + + #endregion + + protected override void Configure() + { + RestServerSettings.Load(GetConfiguration()); + _settings = RestServerSettings.Current; + } + + protected override void OnSystemLoaded(NeoSystem system) + { + if (_settings is null) + { + throw new Exception("'Configure' must be called first"); + } + + if (_settings.EnableCors && _settings.EnableBasicAuthentication && _settings.AllowOrigins.Length == 0) + { + ConsoleHelper.Warning("RestServer: CORS is misconfigured!"); + ConsoleHelper.Info($"You have {nameof(_settings.EnableCors)} and {nameof(_settings.EnableBasicAuthentication)} enabled but"); + ConsoleHelper.Info($"{nameof(_settings.AllowOrigins)} is empty in config.json for RestServer."); + ConsoleHelper.Info("You must add url origins to the list to have CORS work from"); + ConsoleHelper.Info($"browser with basic authentication enabled."); + ConsoleHelper.Info($"Example: \"AllowOrigins\": [\"http://{_settings.BindAddress}:{_settings.Port}\"]"); + } + if (system.Settings.Network == _settings.Network) + { + NeoSystem = system; + LocalNode = system.LocalNode.Ask(new LocalNode.GetInstance()).Result; + } + _server = new RestWebServer(); + _server.Start(); + } + } +} diff --git a/src/Plugins/RestServer/RestServerSettings.cs b/src/Plugins/RestServer/RestServerSettings.cs new file mode 100644 index 0000000000..38c886941b --- /dev/null +++ b/src/Plugins/RestServer/RestServerSettings.cs @@ -0,0 +1,157 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RestServerSettings.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.Extensions.Configuration; +using Neo.Plugins.RestServer.Newtonsoft.Json; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Serialization; +using System; +using System.IO.Compression; +using System.Net; + +namespace Neo.Plugins.RestServer +{ + public class RestServerSettings + { + #region Settings + + public uint Network { get; init; } + public IPAddress BindAddress { get; init; } = IPAddress.None; + public uint Port { get; init; } + public uint KeepAliveTimeout { get; init; } + public string? SslCertFile { get; init; } + public string? SslCertPassword { get; init; } + public string[] TrustedAuthorities { get; init; } = []; + public bool EnableBasicAuthentication { get; init; } + public string RestUser { get; init; } = string.Empty; + public string RestPass { get; init; } = string.Empty; + public bool EnableCors { get; init; } + public string[] AllowOrigins { get; init; } = []; + public string[] DisableControllers { get; init; } = []; + public bool EnableCompression { get; init; } + public CompressionLevel CompressionLevel { get; init; } + public bool EnableForwardedHeaders { get; init; } + public bool EnableSwagger { get; init; } + public uint MaxPageSize { get; init; } + public long MaxConcurrentConnections { get; init; } + public long MaxGasInvoke { get; init; } + public required JsonSerializerSettings JsonSerializerSettings { get; init; } + + #endregion + + #region Static Functions + + public static RestServerSettings Default { get; } = new() + { + Network = 860833102u, + BindAddress = IPAddress.Loopback, + Port = 10339u, + KeepAliveTimeout = 120u, + SslCertFile = "", + SslCertPassword = "", + TrustedAuthorities = Array.Empty(), + EnableBasicAuthentication = false, + RestUser = string.Empty, + RestPass = string.Empty, + EnableCors = false, + AllowOrigins = Array.Empty(), + DisableControllers = Array.Empty(), + EnableCompression = false, + CompressionLevel = CompressionLevel.SmallestSize, + EnableForwardedHeaders = false, + EnableSwagger = false, + MaxPageSize = 50u, + MaxConcurrentConnections = 40L, + MaxGasInvoke = 0_200000000L, + JsonSerializerSettings = new JsonSerializerSettings() + { + ContractResolver = new CamelCasePropertyNamesContractResolver(), + MissingMemberHandling = MissingMemberHandling.Error, + NullValueHandling = NullValueHandling.Include, + Formatting = Formatting.None, + Converters = + [ + new StringEnumConverter(), + new BigDecimalJsonConverter(), + new BlockHeaderJsonConverter(), + new BlockJsonConverter(), + new ContractAbiJsonConverter(), + new ContractEventDescriptorJsonConverter(), + new ContractGroupJsonConverter(), + new ContractInvokeParametersJsonConverter(), + new ContractJsonConverter(), + new ContractManifestJsonConverter(), + new ContractMethodJsonConverter(), + new ContractMethodParametersJsonConverter(), + new ContractParameterDefinitionJsonConverter(), + new ContractParameterJsonConverter(), + new ContractPermissionDescriptorJsonConverter(), + new ContractPermissionJsonConverter(), + new ECPointJsonConverter(), + new GuidJsonConverter(), + new InteropInterfaceJsonConverter(), + new MethodTokenJsonConverter(), + new NefFileJsonConverter(), + new ReadOnlyMemoryBytesJsonConverter(), + new SignerJsonConverter(), + new StackItemJsonConverter(), + new TransactionAttributeJsonConverter(), + new TransactionJsonConverter(), + new UInt160JsonConverter(), + new UInt256JsonConverter(), + new VmArrayJsonConverter(), + new VmBooleanJsonConverter(), + new VmBufferJsonConverter(), + new VmByteStringJsonConverter(), + new VmIntegerJsonConverter(), + new VmMapJsonConverter(), + new VmNullJsonConverter(), + new VmPointerJsonConverter(), + new VmStructJsonConverter(), + new WitnessConditionJsonConverter(), + new WitnessJsonConverter(), + new WitnessRuleJsonConverter(), + ], + }, + }; + + public static RestServerSettings Current { get; private set; } = Default; + + public static void Load(IConfigurationSection section) => + Current = new() + { + Network = section.GetValue(nameof(Network), Default.Network), + BindAddress = IPAddress.Parse(section.GetSection(nameof(BindAddress)).Value ?? "0.0.0.0"), + Port = section.GetValue(nameof(Port), Default.Port), + KeepAliveTimeout = section.GetValue(nameof(KeepAliveTimeout), Default.KeepAliveTimeout), + SslCertFile = section.GetValue(nameof(SslCertFile), Default.SslCertFile), + SslCertPassword = section.GetValue(nameof(SslCertPassword), Default.SslCertPassword), + TrustedAuthorities = section.GetSection(nameof(TrustedAuthorities))?.Get() ?? Default.TrustedAuthorities, + EnableBasicAuthentication = section.GetValue(nameof(EnableBasicAuthentication), Default.EnableBasicAuthentication), + RestUser = section.GetValue(nameof(RestUser), Default.RestUser) ?? string.Empty, + RestPass = section.GetValue(nameof(RestPass), Default.RestPass) ?? string.Empty, + EnableCors = section.GetValue(nameof(EnableCors), Default.EnableCors), + AllowOrigins = section.GetSection(nameof(AllowOrigins))?.Get() ?? Default.AllowOrigins, + DisableControllers = section.GetSection(nameof(DisableControllers))?.Get() ?? Default.DisableControllers, + EnableCompression = section.GetValue(nameof(EnableCompression), Default.EnableCompression), + CompressionLevel = section.GetValue(nameof(CompressionLevel), Default.CompressionLevel), + EnableForwardedHeaders = section.GetValue(nameof(EnableForwardedHeaders), Default.EnableForwardedHeaders), + EnableSwagger = section.GetValue(nameof(EnableSwagger), Default.EnableSwagger), + MaxPageSize = section.GetValue(nameof(MaxPageSize), Default.MaxPageSize), + MaxConcurrentConnections = section.GetValue(nameof(MaxConcurrentConnections), Default.MaxConcurrentConnections), + MaxGasInvoke = section.GetValue(nameof(MaxGasInvoke), Default.MaxGasInvoke), + JsonSerializerSettings = Default.JsonSerializerSettings, + }; + + #endregion + } +} diff --git a/src/Plugins/RestServer/RestServerUtility.JTokens.cs b/src/Plugins/RestServer/RestServerUtility.JTokens.cs new file mode 100644 index 0000000000..862697c943 --- /dev/null +++ b/src/Plugins/RestServer/RestServerUtility.JTokens.cs @@ -0,0 +1,335 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RestServerUtility.JTokens.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.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads.Conditions; +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using Neo.SmartContract.Native; +using Newtonsoft.Json.Linq; +using System.Linq; + +namespace Neo.Plugins.RestServer +{ + public static partial class RestServerUtility + { + public static JToken BlockHeaderToJToken(Header header, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(new + { + header.Timestamp, + header.Version, + header.PrimaryIndex, + header.Index, + header.Nonce, + header.Hash, + header.MerkleRoot, + header.PrevHash, + header.NextConsensus, + Witness = WitnessToJToken(header.Witness, serializer), + header.Size, + }, serializer); + + public static JToken WitnessToJToken(Witness witness, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(new + { + witness.InvocationScript, + witness.VerificationScript, + witness.ScriptHash, + }, serializer); + + public static JToken BlockToJToken(Block block, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(new + { + block.Timestamp, + block.Version, + block.PrimaryIndex, + block.Index, + block.Nonce, + block.Hash, + block.MerkleRoot, + block.PrevHash, + block.NextConsensus, + Witness = WitnessToJToken(block.Witness, serializer), + block.Size, + Confirmations = NativeContract.Ledger.CurrentIndex(RestServerPlugin.NeoSystem?.StoreView) - block.Index + 1, + Transactions = block.Transactions.Select(s => TransactionToJToken(s, serializer)), + }, serializer); + + public static JToken TransactionToJToken(Transaction tx, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(new + { + tx.Hash, + tx.Sender, + tx.Script, + tx.FeePerByte, + tx.NetworkFee, + tx.SystemFee, + tx.Size, + tx.Nonce, + tx.Version, + tx.ValidUntilBlock, + Witnesses = tx.Witnesses.Select(s => WitnessToJToken(s, serializer)), + Signers = tx.Signers.Select(s => SignerToJToken(s, serializer)), + Attributes = tx.Attributes.Select(s => TransactionAttributeToJToken(s, serializer)), + }, serializer); + + public static JToken SignerToJToken(Signer signer, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(new + { + Rules = signer.Rules != null ? signer.Rules.Select(s => WitnessRuleToJToken(s, serializer)) : [], + signer.Account, + signer.AllowedContracts, + signer.AllowedGroups, + signer.Scopes, + }, serializer); + + public static JToken TransactionAttributeToJToken(TransactionAttribute attribute, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(attribute switch + { + Conflicts c => new + { + c.Type, + c.Hash, + c.Size, + }, + OracleResponse o => new + { + o.Type, + o.Id, + o.Code, + o.Result, + o.Size, + }, + HighPriorityAttribute h => new + { + h.Type, + h.Size, + }, + NotValidBefore n => new + { + n.Type, + n.Height, + n.Size, + }, + _ => new + { + attribute.Type, + attribute.Size, + } + }, serializer); + + public static JToken WitnessRuleToJToken(WitnessRule rule, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(new + { + rule.Action, + Condition = WitnessConditionToJToken(rule.Condition, serializer), + }, serializer); + + public static JToken WitnessConditionToJToken(WitnessCondition condition, global::Newtonsoft.Json.JsonSerializer serializer) + { + JToken j = JValue.CreateNull(); + switch (condition.Type) + { + case WitnessConditionType.Boolean: + var b = (BooleanCondition)condition; + j = JToken.FromObject(new + { + b.Type, + b.Expression, + }, serializer); + break; + case WitnessConditionType.Not: + var n = (NotCondition)condition; + j = JToken.FromObject(new + { + n.Type, + Expression = WitnessConditionToJToken(n.Expression, serializer), + }, serializer); + break; + case WitnessConditionType.And: + var a = (AndCondition)condition; + j = JToken.FromObject(new + { + a.Type, + Expressions = a.Expressions.Select(s => WitnessConditionToJToken(s, serializer)), + }, serializer); + break; + case WitnessConditionType.Or: + var o = (OrCondition)condition; + j = JToken.FromObject(new + { + o.Type, + Expressions = o.Expressions.Select(s => WitnessConditionToJToken(s, serializer)), + }, serializer); + break; + case WitnessConditionType.ScriptHash: + var s = (ScriptHashCondition)condition; + j = JToken.FromObject(new + { + s.Type, + s.Hash, + }, serializer); + break; + case WitnessConditionType.Group: + var g = (GroupCondition)condition; + j = JToken.FromObject(new + { + g.Type, + g.Group, + }, serializer); + break; + case WitnessConditionType.CalledByEntry: + var e = (CalledByEntryCondition)condition; + j = JToken.FromObject(new + { + e.Type, + }, serializer); + break; + case WitnessConditionType.CalledByContract: + var c = (CalledByContractCondition)condition; + j = JToken.FromObject(new + { + c.Type, + c.Hash, + }, serializer); + break; + case WitnessConditionType.CalledByGroup: + var p = (CalledByGroupCondition)condition; + j = JToken.FromObject(new + { + p.Type, + p.Group, + }, serializer); + break; + default: + break; + } + return j; + } + + public static JToken ContractStateToJToken(ContractState contract, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(new + { + contract.Id, + contract.UpdateCounter, + contract.Manifest.Name, + contract.Hash, + Manifest = ContractManifestToJToken(contract.Manifest, serializer), + NefFile = ContractNefFileToJToken(contract.Nef, serializer), + }, serializer); + + public static JToken ContractManifestToJToken(ContractManifest manifest, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(new + { + manifest.Name, + Abi = ContractAbiToJToken(manifest.Abi, serializer), + Groups = manifest.Groups.Select(s => ContractGroupToJToken(s, serializer)), + Permissions = manifest.Permissions.Select(s => ContractPermissionToJToken(s, serializer)), + Trusts = manifest.Trusts.Select(s => ContractPermissionDescriptorToJToken(s, serializer)), + manifest.SupportedStandards, + Extra = manifest.Extra?.Count > 0 ? + new JObject(manifest.Extra.Properties.Select(s => new JProperty(s.Key.ToString(), s.Value?.AsString()))) : + null, + }, serializer); + + public static JToken ContractAbiToJToken(ContractAbi abi, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(new + { + Methods = abi.Methods.Select(s => ContractMethodToJToken(s, serializer)), + Events = abi.Events.Select(s => ContractEventToJToken(s, serializer)), + }, serializer); + + public static JToken ContractMethodToJToken(ContractMethodDescriptor method, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(new + { + method.Name, + method.Safe, + method.Offset, + Parameters = method.Parameters.Select(s => ContractMethodParameterToJToken(s, serializer)), + method.ReturnType, + }, serializer); + + public static JToken ContractMethodParameterToJToken(ContractParameterDefinition parameter, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(new + { + parameter.Type, + parameter.Name, + }, serializer); + + public static JToken ContractGroupToJToken(ContractGroup group, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(new + { + group.PubKey, + group.Signature, + }, serializer); + + public static JToken ContractPermissionToJToken(ContractPermission permission, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(new + { + Contract = ContractPermissionDescriptorToJToken(permission.Contract, serializer), + Methods = permission.Methods.Count > 0 ? + permission.Methods.Select(s => s).ToArray() : + (object)"*", + }, serializer); + + public static JToken ContractPermissionDescriptorToJToken(ContractPermissionDescriptor desc, global::Newtonsoft.Json.JsonSerializer serializer) + { + JToken j = JValue.CreateNull(); + if (desc.IsWildcard) + j = JValue.CreateString("*"); + else if (desc.IsGroup) + j = JToken.FromObject(new + { + desc.Group + }, serializer); + else if (desc.IsHash) + j = JToken.FromObject(new + { + desc.Hash, + }, serializer); + return j; + } + + public static JToken ContractEventToJToken(ContractEventDescriptor desc, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(new + { + desc.Name, + Parameters = desc.Parameters.Select(s => ContractParameterDefinitionToJToken(s, serializer)), + }, serializer); + + public static JToken ContractParameterDefinitionToJToken(ContractParameterDefinition definition, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(new + { + definition.Type, + definition.Name, + }, serializer); + + public static JToken ContractNefFileToJToken(NefFile nef, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(new + { + Checksum = nef.CheckSum, + nef.Compiler, + nef.Script, + nef.Source, + Tokens = nef.Tokens.Select(s => MethodTokenToJToken(s, serializer)), + }, serializer); + + public static JToken MethodTokenToJToken(MethodToken token, global::Newtonsoft.Json.JsonSerializer serializer) => + JToken.FromObject(new + { + token.Hash, + token.Method, + token.CallFlags, + token.ParametersCount, + token.HasReturnValue, + }, serializer); + } +} diff --git a/src/Plugins/RestServer/RestServerUtility.cs b/src/Plugins/RestServer/RestServerUtility.cs new file mode 100644 index 0000000000..53bf798c17 --- /dev/null +++ b/src/Plugins/RestServer/RestServerUtility.cs @@ -0,0 +1,402 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RestServerUtility.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; +using Neo.Network.P2P.Payloads; +using Neo.Plugins.RestServer.Models.Contract; +using Neo.SmartContract; +using Neo.VM; +using Neo.VM.Types; +using Neo.Wallets; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Array = Neo.VM.Types.Array; +using Boolean = Neo.VM.Types.Boolean; +using Buffer = Neo.VM.Types.Buffer; + +namespace Neo.Plugins.RestServer +{ + public static partial class RestServerUtility + { + private readonly static Script EmptyScript = System.Array.Empty(); + + public static UInt160 ConvertToScriptHash(string address, ProtocolSettings settings) + { + if (UInt160.TryParse(address, out var scriptHash)) + return scriptHash; + return address.ToScriptHash(settings.AddressVersion); + } + + public static bool TryConvertToScriptHash(string address, ProtocolSettings settings, out UInt160 scriptHash) + { + try + { + if (UInt160.TryParse(address, out scriptHash)) + return true; + scriptHash = address.ToScriptHash(settings.AddressVersion); + return true; + } + catch + { + scriptHash = UInt160.Zero; + return false; + } + } + + public static StackItem StackItemFromJToken(JToken? json) + { + if (json is null) return StackItem.Null; + + if (json.Type == JTokenType.Object && json is JObject jsonObject) + { + var props = jsonObject.Properties(); + var typeProp = props.SingleOrDefault(s => s.Name.Equals("type", StringComparison.InvariantCultureIgnoreCase)); + var valueProp = props.SingleOrDefault(s => s.Name.Equals("value", StringComparison.InvariantCultureIgnoreCase)); + + if (typeProp != null && valueProp != null) + { + StackItem s = StackItem.Null; + var type = Enum.Parse(typeProp.ToObject() ?? throw new ArgumentNullException(), true); + var value = valueProp.Value; + + switch (type) + { + case StackItemType.Struct: + if (value.Type == JTokenType.Array) + { + var st = new Struct(); + foreach (var item in (JArray)value) + st.Add(StackItemFromJToken(item)); + s = st; + } + break; + case StackItemType.Array: + if (value.Type == JTokenType.Array) + { + var a = new Array(); + foreach (var item in (JArray)value) + a.Add(StackItemFromJToken(item)); + s = a; + } + break; + case StackItemType.Map: + if (value.Type == JTokenType.Array) + { + var m = new Map(); + foreach (var item in (JArray)value) + { + if (item.Type != JTokenType.Object) + continue; + var vprops = ((JObject)item).Properties(); + var keyProps = vprops.SingleOrDefault(s => s.Name.Equals("key", StringComparison.InvariantCultureIgnoreCase)); + var keyValueProps = vprops.SingleOrDefault(s => s.Name.Equals("value", StringComparison.InvariantCultureIgnoreCase)); + if (keyProps == null && keyValueProps == null) + continue; + var key = (PrimitiveType)StackItemFromJToken(keyProps?.Value); + m[key] = StackItemFromJToken(keyValueProps?.Value); + } + s = m; + } + break; + case StackItemType.Boolean: + s = value.ToObject() ? StackItem.True : StackItem.False; + break; + case StackItemType.Buffer: + s = new Buffer(Convert.FromBase64String(value.ToObject() ?? throw new ArgumentNullException())); + break; + case StackItemType.ByteString: + s = new ByteString(Convert.FromBase64String(value.ToObject() ?? throw new ArgumentNullException())); + break; + case StackItemType.Integer: + s = value.ToObject(); + break; + case StackItemType.InteropInterface: + s = new InteropInterface(Convert.FromBase64String(value.ToObject() ?? throw new ArgumentNullException())); + break; + case StackItemType.Pointer: + s = new Pointer(EmptyScript, value.ToObject()); + break; + default: + break; + } + return s; + } + } + throw new FormatException(); + } + + public static JToken StackItemToJToken(StackItem item, IList<(StackItem, JToken?)>? context, global::Newtonsoft.Json.JsonSerializer serializer) + { + JToken? o = null; + switch (item) + { + case Struct @struct: + if (context is null) + context = new List<(StackItem, JToken?)>(); + else + (_, o) = context.FirstOrDefault(f => ReferenceEquals(f.Item1, item)); + if (o is null) + { + context.Add((item, o)); + var a = @struct.Select(s => StackItemToJToken(s, context, serializer)); + o = JToken.FromObject(new + { + Type = StackItemType.Struct.ToString(), + Value = JArray.FromObject(a), + }, serializer); + } + break; + case Array array: + if (context is null) + context = new List<(StackItem, JToken?)>(); + else + (_, o) = context.FirstOrDefault(f => ReferenceEquals(f.Item1, item)); + if (o is null) + { + context.Add((item, o)); + var a = array.Select(s => StackItemToJToken(s, context, serializer)); + o = JToken.FromObject(new + { + Type = StackItemType.Array.ToString(), + Value = JArray.FromObject(a, serializer), + }, serializer); + } + break; + case Map map: + if (context is null) + context = new List<(StackItem, JToken?)>(); + else + (_, o) = context.FirstOrDefault(f => ReferenceEquals(f.Item1, item)); + if (o is null) + { + context.Add((item, o)); + var kvp = map.Select(s => + new KeyValuePair( + StackItemToJToken(s.Key, context, serializer), + StackItemToJToken(s.Value, context, serializer))); + o = JToken.FromObject(new + { + Type = StackItemType.Map.ToString(), + Value = JArray.FromObject(kvp, serializer), + }, serializer); + } + break; + case Boolean: + o = JToken.FromObject(new + { + Type = StackItemType.Boolean.ToString(), + Value = item.GetBoolean(), + }, serializer); + break; + case Buffer: + o = JToken.FromObject(new + { + Type = StackItemType.Buffer.ToString(), + Value = Convert.ToBase64String(item.GetSpan()), + }, serializer); + break; + case ByteString: + o = JToken.FromObject(new + { + Type = StackItemType.ByteString.ToString(), + Value = Convert.ToBase64String(item.GetSpan()), + }, serializer); + break; + case Integer: + o = JToken.FromObject(new + { + Type = StackItemType.Integer.ToString(), + Value = item.GetInteger(), + }, serializer); + break; + case InteropInterface: + o = JToken.FromObject(new + { + Type = StackItemType.InteropInterface.ToString(), + Value = Convert.ToBase64String(item.GetSpan()), + }); + break; + case Pointer pointer: + o = JToken.FromObject(new + { + Type = StackItemType.Pointer.ToString(), + Value = pointer.Position, + }, serializer); + break; + case Null: + case null: + o = JToken.FromObject(new + { + Type = StackItemType.Any.ToString(), + Value = JValue.CreateNull(), + }, serializer); + break; + default: + throw new NotSupportedException($"StackItemType({item.Type}) is not supported to JSON."); + } + return o; + } + + public static InvokeParams ContractInvokeParametersFromJToken(JToken token) + { + if (token is null) + throw new ArgumentNullException(); + if (token.Type != JTokenType.Object) + throw new FormatException(); + + var obj = (JObject)token; + var contractParametersProp = obj + .Properties() + .SingleOrDefault(a => a.Name.Equals("contractParameters", StringComparison.InvariantCultureIgnoreCase)); + var signersProp = obj + .Properties() + .SingleOrDefault(a => a.Name.Equals("signers", StringComparison.InvariantCultureIgnoreCase)); + + return new() + { + ContractParameters = [.. contractParametersProp!.Value.Select(ContractParameterFromJToken)], + Signers = [.. signersProp!.Value.Select(SignerFromJToken)], + }; + } + + public static Signer SignerFromJToken(JToken? token) + { + if (token is null) + throw new ArgumentNullException(); + if (token.Type != JTokenType.Object) + throw new FormatException(); + + var obj = (JObject)token; + var accountProp = obj + .Properties() + .SingleOrDefault(a => a.Name.Equals("account", StringComparison.InvariantCultureIgnoreCase)); + var scopesProp = obj + .Properties() + .SingleOrDefault(a => a.Name.Equals("scopes", StringComparison.InvariantCultureIgnoreCase)); + + if (accountProp == null || scopesProp == null) + throw new FormatException(); + + return new() + { + Account = UInt160.Parse(accountProp.ToObject()), + Scopes = Enum.Parse(scopesProp.ToObject()!), + }; + } + + public static ContractParameter ContractParameterFromJToken(JToken? token) + { + if (token is null) + throw new ArgumentNullException(); + if (token.Type != JTokenType.Object) + throw new FormatException(); + + var obj = (JObject)token; + var typeProp = obj + .Properties() + .SingleOrDefault(a => a.Name.Equals("type", StringComparison.InvariantCultureIgnoreCase)); + var valueProp = obj + .Properties() + .SingleOrDefault(a => a.Name.Equals("value", StringComparison.InvariantCultureIgnoreCase)); + + if (typeProp == null || valueProp == null) + throw new FormatException(); + + var typeValue = Enum.Parse(typeProp.ToObject() ?? throw new ArgumentNullException()); + + switch (typeValue) + { + case ContractParameterType.Any: + return new ContractParameter(ContractParameterType.Any); + case ContractParameterType.ByteArray: + return new ContractParameter() + { + Type = ContractParameterType.ByteArray, + Value = Convert.FromBase64String(valueProp.ToObject() ?? throw new ArgumentNullException()), + }; + case ContractParameterType.Signature: + return new ContractParameter() + { + Type = ContractParameterType.Signature, + Value = Convert.FromBase64String(valueProp.ToObject() ?? throw new ArgumentNullException()), + }; + case ContractParameterType.Boolean: + return new ContractParameter() + { + Type = ContractParameterType.Boolean, + Value = valueProp.ToObject(), + }; + case ContractParameterType.Integer: + return new ContractParameter() + { + Type = ContractParameterType.Integer, + Value = BigInteger.Parse(valueProp.ToObject() ?? throw new ArgumentNullException()), + }; + case ContractParameterType.String: + return new ContractParameter() + { + Type = ContractParameterType.String, + Value = valueProp.ToObject(), + }; + case ContractParameterType.Hash160: + return new ContractParameter() + { + Type = ContractParameterType.Hash160, + Value = UInt160.Parse(valueProp.ToObject()), + }; + case ContractParameterType.Hash256: + return new ContractParameter() + { + Type = ContractParameterType.Hash256, + Value = UInt256.Parse(valueProp.ToObject()), + }; + case ContractParameterType.PublicKey: + return new ContractParameter() + { + Type = ContractParameterType.PublicKey, + Value = ECPoint.Parse(valueProp.ToObject(), ECCurve.Secp256r1), + }; + case ContractParameterType.Array: + if (valueProp.Value is not JArray array) + throw new FormatException(); + return new ContractParameter() + { + Type = ContractParameterType.Array, + Value = array.Select(ContractParameterFromJToken).ToList(), + }; + case ContractParameterType.Map: + if (valueProp.Value is not JArray map) + throw new FormatException(); + return new ContractParameter() + { + Type = ContractParameterType.Map, + Value = map.Select(s => + { + if (valueProp.Value is not JObject mapProp) + throw new FormatException(); + var keyProp = mapProp + .Properties() + .SingleOrDefault(ss => ss.Name.Equals("key", StringComparison.InvariantCultureIgnoreCase)); + var keyValueProp = mapProp + .Properties() + .SingleOrDefault(ss => ss.Name.Equals("value", StringComparison.InvariantCultureIgnoreCase)); + return new KeyValuePair(ContractParameterFromJToken(keyProp?.Value), ContractParameterFromJToken(keyValueProp?.Value)); + }).ToList(), + }; + default: + throw new NotSupportedException($"ContractParameterType({typeValue}) is not supported to JSON."); + } + } + } +} diff --git a/src/Plugins/RestServer/RestWebServer.cs b/src/Plugins/RestServer/RestWebServer.cs new file mode 100644 index 0000000000..a0fa8b9859 --- /dev/null +++ b/src/Plugins/RestServer/RestWebServer.cs @@ -0,0 +1,423 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RestWebServer.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.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.HttpOverrides; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Authorization; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Versioning; +using Microsoft.AspNetCore.ResponseCompression; +using Microsoft.AspNetCore.Server.Kestrel.Https; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads.Conditions; +using Neo.Plugins.RestServer.Binder; +using Neo.Plugins.RestServer.Middleware; +using Neo.Plugins.RestServer.Models.Error; +using Neo.Plugins.RestServer.Providers; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using RestServer.Authentication; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Mime; +using System.Net.Security; +using System.Numerics; + +namespace Neo.Plugins.RestServer +{ + internal class RestWebServer + { + #region Globals + + private readonly RestServerSettings _settings; + private IWebHost? _host; + + #endregion + + public static bool IsRunning { get; private set; } + + public RestWebServer() + { + _settings = RestServerSettings.Current; + } + + public void Start() + { + if (IsRunning) + return; + + IsRunning = true; + + _host = new WebHostBuilder() + .UseKestrel(options => + { + // Web server configuration + options.AddServerHeader = false; + options.Limits.MaxConcurrentConnections = _settings.MaxConcurrentConnections; + options.Limits.KeepAliveTimeout = TimeSpan.FromSeconds(_settings.KeepAliveTimeout); + options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(15); + options.Listen(_settings.BindAddress, unchecked((int)_settings.Port), + listenOptions => + { + if (string.IsNullOrEmpty(_settings.SslCertFile)) + return; + listenOptions.UseHttps(_settings.SslCertFile, _settings.SslCertPassword, httpsOptions => + { + if (_settings.TrustedAuthorities.Length == 0) + { + httpsOptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate; + httpsOptions.ClientCertificateValidation = (cert, chain, err) => + { + if (chain is null || err != SslPolicyErrors.None) + return false; + var authority = chain.ChainElements[^1].Certificate; + return _settings.TrustedAuthorities.Any(a => a.Equals(authority.Thumbprint, StringComparison.OrdinalIgnoreCase)); + }; + } + }); + }); + }) + .ConfigureServices(services => + { + #region Add Basic auth + + if (_settings.EnableBasicAuthentication) + services.AddAuthentication() + .AddScheme("Basic", null); + + #endregion + + #region CORS + + // Server configuration + if (_settings.EnableCors) + { + if (_settings.AllowOrigins.Length == 0) + services.AddCors(options => + { + options.AddPolicy("All", policy => + { + policy.AllowAnyOrigin() + .AllowAnyHeader() + .WithMethods("GET", "POST"); + // The CORS specification states that setting origins to "*" (all origins) + // is invalid if the Access-Control-Allow-Credentials header is present. + //.AllowCredentials() + }); + }); + else + services.AddCors(options => + { + options.AddPolicy("All", policy => + { + policy.WithOrigins(_settings.AllowOrigins) + .AllowAnyHeader() + .AllowCredentials() + .WithMethods("GET", "POST"); + }); + }); + } + + #endregion + + services.AddRouting(options => options.LowercaseUrls = options.LowercaseQueryStrings = true); + + #region Compression Configuration + + if (_settings.EnableCompression) + services.AddResponseCompression(options => + { + options.EnableForHttps = false; + options.Providers.Add(); + options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Append(MediaTypeNames.Application.Json); + }); + + #endregion + + #region Controllers + + var controllers = services + .AddControllers(options => + { + options.EnableEndpointRouting = false; + + if (_settings.EnableBasicAuthentication) + { + var policy = new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .Build(); + options.Filters.Add(new AuthorizeFilter(policy)); + } + options.ModelBinderProviders.Insert(0, new NeoBinderProvider()); + }) + .ConfigureApiBehaviorOptions(options => + { + options.InvalidModelStateResponseFactory = context => + new BadRequestObjectResult( + new ParameterFormatExceptionModel(string.Join(' ', context.ModelState.Values.SelectMany(s => s.Errors).Select(s => s.ErrorMessage)))) + { + ContentTypes = + { + MediaTypeNames.Application.Json, + } + }; + }) + .ConfigureApplicationPartManager(manager => + { + var controllerFeatureProvider = manager.FeatureProviders.Single(p => p.GetType() == typeof(ControllerFeatureProvider)); + var index = manager.FeatureProviders.IndexOf(controllerFeatureProvider); + manager.FeatureProviders[index] = new BlackListControllerFeatureProvider(); + + foreach (var plugin in Plugin.Plugins) + manager.ApplicationParts.Add(new AssemblyPart(plugin.GetType().Assembly)); + }) + .AddNewtonsoftJson(options => + { + options.AllowInputFormatterExceptionMessages = true; + options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); + options.SerializerSettings.Formatting = Formatting.None; + + foreach (var converter in _settings.JsonSerializerSettings.Converters) + options.SerializerSettings.Converters.Add(converter); + }); + + #endregion + + #region API Versioning + + services.AddVersionedApiExplorer(setupAction => + { + setupAction.GroupNameFormat = "'v'VV"; + }); + + services.AddApiVersioning(options => + { + options.AssumeDefaultVersionWhenUnspecified = true; + options.DefaultApiVersion = new ApiVersion(1, 0); + options.ReportApiVersions = true; + }); + + #endregion + + #region Swagger Configuration + + if (_settings.EnableSwagger) + { + var apiVersionDescriptionProvider = services.BuildServiceProvider().GetRequiredService(); + services.AddSwaggerGen(options => + { + foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions) + { + options.SwaggerDoc(description.GroupName, new OpenApiInfo() + { + Title = "RestServer Plugin API", + Description = "RESTful Web Sevices for neo-cli.", + Version = description.ApiVersion.ToString(), + Contact = new OpenApiContact() + { + Name = "The Neo Project", + Url = new Uri("https://github.com/neo-project/neo-modules"), + Email = "dev@neo.org", + }, + License = new OpenApiLicense() + { + Name = "MIT", + Url = new Uri("http://www.opensource.org/licenses/mit-license.php"), + }, + }); + } + + #region Enable Basic Auth for Swagger + + if (_settings.EnableBasicAuthentication) + { + options.AddSecurityDefinition("basicAuth", new OpenApiSecurityScheme() + { + Type = SecuritySchemeType.Http, + Scheme = "basic", + Description = "Input your username and password to access this API.", + }); + options.AddSecurityRequirement(new OpenApiSecurityRequirement() + { + { + new OpenApiSecurityScheme() + { + Reference = new OpenApiReference() + { + Type = ReferenceType.SecurityScheme, + Id = "basicAuth", + }, + }, + new List() + } + }); + } + + #endregion + + options.DocInclusionPredicate((docmentName, apiDescription) => + { + var actionApiVersionModel = apiDescription.ActionDescriptor.GetApiVersionModel(ApiVersionMapping.Explicit | ApiVersionMapping.Implicit); + if (actionApiVersionModel == null) + return true; + if (actionApiVersionModel.DeclaredApiVersions.Any()) + return actionApiVersionModel.DeclaredApiVersions.Any(a => $"v{a}" == docmentName); + return actionApiVersionModel.ImplementedApiVersions.Any(a => $"v{a}" == docmentName); + }); + + options.UseOneOfForPolymorphism(); + options.SelectSubTypesUsing(baseType => + { + if (baseType == typeof(WitnessCondition)) + { + return new[] + { + typeof(BooleanCondition), + typeof(NotCondition), + typeof(AndCondition), + typeof(OrCondition), + typeof(ScriptHashCondition), + typeof(GroupCondition), + typeof(CalledByEntryCondition), + typeof(CalledByContractCondition), + typeof(CalledByGroupCondition), + }; + } + + return Enumerable.Empty(); + }); + options.MapType(() => new OpenApiSchema() + { + Type = "string", + Format = "hash256", + }); + options.MapType(() => new OpenApiSchema() + { + Type = "string", + Format = "hash160", + }); + options.MapType(() => new OpenApiSchema() + { + Type = "string", + Format = "hexstring", + }); + options.MapType(() => new OpenApiSchema() + { + Type = "integer", + Format = "bigint", + }); + options.MapType(() => new OpenApiSchema() + { + Type = "string", + Format = "base64", + }); + options.MapType>(() => new OpenApiSchema() + { + Type = "string", + Format = "base64", + }); + foreach (var plugin in Plugin.Plugins) + { + var assemblyName = plugin.GetType().Assembly.GetName().Name ?? nameof(RestServer); + var xmlPathAndFilename = Path.Combine(AppContext.BaseDirectory, "Plugins", assemblyName, $"{assemblyName}.xml"); + if (File.Exists(xmlPathAndFilename)) + options.IncludeXmlComments(xmlPathAndFilename); + } + }); + services.AddSwaggerGenNewtonsoftSupport(); + } + + #endregion + + #region Forward Headers + + if (_settings.EnableForwardedHeaders) + services.Configure(options => options.ForwardedHeaders = ForwardedHeaders.All); + + #endregion + + #region Compression + + if (_settings.EnableCompression) + services.Configure(options => options.Level = _settings.CompressionLevel); + + #endregion + }) + .Configure(app => + { + app.UseMiddleware(); + + if (_settings.EnableForwardedHeaders) + app.UseForwardedHeaders(); + + app.UseRouting(); + + if (_settings.EnableCors) + app.UseCors("All"); + + if (_settings.EnableCompression) + app.UseResponseCompression(); + + if (_settings.EnableBasicAuthentication) + app.UseAuthentication(); + + app.UseExceptionHandler(config => + config.Run(async context => + { + var exception = context.Features + .GetRequiredFeature() + .Error; + var response = new ErrorModel() + { + Code = exception.HResult, + Name = exception.GetType().Name, + Message = exception.InnerException?.Message ?? exception.Message, + }; + RestServerMiddleware.SetServerInfomationHeader(context.Response); + context.Response.StatusCode = 400; + await context.Response.WriteAsJsonAsync(response); + })); + + if (_settings.EnableSwagger) + { + app.UseSwagger(); + //app.UseSwaggerUI(options => options.DefaultModelsExpandDepth(-1)); + app.UseSwaggerUI(options => + { + var apiVersionDescriptionProvider = app.ApplicationServices.GetRequiredService(); + foreach (var description in apiVersionDescriptionProvider.ApiVersionDescriptions) + { + options.SwaggerEndpoint($"/swagger/{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant()); + } + }); + } + + app.UseMvc(); + }) + .Build(); + _host.Start(); + } + } +} diff --git a/src/Plugins/RestServer/Tokens/NEP11Token.cs b/src/Plugins/RestServer/Tokens/NEP11Token.cs new file mode 100644 index 0000000000..ea19b6bffd --- /dev/null +++ b/src/Plugins/RestServer/Tokens/NEP11Token.cs @@ -0,0 +1,178 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// NEP11Token.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.Persistence; +using Neo.Plugins.RestServer.Helpers; +using Neo.SmartContract; +using Neo.SmartContract.Iterators; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.VM.Types; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +namespace Neo.Plugins.RestServer.Tokens +{ + internal class NEP11Token + { + public UInt160 ScriptHash { get; private set; } + public string Name { get; private set; } + public string Symbol { get; private set; } + public byte Decimals { get; private set; } + + private readonly NeoSystem _neoSystem; + private readonly DataCache _snapshot; + private readonly ContractState _contract; + + public NEP11Token( + NeoSystem neoSystem, + UInt160 scriptHash) : this(neoSystem, null, scriptHash) { } + + public NEP11Token( + NeoSystem neoSystem, + DataCache? snapshot, + UInt160 scriptHash) + { + ArgumentNullException.ThrowIfNull(neoSystem, nameof(neoSystem)); + ArgumentNullException.ThrowIfNull(scriptHash, nameof(scriptHash)); + _neoSystem = neoSystem; + _snapshot = snapshot ?? _neoSystem.GetSnapshotCache(); + _contract = NativeContract.ContractManagement.GetContract(_snapshot, scriptHash) ?? throw new ArgumentException(null, nameof(scriptHash)); + if (ContractHelper.IsNep11Supported(_contract) == false) + throw new NotSupportedException(nameof(scriptHash)); + Name = _contract.Manifest.Name; + ScriptHash = scriptHash; + + byte[] scriptBytes; + using var sb = new ScriptBuilder(); + sb.EmitDynamicCall(_contract.Hash, "decimals", CallFlags.ReadOnly); + sb.EmitDynamicCall(_contract.Hash, "symbol", CallFlags.ReadOnly); + scriptBytes = sb.ToArray(); + + using var appEngine = ApplicationEngine.Run(scriptBytes, _snapshot, settings: _neoSystem.Settings, gas: RestServerSettings.Current.MaxGasInvoke); + if (appEngine.State != VMState.HALT) + throw new NotSupportedException(nameof(ScriptHash)); + + Symbol = appEngine.ResultStack.Pop().GetString() ?? throw new ArgumentNullException(); + Decimals = (byte)appEngine.ResultStack.Pop().GetInteger(); + } + + public BigDecimal TotalSupply() + { + if (ContractHelper.GetContractMethod(_snapshot, ScriptHash, "totalSupply", 0) == null) + throw new NotSupportedException(nameof(ScriptHash)); + if (ScriptHelper.InvokeMethod(_neoSystem.Settings, _snapshot, ScriptHash, "totalSupply", out var results)) + return new(results[0].GetInteger(), Decimals); + return new(BigInteger.Zero, Decimals); + } + + public BigDecimal BalanceOf(UInt160 owner) + { + if (ContractHelper.GetContractMethod(_snapshot, ScriptHash, "balanceOf", 1) == null) + throw new NotSupportedException(nameof(ScriptHash)); + if (ScriptHelper.InvokeMethod(_neoSystem.Settings, _snapshot, ScriptHash, "balanceOf", out var results, owner)) + return new(results[0].GetInteger(), Decimals); + return new(BigInteger.Zero, Decimals); + } + + public BigDecimal BalanceOf(UInt160 owner, byte[] tokenId) + { + if (Decimals == 0) + throw new InvalidOperationException(); + if (ContractHelper.GetContractMethod(_snapshot, ScriptHash, "balanceOf", 2) == null) + throw new NotSupportedException(nameof(ScriptHash)); + ArgumentNullException.ThrowIfNull(tokenId, nameof(tokenId)); + if (tokenId.Length > 64) + throw new ArgumentOutOfRangeException(nameof(tokenId)); + if (ScriptHelper.InvokeMethod(_neoSystem.Settings, _snapshot, ScriptHash, "balanceOf", out var results, owner, tokenId)) + return new(results[0].GetInteger(), Decimals); + return new(BigInteger.Zero, Decimals); + } + + public IEnumerable TokensOf(UInt160 owner) + { + if (ContractHelper.GetContractMethod(_snapshot, ScriptHash, "tokensOf", 1) == null) + throw new NotSupportedException(nameof(ScriptHash)); + if (ScriptHelper.InvokeMethod(_neoSystem.Settings, _snapshot, ScriptHash, "tokensOf", out var results, owner)) + { + if (results[0].GetInterface() is IIterator iterator) + { + var refCounter = new ReferenceCounter(); + while (iterator.Next()) + yield return iterator.Value(refCounter).GetSpan().ToArray(); + } + } + } + + public UInt160[] OwnerOf(byte[] tokenId) + { + if (ContractHelper.GetContractMethod(_snapshot, ScriptHash, "ownerOf", 1) == null) + throw new NotSupportedException(nameof(ScriptHash)); + ArgumentNullException.ThrowIfNull(tokenId, nameof(tokenId)); + if (tokenId.Length > 64) + throw new ArgumentOutOfRangeException(nameof(tokenId)); + if (Decimals == 0) + { + if (ScriptHelper.InvokeMethod(_neoSystem.Settings, _snapshot, ScriptHash, "ownerOf", out var results, tokenId)) + return new[] { new UInt160(results[0].GetSpan()) }; + } + else if (Decimals > 0) + { + if (ScriptHelper.InvokeMethod(_neoSystem.Settings, _snapshot, ScriptHash, "ownerOf", out var results, tokenId)) + { + if (results[0].GetInterface() is IIterator iterator) + { + var refCounter = new ReferenceCounter(); + var lstOwners = new List(); + while (iterator.Next()) + lstOwners.Add(new UInt160(iterator.Value(refCounter).GetSpan())); + return lstOwners.ToArray(); + } + } + } + return System.Array.Empty(); + } + + public IEnumerable Tokens() + { + if (ContractHelper.GetContractMethod(_snapshot, ScriptHash, "tokens", 0) == null) + throw new NotImplementedException(); + if (ScriptHelper.InvokeMethod(_neoSystem.Settings, _snapshot, ScriptHash, "tokens", out var results)) + { + if (results[0].GetInterface() is IIterator iterator) + { + var refCounter = new ReferenceCounter(); + while (iterator.Next()) + yield return iterator.Value(refCounter).GetSpan().ToArray(); + } + } + } + + public IReadOnlyDictionary? Properties(byte[] tokenId) + { + ArgumentNullException.ThrowIfNull(tokenId, nameof(tokenId)); + if (ContractHelper.GetContractMethod(_snapshot, ScriptHash, "properties", 1) == null) + throw new NotImplementedException(); + if (tokenId.Length > 64) + throw new ArgumentOutOfRangeException(nameof(tokenId)); + if (ScriptHelper.InvokeMethod(_neoSystem.Settings, _snapshot, ScriptHash, "properties", out var results, tokenId)) + { + if (results[0] is Map map) + { + return map.ToDictionary(key => key.Key.GetString() ?? throw new ArgumentNullException(), value => value.Value); + } + } + return default; + } + } +} diff --git a/src/Plugins/RestServer/Tokens/NEP17Token.cs b/src/Plugins/RestServer/Tokens/NEP17Token.cs new file mode 100644 index 0000000000..18efd3f908 --- /dev/null +++ b/src/Plugins/RestServer/Tokens/NEP17Token.cs @@ -0,0 +1,77 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// NEP17Token.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.Persistence; +using Neo.Plugins.RestServer.Helpers; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using System; +using System.Numerics; + +namespace Neo.Plugins.RestServer.Tokens +{ + internal class NEP17Token + { + public UInt160 ScriptHash { get; private init; } + public string Name { get; private init; } = string.Empty; + public string Symbol { get; private init; } = string.Empty; + public byte Decimals { get; private init; } + + private readonly NeoSystem _neoSystem; + private readonly DataCache _dataCache; + + public NEP17Token( + NeoSystem neoSystem, + UInt160 scriptHash, + DataCache? snapshot = null) + { + _dataCache = snapshot ?? neoSystem.GetSnapshotCache(); + var contractState = NativeContract.ContractManagement.GetContract(_dataCache, scriptHash) ?? throw new ArgumentException(null, nameof(scriptHash)); + if (ContractHelper.IsNep17Supported(contractState) == false) + throw new NotSupportedException(nameof(scriptHash)); + byte[] script; + using (var sb = new ScriptBuilder()) + { + sb.EmitDynamicCall(scriptHash, "decimals", CallFlags.ReadOnly); + sb.EmitDynamicCall(scriptHash, "symbol", CallFlags.ReadOnly); + script = sb.ToArray(); + } + using var engine = ApplicationEngine.Run(script, _dataCache, settings: neoSystem.Settings, gas: RestServerSettings.Current.MaxGasInvoke); + if (engine.State != VMState.HALT) + throw new NotSupportedException(nameof(scriptHash)); + + _neoSystem = neoSystem; + ScriptHash = scriptHash; + Name = contractState.Manifest.Name; + Symbol = engine.ResultStack.Pop().GetString() ?? string.Empty; + Decimals = (byte)engine.ResultStack.Pop().GetInteger(); + } + + public BigDecimal BalanceOf(UInt160 address) + { + if (ContractHelper.GetContractMethod(_dataCache, ScriptHash, "balanceOf", 1) == null) + throw new NotSupportedException(nameof(ScriptHash)); + if (ScriptHelper.InvokeMethod(_neoSystem.Settings, _dataCache, ScriptHash, "balanceOf", out var result, address)) + return new BigDecimal(result[0].GetInteger(), Decimals); + return new BigDecimal(BigInteger.Zero, Decimals); + } + + public BigDecimal TotalSupply() + { + if (ContractHelper.GetContractMethod(_dataCache, ScriptHash, "totalSupply", 0) == null) + throw new NotSupportedException(nameof(ScriptHash)); + if (ScriptHelper.InvokeMethod(_neoSystem.Settings, _dataCache, ScriptHash, "totalSupply", out var result)) + return new BigDecimal(result[0].GetInteger(), Decimals); + return new BigDecimal(BigInteger.Zero, Decimals); + } + } +}