diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5294cd09f4..30939152a7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,7 @@ jobs: - name: Check Format (*.cs) run: dotnet format --verify-no-changes --verbosity diagnostic - Build-Test-Neo-Cli: + Test-Everything: needs: [Format] timeout-minutes: 15 runs-on: ubuntu-latest @@ -37,16 +37,16 @@ jobs: with: dotnet-version: ${{ env.DOTNET_VERSION }} - - name: Build (Neo.CLI) - run: dotnet build ./src/Neo.CLI --output ./out/Neo.CLI + - name: Build (Everything) + run: dotnet build - name: Install dependencies run: | sudo apt-get install libleveldb-dev expect - find ./out -name 'config.json' | xargs perl -pi -e 's|LevelDBStore|MemoryStore|g' + find ./bin -name 'config.json' | xargs perl -pi -e 's|LevelDBStore|MemoryStore|g' - name: Run tests with expect - run: expect ./scripts/Neo.CLI/test-neo-cli.expect + run: expect ./scripts/Neo.CLI/test-neo-cli.exp Test: needs: [Format] @@ -68,7 +68,8 @@ jobs: if: matrix.os != 'ubuntu-latest' run: | dotnet sln neo.sln remove ./tests/Neo.Plugins.Storage.Tests/Neo.Plugins.Storage.Tests.csproj - dotnet test + dotnet build + dotnet test --blame-hang --blame-crash --no-build - name: Test for coverall if: matrix.os == 'ubuntu-latest' @@ -80,6 +81,7 @@ jobs: dotnet test ./tests/Neo.UnitTests --output ./bin/tests/Neo.UnitTests dotnet test ./tests/Neo.VM.Tests --output ./bin/tests/Neo.VM.Tests dotnet test ./tests/Neo.Json.UnitTests --output ./bin/tests/Neo.Json.UnitTests + dotnet test ./tests/Neo.Extensions.Tests --output ./bin/tests/Neo.Extensions.Tests # Plugins dotnet test ./tests/Neo.Cryptography.MPTTrie.Tests --output ./bin/tests/Neo.Cryptography.MPTTrie.Tests @@ -104,6 +106,7 @@ jobs: ${{ github.workspace }}/tests/Neo.Plugins.OracleService.Tests/TestResults/coverage.info ${{ github.workspace }}/tests/Neo.Plugins.RpcServer.Tests/TestResults/coverage.info ${{ github.workspace }}/tests/Neo.Plugins.Storage.Tests/TestResults/coverage.info + ${{ github.workspace }}/tests/Neo.Extensions.Tests/TestResults/coverage.info PublishPackage: if: github.ref == 'refs/heads/master' && startsWith(github.repository, 'neo-project/') @@ -123,19 +126,13 @@ jobs: - name: Set Version run: git rev-list --count HEAD | xargs printf 'CI%05d' | xargs -I{} echo 'VERSION_SUFFIX={}' >> $GITHUB_ENV - - name : Pack (Neo) + - name : Pack (Everything) run: | dotnet pack \ --configuration Release \ --output ./out \ --version-suffix ${{ env.VERSION_SUFFIX }} - - name: Remove Unwanted Files - working-directory: ./out - run: | - rm -v Neo.CLI* - rm -v Neo.GUI* - - name: Publish to Github Packages working-directory: ./out run: | diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index 61ff1e3408..3fa6cc4f5e 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -35,8 +35,8 @@ jobs: dotnet pack ./neo.sln \ --configuration Release \ --output ./sbin \ - --verbosity normal \ # Normal verbosity level - -p:VersionPrefix=${{ env.APP_VERSION }} # Set the version prefix from tag_name + --verbosity normal \ + -p:VersionPrefix=${{ env.APP_VERSION }} - name: Publish to NuGet.org run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..bcfddc1185 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,348 @@ +name: Release (neo-cli) + +# Trigger the workflow on a release event when a new release is published +on: + release: + types: [published] + +# Define environment variables +env: + DOTNET_VERSION: 8.0.x + CONFIGURATION: Release + DIST_PATH: /tmp/dist + OUTPUT_PATH: /tmp/out + +jobs: + build-leveldb: + name: Build leveldb win-${{ matrix.arch }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [windows-latest] + arch: [x64, arm64] + + steps: + # Step to lookup cache for the LevelDB build distribution + - name: Lookup Cache Distribution + id: cache-leveldb + uses: actions/cache@v4 + with: + path: ./leveldb/build/Release/* + key: leveldb-${{ matrix.os }}-${{ matrix.arch }} + enableCrossOsArchive: true + lookup-only: true + + # Conditionally checkout LevelDB repository if cache is not found + - if: ${{ steps.cache-leveldb.outputs.cache-hit != 'true' }} + name: Checkout Repository Code (leveldb) + uses: actions/checkout@v4 + with: + repository: google/leveldb + path: leveldb + submodules: true + fetch-depth: 0 + + # Conditionally setup MSBuild if cache is not found + - if: ${{ matrix.os == 'windows-latest' && steps.cache-leveldb.outputs.cache-hit != 'true' }} + name: Setup MSBuild + uses: microsoft/setup-msbuild@v2 + + # Conditionally setup LevelDB build directory if cache is not found + - if: ${{ steps.cache-leveldb.outputs.cache-hit != 'true' }} + name: Setup LevelDb + working-directory: ./leveldb + run: mkdir -p ./build/Release + + # Conditionally create build files for LevelDB if cache is not found + - if: ${{ steps.cache-leveldb.outputs.cache-hit != 'true' }} + name: Create Build Files (win-${{ matrix.arch }}) + working-directory: ./leveldb/build + run: cmake -DBUILD_SHARED_LIBS=ON -A ${{ matrix.arch }} .. + + # Conditionally build LevelDB using MSBuild if cache is not found + - if: ${{ matrix.os == 'windows-latest' && steps.cache-leveldb.outputs.cache-hit != 'true' }} + name: Build (MSBuild) + working-directory: ./leveldb/build + run: msbuild ./leveldb.sln /p:Configuration=Release + + # Conditionally cache the LevelDB distribution if it was built + - if: ${{ steps.cache-leveldb.outputs.cache-hit != 'true' }} + name: Cache Distribution + uses: actions/cache/save@v4 + with: + path: ./leveldb/build/Release/* + key: leveldb-${{ matrix.os }}-${{ matrix.arch }} + enableCrossOsArchive: true + + build-neo-cli: + needs: [build-leveldb] + name: ${{ matrix.runtime }} + runs-on: ubuntu-latest + strategy: + matrix: + runtime: [linux-x64, linux-arm64, win-x64, win-arm64, osx-x64, osx-arm64] + + steps: + # Step to set the application version from the release tag + - name: Set Application Version (Environment Variable) + run: | + APP_VERSION=$(echo '${{ github.event.release.tag_name }}' | cut -d 'v' -f 2) + echo "APP_VERSION=$APP_VERSION" >> $GITHUB_ENV + + # Checkout the neo-cli repository code + - name: Checkout Repository Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # Setup .NET environment + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + # Publish the neo-cli project + - name: .NET Publish (neo-cli) + run: | + dotnet publish ./src/Neo.CLI \ + --version-suffix ${{ matrix.runtime }} \ + --framework net8.0 \ + --configuration ${{ env.CONFIGURATION }} \ + --runtime ${{ matrix.runtime }} \ + --self-contained true \ + --output ${{ env.OUTPUT_PATH }}/${{ matrix.runtime }} \ + --verbosity normal \ + -p:VersionPrefix=${{ env.APP_VERSION }} \ + -p:RuntimeIdentifier=${{ matrix.runtime }} \ + -p:SelfContained=true \ + -p:IncludeNativeLibrariesForSelfExtract=false \ + -p:PublishTrimmed=false \ + -p:PublishSingleFile=true \ + -p:PublishReadyToRun=true \ + -p:EnableCompressionInSingleFile=true \ + -p:DebugType=embedded \ + -p:ServerGarbageCollection=true + + # Build the LevelDBStore plugin + - name: .NET Build (LevelDBStore) + run: | + dotnet build ./src/Plugins/LevelDBStore \ + --version-suffix ${{ matrix.runtime }} \ + --framework net8.0 \ + --configuration ${{ env.CONFIGURATION }} \ + --output ${{ env.OUTPUT_PATH }}/${{ matrix.runtime }}/Plugins/LevelDBStore \ + --verbosity normal \ + -p:VersionPrefix=${{ env.APP_VERSION }} + + # Remove unnecessary files from the LevelDBStore plugin output + - name: Remove files (junk) + working-directory: ${{ env.OUTPUT_PATH }}/${{ matrix.runtime }}/Plugins/LevelDBStore + run: | + rm -v Neo* + rm -v *.pdb + rm -v *.xml + + # Remove XML comment files from the neo-cli output + - name: Remove Xml Comment Files + working-directory: ${{ env.OUTPUT_PATH }}/${{ matrix.runtime }} + run: rm -v *.xml + + # Get cached LevelDB distribution for Windows x64 if applicable + - if: ${{ startsWith(matrix.runtime, 'win-x64') }} + name: Get Distribution Caches (win-x64) + uses: actions/cache@v4 + with: + path: ./leveldb/build/Release/* + key: leveldb-windows-latest-x64 + enableCrossOsArchive: true + fail-on-cache-miss: true + + # Get cached LevelDB distribution for Windows arm64 if applicable + - if: ${{ startsWith(matrix.runtime, 'win-arm64') }} + name: Get Distribution Caches (win-arm64) + uses: actions/cache@v4 + with: + path: ./leveldb/build/Release/* + key: leveldb-windows-latest-arm64 + enableCrossOsArchive: true + fail-on-cache-miss: true + + # Copy LevelDB files to the output directory for Windows + - if: ${{ startsWith(matrix.runtime, 'win') }} + name: Copy Files (leveldb) (win) + run: cp -v ./leveldb/build/Release/leveldb.dll ${{ env.OUTPUT_PATH }}/${{ matrix.runtime }}/libleveldb.dll + + # Create the distribution directory + - name: Create Distribution Directory + run: mkdir -p ${{ env.DIST_PATH }} + + # Create a tarball file for Linux distributions + - name: Create Tarball File (linux) + if: ${{ startsWith(matrix.runtime, 'linux') }} + working-directory: ${{ env.OUTPUT_PATH }}/${{ matrix.runtime }} + run: tar -czvf ${{ env.DIST_PATH }}/neo-cli.v${{ env.APP_VERSION }}-${{ matrix.runtime }}.tar.gz . + + # Create a tarball file for macOS distributions + - name: Cache Distribution + uses: actions/cache/save@v4 + with: + path: ${{ env.OUTPUT_PATH }}/${{ matrix.runtime }}/* + key: neo-${{ matrix.runtime }} + enableCrossOsArchive: true + + # Create a zip file for Windows distributions + - name: Create Zip File (win) + if: ${{ startsWith(matrix.runtime, 'win') }} + working-directory: ${{ env.OUTPUT_PATH }}/${{ matrix.runtime }} + run: zip ${{ env.DIST_PATH }}/neo-cli.v${{ env.APP_VERSION }}-${{ matrix.runtime }}.zip -r * + + # Create checksum files for Linux distributions + - name: Create Checksum Files (linux) + if: ${{ startsWith(matrix.runtime, 'linux') }} + working-directory: ${{ env.DIST_PATH }} + env: + FILENAME: neo-cli.v${{ env.APP_VERSION }}-${{ matrix.runtime }} + run: | + sha256sum ${{ env.FILENAME }}.tar.gz > ${{ env.FILENAME }}.sha256 + + # Create checksum files for Windows distributions + - name: Create Checksum Files (win) + if: ${{ startsWith(matrix.runtime, 'win') }} + working-directory: ${{ env.DIST_PATH }} + env: + FILENAME: neo-cli.v${{ env.APP_VERSION }}-${{ matrix.runtime }} + run: | + sha256sum ${{ env.FILENAME }}.zip > ${{ env.FILENAME }}.sha256 + + # List the contents of the distribution and output directories + - name: Output/Distribution Directory Contents + run: | + ls -la ${{ env.DIST_PATH }} + ls -la ${{ env.OUTPUT_PATH }}/${{ matrix.runtime }} + + # Upload tarball files for Linux distributions + - name: Upload Tarball File (linux) + if: ${{ startsWith(matrix.runtime, 'linux') }} + uses: actions/upload-release-asset@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ${{ env.DIST_PATH }}/neo-cli.v${{ env.APP_VERSION }}-${{ matrix.runtime }}.tar.gz + asset_name: neo-cli.v${{ env.APP_VERSION }}-${{ matrix.runtime }}.tar.gz + asset_content_type: application/x-gtar + + # Upload zip files for Windows distributions + - name: Upload Zip File (win) + if: ${{ startsWith(matrix.runtime, 'win') }} + uses: actions/upload-release-asset@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ${{ env.DIST_PATH }}/neo-cli.v${{ env.APP_VERSION }}-${{ matrix.runtime }}.zip + asset_name: neo-cli.v${{ env.APP_VERSION }}-${{ matrix.runtime }}.zip + asset_content_type: application/zip + + # Upload checksum files for all distributions + - name: Upload Checksum File (all) + if: ${{ startsWith(matrix.runtime, 'osx') == false }} + uses: actions/upload-release-asset@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ${{ env.DIST_PATH }}/neo-cli.v${{ env.APP_VERSION }}-${{ matrix.runtime }}.sha256 + asset_name: neo-cli.v${{ env.APP_VERSION }}-${{ matrix.runtime }}.sha256 + asset_content_type: text/plain + + code-sign: + needs: [build-neo-cli] + name: CodeSign & Publish (neo-cli) ${{ matrix.arch }} + runs-on: macos-latest + strategy: + matrix: + arch: [x64, arm64] + + steps: + # Step to set the application version from the release tag + - name: Set Application Version (Environment Variable) + run: | + APP_VERSION=$(echo '${{ github.event.release.tag_name }}' | cut -d 'v' -f 2) + echo "APP_VERSION=$APP_VERSION" >> $GITHUB_ENV + + - name: Get Distribution Caches (win-${{ matrix.arch}}) + uses: actions/cache@v4 + with: + path: ${{ env.OUTPUT_PATH }}/osx-${{ matrix.arch }}/* + key: neo-osx-${{ matrix.arch }} + enableCrossOsArchive: true + fail-on-cache-miss: true + + - name: Sign (neo-cli) + working-directory: ${{ env.OUTPUT_PATH }}/osx-${{ matrix.arch }} + run: codesign --force --deep -s - neo-cli + + # Create the distribution directory + - name: Create Distribution Directory + run: mkdir -p ${{ env.DIST_PATH }} + + # Create a tarball file for macOS distributions + - name: Create Tarball File (osx) + working-directory: ${{ env.OUTPUT_PATH }}/osx-${{ matrix.arch }} + run: tar -cJf ${{ env.DIST_PATH }}/neo-cli.v${{ env.APP_VERSION }}-osx-${{ matrix.arch }}.tar.xz . + + # Create checksum files for macOS distributions + - name: Create Checksum Files (osx) + working-directory: ${{ env.DIST_PATH }} + env: + FILENAME: neo-cli.v${{ env.APP_VERSION }}-osx-${{ matrix.arch }} + run: | + shasum -a 256 ${{ env.FILENAME }}.tar.xz > ${{ env.FILENAME }}.sha256 + + # Upload tarball files for macOS distributions + - name: Upload Tarball File (osx) + uses: actions/upload-release-asset@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ${{ env.DIST_PATH }}/neo-cli.v${{ env.APP_VERSION }}-osx-${{ matrix.arch }}.tar.xz + asset_name: neo-cli.v${{ env.APP_VERSION }}-osx-${{ matrix.arch }}.tar.xz + asset_content_type: application/x-gtar + + # Upload checksum files for all distributions + - name: Upload Checksum File (all) + uses: actions/upload-release-asset@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ${{ env.DIST_PATH }}/neo-cli.v${{ env.APP_VERSION }}-osx-${{ matrix.arch }}.sha256 + asset_name: neo-cli.v${{ env.APP_VERSION }}-osx-${{ matrix.arch }}.sha256 + asset_content_type: text/plain + + cleanup: + needs: [build-neo-cli, code-sign] + runs-on: ubuntu-latest + steps: + # Cleanup step to delete old caches + - name: Cleanup + run: | + gh extension install actions/gh-actions-cache + + echo "Fetching list of cache key" + cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 ) + + ## Setting this to not fail the workflow while deleting cache keys. + set +e + echo "Deleting caches..." + for cacheKey in $cacheKeysForPR + do + gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm + done + echo "Done" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + BRANCH: ${{ github.ref }} diff --git a/benchmarks/Neo.Benchmarks/Benchmarks.POC.cs b/benchmarks/Neo.Benchmarks/Benchmarks.POC.cs index 379192cc31..a42a1e2944 100644 --- a/benchmarks/Neo.Benchmarks/Benchmarks.POC.cs +++ b/benchmarks/Neo.Benchmarks/Benchmarks.POC.cs @@ -69,7 +69,7 @@ private static void Run(string name, string poc) Script = Convert.FromBase64String(poc), Witnesses = Array.Empty() }; - using var snapshot = system.GetSnapshot(); + using var snapshot = system.GetSnapshotCache(); using var engine = ApplicationEngine.Create(TriggerType.Application, tx, snapshot, system.GenesisBlock, protocol, tx.SystemFee); engine.LoadScript(tx.Script); Stopwatch stopwatch = Stopwatch.StartNew(); diff --git a/neo.sln b/neo.sln index b0de1c27b1..5d55b0d785 100644 --- a/neo.sln +++ b/neo.sln @@ -78,6 +78,8 @@ 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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Extensions.Tests", "tests\Neo.Extensions.Tests\Neo.Extensions.Tests.csproj", "{77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -216,6 +218,10 @@ Global {185ADAFC-BFC6-413D-BC2E-97F9FB0A8AF0}.Debug|Any CPU.Build.0 = Debug|Any CPU {185ADAFC-BFC6-413D-BC2E-97F9FB0A8AF0}.Release|Any CPU.ActiveCfg = Release|Any CPU {185ADAFC-BFC6-413D-BC2E-97F9FB0A8AF0}.Release|Any CPU.Build.0 = Release|Any CPU + {77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -255,6 +261,7 @@ 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} + {77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F} = {EDE05FA8-8E73-4924-BC63-DD117127EEE1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BCBA19D9-F868-4C6D-8061-A2B91E06E3EC} diff --git a/scripts/Neo.CLI/test-neo-cli.expect b/scripts/Neo.CLI/test-neo-cli.exp similarity index 80% rename from scripts/Neo.CLI/test-neo-cli.expect rename to scripts/Neo.CLI/test-neo-cli.exp index 8319c18f8e..e7124402c3 100644 --- a/scripts/Neo.CLI/test-neo-cli.expect +++ b/scripts/Neo.CLI/test-neo-cli.exp @@ -1,12 +1,12 @@ -#!/usr/bin/expect -f +#!/usr/bin/expect -d -f # # This script uses expect to test neo-cli # set timeout 10 - +exp_internal true # Start neo-cli -spawn dotnet out/Neo.CLI/neo-cli.dll +spawn dotnet ./bin/Neo.CLI/net8.0/neo-cli.dll # Expect the main input prompt expect { @@ -18,7 +18,7 @@ expect { # # Test 'create wallet' # -send "create wallet test-wallet1.json\n" +send "create wallet ./bin/Neo.CLI/test-wallet1.json\n" expect { "password:" { send "asd\n" } @@ -42,7 +42,7 @@ expect { # # Test 'create wallet' # -send "create wallet test-wallet2.json L2ArHTuiDL4FHu4nfyhamrG8XVYB4QyRbmhj7vD6hFMB5iAMSTf6\n" +send "create wallet ./bin/Neo.CLI/test-wallet2.json L2ArHTuiDL4FHu4nfyhamrG8XVYB4QyRbmhj7vD6hFMB5iAMSTf6\n" expect { "password:" { send "abcd\n" } diff --git a/src/Neo.CLI/CLI/MainService.Plugins.cs b/src/Neo.CLI/CLI/MainService.Plugins.cs index 761dd386d2..a3974d5e48 100644 --- a/src/Neo.CLI/CLI/MainService.Plugins.cs +++ b/src/Neo.CLI/CLI/MainService.Plugins.cs @@ -80,6 +80,7 @@ private void OnReinstallCommand(string pluginName) /// Downloaded content private static async Task DownloadPluginAsync(string pluginName, Version pluginVersion, string? customDownloadUrl = null, bool prerelease = false) { + ConsoleHelper.Info($"Downloading {pluginName} {pluginVersion}..."); using var httpClient = new HttpClient(); var asmName = Assembly.GetExecutingAssembly().GetName(); @@ -104,7 +105,6 @@ private static async Task DownloadPluginAsync(string pluginName, Version ?? throw new Exception($"Could not find {pluginName}"); var downloadUrl = jsonPlugin["browser_download_url"]!.GetValue(); - return await httpClient.GetStreamAsync(downloadUrl); } @@ -269,13 +269,15 @@ private async Task> GetPluginListAsync() var asmName = Assembly.GetExecutingAssembly().GetName(); httpClient.DefaultRequestHeaders.UserAgent.Add(new(asmName.Name!, asmName.Version!.ToString(3))); - var json = await httpClient.GetFromJsonAsync(Settings.Default.Plugins.DownloadUrl) ?? throw new HttpRequestException($"Failed: {Settings.Default.Plugins.DownloadUrl}"); + var json = await httpClient.GetFromJsonAsync(Settings.Default.Plugins.DownloadUrl) + ?? throw new HttpRequestException($"Failed: {Settings.Default.Plugins.DownloadUrl}"); return json.AsArray() .Where(w => w != null && w["tag_name"]!.GetValue() == $"v{Settings.Default.Plugins.Version.ToString(3)}") .SelectMany(s => s!["assets"]!.AsArray()) - .Select(s => Path.GetFileNameWithoutExtension(s!["name"]!.GetValue())); + .Select(s => Path.GetFileNameWithoutExtension(s!["name"]!.GetValue())) + .Where(s => !s.StartsWith("neo-cli", StringComparison.InvariantCultureIgnoreCase)); } } } diff --git a/src/Neo.CLI/CLI/MainService.cs b/src/Neo.CLI/CLI/MainService.cs index 19ea5d9334..de667bc63d 100644 --- a/src/Neo.CLI/CLI/MainService.cs +++ b/src/Neo.CLI/CLI/MainService.cs @@ -33,7 +33,6 @@ using System.Net; using System.Numerics; using System.Reflection; -using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -383,7 +382,7 @@ public async void Start(CommandLineOptions options) } catch (DllNotFoundException ex) when (ex.Message.Contains("libleveldb")) { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + if (OperatingSystem.IsWindows()) { if (File.Exists("libleveldb.dll")) { @@ -392,25 +391,32 @@ public async void Start(CommandLineOptions options) } else { - DisplayError("DLL not found, please get libleveldb.dll."); + DisplayError("DLL not found, please get libleveldb.dll.", + "Download from https://github.com/neo-ngd/leveldb/releases"); } } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + else if (OperatingSystem.IsLinux()) + { + DisplayError("Shared library libleveldb.so not found, please get libleveldb.so.", + "Use command \"sudo apt-get install libleveldb-dev\" in terminal or download from https://github.com/neo-ngd/leveldb/releases"); + } + else if (OperatingSystem.IsMacOS() || OperatingSystem.IsMacCatalyst()) { DisplayError("Shared library libleveldb.dylib not found, please get libleveldb.dylib.", - "From https://github.com/neo-project/neo/releases"); + "Use command \"brew install leveldb\" in terminal or download from https://github.com/neo-ngd/leveldb/releases"); } else { DisplayError("Neo CLI is broken, please reinstall it.", - "From https://github.com/neo-project/neo/releases"); + "Download from https://github.com/neo-project/neo/releases"); } - + return; } catch (DllNotFoundException) { DisplayError("Neo CLI is broken, please reinstall it.", - "From https://github.com/neo-project/neo/releases"); + "Download from https://github.com/neo-project/neo/releases"); + return; } NeoSystem.AddService(this); diff --git a/src/Neo.Extensions/BigIntegerExtensions.cs b/src/Neo.Extensions/BigIntegerExtensions.cs new file mode 100644 index 0000000000..38a0e60bb5 --- /dev/null +++ b/src/Neo.Extensions/BigIntegerExtensions.cs @@ -0,0 +1,91 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BigIntegerExtensions.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; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Neo.Extensions +{ + public static class BigIntegerExtensions + { + public static int GetLowestSetBit(this BigInteger i) + { + if (i.Sign == 0) + return -1; + var b = i.ToByteArray(); + var w = 0; + while (b[w] == 0) + w++; + for (var x = 0; x < 8; x++) + if ((b[w] & 1 << x) > 0) + return x + w * 8; + throw new Exception(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BigInteger Mod(this BigInteger x, BigInteger y) + { + x %= y; + if (x.Sign < 0) + x += y; + return x; + } + + public static BigInteger ModInverse(this BigInteger a, BigInteger n) + { + BigInteger i = n, v = 0, d = 1; + while (a > 0) + { + BigInteger t = i / a, x = a; + a = i % x; + i = x; + x = d; + d = v - t * x; + v = x; + } + v %= n; + if (v < 0) v = (v + n) % n; + return v; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TestBit(this BigInteger i, int index) + { + return (i & (BigInteger.One << index)) > BigInteger.Zero; + } + + /// + /// Finds the sum of the specified integers. + /// + /// The specified integers. + /// The sum of the integers. + public static BigInteger Sum(this IEnumerable source) + { + var sum = BigInteger.Zero; + foreach (var bi in source) sum += bi; + return sum; + } + + /// + /// Converts a to byte array and eliminates all the leading zeros. + /// + /// The to convert. + /// The converted byte array. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] ToByteArrayStandard(this BigInteger i) + { + if (i.IsZero) return Array.Empty(); + return i.ToByteArray(); + } + } +} diff --git a/src/Neo.Extensions/Neo.Extensions.csproj b/src/Neo.Extensions/Neo.Extensions.csproj index a7f4870568..aa79170ddb 100644 --- a/src/Neo.Extensions/Neo.Extensions.csproj +++ b/src/Neo.Extensions/Neo.Extensions.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Neo.GUI/GUI/MainForm.cs b/src/Neo.GUI/GUI/MainForm.cs index 3114e542b6..5269d992bd 100644 --- a/src/Neo.GUI/GUI/MainForm.cs +++ b/src/Neo.GUI/GUI/MainForm.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Akka.Actor; +using Neo.Extensions; using Neo.IO.Actors; using Neo.Ledger; using Neo.Network.P2P.Payloads; @@ -201,7 +202,7 @@ private void timer1_Tick(object sender, EventArgs e) check_nep5_balance = false; UInt160[] addresses = Service.CurrentWallet.GetAccounts().Select(p => p.ScriptHash).ToArray(); if (addresses.Length == 0) return; - using var snapshot = Service.NeoSystem.GetSnapshot(); + using var snapshot = Service.NeoSystem.GetSnapshotCache(); foreach (UInt160 assetId in NEP5Watched) { byte[] script; diff --git a/src/Neo/IO/Actors/Idle.cs b/src/Neo.IO/Actors/Idle.cs similarity index 89% rename from src/Neo/IO/Actors/Idle.cs rename to src/Neo.IO/Actors/Idle.cs index e722d057ee..c372062783 100644 --- a/src/Neo/IO/Actors/Idle.cs +++ b/src/Neo.IO/Actors/Idle.cs @@ -13,6 +13,6 @@ namespace Neo.IO.Actors { internal sealed class Idle { - public static Idle Instance { get; } = new Idle(); + public static Idle Instance { get; } = new(); } } diff --git a/src/Neo/IO/Caching/Cache.cs b/src/Neo.IO/Caching/Cache.cs similarity index 80% rename from src/Neo/IO/Caching/Cache.cs rename to src/Neo.IO/Caching/Cache.cs index 7895a8ca2d..1a66e6dca5 100644 --- a/src/Neo/IO/Caching/Cache.cs +++ b/src/Neo.IO/Caching/Cache.cs @@ -17,25 +17,21 @@ namespace Neo.IO.Caching { - internal abstract class Cache : ICollection, IDisposable + internal abstract class Cache + (int max_capacity, IEqualityComparer? comparer = null) : ICollection, IDisposable + where TKey : notnull { protected class CacheItem + (TKey key, TValue value) { - public readonly TKey Key; - public readonly TValue Value; - public readonly DateTime Time; - - public CacheItem(TKey key, TValue value) - { - Key = key; - Value = value; - Time = TimeProvider.Current.UtcNow; - } + public readonly TKey Key = key; + public readonly TValue Value = value; + public readonly DateTime Time = DateTime.UtcNow; } protected readonly ReaderWriterLockSlim RwSyncRootLock = new(LockRecursionPolicy.SupportsRecursion); - protected readonly Dictionary InnerDictionary; - private readonly int max_capacity; + protected readonly Dictionary InnerDictionary = new Dictionary(comparer); + private readonly int _max_capacity = max_capacity; public TValue this[TKey key] { @@ -44,7 +40,7 @@ public TValue this[TKey key] RwSyncRootLock.EnterReadLock(); try { - if (!InnerDictionary.TryGetValue(key, out CacheItem item)) throw new KeyNotFoundException(); + if (!InnerDictionary.TryGetValue(key, out CacheItem? item)) throw new KeyNotFoundException(); OnAccess(item); return item.Value; } @@ -73,15 +69,9 @@ public int Count public bool IsReadOnly => false; - public Cache(int max_capacity, IEqualityComparer comparer = null) - { - this.max_capacity = max_capacity; - InnerDictionary = new Dictionary(comparer); - } - public void Add(TValue item) { - TKey key = GetKeyForItem(item); + var key = GetKeyForItem(item); RwSyncRootLock.EnterWriteLock(); try { @@ -95,16 +85,16 @@ public void Add(TValue item) private void AddInternal(TKey key, TValue item) { - if (InnerDictionary.TryGetValue(key, out CacheItem cacheItem)) + if (InnerDictionary.TryGetValue(key, out CacheItem? cacheItem)) { OnAccess(cacheItem); } else { - if (InnerDictionary.Count >= max_capacity) + if (InnerDictionary.Count >= _max_capacity) { //TODO: Perform a performance test on the PLINQ query to determine which algorithm is better here (parallel or not) - foreach (CacheItem item_del in InnerDictionary.Values.AsParallel().OrderBy(p => p.Time).Take(InnerDictionary.Count - max_capacity + 1)) + foreach (var item_del in InnerDictionary.Values.AsParallel().OrderBy(p => p.Time).Take(InnerDictionary.Count - _max_capacity + 1)) { RemoveInternal(item_del); } @@ -118,9 +108,9 @@ public void AddRange(IEnumerable items) RwSyncRootLock.EnterWriteLock(); try { - foreach (TValue item in items) + foreach (var item in items) { - TKey key = GetKeyForItem(item); + var key = GetKeyForItem(item); AddInternal(key, item); } } @@ -135,7 +125,7 @@ public void Clear() RwSyncRootLock.EnterWriteLock(); try { - foreach (CacheItem item_del in InnerDictionary.Values.ToArray()) + foreach (var item_del in InnerDictionary.Values.ToArray()) { RemoveInternal(item_del); } @@ -151,7 +141,7 @@ public bool Contains(TKey key) RwSyncRootLock.EnterReadLock(); try { - if (!InnerDictionary.TryGetValue(key, out CacheItem cacheItem)) return false; + if (!InnerDictionary.TryGetValue(key, out CacheItem? cacheItem)) return false; OnAccess(cacheItem); return true; } @@ -171,7 +161,7 @@ public void CopyTo(TValue[] array, int arrayIndex) if (array == null) throw new ArgumentNullException(); if (arrayIndex < 0) throw new ArgumentOutOfRangeException(); if (arrayIndex + InnerDictionary.Count > array.Length) throw new ArgumentException(); - foreach (TValue item in this) + foreach (var item in this) { array[arrayIndex++] = item; } @@ -188,7 +178,7 @@ public IEnumerator GetEnumerator() RwSyncRootLock.EnterReadLock(); try { - foreach (TValue item in InnerDictionary.Values.Select(p => p.Value)) + foreach (var item in InnerDictionary.Values.Select(p => p.Value)) { yield return item; } @@ -211,7 +201,7 @@ public bool Remove(TKey key) RwSyncRootLock.EnterWriteLock(); try { - if (!InnerDictionary.TryGetValue(key, out CacheItem cacheItem)) return false; + if (!InnerDictionary.TryGetValue(key, out CacheItem? cacheItem)) return false; RemoveInternal(cacheItem); return true; } @@ -242,7 +232,7 @@ public bool TryGet(TKey key, out TValue item) RwSyncRootLock.EnterReadLock(); try { - if (InnerDictionary.TryGetValue(key, out CacheItem cacheItem)) + if (InnerDictionary.TryGetValue(key, out CacheItem? cacheItem)) { OnAccess(cacheItem); item = cacheItem.Value; @@ -253,7 +243,7 @@ public bool TryGet(TKey key, out TValue item) { RwSyncRootLock.ExitReadLock(); } - item = default; + item = default!; return false; } } diff --git a/src/Neo/IO/Caching/FIFOCache.cs b/src/Neo.IO/Caching/FIFOCache.cs similarity index 72% rename from src/Neo/IO/Caching/FIFOCache.cs rename to src/Neo.IO/Caching/FIFOCache.cs index af3e5e469d..b7e9f39e98 100644 --- a/src/Neo/IO/Caching/FIFOCache.cs +++ b/src/Neo.IO/Caching/FIFOCache.cs @@ -13,13 +13,10 @@ namespace Neo.IO.Caching { - internal abstract class FIFOCache : Cache + internal abstract class FIFOCache + (int max_capacity, IEqualityComparer? comparer = null) : Cache(max_capacity, comparer) + where TKey : notnull { - public FIFOCache(int max_capacity, IEqualityComparer comparer = null) - : base(max_capacity, comparer) - { - } - protected override void OnAccess(CacheItem item) { } diff --git a/src/Neo/IO/Caching/HashSetCache.cs b/src/Neo.IO/Caching/HashSetCache.cs similarity index 76% rename from src/Neo/IO/Caching/HashSetCache.cs rename to src/Neo.IO/Caching/HashSetCache.cs index bdef1c5e3a..577893e924 100644 --- a/src/Neo/IO/Caching/HashSetCache.cs +++ b/src/Neo.IO/Caching/HashSetCache.cs @@ -20,17 +20,17 @@ class HashSetCache : IReadOnlyCollection where T : IEquatable /// /// Sets where the Hashes are stored /// - private readonly LinkedList> sets = new(); + private readonly LinkedList> _sets = new(); /// - /// Maximum capacity of each bucket inside each HashSet of . + /// Maximum capacity of each bucket inside each HashSet of . /// - private readonly int bucketCapacity; + private readonly int _bucketCapacity; /// /// Maximum number of buckets for the LinkedList, meaning its maximum cardinality. /// - private readonly int maxBucketCount; + private readonly int _maxBucketCount; /// /// Entry count @@ -43,32 +43,32 @@ public HashSetCache(int bucketCapacity, int maxBucketCount = 10) if (maxBucketCount <= 0) throw new ArgumentOutOfRangeException($"{nameof(maxBucketCount)} should be greater than 0"); Count = 0; - this.bucketCapacity = bucketCapacity; - this.maxBucketCount = maxBucketCount; - sets.AddFirst(new HashSet()); + _bucketCapacity = bucketCapacity; + _maxBucketCount = maxBucketCount; + _sets.AddFirst([]); } public bool Add(T item) { if (Contains(item)) return false; Count++; - if (sets.First.Value.Count < bucketCapacity) return sets.First.Value.Add(item); + if (_sets.First?.Value.Count < _bucketCapacity) return _sets.First.Value.Add(item); var newSet = new HashSet { item }; - sets.AddFirst(newSet); - if (sets.Count > maxBucketCount) + _sets.AddFirst(newSet); + if (_sets.Count > _maxBucketCount) { - Count -= sets.Last.Value.Count; - sets.RemoveLast(); + Count -= _sets.Last?.Value.Count ?? 0; + _sets.RemoveLast(); } return true; } public bool Contains(T item) { - foreach (var set in sets) + foreach (var set in _sets) { if (set.Contains(item)) return true; } @@ -77,17 +77,17 @@ public bool Contains(T item) public void ExceptWith(IEnumerable items) { - List> removeList = null; + List> removeList = default!; foreach (var item in items) { - foreach (var set in sets) + foreach (var set in _sets) { if (set.Remove(item)) { Count--; if (set.Count == 0) { - removeList ??= new List>(); + removeList ??= []; removeList.Add(set); } break; @@ -97,13 +97,13 @@ public void ExceptWith(IEnumerable items) if (removeList == null) return; foreach (var set in removeList) { - sets.Remove(set); + _sets.Remove(set); } } public IEnumerator GetEnumerator() { - foreach (var set in sets) + foreach (var set in _sets) { foreach (var item in set) { diff --git a/src/Neo/IO/Caching/IndexedQueue.cs b/src/Neo.IO/Caching/IndexedQueue.cs similarity index 95% rename from src/Neo/IO/Caching/IndexedQueue.cs rename to src/Neo.IO/Caching/IndexedQueue.cs index 54440a871b..29b39a4947 100644 --- a/src/Neo/IO/Caching/IndexedQueue.cs +++ b/src/Neo.IO/Caching/IndexedQueue.cs @@ -89,14 +89,14 @@ public void Enqueue(T item) { if (_array.Length == _count) { - int newSize = _array.Length * GrowthFactor; + var newSize = _array.Length * GrowthFactor; if (_head == 0) { Array.Resize(ref _array, newSize); } else { - T[] buffer = new T[newSize]; + var buffer = new T[newSize]; Array.Copy(_array, _head, buffer, 0, _array.Length - _head); Array.Copy(_array, 0, buffer, _array.Length - _head, _head); _array = buffer; @@ -127,7 +127,7 @@ public bool TryPeek(out T item) { if (_count == 0) { - item = default; + item = default!; return false; } else @@ -145,7 +145,7 @@ public T Dequeue() { if (_count == 0) throw new InvalidOperationException("The queue is empty"); - T result = _array[_head]; + var result = _array[_head]; ++_head; _head %= _array.Length; --_count; @@ -161,7 +161,7 @@ public bool TryDequeue(out T item) { if (_count == 0) { - item = default; + item = default!; return false; } else @@ -194,7 +194,7 @@ public void TrimExcess() } else if (_array.Length * TrimThreshold >= _count) { - T[] arr = new T[_count]; + var arr = new T[_count]; CopyTo(arr, 0); _array = arr; _head = 0; @@ -228,14 +228,14 @@ public void CopyTo(T[] array, int arrayIndex) /// An array containing the queue's items public T[] ToArray() { - T[] result = new T[_count]; + var result = new T[_count]; CopyTo(result, 0); return result; } public IEnumerator GetEnumerator() { - for (int i = 0; i < _count; i++) + for (var i = 0; i < _count; i++) yield return _array[(_head + i) % _array.Length]; } diff --git a/src/Neo/IO/Caching/KeyedCollectionSlim.cs b/src/Neo.IO/Caching/KeyedCollectionSlim.cs similarity index 61% rename from src/Neo/IO/Caching/KeyedCollectionSlim.cs rename to src/Neo.IO/Caching/KeyedCollectionSlim.cs index a24dd87e4c..f632dc0927 100644 --- a/src/Neo/IO/Caching/KeyedCollectionSlim.cs +++ b/src/Neo.IO/Caching/KeyedCollectionSlim.cs @@ -10,43 +10,46 @@ // modifications are permitted. using System; +using System.Collections; using System.Collections.Generic; namespace Neo.IO.Caching; -abstract class KeyedCollectionSlim where TKey : notnull +abstract class KeyedCollectionSlim + where TKey : notnull + where TItem : class, IStructuralEquatable, IStructuralComparable, IComparable { private readonly LinkedList _items = new(); - private readonly Dictionary> dict = new(); + private readonly Dictionary> _dict = []; - public int Count => dict.Count; - public TItem First => _items.First.Value; + public int Count => _dict.Count; + public TItem? First => _items.First?.Value; - protected abstract TKey GetKeyForItem(TItem item); + protected abstract TKey GetKeyForItem(TItem? item); public void Add(TItem item) { var key = GetKeyForItem(item); var node = _items.AddLast(item); - if (!dict.TryAdd(key, node)) + if (!_dict.TryAdd(key, node)) { _items.RemoveLast(); throw new ArgumentException("An element with the same key already exists in the collection."); } } - public bool Contains(TKey key) => dict.ContainsKey(key); + public bool Contains(TKey key) => _dict.ContainsKey(key); public void Remove(TKey key) { - if (dict.Remove(key, out var node)) + if (_dict.Remove(key, out var node)) _items.Remove(node); } public void RemoveFirst() { - var key = GetKeyForItem(_items.First.Value); - dict.Remove(key); + var key = GetKeyForItem(_items.First?.Value); + _dict.Remove(key); _items.RemoveFirst(); } } diff --git a/src/Neo/IO/Caching/ReflectionCacheAttribute.cs b/src/Neo.IO/Caching/ReflectionCacheAttribute.cs similarity index 67% rename from src/Neo/IO/Caching/ReflectionCacheAttribute.cs rename to src/Neo.IO/Caching/ReflectionCacheAttribute.cs index 20aeb91320..7071e879e4 100644 --- a/src/Neo/IO/Caching/ReflectionCacheAttribute.cs +++ b/src/Neo.IO/Caching/ReflectionCacheAttribute.cs @@ -13,21 +13,17 @@ namespace Neo.IO.Caching { + /// + /// Constructor + /// + /// Type [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] - internal class ReflectionCacheAttribute : Attribute + internal class ReflectionCacheAttribute + (Type type) : Attribute { /// /// Type /// - public Type Type { get; } - - /// - /// Constructor - /// - /// Type - public ReflectionCacheAttribute(Type type) - { - Type = type; - } + public Type Type { get; } = type; } } diff --git a/src/Neo.Json/Neo.Json.csproj b/src/Neo.Json/Neo.Json.csproj index 15924436e7..8d8fd33ac9 100644 --- a/src/Neo.Json/Neo.Json.csproj +++ b/src/Neo.Json/Neo.Json.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Neo/Cryptography/Crypto.cs b/src/Neo/Cryptography/Crypto.cs index 85ed0411a8..a2e89dc787 100644 --- a/src/Neo/Cryptography/Crypto.cs +++ b/src/Neo/Cryptography/Crypto.cs @@ -170,7 +170,7 @@ public static ECDsa CreateECDsa(ECC.ECPoint pubkey) { if (CacheECDsa.TryGet(pubkey, out var cache)) { - return cache.value; + return cache.Value; } var curve = pubkey.Curve == ECC.ECCurve.Secp256r1 ? ECCurve.NamedCurves.nistP256 : diff --git a/src/Neo/Cryptography/ECC/ECFieldElement.cs b/src/Neo/Cryptography/ECC/ECFieldElement.cs index 47d8bfa274..d77a8b0451 100644 --- a/src/Neo/Cryptography/ECC/ECFieldElement.cs +++ b/src/Neo/Cryptography/ECC/ECFieldElement.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using System; using System.Numerics; diff --git a/src/Neo/Cryptography/ECC/ECPoint.cs b/src/Neo/Cryptography/ECC/ECPoint.cs index 3b7bdcc885..77d01b90ea 100644 --- a/src/Neo/Cryptography/ECC/ECPoint.cs +++ b/src/Neo/Cryptography/ECC/ECPoint.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.IO.Caching; using System; diff --git a/src/Neo/Helper.cs b/src/Neo/Helper.cs index 00099bc148..7acf69b0e9 100644 --- a/src/Neo/Helper.cs +++ b/src/Neo/Helper.cs @@ -82,20 +82,6 @@ public static byte[] Concat(ReadOnlySpan a, ReadOnlySpan b) return buffer; } - internal static int GetLowestSetBit(this BigInteger i) - { - if (i.Sign == 0) - return -1; - byte[] b = i.ToByteArray(); - int w = 0; - while (b[w] == 0) - w++; - for (int x = 0; x < 8; x++) - if ((b[w] & 1 << x) > 0) - return x + w * 8; - throw new Exception(); - } - internal static void Remove(this HashSet set, ISet other) { if (set.Count > other.Count) @@ -157,32 +143,6 @@ public static byte[] HexToBytes(this string value) return result; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static BigInteger Mod(this BigInteger x, BigInteger y) - { - x %= y; - if (x.Sign < 0) - x += y; - return x; - } - - internal static BigInteger ModInverse(this BigInteger a, BigInteger n) - { - BigInteger i = n, v = 0, d = 1; - while (a > 0) - { - BigInteger t = i / a, x = a; - a = i % x; - i = x; - x = d; - d = v - t * x; - v = x; - } - v %= n; - if (v < 0) v = (v + n) % n; - return v; - } - internal static BigInteger NextBigInteger(this Random rand, int sizeInBits) { if (sizeInBits < 0) @@ -198,36 +158,6 @@ internal static BigInteger NextBigInteger(this Random rand, int sizeInBits) return new BigInteger(b); } - /// - /// Finds the sum of the specified integers. - /// - /// The specified integers. - /// The sum of the integers. - public static BigInteger Sum(this IEnumerable source) - { - var sum = BigInteger.Zero; - foreach (var bi in source) sum += bi; - return sum; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool TestBit(this BigInteger i, int index) - { - return (i & (BigInteger.One << index)) > BigInteger.Zero; - } - - /// - /// Converts a to byte array and eliminates all the leading zeros. - /// - /// The to convert. - /// The converted byte array. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte[] ToByteArrayStandard(this BigInteger i) - { - if (i.IsZero) return Array.Empty(); - return i.ToByteArray(); - } - /// /// Converts a byte array to hex . /// diff --git a/src/Neo/IO/Actors/PriorityMailbox.cs b/src/Neo/IO/Actors/PriorityMailbox.cs index 0b08c04d70..b51d5ee861 100644 --- a/src/Neo/IO/Actors/PriorityMailbox.cs +++ b/src/Neo/IO/Actors/PriorityMailbox.cs @@ -17,17 +17,11 @@ namespace Neo.IO.Actors { - internal abstract class PriorityMailbox : MailboxType, IProducesMessageQueue + internal abstract class PriorityMailbox + (Settings settings, Config config) : MailboxType(settings, config), IProducesMessageQueue { - public PriorityMailbox(Akka.Actor.Settings settings, Config config) - : base(settings, config) - { - } - - public override IMessageQueue Create(IActorRef owner, ActorSystem system) - { - return new PriorityMessageQueue(ShallDrop, IsHighPriority); - } + public override IMessageQueue Create(IActorRef owner, ActorSystem system) => + new PriorityMessageQueue(ShallDrop, IsHighPriority); internal protected virtual bool IsHighPriority(object message) => false; internal protected virtual bool ShallDrop(object message, IEnumerable queue) => false; diff --git a/src/Neo/IO/Actors/PriorityMessageQueue.cs b/src/Neo/IO/Actors/PriorityMessageQueue.cs index 225720ec97..8723416adf 100644 --- a/src/Neo/IO/Actors/PriorityMessageQueue.cs +++ b/src/Neo/IO/Actors/PriorityMessageQueue.cs @@ -20,22 +20,17 @@ namespace Neo.IO.Actors { - internal class PriorityMessageQueue : IMessageQueue, IUnboundedMessageQueueSemantics + internal class PriorityMessageQueue + (Func dropper, Func priority_generator) : IMessageQueue, IUnboundedMessageQueueSemantics { - private readonly ConcurrentQueue high = new(); - private readonly ConcurrentQueue low = new(); - private readonly Func dropper; - private readonly Func priority_generator; - private int idle = 1; + private readonly ConcurrentQueue _high = new(); + private readonly ConcurrentQueue _low = new(); + private readonly Func _dropper = dropper; + private readonly Func _priority_generator = priority_generator; + private int _idle = 1; - public bool HasMessages => !high.IsEmpty || !low.IsEmpty; - public int Count => high.Count + low.Count; - - public PriorityMessageQueue(Func dropper, Func priority_generator) - { - this.dropper = dropper; - this.priority_generator = priority_generator; - } + public bool HasMessages => !_high.IsEmpty || !_low.IsEmpty; + public int Count => _high.Count + _low.Count; public void CleanUp(IActorRef owner, IMessageQueue deadletters) { @@ -43,19 +38,19 @@ public void CleanUp(IActorRef owner, IMessageQueue deadletters) public void Enqueue(IActorRef receiver, Envelope envelope) { - Interlocked.Increment(ref idle); + Interlocked.Increment(ref _idle); if (envelope.Message is Idle) return; - if (dropper(envelope.Message, high.Concat(low).Select(p => p.Message))) + if (_dropper(envelope.Message, _high.Concat(_low).Select(p => p.Message))) return; - ConcurrentQueue queue = priority_generator(envelope.Message) ? high : low; + var queue = _priority_generator(envelope.Message) ? _high : _low; queue.Enqueue(envelope); } public bool TryDequeue(out Envelope envelope) { - if (high.TryDequeue(out envelope)) return true; - if (low.TryDequeue(out envelope)) return true; - if (Interlocked.Exchange(ref idle, 0) > 0) + if (_high.TryDequeue(out envelope)) return true; + if (_low.TryDequeue(out envelope)) return true; + if (Interlocked.Exchange(ref _idle, 0) > 0) { envelope = new Envelope(Idle.Instance, ActorRefs.NoSender); return true; diff --git a/src/Neo/IO/Caching/ECDsaCache.cs b/src/Neo/IO/Caching/ECDsaCache.cs index b25f29da8e..b41a058b61 100644 --- a/src/Neo/IO/Caching/ECDsaCache.cs +++ b/src/Neo/IO/Caching/ECDsaCache.cs @@ -14,7 +14,8 @@ namespace Neo.IO.Caching { - record ECDsaCacheItem(Cryptography.ECC.ECPoint key, ECDsa value); + record ECDsaCacheItem(Cryptography.ECC.ECPoint Key, ECDsa Value); + internal class ECDsaCache : FIFOCache { public ECDsaCache(int max_capacity = 20000) : base(max_capacity, EqualityComparer.Default) @@ -23,7 +24,7 @@ public ECDsaCache(int max_capacity = 20000) : base(max_capacity, EqualityCompare protected override Cryptography.ECC.ECPoint GetKeyForItem(ECDsaCacheItem item) { - return item.key; + return item.Key; } } } diff --git a/src/Neo/IO/Caching/ReflectionCache.cs b/src/Neo/IO/Caching/ReflectionCache.cs index 2fd8f5fceb..5007b67740 100644 --- a/src/Neo/IO/Caching/ReflectionCache.cs +++ b/src/Neo/IO/Caching/ReflectionCache.cs @@ -15,29 +15,30 @@ namespace Neo.IO.Caching { - internal static class ReflectionCache where T : Enum + internal static class ReflectionCache + where T : Enum { - private static readonly Dictionary dictionary = new(); + private static readonly Dictionary s_dictionary = []; - public static int Count => dictionary.Count; + public static int Count => s_dictionary.Count; static ReflectionCache() { - foreach (FieldInfo field in typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static)) + foreach (var field in typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static)) { // Get attribute - ReflectionCacheAttribute attribute = field.GetCustomAttribute(); + var attribute = field.GetCustomAttribute(); if (attribute == null) continue; // Append to cache - dictionary.Add((T)field.GetValue(null), attribute.Type); + s_dictionary.Add((T)field.GetValue(null), attribute.Type); } } public static object CreateInstance(T key, object def = null) { // Get Type from cache - if (dictionary.TryGetValue(key, out Type t)) + if (s_dictionary.TryGetValue(key, out var t)) return Activator.CreateInstance(t); // return null @@ -46,7 +47,7 @@ public static object CreateInstance(T key, object def = null) public static ISerializable CreateSerializable(T key, ReadOnlyMemory data) { - if (dictionary.TryGetValue(key, out Type t)) + if (s_dictionary.TryGetValue(key, out var t)) return data.AsSerializable(t); return null; } diff --git a/src/Neo/IO/Caching/RelayCache.cs b/src/Neo/IO/Caching/RelayCache.cs index 0f617f4d24..56466d9a7f 100644 --- a/src/Neo/IO/Caching/RelayCache.cs +++ b/src/Neo/IO/Caching/RelayCache.cs @@ -13,13 +13,9 @@ namespace Neo.IO.Caching { - internal class RelayCache : FIFOCache + internal class RelayCache + (int max_capacity) : FIFOCache(max_capacity) { - public RelayCache(int max_capacity) - : base(max_capacity) - { - } - protected override UInt256 GetKeyForItem(IInventory item) { return item.Hash; diff --git a/src/Neo/Ledger/Blockchain.cs b/src/Neo/Ledger/Blockchain.cs index b35dd069e3..ca27bb2124 100644 --- a/src/Neo/Ledger/Blockchain.cs +++ b/src/Neo/Ledger/Blockchain.cs @@ -12,22 +12,18 @@ using Akka.Actor; using Akka.Configuration; using Akka.IO; -using Akka.Util.Internal; using Neo.IO.Actors; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; using Neo.Persistence; -using Neo.Plugins; using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.VM; using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; -using System.Threading.Tasks; namespace Neo.Ledger { @@ -421,7 +417,7 @@ private void OnTransaction(Transaction tx) private void Persist(Block block) { - using (SnapshotCache snapshot = system.GetSnapshot()) + using (SnapshotCache snapshot = system.GetSnapshotCache()) { List all_application_executed = new(); TransactionState[] transactionStates; @@ -439,7 +435,7 @@ private void Persist(Block block) all_application_executed.Add(application_executed); transactionStates = engine.GetState(); } - DataCache clonedSnapshot = snapshot.CreateSnapshot(); + DataCache clonedSnapshot = snapshot.CloneCache(); // Warning: Do not write into variable snapshot directly. Write into variable clonedSnapshot and commit instead. foreach (TransactionState transactionState in transactionStates) { @@ -453,7 +449,7 @@ private void Persist(Block block) } else { - clonedSnapshot = snapshot.CreateSnapshot(); + clonedSnapshot = snapshot.CloneCache(); } ApplicationExecuted application_executed = new(engine); Context.System.EventStream.Publish(application_executed); @@ -472,10 +468,10 @@ private void Persist(Block block) Context.System.EventStream.Publish(application_executed); all_application_executed.Add(application_executed); } - _ = InvokeCommittingAsync(system, block, snapshot, all_application_executed); + Committing?.Invoke(system, block, snapshot, all_application_executed); snapshot.Commit(); } - _ = InvokeCommittedAsync(system, block); + Committed?.Invoke(system, block); system.MemPool.UpdatePoolForBlockPersisted(block, system.StoreView); extensibleWitnessWhiteList = null; block_cache.Remove(block.PrevHash); @@ -484,64 +480,6 @@ private void Persist(Block block) Debug.Assert(header.Index == block.Index); } - internal static async Task InvokeCommittingAsync(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) - { - await InvokeHandlersAsync(Committing?.GetInvocationList(), h => ((CommittingHandler)h)(system, block, snapshot, applicationExecutedList)); - } - - internal static async Task InvokeCommittedAsync(NeoSystem system, Block block) - { - await InvokeHandlersAsync(Committed?.GetInvocationList(), h => ((CommittedHandler)h)(system, block)); - } - - private static async Task InvokeHandlersAsync(Delegate[] handlers, Action handlerAction) - { - if (handlers == null) return; - - var exceptions = new ConcurrentBag(); - var tasks = handlers.Select(handler => Task.Run(() => - { - try - { - // skip stopped plugin. - if (handler.Target is Plugin { IsStopped: true }) - { - return; - } - - handlerAction(handler); - } - catch (Exception ex) when (handler.Target is Plugin plugin) - { - switch (plugin.ExceptionPolicy) - { - case UnhandledExceptionPolicy.StopNode: - exceptions.Add(ex); - throw; - case UnhandledExceptionPolicy.StopPlugin: - //Stop plugin on exception - plugin.IsStopped = true; - break; - case UnhandledExceptionPolicy.Ignore: - // Log the exception and continue with the next handler - break; - default: - throw new InvalidCastException($"The exception policy {plugin.ExceptionPolicy} is not valid."); - } - - Utility.Log(nameof(plugin), LogLevel.Error, ex); - } - catch (Exception ex) - { - exceptions.Add(ex); - } - })).ToList(); - - await Task.WhenAll(tasks); - - exceptions.ForEach(e => throw e); - } - /// /// Gets a object used for creating the actor. /// diff --git a/src/Neo/Ledger/MemoryPool.cs b/src/Neo/Ledger/MemoryPool.cs index 23eb711e87..ff9c6b1069 100644 --- a/src/Neo/Ledger/MemoryPool.cs +++ b/src/Neo/Ledger/MemoryPool.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +#nullable enable using Akka.Util.Internal; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; @@ -16,6 +17,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; @@ -27,8 +29,8 @@ namespace Neo.Ledger /// public class MemoryPool : IReadOnlyCollection { - public event EventHandler TransactionAdded; - public event EventHandler TransactionRemoved; + public event EventHandler? TransactionAdded; + public event EventHandler? TransactionRemoved; // Allow a reverified transaction to be rebroadcast if it has been this many block times since last broadcast. private const int BlocksTillRebroadcast = 10; @@ -157,15 +159,15 @@ public bool ContainsKey(UInt256 hash) /// The hash of the to get. /// When this method returns, contains the associated with the specified hash, if the hash is found; otherwise, . /// if the contains a with the specified hash; otherwise, . - public bool TryGetValue(UInt256 hash, out Transaction tx) + public bool TryGetValue(UInt256 hash, [MaybeNullWhen(false)] out Transaction? tx) { _txRwLock.EnterReadLock(); try { - bool ret = _unsortedTransactions.TryGetValue(hash, out PoolItem item) - || _unverifiedTransactions.TryGetValue(hash, out item); - tx = ret ? item.Tx : null; - return ret; + _ = _unsortedTransactions.TryGetValue(hash, out var item) + || _unverifiedTransactions.TryGetValue(hash, out item); + tx = item?.Tx; + return tx != null; } finally { @@ -247,13 +249,13 @@ public IEnumerable GetSortedVerifiedTransactions() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static PoolItem GetLowestFeeTransaction(SortedSet verifiedTxSorted, - SortedSet unverifiedTxSorted, out SortedSet sortedPool) + private static PoolItem? GetLowestFeeTransaction(SortedSet verifiedTxSorted, + SortedSet unverifiedTxSorted, out SortedSet? sortedPool) { - PoolItem minItem = unverifiedTxSorted.Min; + var minItem = unverifiedTxSorted.Min; sortedPool = minItem != null ? unverifiedTxSorted : null; - PoolItem verifiedMin = verifiedTxSorted.Min; + var verifiedMin = verifiedTxSorted.Min; if (verifiedMin == null) return minItem; if (minItem != null && verifiedMin.CompareTo(minItem) >= 0) @@ -265,7 +267,7 @@ private static PoolItem GetLowestFeeTransaction(SortedSet verifiedTxSo return minItem; } - private PoolItem GetLowestFeeTransaction(out Dictionary unsortedTxPool, out SortedSet sortedPool) + private PoolItem? GetLowestFeeTransaction(out Dictionary unsortedTxPool, out SortedSet? sortedPool) { sortedPool = null; @@ -286,7 +288,10 @@ internal bool CanTransactionFitInPool(Transaction tx) { if (Count < Capacity) return true; - return GetLowestFeeTransaction(out _, out _).CompareTo(tx) <= 0; + var item = GetLowestFeeTransaction(out _, out _); + if (item == null) return false; + + return item.CompareTo(tx) <= 0; } internal VerifyResult TryAdd(Transaction tx, DataCache snapshot) @@ -295,7 +300,7 @@ internal VerifyResult TryAdd(Transaction tx, DataCache snapshot) if (_unsortedTransactions.ContainsKey(tx.Hash)) return VerifyResult.AlreadyInPool; - List removedTransactions = null; + List? removedTransactions = null; _txRwLock.EnterWriteLock(); try { @@ -368,7 +373,7 @@ private bool CheckConflicts(Transaction tx, out List conflictsList) // Step 2: check if unsorted transactions were in `tx`'s Conflicts attributes. foreach (var hash in tx.GetAttributes().Select(p => p.Hash)) { - if (_unsortedTransactions.TryGetValue(hash, out PoolItem unsortedTx)) + if (_unsortedTransactions.TryGetValue(hash, out var unsortedTx)) { if (!tx.Signers.Select(p => p.Account).Intersect(unsortedTx.Tx.Signers.Select(p => p.Account)).Any()) return false; conflictsFeeSum += unsortedTx.Tx.NetworkFee; @@ -390,7 +395,8 @@ private List RemoveOverCapacity() List removedTransactions = new(); do { - PoolItem minItem = GetLowestFeeTransaction(out var unsortedPool, out var sortedPool); + var minItem = GetLowestFeeTransaction(out var unsortedPool, out var sortedPool); + if (minItem == null || sortedPool == null) break; unsortedPool.Remove(minItem.Tx.Hash); sortedPool.Remove(minItem); @@ -407,7 +413,7 @@ private List RemoveOverCapacity() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool TryRemoveVerified(UInt256 hash, out PoolItem item) + private bool TryRemoveVerified(UInt256 hash, [MaybeNullWhen(false)] out PoolItem? item) { if (!_unsortedTransactions.TryGetValue(hash, out item)) return false; @@ -425,7 +431,7 @@ private void RemoveConflictsOfVerified(PoolItem item) { foreach (var h in item.Tx.GetAttributes().Select(attr => attr.Hash)) { - if (_conflicts.TryGetValue(h, out HashSet conflicts)) + if (_conflicts.TryGetValue(h, out var conflicts)) { conflicts.Remove(item.Tx.Hash); if (conflicts.Count() == 0) @@ -437,7 +443,7 @@ private void RemoveConflictsOfVerified(PoolItem item) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool TryRemoveUnVerified(UInt256 hash, out PoolItem item) + internal bool TryRemoveUnVerified(UInt256 hash, [MaybeNullWhen(false)] out PoolItem? item) { if (!_unverifiedTransactions.TryGetValue(hash, out item)) return false; @@ -653,5 +659,24 @@ internal bool ReVerifyTopUnverifiedTransactionsIfNeeded(int maxToVerify, DataCac return _unverifiedTransactions.Count > 0; } + + // This method is only for test purpose + // Do not use this method outside of unit tests + internal void Clear() + { + _txRwLock.EnterReadLock(); + try + { + _unsortedTransactions.Clear(); + _conflicts.Clear(); + _sortedTransactions.Clear(); + _unverifiedTransactions.Clear(); + _unverifiedSortedTransactions.Clear(); + } + finally + { + _txRwLock.ExitReadLock(); + } + } } } diff --git a/src/Neo/NeoSystem.cs b/src/Neo/NeoSystem.cs index b752f16712..3121432db5 100644 --- a/src/Neo/NeoSystem.cs +++ b/src/Neo/NeoSystem.cs @@ -275,12 +275,24 @@ public void SuspendNodeStartup() /// /// Gets a snapshot of the blockchain storage. /// - /// + /// An instance of + [Obsolete("This method is obsolete, use GetSnapshotCache instead.")] public SnapshotCache GetSnapshot() { return new SnapshotCache(store.GetSnapshot()); } + /// + /// Gets a snapshot of the blockchain storage with an execution cache. + /// With the snapshot, we have the latest state of the blockchain, with the cache, + /// we can run transactions in a sandboxed environment. + /// + /// An instance of + public SnapshotCache GetSnapshotCache() + { + return new SnapshotCache(store.GetSnapshot()); + } + /// /// Determines whether the specified transaction exists in the memory pool or storage. /// diff --git a/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs b/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs index 4606723cd0..4e7861a562 100644 --- a/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs +++ b/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs @@ -30,9 +30,9 @@ namespace Neo.Network.P2P partial class RemoteNode { private class Timer { } - private class PendingKnownHashesCollection : KeyedCollectionSlim + private class PendingKnownHashesCollection : KeyedCollectionSlim> { - protected override UInt256 GetKeyForItem((UInt256, DateTime) item) + protected override UInt256 GetKeyForItem(Tuple item) { return item.Item1; } @@ -354,7 +354,7 @@ private void OnInvMessageReceived(InvPayload payload) } if (hashes.Length == 0) return; foreach (UInt256 hash in hashes) - pendingKnownHashes.Add((hash, TimeProvider.Current.UtcNow)); + pendingKnownHashes.Add(Tuple.Create(hash, TimeProvider.Current.UtcNow)); system.TaskManager.Tell(new TaskManager.NewTasks { Payload = InvPayload.Create(payload.Type, hashes) }); } diff --git a/src/Neo/Persistence/DataCache.cs b/src/Neo/Persistence/DataCache.cs index 86fbd69961..29c610c6c8 100644 --- a/src/Neo/Persistence/DataCache.cs +++ b/src/Neo/Persistence/DataCache.cs @@ -152,11 +152,21 @@ public virtual void Commit() /// Creates a snapshot, which uses this instance as the underlying storage. /// /// The snapshot of this instance. + [Obsolete("CreateSnapshot is deprecated, please use CloneCache instead.")] public DataCache CreateSnapshot() { return new ClonedCache(this); } + /// + /// Creates a clone of the snapshot cache, which uses this instance as the underlying storage. + /// + /// The of this instance. + public DataCache CloneCache() + { + return new ClonedCache(this); + } + /// /// Deletes an entry from the cache. /// diff --git a/src/Neo/Plugins/Plugin.cs b/src/Neo/Plugins/Plugin.cs index 9feee25d57..248301af56 100644 --- a/src/Neo/Plugins/Plugin.cs +++ b/src/Neo/Plugins/Plugin.cs @@ -33,8 +33,7 @@ public abstract class Plugin : IDisposable /// /// The directory containing the plugin folders. Files can be contained in any subdirectory. /// - public static readonly string PluginsDirectory = - Combine(GetDirectoryName(System.AppContext.BaseDirectory), "Plugins"); + public static readonly string PluginsDirectory = Combine(GetDirectoryName(System.AppContext.BaseDirectory), "Plugins"); private static readonly FileSystemWatcher configWatcher; @@ -68,18 +67,6 @@ public abstract class Plugin : IDisposable /// public virtual Version Version => GetType().Assembly.GetName().Version; - /// - /// If the plugin should be stopped when an exception is thrown. - /// Default is . - /// - protected internal virtual UnhandledExceptionPolicy ExceptionPolicy { get; init; } = UnhandledExceptionPolicy.StopNode; - - /// - /// The plugin will be stopped if an exception is thrown. - /// But it also depends on . - /// - internal bool IsStopped { get; set; } - static Plugin() { if (!Directory.Exists(PluginsDirectory)) return; @@ -87,8 +74,7 @@ static Plugin() { EnableRaisingEvents = true, IncludeSubdirectories = true, - NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.CreationTime | - NotifyFilters.LastWrite | NotifyFilters.Size, + NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.Size, }; configWatcher.Changed += ConfigWatcher_Changed; configWatcher.Created += ConfigWatcher_Changed; @@ -120,8 +106,7 @@ private static void ConfigWatcher_Changed(object sender, FileSystemEventArgs e) { case ".json": case ".dll": - Utility.Log(nameof(Plugin), LogLevel.Warning, - $"File {e.Name} is {e.ChangeType}, please restart node."); + Utility.Log(nameof(Plugin), LogLevel.Warning, $"File {e.Name} is {e.ChangeType}, please restart node."); break; } } @@ -134,8 +119,7 @@ private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEven AssemblyName an = new(args.Name); Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name) ?? - AppDomain.CurrentDomain.GetAssemblies() - .FirstOrDefault(a => a.GetName().Name == an.Name); + AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == an.Name); if (assembly != null) return assembly; string filename = an.Name + ".dll"; @@ -166,8 +150,7 @@ public virtual void Dispose() /// The content of the configuration file read. protected IConfigurationSection GetConfiguration() { - return new ConfigurationBuilder().AddJsonFile(ConfigFile, optional: true).Build() - .GetSection("PluginConfiguration"); + return new ConfigurationBuilder().AddJsonFile(ConfigFile, optional: true).Build().GetSection("PluginConfiguration"); } private static void LoadPlugin(Assembly assembly) @@ -204,7 +187,6 @@ internal static void LoadPlugins() catch { } } } - foreach (Assembly assembly in assemblies) { LoadPlugin(assembly); @@ -247,33 +229,7 @@ protected internal virtual void OnSystemLoaded(NeoSystem system) /// if the is handled by a plugin; otherwise, . public static bool SendMessage(object message) { - - return Plugins.Any(plugin => - { - try - { - return !plugin.IsStopped && - plugin.OnMessage(message); - } - catch (Exception ex) - { - switch (plugin.ExceptionPolicy) - { - case UnhandledExceptionPolicy.StopNode: - throw; - case UnhandledExceptionPolicy.StopPlugin: - plugin.IsStopped = true; - break; - case UnhandledExceptionPolicy.Ignore: - break; - default: - throw new InvalidCastException($"The exception policy {plugin.ExceptionPolicy} is not valid."); - } - Utility.Log(nameof(Plugin), LogLevel.Error, ex); - return false; - } - } - ); + return Plugins.Any(plugin => plugin.OnMessage(message)); } } } diff --git a/src/Neo/Plugins/PluginSettings.cs b/src/Neo/Plugins/PluginSettings.cs deleted file mode 100644 index af33e44eea..0000000000 --- a/src/Neo/Plugins/PluginSettings.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (C) 2015-2024 The Neo Project. -// -// PluginSettings.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 Org.BouncyCastle.Security; -using System; - -namespace Neo.Plugins; - -public abstract class PluginSettings(IConfigurationSection section) -{ - public UnhandledExceptionPolicy ExceptionPolicy - { - get - { - var policyString = section.GetValue(nameof(UnhandledExceptionPolicy), nameof(UnhandledExceptionPolicy.StopNode)); - if (Enum.TryParse(policyString, out UnhandledExceptionPolicy policy)) - { - return policy; - } - - throw new InvalidParameterException($"{policyString} is not a valid UnhandledExceptionPolicy"); - } - } -} diff --git a/src/Neo/SmartContract/ApplicationEngine.cs b/src/Neo/SmartContract/ApplicationEngine.cs index fab17a2bb1..6fd69439ab 100644 --- a/src/Neo/SmartContract/ApplicationEngine.cs +++ b/src/Neo/SmartContract/ApplicationEngine.cs @@ -460,7 +460,7 @@ public ExecutionContext LoadScript(Script script, int rvcount = -1, int initialP // Create and configure context ExecutionContext context = CreateContext(script, rvcount, initialPosition); ExecutionContextState state = context.GetState(); - state.Snapshot = Snapshot?.CreateSnapshot(); + state.Snapshot = Snapshot?.CloneCache(); configureState?.Invoke(state); // Load context diff --git a/src/Neo/SmartContract/Helper.cs b/src/Neo/SmartContract/Helper.cs index ae31d66a50..d0bc3c939b 100644 --- a/src/Neo/SmartContract/Helper.cs +++ b/src/Neo/SmartContract/Helper.cs @@ -326,7 +326,7 @@ internal static bool VerifyWitness(this IVerifiable verifiable, ProtocolSettings { return false; } - using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, verifiable, snapshot?.CreateSnapshot(), null, settings, datoshi)) + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, verifiable, snapshot?.CloneCache(), null, settings, datoshi)) { if (witness.VerificationScript.Length == 0) { diff --git a/src/Neo/SmartContract/Manifest/ContractAbi.cs b/src/Neo/SmartContract/Manifest/ContractAbi.cs index 3660d1a99e..cedd431d62 100644 --- a/src/Neo/SmartContract/Manifest/ContractAbi.cs +++ b/src/Neo/SmartContract/Manifest/ContractAbi.cs @@ -62,8 +62,8 @@ public static ContractAbi FromJson(JObject json) { ContractAbi abi = new() { - Methods = ((JArray)json["methods"]).Select(u => ContractMethodDescriptor.FromJson((JObject)u)).ToArray(), - Events = ((JArray)json["events"]).Select(u => ContractEventDescriptor.FromJson((JObject)u)).ToArray() + Methods = ((JArray)json!["methods"])?.Select(u => ContractMethodDescriptor.FromJson((JObject)u)).ToArray() ?? [], + Events = ((JArray)json!["events"])?.Select(u => ContractEventDescriptor.FromJson((JObject)u)).ToArray() ?? [] }; if (abi.Methods.Length == 0) throw new FormatException(); return abi; diff --git a/src/Neo/SmartContract/Manifest/ContractManifest.cs b/src/Neo/SmartContract/Manifest/ContractManifest.cs index 078e8fd35e..b1bd317e41 100644 --- a/src/Neo/SmartContract/Manifest/ContractManifest.cs +++ b/src/Neo/SmartContract/Manifest/ContractManifest.cs @@ -112,20 +112,21 @@ public static ContractManifest FromJson(JObject json) { ContractManifest manifest = new() { - Name = json["name"].GetString(), - Groups = ((JArray)json["groups"]).Select(u => ContractGroup.FromJson((JObject)u)).ToArray(), - SupportedStandards = ((JArray)json["supportedstandards"]).Select(u => u.GetString()).ToArray(), + Name = json["name"]!.GetString(), + Groups = ((JArray)json["groups"])?.Select(u => ContractGroup.FromJson((JObject)u)).ToArray() ?? [], + SupportedStandards = ((JArray)json["supportedstandards"])?.Select(u => u.GetString()).ToArray() ?? [], Abi = ContractAbi.FromJson((JObject)json["abi"]), - Permissions = ((JArray)json["permissions"]).Select(u => ContractPermission.FromJson((JObject)u)).ToArray(), + Permissions = ((JArray)json["permissions"])?.Select(u => ContractPermission.FromJson((JObject)u)).ToArray() ?? [], Trusts = WildcardContainer.FromJson(json["trusts"], u => ContractPermissionDescriptor.FromJson((JString)u)), Extra = (JObject)json["extra"] }; + if (string.IsNullOrEmpty(manifest.Name)) throw new FormatException(); _ = manifest.Groups.ToDictionary(p => p.PubKey); if (json["features"] is not JObject features || features.Count != 0) throw new FormatException(); - if (manifest.SupportedStandards.Any(p => string.IsNullOrEmpty(p))) + if (manifest.SupportedStandards.Any(string.IsNullOrEmpty)) throw new FormatException(); _ = manifest.SupportedStandards.ToDictionary(p => p); _ = manifest.Permissions.ToDictionary(p => p.Contract); diff --git a/src/Neo/SmartContract/Manifest/ContractPermissionDescriptor.cs b/src/Neo/SmartContract/Manifest/ContractPermissionDescriptor.cs index f1f22d99d8..d950d25c62 100644 --- a/src/Neo/SmartContract/Manifest/ContractPermissionDescriptor.cs +++ b/src/Neo/SmartContract/Manifest/ContractPermissionDescriptor.cs @@ -114,7 +114,8 @@ public bool Equals(ContractPermissionDescriptor other) if (this == other) return true; if (IsWildcard == other.IsWildcard) return true; if (IsHash) return Hash.Equals(other.Hash); - else return Group.Equals(other.Group); + if (IsGroup) return Group.Equals(other.Group); + return false; } public override int GetHashCode() diff --git a/src/Neo/SmartContract/Native/ContractEventAttribute.cs b/src/Neo/SmartContract/Native/ContractEventAttribute.cs index 656ecef725..b2ff30891a 100644 --- a/src/Neo/SmartContract/Native/ContractEventAttribute.cs +++ b/src/Neo/SmartContract/Native/ContractEventAttribute.cs @@ -17,11 +17,12 @@ namespace Neo.SmartContract.Native { [DebuggerDisplay("{Descriptor.Name}")] [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = true)] - internal class ContractEventAttribute : Attribute + internal class ContractEventAttribute : Attribute, IHardforkActivable { public int Order { get; init; } public ContractEventDescriptor Descriptor { get; set; } public Hardfork? ActiveIn { get; init; } = null; + public Hardfork? DeprecatedIn { get; init; } = null; public ContractEventAttribute(Hardfork activeIn, int order, string name, string arg1Name, ContractParameterType arg1Value) : this(order, name, arg1Name, arg1Value) @@ -29,23 +30,35 @@ public ContractEventAttribute(Hardfork activeIn, int order, string name, ActiveIn = activeIn; } + public ContractEventAttribute(Hardfork activeIn, int order, string name, + string arg1Name, ContractParameterType arg1Value, Hardfork deprecatedIn) : this(activeIn, order, name, arg1Name, arg1Value) + { + DeprecatedIn = deprecatedIn; + } + public ContractEventAttribute(int order, string name, string arg1Name, ContractParameterType arg1Value) { Order = order; Descriptor = new ContractEventDescriptor() { Name = name, - Parameters = new ContractParameterDefinition[] - { + Parameters = + [ new ContractParameterDefinition() { Name = arg1Name, Type = arg1Value } - } + ] }; } + public ContractEventAttribute(int order, string name, string arg1Name, ContractParameterType arg1Value, Hardfork deprecatedIn) + : this(order, name, arg1Name, arg1Value) + { + DeprecatedIn = deprecatedIn; + } + public ContractEventAttribute(Hardfork activeIn, int order, string name, string arg1Name, ContractParameterType arg1Value, string arg2Name, ContractParameterType arg2Value) : this(order, name, arg1Name, arg1Value, arg2Name, arg2Value) @@ -53,6 +66,13 @@ public ContractEventAttribute(Hardfork activeIn, int order, string name, ActiveIn = activeIn; } + public ContractEventAttribute(Hardfork activeIn, int order, string name, + string arg1Name, ContractParameterType arg1Value, + string arg2Name, ContractParameterType arg2Value, Hardfork deprecatedIn) : this(activeIn, order, name, arg1Name, arg1Value, arg2Name, arg2Value) + { + DeprecatedIn = deprecatedIn; + } + public ContractEventAttribute(int order, string name, string arg1Name, ContractParameterType arg1Value, string arg2Name, ContractParameterType arg2Value) @@ -61,8 +81,8 @@ public ContractEventAttribute(int order, string name, Descriptor = new ContractEventDescriptor() { Name = name, - Parameters = new ContractParameterDefinition[] - { + Parameters = + [ new ContractParameterDefinition() { Name = arg1Name, @@ -73,10 +93,18 @@ public ContractEventAttribute(int order, string name, Name = arg2Name, Type = arg2Value } - } + ] }; } + public ContractEventAttribute(int order, string name, + string arg1Name, ContractParameterType arg1Value, + string arg2Name, ContractParameterType arg2Value, Hardfork deprecatedIn) : this(order, name, arg1Name, arg1Value, arg2Name, arg2Value) + { + DeprecatedIn = deprecatedIn; + } + + public ContractEventAttribute(Hardfork activeIn, int order, string name, string arg1Name, ContractParameterType arg1Value, string arg2Name, ContractParameterType arg2Value, @@ -95,8 +123,8 @@ public ContractEventAttribute(int order, string name, Descriptor = new ContractEventDescriptor() { Name = name, - Parameters = new ContractParameterDefinition[] - { + Parameters = + [ new ContractParameterDefinition() { Name = arg1Name, @@ -112,7 +140,7 @@ public ContractEventAttribute(int order, string name, Name = arg3Name, Type = arg3Value } - } + ] }; } @@ -136,8 +164,8 @@ public ContractEventAttribute(int order, string name, Descriptor = new ContractEventDescriptor() { Name = name, - Parameters = new ContractParameterDefinition[] - { + Parameters = + [ new ContractParameterDefinition() { Name = arg1Name, @@ -158,7 +186,7 @@ public ContractEventAttribute(int order, string name, Name = arg4Name, Type = arg4Value } - } + ] }; } } diff --git a/src/Neo/SmartContract/Native/ContractMethodAttribute.cs b/src/Neo/SmartContract/Native/ContractMethodAttribute.cs index 7caa27c8b0..38bc065533 100644 --- a/src/Neo/SmartContract/Native/ContractMethodAttribute.cs +++ b/src/Neo/SmartContract/Native/ContractMethodAttribute.cs @@ -16,7 +16,7 @@ namespace Neo.SmartContract.Native { [DebuggerDisplay("{Name}")] [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false)] - internal class ContractMethodAttribute : Attribute + internal class ContractMethodAttribute : Attribute, IHardforkActivable { public string Name { get; init; } public CallFlags RequiredCallFlags { get; init; } @@ -32,6 +32,11 @@ public ContractMethodAttribute(Hardfork activeIn) ActiveIn = activeIn; } + public ContractMethodAttribute(Hardfork activeIn, Hardfork deprecatedIn) : this(activeIn) + { + DeprecatedIn = deprecatedIn; + } + public ContractMethodAttribute(bool isDeprecated, Hardfork deprecatedIn) { if (!isDeprecated) throw new ArgumentException("isDeprecated must be true", nameof(isDeprecated)); diff --git a/src/Neo/SmartContract/Native/ContractMethodMetadata.cs b/src/Neo/SmartContract/Native/ContractMethodMetadata.cs index 30874efb47..fd5a02be6a 100644 --- a/src/Neo/SmartContract/Native/ContractMethodMetadata.cs +++ b/src/Neo/SmartContract/Native/ContractMethodMetadata.cs @@ -24,7 +24,7 @@ namespace Neo.SmartContract.Native { [DebuggerDisplay("{Name}")] - internal class ContractMethodMetadata + internal class ContractMethodMetadata : IHardforkActivable { public string Name { get; } public MethodInfo Handler { get; } diff --git a/src/Neo/Plugins/UnhandledExceptionPolicy.cs b/src/Neo/SmartContract/Native/IHardforkActivable.cs similarity index 61% rename from src/Neo/Plugins/UnhandledExceptionPolicy.cs rename to src/Neo/SmartContract/Native/IHardforkActivable.cs index 035e173aa3..7794d03f25 100644 --- a/src/Neo/Plugins/UnhandledExceptionPolicy.cs +++ b/src/Neo/SmartContract/Native/IHardforkActivable.cs @@ -1,6 +1,6 @@ // Copyright (C) 2015-2024 The Neo Project. // -// UnhandledExceptionPolicy.cs file belongs to the neo project and is free +// IHardforkActivable.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 @@ -9,12 +9,11 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -namespace Neo.Plugins +namespace Neo.SmartContract.Native { - public enum UnhandledExceptionPolicy + internal interface IHardforkActivable { - Ignore = 0, - StopPlugin = 1, - StopNode = 2, + public Hardfork? ActiveIn { get; } + public Hardfork? DeprecatedIn { get; } } } diff --git a/src/Neo/SmartContract/Native/NativeContract.cs b/src/Neo/SmartContract/Native/NativeContract.cs index 1cc244408d..b030f06c9b 100644 --- a/src/Neo/SmartContract/Native/NativeContract.cs +++ b/src/Neo/SmartContract/Native/NativeContract.cs @@ -161,6 +161,7 @@ protected NativeContract() _usedHardforks = _methodDescriptors.Select(u => u.ActiveIn) .Concat(_methodDescriptors.Select(u => u.DeprecatedIn)) + .Concat(_eventsDescriptors.Select(u => u.DeprecatedIn)) .Concat(_eventsDescriptors.Select(u => u.ActiveIn)) .Concat([ActiveIn]) .Where(u => u is not null) @@ -184,15 +185,7 @@ private NativeContractsCache.CacheEntry GetAllowedMethods(IsHardforkEnabledDeleg byte[] script; using (ScriptBuilder sb = new()) { - foreach (ContractMethodMetadata method in _methodDescriptors.Where(u - => - // no hardfork is involved - u.ActiveIn is null && u.DeprecatedIn is null || - // deprecated method hardfork is involved - u.DeprecatedIn is not null && hfChecker(u.DeprecatedIn.Value, blockHeight) == false || - // active method hardfork is involved - u.ActiveIn is not null && hfChecker(u.ActiveIn.Value, blockHeight)) - ) + foreach (ContractMethodMetadata method in _methodDescriptors.Where(u => IsActive(u, hfChecker, blockHeight))) { method.Descriptor.Offset = sb.Length; sb.EmitPush(0); //version @@ -215,6 +208,16 @@ u.ActiveIn is null && u.DeprecatedIn is null || [MethodImpl(MethodImplOptions.AggressiveInlining)] public ContractState GetContractState(ProtocolSettings settings, uint blockHeight) => GetContractState(settings.IsHardforkEnabled, blockHeight); + internal static bool IsActive(IHardforkActivable u, IsHardforkEnabledDelegate hfChecker, uint blockHeight) + { + return // no hardfork is involved + u.ActiveIn is null && u.DeprecatedIn is null || + // deprecated method hardfork is involved + u.DeprecatedIn is not null && hfChecker(u.DeprecatedIn.Value, blockHeight) == false || + // active method hardfork is involved + u.ActiveIn is not null && hfChecker(u.ActiveIn.Value, blockHeight); + } + /// /// The of the native contract. /// @@ -245,7 +248,7 @@ public ContractState GetContractState(IsHardforkEnabledDelegate hfChecker, uint Abi = new ContractAbi { Events = _eventsDescriptors - .Where(u => u.ActiveIn is null || hfChecker(u.ActiveIn.Value, blockHeight)) + .Where(u => IsActive(u, hfChecker, blockHeight)) .Select(p => p.Descriptor).ToArray(), Methods = allowedMethods.Methods.Values .Select(p => p.Descriptor).ToArray() diff --git a/src/Neo/SmartContract/Native/NeoToken.cs b/src/Neo/SmartContract/Native/NeoToken.cs index 99ecd41e29..f43a0d6079 100644 --- a/src/Neo/SmartContract/Native/NeoToken.cs +++ b/src/Neo/SmartContract/Native/NeoToken.cs @@ -421,7 +421,7 @@ private async ContractTask Vote(ApplicationEngine engine, UInt160 account, /// The snapshot used to read data. /// All the registered candidates. [ContractMethod(CpuFee = 1 << 22, RequiredCallFlags = CallFlags.ReadStates)] - private (ECPoint PublicKey, BigInteger Votes)[] GetCandidates(DataCache snapshot) + internal (ECPoint PublicKey, BigInteger Votes)[] GetCandidates(DataCache snapshot) { return GetCandidatesInternal(snapshot) .Select(p => (p.PublicKey, p.State.Votes)) diff --git a/src/Neo/SmartContract/StorageItem.cs b/src/Neo/SmartContract/StorageItem.cs index 133a8fa1dd..199b31c7ec 100644 --- a/src/Neo/SmartContract/StorageItem.cs +++ b/src/Neo/SmartContract/StorageItem.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.VM; using System; @@ -193,5 +194,15 @@ public static implicit operator BigInteger(StorageItem item) item.cache ??= new BigInteger(item.value.Span); return (BigInteger)item.cache; } + + public static implicit operator StorageItem(BigInteger value) + { + return new StorageItem(value); + } + + public static implicit operator StorageItem(byte[] value) + { + return new StorageItem(value); + } } } diff --git a/src/Neo/SmartContract/StorageKey.cs b/src/Neo/SmartContract/StorageKey.cs index a0136e4456..9c5e37827f 100644 --- a/src/Neo/SmartContract/StorageKey.cs +++ b/src/Neo/SmartContract/StorageKey.cs @@ -12,6 +12,7 @@ using Neo.Cryptography; using System; using System.Buffers.Binary; +using System.Runtime.CompilerServices; namespace Neo.SmartContract { @@ -79,5 +80,14 @@ public byte[] ToArray() } return cache; } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator StorageKey(byte[] value) => new StorageKey(value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator StorageKey(ReadOnlyMemory value) => new StorageKey(value.Span.ToArray()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator StorageKey(ReadOnlySpan value) => new StorageKey(value.ToArray()); } } diff --git a/src/Neo/Wallets/Helper.cs b/src/Neo/Wallets/Helper.cs index 1273bafc50..b8427d078d 100644 --- a/src/Neo/Wallets/Helper.cs +++ b/src/Neo/Wallets/Helper.cs @@ -133,7 +133,7 @@ public static long CalculateNetworkFee(this Transaction tx, DataCache snapshot, size += Array.Empty().GetVarSize() + invSize; // Check verify cost - using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CreateSnapshot(), settings: settings, gas: maxExecutionCost); + using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CloneCache(), settings: settings, gas: maxExecutionCost); engine.LoadContract(contract, md, CallFlags.ReadOnly); if (invocationScript != null) engine.LoadScript(invocationScript, configureState: p => p.CallFlags = CallFlags.None); if (engine.Execute() == VMState.FAULT) throw new ArgumentException($"Smart contract {contract.Hash} verification fault."); diff --git a/src/Neo/Wallets/Wallet.cs b/src/Neo/Wallets/Wallet.cs index aca30b008f..d23bdcfb9f 100644 --- a/src/Neo/Wallets/Wallet.cs +++ b/src/Neo/Wallets/Wallet.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography; +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; @@ -574,7 +575,7 @@ private Transaction MakeTransaction(DataCache snapshot, ReadOnlyMemory scr }; // will try to execute 'transfer' script to check if it works - using (ApplicationEngine engine = ApplicationEngine.Run(script, snapshot.CreateSnapshot(), tx, settings: ProtocolSettings, gas: maxGas, persistingBlock: persistingBlock)) + using (ApplicationEngine engine = ApplicationEngine.Run(script, snapshot.CloneCache(), tx, settings: ProtocolSettings, gas: maxGas, persistingBlock: persistingBlock)) { if (engine.State == VMState.FAULT) { diff --git a/src/Plugins/ApplicationLogs/ApplicationLogs.json b/src/Plugins/ApplicationLogs/ApplicationLogs.json index 2664665dd2..af601bc81e 100644 --- a/src/Plugins/ApplicationLogs/ApplicationLogs.json +++ b/src/Plugins/ApplicationLogs/ApplicationLogs.json @@ -3,8 +3,7 @@ "Path": "ApplicationLogs_{0}", "Network": 860833102, "MaxStackSize": 65535, - "Debug": false, - "UnhandledExceptionPolicy": "StopPlugin" + "Debug": false }, "Dependency": [ "RpcServer" diff --git a/src/Plugins/ApplicationLogs/LogReader.cs b/src/Plugins/ApplicationLogs/LogReader.cs index f9d1470f03..e0e886d93b 100644 --- a/src/Plugins/ApplicationLogs/LogReader.cs +++ b/src/Plugins/ApplicationLogs/LogReader.cs @@ -38,7 +38,6 @@ public class LogReader : Plugin, ICommittingHandler, ICommittedHandler, ILogHand public override string Name => "ApplicationLogs"; public override string Description => "Synchronizes smart contract VM executions and notifications (NotifyLog) on blockchain."; - protected override UnhandledExceptionPolicy ExceptionPolicy => Settings.Default.ExceptionPolicy; #region Ctor @@ -143,12 +142,11 @@ private void OnGetBlockCommand(string blockHashOrIndex, string eventName = null) _neostore.GetBlockLog(blockhash, TriggerType.PostPersist) : _neostore.GetBlockLog(blockhash, TriggerType.PostPersist, eventName); - if (blockOnPersist == null && blockOnPersist == null) + if (blockOnPersist == null) ConsoleHelper.Error($"No logs."); - if (blockOnPersist != null) - PrintExecutionToConsole(blockOnPersist); - if (blockPostPersist != null) + else { + PrintExecutionToConsole(blockOnPersist); ConsoleHelper.Info("--------------------------------"); PrintExecutionToConsole(blockPostPersist); } diff --git a/src/Plugins/ApplicationLogs/Settings.cs b/src/Plugins/ApplicationLogs/Settings.cs index 6a5f238272..8f2a0da1e1 100644 --- a/src/Plugins/ApplicationLogs/Settings.cs +++ b/src/Plugins/ApplicationLogs/Settings.cs @@ -13,7 +13,7 @@ namespace Neo.Plugins.ApplicationLogs { - internal class Settings : PluginSettings + internal class Settings { public string Path { get; } public uint Network { get; } @@ -23,7 +23,7 @@ internal class Settings : PluginSettings public static Settings Default { get; private set; } - private Settings(IConfigurationSection section) : base(section) + private Settings(IConfigurationSection section) { Path = section.GetValue("Path", "ApplicationLogs_{0}"); Network = section.GetValue("Network", 5195086u); diff --git a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.cs b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.cs index abd0309783..2e6a3a19a6 100644 --- a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.cs +++ b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.cs @@ -192,7 +192,7 @@ public void Reset(byte viewNumber) if (viewNumber == 0) { Snapshot?.Dispose(); - Snapshot = neoSystem.GetSnapshot(); + Snapshot = neoSystem.GetSnapshotCache(); uint height = NativeContract.Ledger.CurrentIndex(Snapshot); Block = new Block { diff --git a/src/Plugins/DBFTPlugin/DBFTPlugin.cs b/src/Plugins/DBFTPlugin/DBFTPlugin.cs index 65fc5011dc..f09be9d291 100644 --- a/src/Plugins/DBFTPlugin/DBFTPlugin.cs +++ b/src/Plugins/DBFTPlugin/DBFTPlugin.cs @@ -31,8 +31,6 @@ public class DBFTPlugin : Plugin, IServiceAddedHandler, IMessageReceivedHandler, public override string ConfigFile => System.IO.Path.Combine(RootPath, "DBFTPlugin.json"); - protected override UnhandledExceptionPolicy ExceptionPolicy => settings.ExceptionPolicy; - public DBFTPlugin() { RemoteNode.MessageReceived += ((IMessageReceivedHandler)this).RemoteNode_MessageReceived_Handler; diff --git a/src/Plugins/DBFTPlugin/DBFTPlugin.json b/src/Plugins/DBFTPlugin/DBFTPlugin.json index 705b2b77cb..2e2b710ba3 100644 --- a/src/Plugins/DBFTPlugin/DBFTPlugin.json +++ b/src/Plugins/DBFTPlugin/DBFTPlugin.json @@ -5,7 +5,6 @@ "AutoStart": false, "Network": 860833102, "MaxBlockSize": 2097152, - "MaxBlockSystemFee": 150000000000, - "UnhandledExceptionPolicy": "StopNode" + "MaxBlockSystemFee": 150000000000 } } diff --git a/src/Plugins/DBFTPlugin/Settings.cs b/src/Plugins/DBFTPlugin/Settings.cs index 1f37feaf16..28ad21f37a 100644 --- a/src/Plugins/DBFTPlugin/Settings.cs +++ b/src/Plugins/DBFTPlugin/Settings.cs @@ -13,7 +13,7 @@ namespace Neo.Plugins.DBFTPlugin { - public class Settings : PluginSettings + public class Settings { public string RecoveryLogs { get; } public bool IgnoreRecoveryLogs { get; } @@ -22,7 +22,7 @@ public class Settings : PluginSettings public uint MaxBlockSize { get; } public long MaxBlockSystemFee { get; } - public Settings(IConfigurationSection section) : base(section) + public Settings(IConfigurationSection section) { RecoveryLogs = section.GetValue("RecoveryLogs", "ConsensusState"); IgnoreRecoveryLogs = section.GetValue("IgnoreRecoveryLogs", false); diff --git a/src/Plugins/Directory.Build.props b/src/Plugins/Directory.Build.props index 72e96f0300..ecd9a2735f 100644 --- a/src/Plugins/Directory.Build.props +++ b/src/Plugins/Directory.Build.props @@ -3,11 +3,6 @@ - - - - - diff --git a/src/Plugins/OracleService/OracleService.cs b/src/Plugins/OracleService/OracleService.cs index a1a3e92eae..d7a54c5c9d 100644 --- a/src/Plugins/OracleService/OracleService.cs +++ b/src/Plugins/OracleService/OracleService.cs @@ -62,8 +62,6 @@ public class OracleService : Plugin, ICommittingHandler, IServiceAddedHandler, I public override string Description => "Built-in oracle plugin"; - protected override UnhandledExceptionPolicy ExceptionPolicy => Settings.Default.ExceptionPolicy; - public override string ConfigFile => System.IO.Path.Combine(RootPath, "OracleService.json"); public OracleService() @@ -431,7 +429,7 @@ public static Transaction CreateResponseTx(DataCache snapshot, OracleRequest req // Calculate network fee var oracleContract = NativeContract.ContractManagement.GetContract(snapshot, NativeContract.Oracle.Hash); - var engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CreateSnapshot(), settings: settings); + var engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CloneCache(), settings: settings); ContractMethodDescriptor md = oracleContract.Manifest.Abi.GetMethod(ContractBasicMethod.Verify, ContractBasicMethod.VerifyPCount); engine.LoadContract(oracleContract, md, CallFlags.None); if (engine.Execute() != VMState.HALT) return null; @@ -532,7 +530,7 @@ private bool CheckTxSign(DataCache snapshot, Transaction tx, ConcurrentDictionar } ECPoint[] oraclesNodes = NativeContract.RoleManagement.GetDesignatedByRole(snapshot, Role.Oracle, height); int neededThreshold = oraclesNodes.Length - (oraclesNodes.Length - 1) / 3; - if (OracleSigns.Count >= neededThreshold && tx != null) + if (OracleSigns.Count >= neededThreshold) { var contract = Contract.CreateMultiSigContract(neededThreshold, oraclesNodes); ScriptBuilder sb = new ScriptBuilder(); diff --git a/src/Plugins/OracleService/OracleService.json b/src/Plugins/OracleService/OracleService.json index 49bf1153b3..1ab0d93399 100644 --- a/src/Plugins/OracleService/OracleService.json +++ b/src/Plugins/OracleService/OracleService.json @@ -6,7 +6,6 @@ "MaxOracleTimeout": 10000, "AllowPrivateHost": false, "AllowedContentTypes": [ "application/json" ], - "UnhandledExceptionPolicy": "Ignore", "Https": { "Timeout": 5000 }, diff --git a/src/Plugins/OracleService/Settings.cs b/src/Plugins/OracleService/Settings.cs index db93c1c400..952ea0c27b 100644 --- a/src/Plugins/OracleService/Settings.cs +++ b/src/Plugins/OracleService/Settings.cs @@ -37,7 +37,7 @@ public NeoFSSettings(IConfigurationSection section) } } - class Settings : PluginSettings + class Settings { public uint Network { get; } public Uri[] Nodes { get; } @@ -51,7 +51,7 @@ class Settings : PluginSettings public static Settings Default { get; private set; } - private Settings(IConfigurationSection section) : base(section) + private Settings(IConfigurationSection section) { Network = section.GetValue("Network", 5195086u); Nodes = section.GetSection("Nodes").GetChildren().Select(p => new Uri(p.Get(), UriKind.Absolute)).ToArray(); diff --git a/src/Plugins/RpcServer/RpcServer.Blockchain.cs b/src/Plugins/RpcServer/RpcServer.Blockchain.cs index 7898dd9d9b..7bc4711f1c 100644 --- a/src/Plugins/RpcServer/RpcServer.Blockchain.cs +++ b/src/Plugins/RpcServer/RpcServer.Blockchain.cs @@ -24,18 +24,32 @@ namespace Neo.Plugins.RpcServer { partial class RpcServer { + /// + /// Gets the hash of the best (most recent) block. + /// + /// An empty array; no parameters are required. + /// The hash of the best block as a . [RpcMethod] - protected virtual JToken GetBestBlockHash(JArray _params) + protected internal virtual JToken GetBestBlockHash(JArray _params) { return NativeContract.Ledger.CurrentHash(system.StoreView).ToString(); } + /// + /// Gets a block by its hash or index. + /// + /// + /// An array containing the block hash or index as the first element, + /// and an optional boolean indicating whether to return verbose information. + /// + /// The block data as a . If the second item of _params is true, then + /// block data is json format, otherwise, the return type is Base64-encoded byte array. [RpcMethod] - protected virtual JToken GetBlock(JArray _params) + protected internal virtual JToken GetBlock(JArray _params) { JToken key = Result.Ok_Or(() => _params[0], RpcError.InvalidParams.WithData($"Invalid Block Hash or Index: {_params[0]}")); bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); - using var snapshot = system.GetSnapshot(); + using var snapshot = system.GetSnapshotCache(); Block block; if (key is JNumber) { @@ -60,20 +74,35 @@ protected virtual JToken GetBlock(JArray _params) return Convert.ToBase64String(block.ToArray()); } + /// + /// Gets the number of block headers in the blockchain. + /// + /// An empty array; no parameters are required. + /// The count of block headers as a . [RpcMethod] internal virtual JToken GetBlockHeaderCount(JArray _params) { return (system.HeaderCache.Last?.Index ?? NativeContract.Ledger.CurrentIndex(system.StoreView)) + 1; } + /// + /// Gets the number of blocks in the blockchain. + /// + /// An empty array; no parameters are required. + /// The count of blocks as a . [RpcMethod] - protected virtual JToken GetBlockCount(JArray _params) + protected internal virtual JToken GetBlockCount(JArray _params) { return NativeContract.Ledger.CurrentIndex(system.StoreView) + 1; } + /// + /// Gets the hash of the block at the specified height. + /// + /// An array containing the block height as the first element. + /// The hash of the block at the specified height as a . [RpcMethod] - protected virtual JToken GetBlockHash(JArray _params) + protected internal virtual JToken GetBlockHash(JArray _params) { uint height = Result.Ok_Or(() => uint.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Height: {_params[0]}")); var snapshot = system.StoreView; @@ -84,8 +113,16 @@ protected virtual JToken GetBlockHash(JArray _params) throw new RpcException(RpcError.UnknownHeight); } + /// + /// Gets a block header by its hash or index. + /// + /// + /// An array containing the block header hash or index as the first element, + /// and an optional boolean indicating whether to return verbose information. + /// + /// The block header data as a . In json format if the second item of _params is true, otherwise Base64-encoded byte array. [RpcMethod] - protected virtual JToken GetBlockHeader(JArray _params) + protected internal virtual JToken GetBlockHeader(JArray _params) { JToken key = _params[0]; bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); @@ -114,8 +151,13 @@ protected virtual JToken GetBlockHeader(JArray _params) return Convert.ToBase64String(header.ToArray()); } + /// + /// Gets the state of a contract by its ID or script hash or (only for native contracts) by case-insensitive name. + /// + /// An array containing the contract ID or script hash or case-insensitive native contract name as the first element. + /// The contract state in json format as a . [RpcMethod] - protected virtual JToken GetContractState(JArray _params) + protected internal virtual JToken GetContractState(JArray _params) { if (int.TryParse(_params[0].AsString(), out int contractId)) { @@ -139,8 +181,13 @@ private static UInt160 ToScriptHash(string keyword) return UInt160.Parse(keyword); } + /// + /// Gets the current memory pool transactions. + /// + /// An array containing an optional boolean indicating whether to include unverified transactions. + /// The memory pool transactions in json format as a . [RpcMethod] - protected virtual JToken GetRawMemPool(JArray _params) + protected internal virtual JToken GetRawMemPool(JArray _params) { bool shouldGetUnverified = _params.Count >= 1 && _params[0].AsBoolean(); if (!shouldGetUnverified) @@ -156,8 +203,16 @@ protected virtual JToken GetRawMemPool(JArray _params) return json; } + /// + /// Gets a transaction by its hash. + /// + /// + /// An array containing the transaction hash as the first element, + /// and an optional boolean indicating whether to return verbose information. + /// + /// The transaction data as a . In json format if the second item of _params is true, otherwise base64string. [RpcMethod] - protected virtual JToken GetRawTransaction(JArray _params) + protected internal virtual JToken GetRawTransaction(JArray _params) { UInt256 hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Transaction Hash: {_params[0]}")); bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); @@ -179,10 +234,18 @@ protected virtual JToken GetRawTransaction(JArray _params) return json; } + /// + /// Gets the storage item by contract ID or script hash and key. + /// + /// + /// An array containing the contract ID or script hash as the first element, + /// and the storage key as the second element. + /// + /// The storage item as a . [RpcMethod] - protected virtual JToken GetStorage(JArray _params) + protected internal virtual JToken GetStorage(JArray _params) { - using var snapshot = system.GetSnapshot(); + using var snapshot = system.GetSnapshotCache(); if (!int.TryParse(_params[0].AsString(), out int id)) { UInt160 hash = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid contract hash {_params[0]}")); @@ -198,10 +261,19 @@ protected virtual JToken GetStorage(JArray _params) return Convert.ToBase64String(item.Value.Span); } + /// + /// Finds storage items by contract ID or script hash and prefix. + /// + /// + /// An array containing the contract ID or script hash as the first element, + /// the Base64-encoded storage key prefix as the second element, + /// and an optional start index as the third element. + /// + /// The found storage items as a . [RpcMethod] - protected virtual JToken FindStorage(JArray _params) + protected internal virtual JToken FindStorage(JArray _params) { - using var snapshot = system.GetSnapshot(); + using var snapshot = system.GetSnapshotCache(); if (!int.TryParse(_params[0].AsString(), out int id)) { UInt160 hash = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid contract hash {_params[0]}")); @@ -247,8 +319,13 @@ protected virtual JToken FindStorage(JArray _params) return json; } + /// + /// Gets the height of a transaction by its hash. + /// + /// An array containing the transaction hash as the first element. + /// The height of the transaction as a . [RpcMethod] - protected virtual JToken GetTransactionHeight(JArray _params) + protected internal virtual JToken GetTransactionHeight(JArray _params) { UInt256 hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Transaction Hash: {_params[0]}")); uint? height = NativeContract.Ledger.GetTransactionState(system.StoreView, hash)?.BlockIndex; @@ -256,10 +333,15 @@ protected virtual JToken GetTransactionHeight(JArray _params) throw new RpcException(RpcError.UnknownTransaction); } + /// + /// Gets the next block validators. + /// + /// An empty array; no parameters are required. + /// The next block validators as a . [RpcMethod] - protected virtual JToken GetNextBlockValidators(JArray _params) + protected internal virtual JToken GetNextBlockValidators(JArray _params) { - using var snapshot = system.GetSnapshot(); + using var snapshot = system.GetSnapshotCache(); var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, system.Settings.ValidatorsCount); return validators.Select(p => { @@ -270,10 +352,15 @@ protected virtual JToken GetNextBlockValidators(JArray _params) }).ToArray(); } + /// + /// Gets the list of candidates for the next block validators. + /// + /// An empty array; no parameters are required. + /// The candidates public key list as a JToken. [RpcMethod] - protected virtual JToken GetCandidates(JArray _params) + protected internal virtual JToken GetCandidates(JArray _params) { - using var snapshot = system.GetSnapshot(); + using var snapshot = system.GetSnapshotCache(); byte[] script; using (ScriptBuilder sb = new()) { @@ -322,14 +409,24 @@ protected virtual JToken GetCandidates(JArray _params) return json; } + /// + /// Gets the list of committee members. + /// + /// An empty array; no parameters are required. + /// The committee members publickeys as a . [RpcMethod] - protected virtual JToken GetCommittee(JArray _params) + protected internal virtual JToken GetCommittee(JArray _params) { return new JArray(NativeContract.NEO.GetCommittee(system.StoreView).Select(p => (JToken)p.ToString())); } + /// + /// Gets the list of native contracts. + /// + /// An empty array; no parameters are required. + /// The native contract states as a . [RpcMethod] - protected virtual JToken GetNativeContracts(JArray _params) + protected internal virtual JToken GetNativeContracts(JArray _params) { return new JArray(NativeContract.Contracts.Select(p => NativeContract.ContractManagement.GetContract(system.StoreView, p.Hash).ToJson())); } diff --git a/src/Plugins/RpcServer/RpcServer.Node.cs b/src/Plugins/RpcServer/RpcServer.Node.cs index 79a8884a0f..21ca583090 100644 --- a/src/Plugins/RpcServer/RpcServer.Node.cs +++ b/src/Plugins/RpcServer/RpcServer.Node.cs @@ -110,7 +110,7 @@ private static JObject GetRelayResult(VerifyResult reason, UInt256 hash) } [RpcMethod] - protected virtual JToken GetVersion(JArray _params) + protected internal virtual JToken GetVersion(JArray _params) { JObject json = new(); json["tcpport"] = localNode.ListenerTcpPort; @@ -150,7 +150,7 @@ private static string StripPrefix(string s, string prefix) } [RpcMethod] - protected virtual JToken SendRawTransaction(JArray _params) + protected internal virtual JToken SendRawTransaction(JArray _params) { Transaction tx = Result.Ok_Or(() => Convert.FromBase64String(_params[0].AsString()).AsSerializable(), RpcError.InvalidParams.WithData($"Invalid Transaction Format: {_params[0]}")); RelayResult reason = system.Blockchain.Ask(tx).Result; @@ -158,7 +158,7 @@ protected virtual JToken SendRawTransaction(JArray _params) } [RpcMethod] - protected virtual JToken SubmitBlock(JArray _params) + protected internal virtual JToken SubmitBlock(JArray _params) { Block block = Result.Ok_Or(() => Convert.FromBase64String(_params[0].AsString()).AsSerializable(), RpcError.InvalidParams.WithData($"Invalid Block Format: {_params[0]}")); RelayResult reason = system.Blockchain.Ask(block).Result; diff --git a/src/Plugins/RpcServer/RpcServer.Wallet.cs b/src/Plugins/RpcServer/RpcServer.Wallet.cs index 9083f916c6..ab5724e588 100644 --- a/src/Plugins/RpcServer/RpcServer.Wallet.cs +++ b/src/Plugins/RpcServer/RpcServer.Wallet.cs @@ -196,7 +196,7 @@ protected virtual JToken SendFrom(JArray _params) UInt160 assetId = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid asset id: {_params[0]}")); UInt160 from = AddressToScriptHash(_params[1].AsString(), system.Settings.AddressVersion); UInt160 to = AddressToScriptHash(_params[2].AsString(), system.Settings.AddressVersion); - using var snapshot = system.GetSnapshot(); + using var snapshot = system.GetSnapshotCache(); AssetDescriptor descriptor = new(snapshot, system.Settings, assetId); BigDecimal amount = new(BigInteger.Parse(_params[3].AsString()), descriptor.Decimals); (amount.Sign > 0).True_Or(RpcErrorFactory.InvalidParams("Amount can't be negative.")); @@ -243,7 +243,7 @@ protected virtual JToken SendMany(JArray _params) Signer[] signers = _params.Count >= to_start + 2 ? ((JArray)_params[to_start + 1]).Select(p => new Signer() { Account = AddressToScriptHash(p.AsString(), system.Settings.AddressVersion), Scopes = WitnessScope.CalledByEntry }).ToArray() : null; TransferOutput[] outputs = new TransferOutput[to.Count]; - using var snapshot = system.GetSnapshot(); + using var snapshot = system.GetSnapshotCache(); for (int i = 0; i < to.Count; i++) { UInt160 asset_id = UInt160.Parse(to[i]["asset"].AsString()); @@ -279,7 +279,7 @@ protected virtual JToken SendToAddress(JArray _params) CheckWallet(); UInt160 assetId = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid asset hash: {_params[0]}")); UInt160 to = AddressToScriptHash(_params[1].AsString(), system.Settings.AddressVersion); - using var snapshot = system.GetSnapshot(); + using var snapshot = system.GetSnapshotCache(); AssetDescriptor descriptor = new(snapshot, system.Settings, assetId); BigDecimal amount = new(BigInteger.Parse(_params[2].AsString()), descriptor.Decimals); (amount.Sign > 0).True_Or(RpcError.InvalidParams); @@ -354,7 +354,7 @@ protected virtual JToken InvokeContractVerify(JArray _params) private JObject GetVerificationResult(UInt160 scriptHash, ContractParameter[] args, Signer[] signers = null, Witness[] witnesses = null) { - using var snapshot = system.GetSnapshot(); + using var snapshot = system.GetSnapshotCache(); var contract = NativeContract.ContractManagement.GetContract(snapshot, scriptHash).NotNull_Or(RpcError.UnknownContract); var md = contract.Manifest.Abi.GetMethod(ContractBasicMethod.Verify, ContractBasicMethod.VerifyPCount).NotNull_Or(RpcErrorFactory.InvalidContractVerification(contract.Hash)); (md.ReturnType == ContractParameterType.Boolean).True_Or(RpcErrorFactory.InvalidContractVerification("The verify method doesn't return boolean value.")); @@ -365,7 +365,7 @@ private JObject GetVerificationResult(UInt160 scriptHash, ContractParameter[] ar Witnesses = witnesses, Script = new[] { (byte)OpCode.RET } }; - using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CreateSnapshot(), settings: system.Settings); + using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CloneCache(), settings: system.Settings); engine.LoadContract(contract, md, CallFlags.ReadOnly); var invocationScript = Array.Empty(); diff --git a/src/Plugins/RpcServer/RpcServer.json b/src/Plugins/RpcServer/RpcServer.json index dc9c25b8da..8f6905dead 100644 --- a/src/Plugins/RpcServer/RpcServer.json +++ b/src/Plugins/RpcServer/RpcServer.json @@ -1,6 +1,5 @@ { "PluginConfiguration": { - "UnhandledExceptionPolicy": "Ignore", "Servers": [ { "Network": 860833102, diff --git a/src/Plugins/RpcServer/RpcServerPlugin.cs b/src/Plugins/RpcServer/RpcServerPlugin.cs index 03416c1be5..c22462d139 100644 --- a/src/Plugins/RpcServer/RpcServerPlugin.cs +++ b/src/Plugins/RpcServer/RpcServerPlugin.cs @@ -24,7 +24,6 @@ public class RpcServerPlugin : Plugin private static readonly Dictionary> handlers = new(); public override string ConfigFile => System.IO.Path.Combine(RootPath, "RpcServer.json"); - protected override UnhandledExceptionPolicy ExceptionPolicy => settings.ExceptionPolicy; protected override void Configure() { diff --git a/src/Plugins/RpcServer/Session.cs b/src/Plugins/RpcServer/Session.cs index 1dd8808dde..60be3e19b1 100644 --- a/src/Plugins/RpcServer/Session.cs +++ b/src/Plugins/RpcServer/Session.cs @@ -29,7 +29,7 @@ class Session : IDisposable public Session(NeoSystem system, byte[] script, Signer[] signers, Witness[] witnesses, long datoshi, Diagnostic diagnostic) { Random random = new(); - Snapshot = system.GetSnapshot(); + Snapshot = system.GetSnapshotCache(); Transaction tx = signers == null ? null : new Transaction { Version = 0, diff --git a/src/Plugins/RpcServer/Settings.cs b/src/Plugins/RpcServer/Settings.cs index 2cf7b72fb8..ad624d9082 100644 --- a/src/Plugins/RpcServer/Settings.cs +++ b/src/Plugins/RpcServer/Settings.cs @@ -18,11 +18,11 @@ namespace Neo.Plugins.RpcServer { - class Settings : PluginSettings + class Settings { public IReadOnlyList Servers { get; init; } - public Settings(IConfigurationSection section) : base(section) + public Settings(IConfigurationSection section) { Servers = section.GetSection(nameof(Servers)).GetChildren().Select(p => RpcServerSettings.Load(p)).ToArray(); } diff --git a/src/Plugins/StateService/Settings.cs b/src/Plugins/StateService/Settings.cs index a425b57d7e..8557866bc1 100644 --- a/src/Plugins/StateService/Settings.cs +++ b/src/Plugins/StateService/Settings.cs @@ -13,7 +13,7 @@ namespace Neo.Plugins.StateService { - internal class Settings : PluginSettings + internal class Settings { public string Path { get; } public bool FullState { get; } @@ -23,7 +23,7 @@ internal class Settings : PluginSettings public static Settings Default { get; private set; } - private Settings(IConfigurationSection section) : base(section) + private Settings(IConfigurationSection section) { Path = section.GetValue("Path", "Data_MPT_{0}"); FullState = section.GetValue("FullState", false); diff --git a/src/Plugins/StateService/StatePlugin.cs b/src/Plugins/StateService/StatePlugin.cs index 03dcc55aac..5f6e1ef1d8 100644 --- a/src/Plugins/StateService/StatePlugin.cs +++ b/src/Plugins/StateService/StatePlugin.cs @@ -41,8 +41,6 @@ public class StatePlugin : Plugin, ICommittingHandler, ICommittedHandler, IWalle public override string Description => "Enables MPT for the node"; public override string ConfigFile => System.IO.Path.Combine(RootPath, "StateService.json"); - protected override UnhandledExceptionPolicy ExceptionPolicy => Settings.Default.ExceptionPolicy; - internal IActorRef Store; internal IActorRef Verifier; diff --git a/src/Plugins/StateService/StateService.json b/src/Plugins/StateService/StateService.json index cadd2da5fd..265436fc30 100644 --- a/src/Plugins/StateService/StateService.json +++ b/src/Plugins/StateService/StateService.json @@ -4,8 +4,7 @@ "FullState": false, "Network": 860833102, "AutoVerify": false, - "MaxFindResultItems": 100, - "UnhandledExceptionPolicy": "StopPlugin" + "MaxFindResultItems": 100 }, "Dependency": [ "RpcServer" diff --git a/src/Plugins/StorageDumper/Settings.cs b/src/Plugins/StorageDumper/Settings.cs index e645cd7074..c2761ce6b9 100644 --- a/src/Plugins/StorageDumper/Settings.cs +++ b/src/Plugins/StorageDumper/Settings.cs @@ -14,7 +14,7 @@ namespace Neo.Plugins.StorageDumper { - internal class Settings : PluginSettings + internal class Settings { /// /// Amount of storages states (heights) to be dump in a given json file @@ -32,7 +32,7 @@ internal class Settings : PluginSettings public static Settings? Default { get; private set; } - private Settings(IConfigurationSection section) : base(section) + private Settings(IConfigurationSection section) { // Geting settings for storage changes state dumper BlockCacheSize = section.GetValue("BlockCacheSize", 1000u); diff --git a/src/Plugins/StorageDumper/StorageDumper.cs b/src/Plugins/StorageDumper/StorageDumper.cs index 6f5498b3a2..c47df9ac1d 100644 --- a/src/Plugins/StorageDumper/StorageDumper.cs +++ b/src/Plugins/StorageDumper/StorageDumper.cs @@ -30,7 +30,7 @@ public class StorageDumper : Plugin, ICommittingHandler, ICommittedHandler /// private JObject? _currentBlock; private string? _lastCreateDirectory; - protected override UnhandledExceptionPolicy ExceptionPolicy => Settings.Default?.ExceptionPolicy ?? UnhandledExceptionPolicy.Ignore; + public override string Description => "Exports Neo-CLI status data"; diff --git a/src/Plugins/StorageDumper/StorageDumper.json b/src/Plugins/StorageDumper/StorageDumper.json index 0c314cf262..b327c37e0c 100644 --- a/src/Plugins/StorageDumper/StorageDumper.json +++ b/src/Plugins/StorageDumper/StorageDumper.json @@ -3,7 +3,6 @@ "BlockCacheSize": 1000, "HeightToBegin": 0, "StoragePerFolder": 100000, - "Exclude": [ -4 ], - "UnhandledExceptionPolicy": "Ignore" + "Exclude": [ -4 ] } } diff --git a/src/Plugins/TokensTracker/TokensTracker.cs b/src/Plugins/TokensTracker/TokensTracker.cs index a7ab075245..e5ffdce3f4 100644 --- a/src/Plugins/TokensTracker/TokensTracker.cs +++ b/src/Plugins/TokensTracker/TokensTracker.cs @@ -16,7 +16,6 @@ using Neo.Persistence; using Neo.Plugins.RpcServer; using Neo.Plugins.Trackers; -using System; using System.Collections.Generic; using System.Linq; using static System.IO.Path; @@ -31,10 +30,8 @@ public class TokensTracker : Plugin, ICommittingHandler, ICommittedHandler private uint _network; private string[] _enabledTrackers; private IStore _db; - private UnhandledExceptionPolicy _exceptionPolicy; private NeoSystem neoSystem; private readonly List trackers = new(); - protected override UnhandledExceptionPolicy ExceptionPolicy => _exceptionPolicy; public override string Description => "Enquiries balances and transaction history of accounts through RPC"; @@ -60,11 +57,6 @@ protected override void Configure() _maxResults = config.GetValue("MaxResults", 1000u); _network = config.GetValue("Network", 860833102u); _enabledTrackers = config.GetSection("EnabledTrackers").GetChildren().Select(p => p.Value).ToArray(); - var policyString = config.GetValue(nameof(UnhandledExceptionPolicy), nameof(UnhandledExceptionPolicy.StopNode)); - if (Enum.TryParse(policyString, out UnhandledExceptionPolicy policy)) - { - _exceptionPolicy = policy; - } } protected override void OnSystemLoaded(NeoSystem system) diff --git a/src/Plugins/TokensTracker/TokensTracker.json b/src/Plugins/TokensTracker/TokensTracker.json index dbdbecfd40..ca63183b68 100644 --- a/src/Plugins/TokensTracker/TokensTracker.json +++ b/src/Plugins/TokensTracker/TokensTracker.json @@ -4,8 +4,7 @@ "TrackHistory": true, "MaxResults": 1000, "Network": 860833102, - "EnabledTrackers": [ "NEP-11", "NEP-17" ], - "UnhandledExceptionPolicy": "StopPlugin" + "EnabledTrackers": [ "NEP-11", "NEP-17" ] }, "Dependency": [ "RpcServer" diff --git a/tests/Neo.Extensions.Tests/Neo.Extensions.Tests.csproj b/tests/Neo.Extensions.Tests/Neo.Extensions.Tests.csproj new file mode 100644 index 0000000000..a2b2b20daf --- /dev/null +++ b/tests/Neo.Extensions.Tests/Neo.Extensions.Tests.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + + + + + + + + + + + + + + + + + + diff --git a/tests/Neo.Extensions.Tests/UT_BigIntegerExtensions.cs b/tests/Neo.Extensions.Tests/UT_BigIntegerExtensions.cs new file mode 100644 index 0000000000..b887d1c973 --- /dev/null +++ b/tests/Neo.Extensions.Tests/UT_BigIntegerExtensions.cs @@ -0,0 +1,47 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_BigIntegerExtensions.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 FluentAssertions; +using Neo.Extensions; +using System.Numerics; + +namespace Neo.Extensions.Tests +{ + [TestClass] + public class UT_BigIntegerExtensions + { + [TestMethod] + public void TestGetLowestSetBit() + { + var big1 = new BigInteger(0); + big1.GetLowestSetBit().Should().Be(-1); + + var big2 = new BigInteger(512); + big2.GetLowestSetBit().Should().Be(9); + + var big3 = new BigInteger(int.MinValue); + big3.GetLowestSetBit().Should().Be(31); + + var big4 = new BigInteger(long.MinValue); + big4.GetLowestSetBit().Should().Be(63); + } + + [TestMethod] + public void TestToByteArrayStandard() + { + BigInteger number = BigInteger.Zero; + Assert.AreEqual("", number.ToByteArrayStandard().ToHexString()); + + number = BigInteger.One; + Assert.AreEqual("01", number.ToByteArrayStandard().ToHexString()); + } + } +} diff --git a/tests/Neo.Json.UnitTests/UT_JArray.cs b/tests/Neo.Json.UnitTests/UT_JArray.cs index 40388d11fc..b6c603ca2a 100644 --- a/tests/Neo.Json.UnitTests/UT_JArray.cs +++ b/tests/Neo.Json.UnitTests/UT_JArray.cs @@ -254,5 +254,18 @@ public void TestAsString() var s = jArray.AsString(); Assert.AreEqual(s, "{\"name\":\"alice\",\"age\":30,\"score\":100.001,\"gender\":\"female\",\"isMarried\":true,\"pet\":{\"name\":\"Tom\",\"type\":\"cat\"}},{\"name\":\"bob\",\"age\":100000,\"score\":0.001,\"gender\":\"male\",\"isMarried\":false,\"pet\":{\"name\":\"Paul\",\"type\":\"dog\"}}"); } + + [TestMethod] + public void TestClone() + { + var jArray = new JArray + { + alice, + bob, + }; + var a = jArray.AsString(); + var b = jArray.Clone().AsString(); + a.Should().Be(b); + } } } diff --git a/tests/Neo.Json.UnitTests/UT_JBoolean.cs b/tests/Neo.Json.UnitTests/UT_JBoolean.cs index 3ab19bd1b3..0e81e99c35 100644 --- a/tests/Neo.Json.UnitTests/UT_JBoolean.cs +++ b/tests/Neo.Json.UnitTests/UT_JBoolean.cs @@ -36,8 +36,10 @@ public void TestEqual() { Assert.IsTrue(jTrue.Equals(new JBoolean(true))); Assert.IsTrue(jTrue == new JBoolean(true)); + Assert.IsTrue(jTrue != new JBoolean(false)); Assert.IsTrue(jFalse.Equals(new JBoolean())); Assert.IsTrue(jFalse == new JBoolean()); + Assert.IsTrue(jFalse.GetBoolean().ToString().ToLowerInvariant() == jFalse.ToString()); } } } diff --git a/tests/Neo.Json.UnitTests/UT_JNumber.cs b/tests/Neo.Json.UnitTests/UT_JNumber.cs index 6eb0598fd3..df8bdca619 100644 --- a/tests/Neo.Json.UnitTests/UT_JNumber.cs +++ b/tests/Neo.Json.UnitTests/UT_JNumber.cs @@ -9,6 +9,8 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using System.Numerics; + namespace Neo.Json.UnitTests { enum Woo @@ -72,6 +74,27 @@ public void TestEqual() Assert.IsTrue(minInt.Equals(JNumber.MIN_SAFE_INTEGER)); Assert.IsTrue(minInt == JNumber.MIN_SAFE_INTEGER); Assert.IsTrue(zero == new JNumber()); + Assert.IsFalse(zero != new JNumber()); + Assert.IsTrue(zero.AsNumber() == zero.GetNumber()); + Assert.IsFalse(zero == null); + + var jnum = new JNumber(1); + jnum.Equals(new JNumber(1)).Should().BeTrue(); + jnum.Equals((uint)1).Should().BeTrue(); + jnum.Equals((int)1).Should().BeTrue(); + jnum.Equals((ulong)1).Should().BeTrue(); + jnum.Equals((long)1).Should().BeTrue(); + jnum.Equals((byte)1).Should().BeTrue(); + jnum.Equals((sbyte)1).Should().BeTrue(); + jnum.Equals((short)1).Should().BeTrue(); + jnum.Equals((ushort)1).Should().BeTrue(); + jnum.Equals((decimal)1).Should().BeTrue(); + jnum.Equals((float)1).Should().BeTrue(); + jnum.Equals((double)1).Should().BeTrue(); + jnum.Equals(null).Should().BeFalse(); + var x = jnum; + jnum.Equals(x).Should().BeTrue(); + Assert.ThrowsException(() => jnum.Equals(new BigInteger(1))); } } } diff --git a/tests/Neo.Json.UnitTests/UT_JString.cs b/tests/Neo.Json.UnitTests/UT_JString.cs index 7e2bec9834..3f0ca159ab 100644 --- a/tests/Neo.Json.UnitTests/UT_JString.cs +++ b/tests/Neo.Json.UnitTests/UT_JString.cs @@ -63,10 +63,22 @@ public void TestGetEnum() public void TestEqual() { var str = "hello world"; + var str2 = "hello world2"; var jString = new JString(str); - Assert.IsTrue(jString.Equals(str)); + var jString2 = new JString(str2); + Assert.IsTrue(jString == str); - Assert.IsTrue(jString != "hello world2"); + Assert.IsFalse(jString == null); + Assert.IsTrue(jString != str2); + Assert.IsFalse(jString == str2); + + Assert.AreEqual(str, jString.GetString()); + Assert.IsTrue(jString.Equals(str)); + Assert.IsFalse(jString.Equals(jString2)); + Assert.IsFalse(jString.Equals(null)); + Assert.IsFalse(jString.Equals(123)); + var reference = jString; + Assert.IsTrue(jString.Equals(reference)); } } } diff --git a/tests/Neo.Plugins.OracleService.Tests/TestBlockchain.cs b/tests/Neo.Plugins.OracleService.Tests/TestBlockchain.cs index d689578e22..bc6be0d8bf 100644 --- a/tests/Neo.Plugins.OracleService.Tests/TestBlockchain.cs +++ b/tests/Neo.Plugins.OracleService.Tests/TestBlockchain.cs @@ -30,7 +30,7 @@ public static void InitializeMockNeoSystem() internal static DataCache GetTestSnapshot() { - return TheNeoSystem.GetSnapshot().CreateSnapshot(); + return TheNeoSystem.GetSnapshotCache().CloneCache(); } } } diff --git a/tests/Neo.Plugins.RpcServer.Tests/MockNeoSystem.cs b/tests/Neo.Plugins.RpcServer.Tests/MockNeoSystem.cs deleted file mode 100644 index 0f45a63d98..0000000000 --- a/tests/Neo.Plugins.RpcServer.Tests/MockNeoSystem.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (C) 2015-2024 The Neo Project. -// -// MockNeoSystem.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.Ledger; -using Neo.Persistence; - -namespace Neo.Plugins.RpcServer.Tests -{ - public class MockNeoSystem : NeoSystem - { - public SnapshotCache SnapshotCache { get; } - public MemoryPool MemoryPool { get; } - - public MockNeoSystem(SnapshotCache snapshotCache, MemoryPool memoryPool) - : base(TestProtocolSettings.Default, new TestBlockchain.StoreProvider()) - { - SnapshotCache = snapshotCache; - MemoryPool = memoryPool; - } - - public SnapshotCache GetSnapshot() - { - return SnapshotCache; - } - } -} diff --git a/tests/Neo.Plugins.RpcServer.Tests/Neo.Plugins.RpcServer.Tests.csproj b/tests/Neo.Plugins.RpcServer.Tests/Neo.Plugins.RpcServer.Tests.csproj index 5b693c96b8..1586b9753d 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/Neo.Plugins.RpcServer.Tests.csproj +++ b/tests/Neo.Plugins.RpcServer.Tests/Neo.Plugins.RpcServer.Tests.csproj @@ -4,6 +4,7 @@ net8.0 Neo.Plugins.RpcServer.Tests Neo.Plugins.RpcServer.Tests + true @@ -16,6 +17,7 @@ + \ No newline at end of file diff --git a/tests/Neo.Plugins.RpcServer.Tests/TestBlockchain.cs b/tests/Neo.Plugins.RpcServer.Tests/TestBlockchain.cs index f6e35cf695..3ed48e982c 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/TestBlockchain.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/TestBlockchain.cs @@ -12,6 +12,7 @@ using Akka.Actor; using Neo.Ledger; using Neo.Persistence; +using Neo.UnitTests; using System; namespace Neo.Plugins.RpcServer.Tests @@ -43,7 +44,7 @@ internal static void ResetStore() internal static DataCache GetTestSnapshot() { - return TheNeoSystem.GetSnapshot().CreateSnapshot(); + return TheNeoSystem.GetSnapshotCache().CloneCache(); } } } diff --git a/tests/Neo.Plugins.RpcServer.Tests/TestMemoryStoreProvider.cs b/tests/Neo.Plugins.RpcServer.Tests/TestMemoryStoreProvider.cs new file mode 100644 index 0000000000..b39b4aeae5 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/TestMemoryStoreProvider.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestMemoryStoreProvider.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; + +namespace Neo.Plugins.RpcServer.Tests; + +public class TestMemoryStoreProvider(MemoryStore memoryStore) : IStoreProvider +{ + public MemoryStore MemoryStore { get; init; } = memoryStore; + public string Name => nameof(MemoryStore); + public IStore GetStore(string path) => MemoryStore; +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/TestProtocolSettings.cs b/tests/Neo.Plugins.RpcServer.Tests/TestProtocolSettings.cs deleted file mode 100644 index edf2df39fb..0000000000 --- a/tests/Neo.Plugins.RpcServer.Tests/TestProtocolSettings.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2015-2024 The Neo Project. -// -// TestProtocolSettings.cs file belongs to the neo project and is free -// software distributed under the MIT software license, see the -// accompanying file LICENSE in the main directory of the -// repository or http://www.opensource.org/licenses/mit-license.php -// for more details. -// -// Redistribution and use in source and binary forms with or without -// modifications are permitted. - -using Neo.Cryptography.ECC; - -namespace Neo.Plugins.RpcServer.Tests -{ - public static class TestProtocolSettings - { - public static readonly ProtocolSettings Default = new() - { - Network = 0x334F454Eu, - AddressVersion = ProtocolSettings.Default.AddressVersion, - StandbyCommittee = new[] - { - //Validators - ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", ECCurve.Secp256r1), - ECPoint.Parse("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", ECCurve.Secp256r1), - ECPoint.Parse("03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", ECCurve.Secp256r1), - ECPoint.Parse("02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", ECCurve.Secp256r1), - ECPoint.Parse("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", ECCurve.Secp256r1), - ECPoint.Parse("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", ECCurve.Secp256r1), - ECPoint.Parse("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", ECCurve.Secp256r1), - //Other Members - ECPoint.Parse("023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe", ECCurve.Secp256r1), - ECPoint.Parse("03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379", ECCurve.Secp256r1), - ECPoint.Parse("03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050", ECCurve.Secp256r1), - ECPoint.Parse("03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0", ECCurve.Secp256r1), - ECPoint.Parse("02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62", ECCurve.Secp256r1), - ECPoint.Parse("03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0", ECCurve.Secp256r1), - ECPoint.Parse("0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654", ECCurve.Secp256r1), - ECPoint.Parse("020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639", ECCurve.Secp256r1), - ECPoint.Parse("0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30", ECCurve.Secp256r1), - ECPoint.Parse("03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde", ECCurve.Secp256r1), - ECPoint.Parse("02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad", ECCurve.Secp256r1), - ECPoint.Parse("0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d", ECCurve.Secp256r1), - ECPoint.Parse("03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc", ECCurve.Secp256r1), - ECPoint.Parse("02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a", ECCurve.Secp256r1) - }, - ValidatorsCount = 7, - SeedList = new[] - { - "seed1.neo.org:10333", - "seed2.neo.org:10333", - "seed3.neo.org:10333", - "seed4.neo.org:10333", - "seed5.neo.org:10333" - }, - MillisecondsPerBlock = ProtocolSettings.Default.MillisecondsPerBlock, - MaxTransactionsPerBlock = ProtocolSettings.Default.MaxTransactionsPerBlock, - MemoryPoolMaxTransactions = ProtocolSettings.Default.MemoryPoolMaxTransactions, - MaxTraceableBlocks = ProtocolSettings.Default.MaxTraceableBlocks, - InitialGasDistribution = ProtocolSettings.Default.InitialGasDistribution, - Hardforks = ProtocolSettings.Default.Hardforks - }; - } -} diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs new file mode 100644 index 0000000000..c9007f6580 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs @@ -0,0 +1,622 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RpcServer.Blockchain.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 Akka.Util.Internal; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.Json; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.UnitTests; +using Neo.UnitTests.Extensions; +using System; +using System.Linq; + +namespace Neo.Plugins.RpcServer.Tests +{ + public partial class UT_RpcServer + { + + [TestMethod] + public void TestGetBestBlockHash() + { + var key = NativeContract.Ledger.CreateStorageKey(12); + var expectedHash = UInt256.Zero; + + var snapshot = _neoSystem.GetSnapshotCache(); + var b = snapshot.GetAndChange(key, () => new StorageItem(new HashIndexState())).GetInteroperable(); + b.Hash = UInt256.Zero; + b.Index = 100; + snapshot.Commit(); + + var result = _rpcServer.GetBestBlockHash([]); + // Assert + Assert.AreEqual(expectedHash.ToString(), result.AsString()); + } + + [TestMethod] + public void TestGetBlockByHash() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(block.Hash.ToString(), false); + var result = _rpcServer.GetBlock(parameters); + var blockArr = Convert.FromBase64String(result.AsString()); + var block2 = blockArr.AsSerializable(); + block2.Transactions.ForEach(tx => + { + Assert.AreEqual(VerifyResult.Succeed, tx.VerifyStateIndependent(UnitTests.TestProtocolSettings.Default)); + }); + } + + [TestMethod] + public void TestGetBlockByIndex() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(block.Index, false); + var result = _rpcServer.GetBlock(parameters); + var blockArr = Convert.FromBase64String(result.AsString()); + var block2 = blockArr.AsSerializable(); + block2.Transactions.ForEach(tx => + { + Assert.AreEqual(VerifyResult.Succeed, tx.VerifyStateIndependent(UnitTests.TestProtocolSettings.Default)); + }); + } + + [TestMethod] + public void TestGetBlockCount() + { + var expectedCount = 1; + var result = _rpcServer.GetBlockCount(new JArray()); + Assert.AreEqual(expectedCount, result.AsNumber()); + } + + [TestMethod] + public void TestGetBlockHeaderCount() + { + var expectedCount = 1; + var result = _rpcServer.GetBlockHeaderCount(new JArray()); + Assert.AreEqual(expectedCount, result.AsNumber()); + } + + [TestMethod] + public void TestGetBlockHash() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + // TestUtils.BlocksAdd(snapshot, block.Hash, block); + // snapshot.Commit(); + var reason = _neoSystem.Blockchain.Ask(block).Result; + var expectedHash = block.Hash.ToString(); + var result = _rpcServer.GetBlockHash(new JArray(block.Index)); + Assert.AreEqual(expectedHash, result.AsString()); + } + + [TestMethod] + public void TestGetBlockHeader() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + var parameters = new JArray(block.Hash.ToString(), true); + var result = _rpcServer.GetBlockHeader(parameters); + var header = block.Header.ToJson(_neoSystem.Settings); + header["confirmations"] = NativeContract.Ledger.CurrentIndex(snapshot) - block.Index + 1; + Assert.AreEqual(header.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetContractState() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var contractState = TestUtils.GetContract(); + snapshot.AddContract(contractState.Hash, contractState); + snapshot.Commit(); + var result = _rpcServer.GetContractState(new JArray(contractState.Hash.ToString())); + + Assert.AreEqual(contractState.ToJson().ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetRawMemPool() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount); + snapshot.Commit(); + _neoSystem.MemPool.TryAdd(tx, snapshot); + + var result = _rpcServer.GetRawMemPool(new JArray()); + + Assert.IsTrue(((JArray)result).Any(p => p.AsString() == tx.Hash.ToString())); + } + + [TestMethod] + public void TestGetRawTransaction() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount); + _neoSystem.MemPool.TryAdd(tx, snapshot); + var parameters = new JArray(tx.Hash.ToString(), true); + snapshot.Commit(); + var result = _rpcServer.GetRawTransaction(parameters); + + var json = Utility.TransactionToJson(tx, _neoSystem.Settings); + Assert.AreEqual(json.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetStorage() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var contractState = TestUtils.GetContract(); + snapshot.AddContract(contractState.Hash, contractState); + var key = new byte[] { 0x01 }; + var value = new byte[] { 0x02 }; + TestUtils.StorageItemAdd(snapshot, contractState.Id, key, value); + snapshot.Commit(); + + var result = _rpcServer.GetStorage(new JArray(contractState.Hash.ToString(), Convert.ToBase64String(key))); + Assert.AreEqual(Convert.ToBase64String(value), result.AsString()); + } + + [TestMethod] + public void TestFindStorage() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var contractState = TestUtils.GetContract(); + snapshot.AddContract(contractState.Hash, contractState); + var key = new byte[] { 0x01 }; + var value = new byte[] { 0x02 }; + TestUtils.StorageItemAdd(snapshot, contractState.Id, key, value); + snapshot.Commit(); + var result = _rpcServer.FindStorage(new JArray(contractState.Hash.ToString(), Convert.ToBase64String(key), 0)); + + var json = new JObject(); + var jarr = new JArray(); + var j = new JObject(); + j["key"] = Convert.ToBase64String(key); + j["value"] = Convert.ToBase64String(value); + jarr.Add(j); + json["truncated"] = false; + json["next"] = 1; + json["results"] = jarr; + Assert.AreEqual(json.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetTransactionHeight() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 1); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + var tx = block.Transactions[0]; + var result = _rpcServer.GetTransactionHeight(new JArray(tx.Hash.ToString())); + Assert.AreEqual(block.Index, result.AsNumber()); + } + + [TestMethod] + public void TestGetNextBlockValidators() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var result = _rpcServer.GetNextBlockValidators(new JArray()); + + var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, _neoSystem.Settings.ValidatorsCount); + var expected = validators.Select(p => + { + var validator = new JObject(); + validator["publickey"] = p.ToString(); + validator["votes"] = (int)NativeContract.NEO.GetCandidateVote(snapshot, p); + return validator; + }).ToArray(); + Assert.AreEqual(new JArray(expected).ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetCandidates() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var result = _rpcServer.GetCandidates(new JArray()); + var json = new JArray(); + var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, _neoSystem.Settings.ValidatorsCount); + snapshot.Commit(); + var candidates = NativeContract.NEO.GetCandidates(_neoSystem.GetSnapshot()); + + foreach (var candidate in candidates) + { + var item = new JObject(); + item["publickey"] = candidate.PublicKey.ToString(); + item["votes"] = candidate.Votes.ToString(); + item["active"] = validators.Contains(candidate.PublicKey); + json.Add(item); + } + Assert.AreEqual(json.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetCommittee() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var result = _rpcServer.GetCommittee(new JArray()); + var committee = NativeContract.NEO.GetCommittee(snapshot); + var expected = new JArray(committee.Select(p => (JToken)p.ToString())); + Assert.AreEqual(expected.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetNativeContracts() + { + var result = _rpcServer.GetNativeContracts(new JArray()); + var contracts = new JArray(NativeContract.Contracts.Select(p => NativeContract.ContractManagement.GetContract(_neoSystem.GetSnapshot(), p.Hash).ToJson())); + Assert.AreEqual(contracts.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetBlockByUnknownIndex() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(int.MaxValue, false); + try + { + _rpcServer.GetBlock(parameters); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownBlock.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockByUnknownHash() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(TestUtils.RandomUInt256().ToString(), false); + try + { + _rpcServer.GetBlock(parameters); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownBlock.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockByUnKnownIndex() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(int.MaxValue, false); + try + { + _rpcServer.GetBlock(parameters); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownBlock.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockByUnKnownHash() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(TestUtils.RandomUInt256().ToString(), false); + try + { + _rpcServer.GetBlock(parameters); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownBlock.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockHashInvalidIndex() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + Assert.ThrowsException(() => _rpcServer.GetBlockHash(new JArray(block.Index + 1))); + } + + [TestMethod] + public void TestGetContractStateUnknownContract() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var randomHash = TestUtils.RandomUInt160(); + try + { + _rpcServer.GetContractState(new JArray(randomHash.ToString())); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownContract.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetStorageUnknownContract() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var randomHash = TestUtils.RandomUInt160(); + var key = new byte[] { 0x01 }; + try + { + _rpcServer.GetStorage(new JArray(randomHash.ToString(), Convert.ToBase64String(key))); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownContract.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetStorageUnknownStorageItem() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var contractState = TestUtils.GetContract(); + snapshot.AddContract(contractState.Hash, contractState); + snapshot.Commit(); + + var key = new byte[] { 0x01 }; + try + { + _rpcServer.GetStorage(new JArray(contractState.Hash.ToString(), Convert.ToBase64String(key))); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownStorageItem.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetTransactionHeightUnknownTransaction() + { + var randomHash = TestUtils.RandomUInt256(); + try + { + _rpcServer.GetTransactionHeight(new JArray(randomHash.ToString())); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownTransaction.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetRawTransactionUnknownTransaction() + { + var randomHash = TestUtils.RandomUInt256(); + try + { + _rpcServer.GetRawTransaction(new JArray(randomHash.ToString(), true)); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownTransaction.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockInvalidParams() + { + try + { + _rpcServer.GetBlock(new JArray("invalid_hash", false)); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + catch (FormatException) + { + } + catch + { + Assert.Fail("Unexpected exception"); + } + } + + [TestMethod] + public void TestGetBlockHashInvalidParams() + { + try + { + _rpcServer.GetBlockHash(new JArray("invalid_index")); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockHeaderInvalidParams() + { + try + { + _rpcServer.GetBlockHeader(new JArray("invalid_hash", true)); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + catch (FormatException) + { + } + catch + { + Assert.Fail("Unexpected exception"); + } + } + + [TestMethod] + public void TestGetContractStateInvalidParams() + { + try + { + _rpcServer.GetContractState(new JArray("invalid_hash")); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + catch (FormatException) + { + } + catch + { + Assert.Fail("Unexpected exception"); + } + } + + [TestMethod] + public void TestGetStorageInvalidParams() + { + try + { + _rpcServer.GetStorage(new JArray("invalid_hash", "invalid_key")); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + catch (FormatException) + { + } + catch + { + Assert.Fail("Unexpected exception"); + } + } + + [TestMethod] + public void TestFindStorageInvalidParams() + { + try + { + _rpcServer.FindStorage(new JArray("invalid_hash", "invalid_prefix", "invalid_start")); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + catch (FormatException) + { + } + catch + { + Assert.Fail("Unexpected exception"); + } + } + + [TestMethod] + public void TestGetTransactionHeightInvalidParams() + { + try + { + _rpcServer.GetTransactionHeight(new JArray("invalid_hash")); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetRawTransactionInvalidParams() + { + try + { + _rpcServer.GetRawTransaction(new JArray("invalid_hash", true)); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + } + + [TestMethod] + public void TestInternalServerError() + { + _memoryStore.Reset(); + try + { + _rpcServer.GetCandidates(new JArray()); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InternalServerError.Code, ex.HResult); + } + } + + [TestMethod] + public void TestUnknownHeight() + { + try + { + _rpcServer.GetBlockHash(new JArray(int.MaxValue)); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownHeight.Code, ex.HResult); + } + } + } +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Node.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Node.cs new file mode 100644 index 0000000000..b75706e001 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Node.cs @@ -0,0 +1,291 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RpcServer.Node.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract.Native; +using Neo.UnitTests; +using System; + +namespace Neo.Plugins.RpcServer.Tests +{ + partial class UT_RpcServer + { + [TestMethod] + public void TestGetVersion() + { + var result = _rpcServer.GetVersion(new JArray()); + Assert.IsInstanceOfType(result, typeof(JObject)); + + var json = (JObject)result; + Assert.IsTrue(json.ContainsProperty("tcpport")); + Assert.IsTrue(json.ContainsProperty("nonce")); + Assert.IsTrue(json.ContainsProperty("useragent")); + + Assert.IsTrue(json.ContainsProperty("protocol")); + var protocol = (JObject)json["protocol"]; + Assert.IsTrue(protocol.ContainsProperty("addressversion")); + Assert.IsTrue(protocol.ContainsProperty("network")); + Assert.IsTrue(protocol.ContainsProperty("validatorscount")); + Assert.IsTrue(protocol.ContainsProperty("msperblock")); + Assert.IsTrue(protocol.ContainsProperty("maxtraceableblocks")); + Assert.IsTrue(protocol.ContainsProperty("maxvaliduntilblockincrement")); + Assert.IsTrue(protocol.ContainsProperty("maxtransactionsperblock")); + Assert.IsTrue(protocol.ContainsProperty("memorypoolmaxtransactions")); + } + + #region SendRawTransaction Tests + + [TestMethod] + public void TestSendRawTransaction_Normal() + { + var snapshot = _neoSystem.GetSnapshot(); + var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount); + var txString = Convert.ToBase64String(tx.ToArray()); + + var result = _rpcServer.SendRawTransaction(new JArray(txString)); + Assert.IsInstanceOfType(result, typeof(JObject)); + Assert.IsTrue(((JObject)result).ContainsProperty("hash")); + } + + [TestMethod] + public void TestSendRawTransaction_InvalidTransactionFormat() + { + Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray("invalid_transaction_string")), + "Should throw RpcException for invalid transaction format"); + } + + [TestMethod] + public void TestSendRawTransaction_InsufficientBalance() + { + var snapshot = _neoSystem.GetSnapshot(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.InsufficientBalance); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray(txString)), + "Should throw RpcException for insufficient balance"); + Assert.AreEqual(RpcError.InsufficientFunds.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_InvalidSignature() + { + var snapshot = _neoSystem.GetSnapshot(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.InvalidSignature); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray(txString)), + "Should throw RpcException for invalid signature"); + Assert.AreEqual(RpcError.InvalidSignature.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_InvalidScript() + { + var snapshot = _neoSystem.GetSnapshot(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.InvalidScript); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray(txString)), + "Should throw RpcException for invalid script"); + Assert.AreEqual(RpcError.InvalidScript.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_InvalidAttribute() + { + var snapshot = _neoSystem.GetSnapshot(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.InvalidAttribute); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray(txString)), + "Should throw RpcException for invalid attribute"); + // Transaction with invalid attribute can not pass the Transaction deserialization + // and will throw invalid params exception. + Assert.AreEqual(RpcError.InvalidParams.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_Oversized() + { + var snapshot = _neoSystem.GetSnapshot(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.Oversized); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray(txString)), + "Should throw RpcException for invalid format transaction"); + // Oversized transaction will not pass the deserialization. + Assert.AreEqual(RpcError.InvalidParams.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_Expired() + { + var snapshot = _neoSystem.GetSnapshot(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.Expired); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray(txString)), + "Should throw RpcException for expired transaction"); + Assert.AreEqual(RpcError.ExpiredTransaction.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_PolicyFailed() + { + var snapshot = _neoSystem.GetSnapshot(); + var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount); + var txString = Convert.ToBase64String(tx.ToArray()); + NativeContract.Policy.BlockAccount(snapshot, _walletAccount.ScriptHash); + snapshot.Commit(); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray(txString)), + "Should throw RpcException for conflicting transaction"); + Assert.AreEqual(RpcError.PolicyFailed.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_AlreadyInPool() + { + var snapshot = _neoSystem.GetSnapshot(); + var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount); + _neoSystem.MemPool.TryAdd(tx, snapshot); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray(txString)), + "Should throw RpcException for transaction already in memory pool"); + Assert.AreEqual(RpcError.AlreadyInPool.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_AlreadyInBlockchain() + { + var snapshot = _neoSystem.GetSnapshot(); + var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount); + TestUtils.AddTransactionToBlockchain(snapshot, tx); + snapshot.Commit(); + var txString = Convert.ToBase64String(tx.ToArray()); + var exception = Assert.ThrowsException(() => _rpcServer.SendRawTransaction(new JArray(txString))); + Assert.AreEqual(RpcError.AlreadyExists.Code, exception.HResult); + } + + #endregion + + #region SubmitBlock Tests + + [TestMethod] + public void TestSubmitBlock_Normal() + { + var snapshot = _neoSystem.GetSnapshot(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 1); + var blockString = Convert.ToBase64String(block.ToArray()); + + var result = _rpcServer.SubmitBlock(new JArray(blockString)); + Assert.IsInstanceOfType(result, typeof(JObject)); + Assert.IsTrue(((JObject)result).ContainsProperty("hash")); + } + + [TestMethod] + public void TestSubmitBlock_InvalidBlockFormat() + { + string invalidBlockString = TestUtils.CreateInvalidBlockFormat(); + + var exception = Assert.ThrowsException(() => + _rpcServer.SubmitBlock(new JArray(invalidBlockString)), + "Should throw RpcException for invalid block format"); + + Assert.AreEqual(RpcError.InvalidParams.Code, exception.HResult); + StringAssert.Contains(exception.Message, "Invalid Block Format"); + } + + [TestMethod] + public void TestSubmitBlock_AlreadyExists() + { + var snapshot = _neoSystem.GetSnapshot(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 1); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + var blockString = Convert.ToBase64String(block.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SubmitBlock(new JArray(blockString)), + "Should throw RpcException when block already exists"); + Assert.AreEqual(RpcError.AlreadyExists.Code, exception.HResult); + } + + [TestMethod] + public void TestSubmitBlock_InvalidBlock() + { + var snapshot = _neoSystem.GetSnapshot(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 1); + block.Header.Witness = new Witness(); + var blockString = Convert.ToBase64String(block.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SubmitBlock(new JArray(blockString)), + "Should throw RpcException for invalid block"); + Assert.AreEqual(RpcError.VerificationFailed.Code, exception.HResult); + } + + #endregion + + #region Edge Cases and Error Handling + + [TestMethod] + public void TestSendRawTransaction_NullInput() + { + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray((string)null)), + "Should throw RpcException for null input"); + Assert.AreEqual(RpcError.InvalidParams.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_EmptyInput() + { + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray(string.Empty)), + "Should throw RpcException for empty input"); + Assert.AreEqual(RpcError.InvalidParams.Code, exception.HResult); + } + + [TestMethod] + public void TestSubmitBlock_NullInput() + { + var exception = Assert.ThrowsException(() => + _rpcServer.SubmitBlock(new JArray((string)null)), + "Should throw RpcException for null input"); + Assert.AreEqual(RpcError.InvalidParams.Code, exception.HResult); + } + + [TestMethod] + public void TestSubmitBlock_EmptyInput() + { + var exception = Assert.ThrowsException(() => + _rpcServer.SubmitBlock(new JArray(string.Empty)), + "Should throw RpcException for empty input"); + Assert.AreEqual(RpcError.InvalidParams.Code, exception.HResult); + } + + #endregion + } +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs index 7bca550b83..2561171c81 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs @@ -11,9 +11,13 @@ using Microsoft.AspNetCore.Http; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; using Neo.Ledger; using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.UnitTests; +using Neo.Wallets; +using Neo.Wallets.NEP6; using System; using System.Text; @@ -22,39 +26,50 @@ namespace Neo.Plugins.RpcServer.Tests [TestClass] public partial class UT_RpcServer { - private Mock _systemMock; - private SnapshotCache _snapshotCache; - private MemoryPool _memoryPool; - private RpcServerSettings _settings; + private NeoSystem _neoSystem; private RpcServer _rpcServer; + private TestMemoryStoreProvider _memoryStoreProvider; + private MemoryStore _memoryStore; + private readonly NEP6Wallet _wallet = TestUtils.GenerateTestWallet("123"); + private WalletAccount _walletAccount; [TestInitialize] public void TestSetup() { - // Mock IReadOnlyStore - var mockStore = new Mock(); - - // Initialize SnapshotCache with the mock IReadOnlyStore - _snapshotCache = new SnapshotCache(mockStore.Object); - - // Initialize NeoSystem - var neoSystem = new NeoSystem(TestProtocolSettings.Default, new TestBlockchain.StoreProvider()); - - // Initialize MemoryPool with the NeoSystem - _memoryPool = new MemoryPool(neoSystem); - - // Set up the mock system with the correct constructor arguments - _systemMock = new Mock(_snapshotCache, _memoryPool); + _memoryStore = new MemoryStore(); + _memoryStoreProvider = new TestMemoryStoreProvider(_memoryStore); + _neoSystem = new NeoSystem(TestProtocolSettings.SoleNode, _memoryStoreProvider); + _rpcServer = new RpcServer(_neoSystem, RpcServerSettings.Default); + _walletAccount = _wallet.Import("KxuRSsHgJMb3AMSN6B9P3JHNGMFtxmuimqgR9MmXPcv3CLLfusTd"); + var key = new KeyBuilder(NativeContract.GAS.Id, 20).Add(_walletAccount.ScriptHash); + var snapshot = _neoSystem.GetSnapshotCache(); + var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + entry.GetInteroperable().Balance = 100_000_000 * NativeContract.GAS.Factor; + snapshot.Commit(); + } - _rpcServer = new RpcServer(_systemMock.Object, RpcServerSettings.Default); + [TestCleanup] + public void TestCleanup() + { + // Please build and test in debug mode + _neoSystem.MemPool.Clear(); + _memoryStore.Reset(); + var snapshot = _neoSystem.GetSnapshotCache(); + var key = new KeyBuilder(NativeContract.GAS.Id, 20).Add(_walletAccount.ScriptHash); + var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + entry.GetInteroperable().Balance = 100_000_000 * NativeContract.GAS.Factor; + snapshot.Commit(); } [TestMethod] public void TestCheckAuth_ValidCredentials_ReturnsTrue() { + // Arrange var context = new DefaultHttpContext(); context.Request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("testuser:testpass")); + // Act var result = _rpcServer.CheckAuth(context); + // Assert Assert.IsTrue(result); } } diff --git a/tests/Neo.Plugins.Storage.Tests/StoreTest.cs b/tests/Neo.Plugins.Storage.Tests/StoreTest.cs index 2773f92e80..189e212bc2 100644 --- a/tests/Neo.Plugins.Storage.Tests/StoreTest.cs +++ b/tests/Neo.Plugins.Storage.Tests/StoreTest.cs @@ -21,9 +21,19 @@ public class StoreTest { private const string path_leveldb = "Data_LevelDB_UT"; private const string path_rocksdb = "Data_RocksDB_UT"; + private static LevelDBStore levelDbStore; + private static RocksDBStore rocksDBStore; [TestInitialize] public void OnStart() + { + levelDbStore ??= new LevelDBStore(); + rocksDBStore ??= new RocksDBStore(); + OnEnd(); + } + + [TestCleanup] + public void OnEnd() { if (Directory.Exists(path_leveldb)) Directory.Delete(path_leveldb, true); if (Directory.Exists(path_rocksdb)) Directory.Delete(path_rocksdb, true); @@ -49,35 +59,138 @@ public void TestMemory() [TestMethod] public void TestLevelDb() { - using var plugin = new LevelDBStore(); - TestPersistenceDelete(plugin.GetStore(path_leveldb)); + TestPersistenceDelete(levelDbStore.GetStore(path_leveldb)); // Test all with the same store - TestStorage(plugin.GetStore(path_leveldb)); + TestStorage(levelDbStore.GetStore(path_leveldb)); // Test with different storages - TestPersistenceWrite(plugin.GetStore(path_leveldb)); - TestPersistenceRead(plugin.GetStore(path_leveldb), true); - TestPersistenceDelete(plugin.GetStore(path_leveldb)); - TestPersistenceRead(plugin.GetStore(path_leveldb), false); + TestPersistenceWrite(levelDbStore.GetStore(path_leveldb)); + TestPersistenceRead(levelDbStore.GetStore(path_leveldb), true); + TestPersistenceDelete(levelDbStore.GetStore(path_leveldb)); + TestPersistenceRead(levelDbStore.GetStore(path_leveldb), false); + } + + [TestMethod] + public void TestLevelDbSnapshot() + { + using var store = levelDbStore.GetStore(path_leveldb); + + var snapshot = store.GetSnapshot(); + + var testKey = new byte[] { 0x01, 0x02, 0x03 }; + var testValue = new byte[] { 0x04, 0x05, 0x06 }; + + snapshot.Put(testKey, testValue); + // Data saved to the leveldb snapshot shall not be visible to the store + Assert.IsNull(snapshot.TryGet(testKey)); + + // Value is in the write batch, not visible to the store and snapshot + Assert.AreEqual(false, snapshot.Contains(testKey)); + Assert.AreEqual(false, store.Contains(testKey)); + + snapshot.Commit(); + + // After commit, the data shall be visible to the store but not to the snapshot + Assert.IsNull(snapshot.TryGet(testKey)); + CollectionAssert.AreEqual(testValue, store.TryGet(testKey)); + Assert.AreEqual(false, snapshot.Contains(testKey)); + Assert.AreEqual(true, store.Contains(testKey)); + + snapshot.Dispose(); + } + + [TestMethod] + public void TestLevelDbMultiSnapshot() + { + using var store = levelDbStore.GetStore(path_leveldb); + + var snapshot = store.GetSnapshot(); + + var testKey = new byte[] { 0x01, 0x02, 0x03 }; + var testValue = new byte[] { 0x04, 0x05, 0x06 }; + + snapshot.Put(testKey, testValue); + snapshot.Commit(); + CollectionAssert.AreEqual(testValue, store.TryGet(testKey)); + + var snapshot2 = store.GetSnapshot(); + + // Data saved to the leveldb from snapshot1 shall be visible to snapshot2 but not visible to snapshot1 + CollectionAssert.AreEqual(testValue, snapshot2.TryGet(testKey)); + Assert.IsNull(snapshot.TryGet(testKey)); + + snapshot.Dispose(); + snapshot2.Dispose(); } [TestMethod] public void TestRocksDb() { - using var plugin = new RocksDBStore(); - TestPersistenceDelete(plugin.GetStore(path_rocksdb)); + TestPersistenceDelete(rocksDBStore.GetStore(path_rocksdb)); // Test all with the same store - TestStorage(plugin.GetStore(path_rocksdb)); + TestStorage(rocksDBStore.GetStore(path_rocksdb)); // Test with different storages - TestPersistenceWrite(plugin.GetStore(path_rocksdb)); - TestPersistenceRead(plugin.GetStore(path_rocksdb), true); - TestPersistenceDelete(plugin.GetStore(path_rocksdb)); - TestPersistenceRead(plugin.GetStore(path_rocksdb), false); + TestPersistenceWrite(rocksDBStore.GetStore(path_rocksdb)); + TestPersistenceRead(rocksDBStore.GetStore(path_rocksdb), true); + TestPersistenceDelete(rocksDBStore.GetStore(path_rocksdb)); + TestPersistenceRead(rocksDBStore.GetStore(path_rocksdb), false); + } + + [TestMethod] + public void TestRocksDbSnapshot() + { + using var store = rocksDBStore.GetStore(path_leveldb); + + var snapshot = store.GetSnapshot(); + + var testKey = new byte[] { 0x01, 0x02, 0x03 }; + var testValue = new byte[] { 0x04, 0x05, 0x06 }; + + snapshot.Put(testKey, testValue); + // Data saved to the leveldb snapshot shall not be visible + Assert.IsNull(snapshot.TryGet(testKey)); + Assert.IsNull(store.TryGet(testKey)); + + // Value is in the write batch, not visible to the store and snapshot + Assert.AreEqual(false, snapshot.Contains(testKey)); + Assert.AreEqual(false, store.Contains(testKey)); + + snapshot.Commit(); + + // After commit, the data shall be visible to the store but not to the snapshot + Assert.IsNull(snapshot.TryGet(testKey)); + CollectionAssert.AreEqual(testValue, store.TryGet(testKey)); + Assert.AreEqual(false, snapshot.Contains(testKey)); + Assert.AreEqual(true, store.Contains(testKey)); + + snapshot.Dispose(); + } + + [TestMethod] + public void TestRocksDbMultiSnapshot() + { + using var store = rocksDBStore.GetStore(path_leveldb); + + var snapshot = store.GetSnapshot(); + + var testKey = new byte[] { 0x01, 0x02, 0x03 }; + var testValue = new byte[] { 0x04, 0x05, 0x06 }; + + snapshot.Put(testKey, testValue); + snapshot.Commit(); + CollectionAssert.AreEqual(testValue, store.TryGet(testKey)); + + var snapshot2 = store.GetSnapshot(); + // Data saved to the leveldb from snapshot1 shall only be visible to snapshot2 + CollectionAssert.AreEqual(testValue, snapshot2.TryGet(testKey)); + + snapshot.Dispose(); + snapshot2.Dispose(); } /// diff --git a/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs b/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs index 907a5df2b9..f7bc1789dd 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs @@ -39,6 +39,8 @@ public void TestBloomFIlterConstructorGetKMTweak() uint nTweak = 123456; Action action = () => new BloomFilter(m, n, nTweak); action.Should().Throw(); + action = () => new BloomFilter(m, n, nTweak, new byte[] { 0, 1, 2, 3, 4 }); + action.Should().Throw(); m = 7; n = -10; diff --git a/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs b/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs index 01022baab9..4bfde89ba2 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs @@ -41,6 +41,17 @@ public void TestBase58CheckDecode() input = "3vQB7B6MrGQZaxCuFg4og"; action = () => input.Base58CheckDecode(); action.Should().Throw(); + + Assert.ThrowsException(() => string.Empty.Base58CheckDecode()); + } + + [TestMethod] + public void TestMurmurReadOnlySpan() + { + ReadOnlySpan input = "Hello, world!"u8; + byte[] input2 = input.ToArray(); + input.Murmur32(0).Should().Be(input2.Murmur32(0)); + input.Murmur128(0).Should().Equal(input2.Murmur128(0)); } [TestMethod] @@ -50,6 +61,19 @@ public void TestSha256() byte[] result = value.Sha256(0, value.Length); string resultStr = result.ToHexString(); resultStr.Should().Be("b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"); + value.Sha256().Should().Equal(result); + ((Span)value).Sha256().Should().Equal(result); + ((ReadOnlySpan)value).Sha256().Should().Equal(result); + } + + [TestMethod] + public void TestKeccak256() + { + var input = "Hello, world!"u8.ToArray(); + var result = input.Keccak256(); + result.ToHexString().Should().Be("b6e16d27ac5ab427a7f68900ac5559ce272dc6c37c82b3e052246c82244c50e4"); + ((Span)input).Keccak256().Should().Equal(result); + ((ReadOnlySpan)input).Keccak256().Should().Equal(result); } [TestMethod] diff --git a/tests/Neo.UnitTests/IO/Caching/UT_CloneCache.cs b/tests/Neo.UnitTests/IO/Caching/UT_CloneCache.cs index 4d57da62b9..3dd3d2dd67 100644 --- a/tests/Neo.UnitTests/IO/Caching/UT_CloneCache.cs +++ b/tests/Neo.UnitTests/IO/Caching/UT_CloneCache.cs @@ -156,7 +156,7 @@ public void TestUpdateInternal() public void TestCacheOverrideIssue2572() { var snapshot = TestBlockchain.GetTestSnapshot(); - var storages = snapshot.CreateSnapshot(); + var storages = snapshot.CloneCache(); storages.Add ( diff --git a/tests/Neo.UnitTests/IO/Caching/UT_DataCache.cs b/tests/Neo.UnitTests/IO/Caching/UT_DataCache.cs index 5235b99c70..242905a13a 100644 --- a/tests/Neo.UnitTests/IO/Caching/UT_DataCache.cs +++ b/tests/Neo.UnitTests/IO/Caching/UT_DataCache.cs @@ -133,7 +133,7 @@ public void TestCommit() [TestMethod] public void TestCreateSnapshot() { - myDataCache.CreateSnapshot().Should().NotBeNull(); + myDataCache.CloneCache().Should().NotBeNull(); } [TestMethod] diff --git a/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs b/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs index 491b4fe21e..2c5ed88ef8 100644 --- a/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs +++ b/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs @@ -57,7 +57,7 @@ public void Clean() [TestMethod] public void TestValidTransaction() { - var snapshot = TestBlockchain.TheNeoSystem.GetSnapshot(); + var snapshot = TestBlockchain.TheNeoSystem.GetSnapshotCache(); var walletA = TestUtils.GenerateTestWallet("123"); var acc = walletA.CreateAccount(); @@ -70,7 +70,7 @@ public void TestValidTransaction() // Make transaction - var tx = CreateValidTx(snapshot, walletA, acc.ScriptHash, 0); + var tx = TestUtils.CreateValidTx(snapshot, walletA, acc.ScriptHash, 0); senderProbe.Send(system.Blockchain, tx); senderProbe.ExpectMsg(p => p.Result == VerifyResult.Succeed); @@ -91,35 +91,11 @@ internal static StorageKey CreateStorageKey(byte prefix, byte[] key = null) }; } - private static Transaction CreateValidTx(DataCache snapshot, NEP6Wallet wallet, UInt160 account, uint nonce) - { - var tx = wallet.MakeTransaction(snapshot, new TransferOutput[] - { - new TransferOutput() - { - AssetId = NativeContract.GAS.Hash, - ScriptHash = account, - Value = new BigDecimal(BigInteger.One,8) - } - }, - account); - - tx.Nonce = nonce; - - var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); - Assert.IsNull(data.GetSignatures(tx.Sender)); - Assert.IsTrue(wallet.Sign(data)); - Assert.IsTrue(data.Completed); - Assert.AreEqual(1, data.GetSignatures(tx.Sender).Count()); - - tx.Witnesses = data.GetWitnesses(); - return tx; - } [TestMethod] public void TestMaliciousOnChainConflict() { - var snapshot = TestBlockchain.TheNeoSystem.GetSnapshot(); + var snapshot = TestBlockchain.TheNeoSystem.GetSnapshotCache(); var walletA = TestUtils.GenerateTestWallet("123"); var accA = walletA.CreateAccount(); var walletB = TestUtils.GenerateTestWallet("456"); @@ -141,9 +117,9 @@ public void TestMaliciousOnChainConflict() // Create transactions: // tx1 conflicts with tx2 and has the same sender (thus, it's a valid conflict and must prevent tx2 from entering the chain); // tx2 conflicts with tx3 and has different sender (thus, this conflict is invalid and must not prevent tx3 from entering the chain). - var tx1 = CreateValidTx(snapshot, walletA, accA.ScriptHash, 0); - var tx2 = CreateValidTx(snapshot, walletA, accA.ScriptHash, 1); - var tx3 = CreateValidTx(snapshot, walletB, accB.ScriptHash, 2); + var tx1 = TestUtils.CreateValidTx(snapshot, walletA, accA.ScriptHash, 0); + var tx2 = TestUtils.CreateValidTx(snapshot, walletA, accA.ScriptHash, 1); + var tx3 = TestUtils.CreateValidTx(snapshot, walletB, accB.ScriptHash, 2); tx1.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = tx2.Hash }, new Conflicts() { Hash = tx3.Hash } }; diff --git a/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs b/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs index 1063413073..73cbb37dc9 100644 --- a/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs +++ b/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs @@ -47,7 +47,7 @@ public static void TestSetup(TestContext ctx) private static DataCache GetSnapshot() { - return testBlockchain.StoreView.CreateSnapshot(); + return testBlockchain.StoreView.CloneCache(); } [TestInitialize] diff --git a/tests/Neo.UnitTests/Ledger/UT_TrimmedBlock.cs b/tests/Neo.UnitTests/Ledger/UT_TrimmedBlock.cs index 7942847f94..ea797f8be3 100644 --- a/tests/Neo.UnitTests/Ledger/UT_TrimmedBlock.cs +++ b/tests/Neo.UnitTests/Ledger/UT_TrimmedBlock.cs @@ -69,11 +69,11 @@ public void TestGetBlock() Transaction = tx2, BlockIndex = 1 }; - UT_SmartContractHelper.TransactionAdd(snapshot, state1, state2); + TestUtils.TransactionAdd(snapshot, state1, state2); TrimmedBlock tblock = GetTrimmedBlockWithNoTransaction(); tblock.Hashes = new UInt256[] { tx1.Hash, tx2.Hash }; - UT_SmartContractHelper.BlocksAdd(snapshot, tblock.Hash, tblock); + TestUtils.BlocksAdd(snapshot, tblock.Hash, tblock); Block block = NativeContract.Ledger.GetBlock(snapshot, tblock.Hash); diff --git a/tests/Neo.UnitTests/Neo.UnitTests.csproj b/tests/Neo.UnitTests/Neo.UnitTests.csproj index fb246cbdc3..04be767fb7 100644 --- a/tests/Neo.UnitTests/Neo.UnitTests.csproj +++ b/tests/Neo.UnitTests/Neo.UnitTests.csproj @@ -12,6 +12,9 @@ + + PreserveNewest + PreserveNewest PreserveNewest @@ -19,6 +22,7 @@ + diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Block.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Block.cs index 2021e5a187..7793e27597 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Block.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Block.cs @@ -13,7 +13,10 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.IO; using Neo.Json; +using Neo.Ledger; using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.SmartContract.Native; namespace Neo.UnitTests.Network.P2P.Payloads { @@ -21,6 +24,15 @@ namespace Neo.UnitTests.Network.P2P.Payloads public class UT_Block { Block uut; + private static ApplicationEngine GetEngine(bool hasContainer = false, bool hasSnapshot = false, bool hasBlock = false, bool addScript = true, long gas = 20_00000000) + { + var tx = hasContainer ? TestUtils.GetTransaction(UInt160.Zero) : null; + var snapshot = hasSnapshot ? TestBlockchain.GetTestSnapshot() : null; + var block = hasBlock ? new Block { Header = new Header() } : null; + var engine = ApplicationEngine.Create(TriggerType.Application, tx, snapshot, block, TestBlockchain.TheNeoSystem.Settings, gas: gas); + if (addScript) engine.LoadScript(new byte[] { 0x01 }); + return engine; + } [TestInitialize] public void TestSetup() @@ -38,7 +50,7 @@ public void Transactions_Get() public void Header_Get() { UInt256 val256 = UInt256.Zero; - TestUtils.SetupBlockWithValues(uut, val256, out var merkRootVal, out _, out var timestampVal, out var nonceVal, out var indexVal, out var scriptVal, out _, 0); + TestUtils.SetupBlockWithValues(null, uut, val256, out var merkRootVal, out _, out var timestampVal, out var nonceVal, out var indexVal, out var scriptVal, out _, 0); uut.Header.Should().NotBeNull(); uut.Header.PrevHash.Should().Be(val256); @@ -53,7 +65,7 @@ public void Header_Get() public void Size_Get() { UInt256 val256 = UInt256.Zero; - TestUtils.SetupBlockWithValues(uut, val256, out var _, out var _, out var _, out var _, out var _, out var _, out var _, 0); + TestUtils.SetupBlockWithValues(null, uut, val256, out var _, out var _, out var _, out var _, out var _, out var _, out var _, 0); // header 4 + 32 + 32 + 8 + 4 + 1 + 20 + 4 // tx 1 uut.Size.Should().Be(114); // 106 + nonce @@ -63,7 +75,7 @@ public void Size_Get() public void Size_Get_1_Transaction() { UInt256 val256 = UInt256.Zero; - TestUtils.SetupBlockWithValues(uut, val256, out var _, out var _, out var _, out var _, out var _, out var _, out var _, 0); + TestUtils.SetupBlockWithValues(null, uut, val256, out var _, out var _, out var _, out var _, out var _, out var _, out var _, 0); uut.Transactions = new[] { @@ -77,7 +89,7 @@ public void Size_Get_1_Transaction() public void Size_Get_3_Transaction() { UInt256 val256 = UInt256.Zero; - TestUtils.SetupBlockWithValues(uut, val256, out var _, out var _, out var _, out var _, out var _, out var _, out var _, 0); + TestUtils.SetupBlockWithValues(null, uut, val256, out var _, out var _, out var _, out var _, out var _, out var _, out var _, 0); uut.Transactions = new[] { @@ -93,9 +105,9 @@ public void Size_Get_3_Transaction() public void Serialize() { UInt256 val256 = UInt256.Zero; - TestUtils.SetupBlockWithValues(uut, val256, out var _, out var _, out var _, out var _, out var _, out var _, out var _, 1); + TestUtils.SetupBlockWithValues(null, uut, val256, out var _, out var _, out var _, out var _, out var _, out var _, out var _, 1); - var hex = "0000000000000000000000000000000000000000000000000000000000000000000000006c23be5d32679baa9c5c2aa0d329fd2a2441d7875d0f34d42f58f70428fbbbb9e913ff854c00000000000000000000000000000000000000000000000000000000000000000000000001000111010000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000001000112010000"; + var hex = "0000000000000000000000000000000000000000000000000000000000000000000000006c23be5d32679baa9c5c2aa0d329fd2a2441d7875d0f34d42f58f70428fbbbb9493ed0e58f01000000000000000000000000000000000000000000000000000000000000000000000001000111010000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000001000112010000"; uut.ToArray().ToHexString().Should().Be(hex); } @@ -103,9 +115,9 @@ public void Serialize() public void Deserialize() { UInt256 val256 = UInt256.Zero; - TestUtils.SetupBlockWithValues(new Block(), val256, out _, out var val160, out var timestampVal, out var indexVal, out var nonceVal, out var scriptVal, out var transactionsVal, 1); + TestUtils.SetupBlockWithValues(null, new Block(), val256, out _, out var val160, out var timestampVal, out var indexVal, out var nonceVal, out var scriptVal, out var transactionsVal, 1); - var hex = "0000000000000000000000000000000000000000000000000000000000000000000000006c23be5d32679baa9c5c2aa0d329fd2a2441d7875d0f34d42f58f70428fbbbb9e913ff854c00000000000000000000000000000000000000000000000000000000000000000000000001000111010000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000001000112010000"; + var hex = "0000000000000000000000000000000000000000000000000000000000000000000000006c23be5d32679baa9c5c2aa0d329fd2a2441d7875d0f34d42f58f70428fbbbb9493ed0e58f01000000000000000000000000000000000000000000000000000000000000000000000001000111010000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000001000112010000"; MemoryReader reader = new(hex.HexToBytes()); uut.Deserialize(ref reader); @@ -136,6 +148,15 @@ private void AssertStandardBlockTestVals(UInt256 val256, UInt256 merkRoot, UInt1 public void Equals_SameObj() { uut.Equals(uut).Should().BeTrue(); + var obj = uut as object; + uut.Equals(obj).Should().BeTrue(); + } + + [TestMethod] + public void TestGetHashCode() + { + var snapshot = GetEngine(true, true).Snapshot; + NativeContract.Ledger.GetBlock(snapshot, 0).GetHashCode().Should().Be(-626492395); } [TestMethod] @@ -144,8 +165,8 @@ public void Equals_DiffObj() Block newBlock = new(); UInt256 val256 = UInt256.Zero; UInt256 prevHash = new(TestUtils.GetByteArray(32, 0x42)); - TestUtils.SetupBlockWithValues(newBlock, val256, out _, out _, out _, out ulong _, out uint _, out _, out _, 1); - TestUtils.SetupBlockWithValues(uut, prevHash, out _, out _, out _, out _, out _, out _, out _, 0); + TestUtils.SetupBlockWithValues(null, newBlock, val256, out _, out _, out _, out ulong _, out uint _, out _, out _, 1); + TestUtils.SetupBlockWithValues(null, uut, prevHash, out _, out _, out _, out _, out _, out _, out _, 0); uut.Equals(newBlock).Should().BeFalse(); } @@ -161,8 +182,8 @@ public void Equals_SameHash() { Block newBlock = new(); UInt256 prevHash = new(TestUtils.GetByteArray(32, 0x42)); - TestUtils.SetupBlockWithValues(newBlock, prevHash, out _, out _, out _, out _, out _, out _, out _, 1); - TestUtils.SetupBlockWithValues(uut, prevHash, out _, out _, out _, out _, out _, out _, out _, 1); + TestUtils.SetupBlockWithValues(null, newBlock, prevHash, out _, out _, out _, out _, out _, out _, out _, 1); + TestUtils.SetupBlockWithValues(null, uut, prevHash, out _, out _, out _, out _, out _, out _, out _, 1); uut.Equals(newBlock).Should().BeTrue(); } @@ -171,11 +192,11 @@ public void Equals_SameHash() public void ToJson() { UInt256 val256 = UInt256.Zero; - TestUtils.SetupBlockWithValues(uut, val256, out _, out _, out var timeVal, out var indexVal, out var nonceVal, out _, out _, 1); + TestUtils.SetupBlockWithValues(null, uut, val256, out _, out _, out var timeVal, out var indexVal, out var nonceVal, out _, out _, 1); JObject jObj = uut.ToJson(TestProtocolSettings.Default); jObj.Should().NotBeNull(); - jObj["hash"].AsString().Should().Be("0x60193a05005c433787d8a9b95da332bbeebb311e904525e9fb1bacc34ff1ead7"); + jObj["hash"].AsString().Should().Be("0x942065e93848732c2e7844061fa92d20c5d9dc0bc71d420a1ea71b3431fc21b4"); jObj["size"].AsNumber().Should().Be(167); // 159 + nonce jObj["version"].AsNumber().Should().Be(0); jObj["previousblockhash"].AsString().Should().Be("0x0000000000000000000000000000000000000000000000000000000000000000"); diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Header.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Header.cs index df33505965..17e0cb894f 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Header.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Header.cs @@ -34,7 +34,7 @@ public void TestSetup() public void Size_Get() { UInt256 val256 = UInt256.Zero; - TestUtils.SetupHeaderWithValues(uut, val256, out _, out _, out _, out _, out _, out _); + TestUtils.SetupHeaderWithValues(null, uut, val256, out _, out _, out _, out _, out _, out _); // blockbase 4 + 64 + 1 + 32 + 4 + 4 + 20 + 4 // header 1 uut.Size.Should().Be(113); // 105 + nonce @@ -44,7 +44,7 @@ public void Size_Get() public void GetHashCodeTest() { UInt256 val256 = UInt256.Zero; - TestUtils.SetupHeaderWithValues(uut, val256, out _, out _, out _, out _, out _, out _); + TestUtils.SetupHeaderWithValues(null, uut, val256, out _, out _, out _, out _, out _, out _); uut.GetHashCode().Should().Be(uut.Hash.GetHashCode()); } @@ -53,10 +53,10 @@ public void TrimTest() { UInt256 val256 = UInt256.Zero; var snapshot = TestBlockchain.GetTestSnapshot().CreateSnapshot(); - TestUtils.SetupHeaderWithValues(uut, val256, out _, out _, out _, out _, out _, out _); + TestUtils.SetupHeaderWithValues(null, uut, val256, out _, out _, out _, out _, out _, out _); uut.Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() }; - UT_SmartContractHelper.BlocksAdd(snapshot, uut.Hash, new TrimmedBlock() + TestUtils.BlocksAdd(snapshot, uut.Hash, new TrimmedBlock() { Header = new Header { @@ -87,11 +87,11 @@ public void TrimTest() public void Deserialize() { UInt256 val256 = UInt256.Zero; - TestUtils.SetupHeaderWithValues(new Header(), val256, out UInt256 merkRoot, out UInt160 val160, out ulong timestampVal, out ulong nonceVal, out uint indexVal, out Witness scriptVal); + TestUtils.SetupHeaderWithValues(null, new Header(), val256, out UInt256 merkRoot, out UInt160 val160, out ulong timestampVal, out ulong nonceVal, out uint indexVal, out Witness scriptVal); uut.MerkleRoot = merkRoot; // need to set for deserialise to be valid - var hex = "0000000000000000000000000000000000000000000000000000000000000000000000007227ba7b747f1a98f68679d4a98b68927646ab195a6f56b542ca5a0e6a412662e913ff854c00000000000000000000000000000000000000000000000000000000000000000000000001000111"; + var hex = "0000000000000000000000000000000000000000000000000000000000000000000000007227ba7b747f1a98f68679d4a98b68927646ab195a6f56b542ca5a0e6a412662493ed0e58f01000000000000000000000000000000000000000000000000000000000000000000000001000111"; MemoryReader reader = new(hex.HexToBytes()); uut.Deserialize(ref reader); @@ -130,8 +130,8 @@ public void Equals_SameHash() { Header newHeader = new(); UInt256 prevHash = new(TestUtils.GetByteArray(32, 0x42)); - TestUtils.SetupHeaderWithValues(newHeader, prevHash, out _, out _, out _, out _, out _, out _); - TestUtils.SetupHeaderWithValues(uut, prevHash, out _, out _, out _, out _, out _, out _); + TestUtils.SetupHeaderWithValues(null, newHeader, prevHash, out _, out _, out _, out _, out _, out _); + TestUtils.SetupHeaderWithValues(null, uut, prevHash, out _, out _, out _, out _, out _, out _); uut.Equals(newHeader).Should().BeTrue(); } @@ -146,9 +146,9 @@ public void Equals_SameObject() public void Serialize() { UInt256 val256 = UInt256.Zero; - TestUtils.SetupHeaderWithValues(uut, val256, out _, out _, out _, out _, out _, out _); + TestUtils.SetupHeaderWithValues(null, uut, val256, out _, out _, out _, out _, out _, out _); - var hex = "0000000000000000000000000000000000000000000000000000000000000000000000007227ba7b747f1a98f68679d4a98b68927646ab195a6f56b542ca5a0e6a412662e913ff854c00000000000000000000000000000000000000000000000000000000000000000000000001000111"; + var hex = "0000000000000000000000000000000000000000000000000000000000000000000000007227ba7b747f1a98f68679d4a98b68927646ab195a6f56b542ca5a0e6a412662493ed0e58f01000000000000000000000000000000000000000000000000000000000000000000000001000111"; uut.ToArray().ToHexString().Should().Be(hex); } } diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_HeadersPayload.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_HeadersPayload.cs index 2ac486ef06..9ecdd7f5e4 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_HeadersPayload.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_HeadersPayload.cs @@ -23,7 +23,7 @@ public class UT_HeadersPayload public void Size_Get() { var header = new Header(); - TestUtils.SetupHeaderWithValues(header, UInt256.Zero, out _, out _, out _, out _, out _, out _); + TestUtils.SetupHeaderWithValues(null, header, UInt256.Zero, out _, out _, out _, out _, out _, out _); var test = HeadersPayload.Create(); test.Size.Should().Be(1); @@ -35,7 +35,7 @@ public void Size_Get() public void DeserializeAndSerialize() { var header = new Header(); - TestUtils.SetupHeaderWithValues(header, UInt256.Zero, out _, out _, out _, out _, out _, out _); + TestUtils.SetupHeaderWithValues(null, header, UInt256.Zero, out _, out _, out _, out _, out _, out _); var test = HeadersPayload.Create(header); var clone = test.ToArray().AsSerializable(); diff --git a/tests/Neo.UnitTests/Persistence/UT_MemoryClonedCache.cs b/tests/Neo.UnitTests/Persistence/UT_MemoryClonedCache.cs new file mode 100644 index 0000000000..56afdc31f1 --- /dev/null +++ b/tests/Neo.UnitTests/Persistence/UT_MemoryClonedCache.cs @@ -0,0 +1,127 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_MemoryClonedCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Persistence; +using Neo.SmartContract; + +namespace Neo.UnitTests.Persistence; + +/// +/// When adding data to `datacache` , +/// it gets passed to `snapshotcache` during commit. +/// If `snapshotcache` commits, the data is then passed +/// to the underlying store . +/// However, because snapshots are immutable, the new data +/// cannot be retrieved from the snapshot . +/// +/// When deleting data from `datacache` , +/// it won't exist in `datacache` upon commit, and therefore will be removed from `snapshotcache` . +/// Upon `snapshotcache` commit, the data is deleted from the store . +/// However, since the snapshot remains unchanged, the data still exists in the snapshot. +/// If you attempt to read this data from `datacache` or `snapshotcache` , +/// which do not have the data, they will retrieve it from the snapshot instead of the store. +/// Thus, they can still access data that has been deleted. +/// +[TestClass] +public class UT_MemoryClonedCache +{ + private MemoryStore _memoryStore; + private MemorySnapshot _snapshot; + private SnapshotCache _snapshotCache; + private DataCache _dataCache; + + [TestInitialize] + public void Setup() + { + _memoryStore = new MemoryStore(); + _snapshot = _memoryStore.GetSnapshot() as MemorySnapshot; + _snapshotCache = new SnapshotCache(_snapshot); + _dataCache = _snapshotCache.CreateSnapshot(); + } + + [TestCleanup] + public void CleanUp() + { + _dataCache.Commit(); + _snapshotCache.Commit(); + _memoryStore.Reset(); + } + + [TestMethod] + public void SingleSnapshotCacheTest() + { + var key1 = new KeyBuilder(0, 1); + var value1 = new StorageItem([0x03, 0x04]); + + Assert.IsFalse(_dataCache.Contains(key1)); + _dataCache.Add(key1, value1); + + Assert.IsTrue(_dataCache.Contains(key1)); + Assert.IsFalse(_snapshotCache.Contains(key1)); + Assert.IsFalse(_snapshot.Contains(key1.ToArray())); + Assert.IsFalse(_memoryStore.Contains(key1.ToArray())); + + // After the data cache is committed, it should be dropped + // so its value after the commit is meaningless and should not be used. + _dataCache.Commit(); + + Assert.IsTrue(_dataCache.Contains(key1)); + Assert.IsTrue(_snapshotCache.Contains(key1)); + Assert.IsFalse(_snapshot.Contains(key1.ToArray())); + Assert.IsFalse(_memoryStore.Contains(key1.ToArray())); + + // After the snapshot is committed, it should be dropped + // so its value after the commit is meaningless and should not be used. + _snapshotCache.Commit(); + + Assert.IsTrue(_dataCache.Contains(key1)); + Assert.IsTrue(_snapshotCache.Contains(key1)); + Assert.IsFalse(_snapshot.Contains(key1.ToArray())); + Assert.IsTrue(_memoryStore.Contains(key1.ToArray())); + + // Test delete + + // Reset the snapshot to make it accessible to the new value. + _snapshot = _memoryStore.GetSnapshot() as MemorySnapshot; + _snapshotCache = new SnapshotCache(_snapshot); + _dataCache = _snapshotCache.CreateSnapshot(); + + Assert.IsTrue(_dataCache.Contains(key1)); + _dataCache.Delete(key1); + + Assert.IsFalse(_dataCache.Contains(key1)); + Assert.IsTrue(_snapshotCache.Contains(key1)); + Assert.IsTrue(_snapshot.Contains(key1.ToArray())); + Assert.IsTrue(_memoryStore.Contains(key1.ToArray())); + + // After the data cache is committed, it should be dropped + // so its value after the commit is meaningless and should not be used. + _dataCache.Commit(); + + Assert.IsFalse(_dataCache.Contains(key1)); + Assert.IsFalse(_snapshotCache.Contains(key1)); + Assert.IsTrue(_snapshot.Contains(key1.ToArray())); + Assert.IsTrue(_memoryStore.Contains(key1.ToArray())); + + + // After the snapshot cache is committed, it should be dropped + // so its value after the commit is meaningless and should not be used. + _snapshotCache.Commit(); + + // The reason that datacache, snapshotcache still contains key1 is because + // they can not find the value from its cache, so they fetch it from the snapshot of the store. + Assert.IsTrue(_dataCache.Contains(key1)); + Assert.IsTrue(_snapshotCache.Contains(key1)); + Assert.IsTrue(_snapshot.Contains(key1.ToArray())); + Assert.IsFalse(_memoryStore.Contains(key1.ToArray())); + } +} diff --git a/tests/Neo.UnitTests/Persistence/UT_MemorySnapshot.cs b/tests/Neo.UnitTests/Persistence/UT_MemorySnapshot.cs new file mode 100644 index 0000000000..629e2bc374 --- /dev/null +++ b/tests/Neo.UnitTests/Persistence/UT_MemorySnapshot.cs @@ -0,0 +1,121 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_MemorySnapshot.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Persistence; +using System.Linq; + +namespace Neo.UnitTests.Persistence; + +[TestClass] +public class UT_MemorySnapshot +{ + private MemoryStore _memoryStore; + private MemorySnapshot _snapshot; + + [TestInitialize] + public void Setup() + { + _memoryStore = new MemoryStore(); + _snapshot = _memoryStore.GetSnapshot() as MemorySnapshot; + } + + [TestCleanup] + public void CleanUp() + { + _memoryStore.Reset(); + } + + [TestMethod] + public void SingleSnapshotTest() + { + var key1 = new byte[] { 0x01, 0x02 }; + var value1 = new byte[] { 0x03, 0x04 }; + + _snapshot.Delete(key1); + Assert.IsNull(_snapshot.TryGet(key1)); + + // Both Store and Snapshot can not get the value that are cached in the snapshot + _snapshot.Put(key1, value1); + Assert.IsNull(_snapshot.TryGet(key1)); + Assert.IsNull(_memoryStore.TryGet(key1)); + + _snapshot.Commit(); + + // After commit the snapshot, the value can be get from the store but still can not get from the snapshot + CollectionAssert.AreEqual(value1, _memoryStore.TryGet(key1)); + Assert.IsNull(_snapshot.TryGet(key1)); + + _snapshot.Delete(key1); + + // Deleted value can not be found from the snapshot but can still get from the store + // This is because snapshot has no key1 at all. + Assert.IsFalse(_snapshot.Contains(key1)); + Assert.IsTrue(_memoryStore.Contains(key1)); + + _snapshot.Commit(); + + // After commit the snapshot, the value can not be found from the store + Assert.IsFalse(_memoryStore.Contains(key1)); + + // Test seek in order + _snapshot.Put([0x00, 0x00, 0x04], [0x04]); + _snapshot.Put([0x00, 0x00, 0x00], [0x00]); + _snapshot.Put([0x00, 0x00, 0x01], [0x01]); + _snapshot.Put([0x00, 0x00, 0x02], [0x02]); + _snapshot.Put([0x00, 0x00, 0x03], [0x03]); + + // Can not get anything from the snapshot + var entries = _snapshot.Seek([0x00, 0x00, 0x02]).ToArray(); + Assert.AreEqual(0, entries.Length); + } + + [TestMethod] + public void MultiSnapshotTest() + { + var key1 = new byte[] { 0x01, 0x02 }; + var value1 = new byte[] { 0x03, 0x04 }; + + _snapshot.Delete(key1); + Assert.IsNull(_snapshot.TryGet(key1)); + + // Both Store and Snapshot can not get the value that are cached in the snapshot + _snapshot.Put(key1, value1); + // After commit the snapshot, the value can be get from the store but still can not get from the snapshot + // But can get the value from a new snapshot + _snapshot.Commit(); + var snapshot2 = _memoryStore.GetSnapshot(); + CollectionAssert.AreEqual(value1, _memoryStore.TryGet(key1)); + Assert.IsNull(_snapshot.TryGet(key1)); + CollectionAssert.AreEqual(value1, snapshot2.TryGet(key1)); + + _snapshot.Delete(key1); + + // Deleted value can not being found from the snapshot but can still get from the store and snapshot2 + Assert.IsFalse(_snapshot.Contains(key1)); + Assert.IsTrue(_memoryStore.Contains(key1)); + Assert.IsTrue(snapshot2.Contains(key1)); + + _snapshot.Commit(); + + // After commit the snapshot, the value can not be found from the store, but can be found in snapshots + // Cause snapshot1 or store can not change the status of snapshot2. + Assert.IsFalse(_memoryStore.Contains(key1)); + Assert.IsTrue(snapshot2.Contains(key1)); + Assert.IsFalse(_snapshot.Contains(key1)); + + // Add value via snapshot2 will not affect snapshot1 at all + snapshot2.Put(key1, value1); + snapshot2.Commit(); + Assert.IsNull(_snapshot.TryGet(key1)); + CollectionAssert.AreEqual(value1, snapshot2.TryGet(key1)); + } +} diff --git a/tests/Neo.UnitTests/Persistence/UT_MemorySnapshotCache.cs b/tests/Neo.UnitTests/Persistence/UT_MemorySnapshotCache.cs new file mode 100644 index 0000000000..8c4843c3f3 --- /dev/null +++ b/tests/Neo.UnitTests/Persistence/UT_MemorySnapshotCache.cs @@ -0,0 +1,134 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_MemorySnapshotCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Persistence; +using Neo.SmartContract; +using System.Linq; + +namespace Neo.UnitTests.Persistence; + +[TestClass] +public class UT_MemorySnapshotCache +{ + private MemoryStore _memoryStore; + private MemorySnapshot _snapshot; + private SnapshotCache _snapshotCache; + + [TestInitialize] + public void Setup() + { + _memoryStore = new MemoryStore(); + _snapshot = _memoryStore.GetSnapshot() as MemorySnapshot; + _snapshotCache = new SnapshotCache(_snapshot); + } + + [TestCleanup] + public void CleanUp() + { + _snapshotCache.Commit(); + _memoryStore.Reset(); + } + + [TestMethod] + public void SingleSnapshotCacheTest() + { + var key1 = new KeyBuilder(0, 1); + var value1 = new StorageItem([0x03, 0x04]); + + _snapshotCache.Delete(key1); + Assert.IsNull(_snapshotCache.TryGet(key1)); + + // Adding value to the snapshot cache will not affect the snapshot or the store + // But the snapshot cache itself can see the added item right after it is added. + _snapshotCache.Add(key1, value1); + + Assert.AreEqual(value1.Value, _snapshotCache.TryGet(key1).Value); + Assert.IsNull(_snapshot.TryGet(key1.ToArray())); + Assert.IsNull(_memoryStore.TryGet(key1.ToArray())); + + // After commit the snapshot cache, it works the same as commit the snapshot. + // the value can be get from the snapshot cache and store but still can not get from the snapshot + _snapshotCache.Commit(); + + Assert.AreEqual(value1.Value, _snapshotCache.TryGet(key1).Value); + Assert.IsFalse(_snapshot.Contains(key1.ToArray())); + Assert.IsTrue(_memoryStore.Contains(key1.ToArray())); + + // Test delete + + // Reset the snapshot to make it accessible to the new value. + _snapshot = _memoryStore.GetSnapshot() as MemorySnapshot; + _snapshotCache = new SnapshotCache(_snapshot); + + // Delete value to the snapshot cache will not affect the snapshot or the store + // But the snapshot cache itself can not see the added item. + _snapshotCache.Delete(key1); + + // Value is removed from the snapshot cache immediately + Assert.IsNull(_snapshotCache.TryGet(key1)); + // But the underline snapshot will not be changed. + Assert.IsTrue(_snapshot.Contains(key1.ToArray())); + // And the store is also not affected. + Assert.IsNotNull(_memoryStore.TryGet(key1.ToArray())); + + // commit the snapshot cache + _snapshotCache.Commit(); + + // Value is removed from both the store, but the snapshot and snapshot cache remains the same. + Assert.IsTrue(_snapshotCache.Contains(key1)); + Assert.IsTrue(_snapshot.Contains(key1.ToArray())); + Assert.IsFalse(_memoryStore.Contains(key1.ToArray())); + } + + [TestMethod] + public void MultiSnapshotCacheTest() + { + var key1 = new KeyBuilder(0, 1); + var value1 = new StorageItem([0x03, 0x04]); + + _snapshotCache.Delete(key1); + Assert.IsNull(_snapshotCache.TryGet(key1)); + + // Adding value to the snapshot cache will not affect the snapshot or the store + // But the snapshot cache itself can see the added item. + _snapshotCache.Add(key1, value1); + + // After commit the snapshot cache, it works the same as commit the snapshot. + // the value can be get from the snapshot cache but still can not get from the snapshot + _snapshotCache.Commit(); + + // Get a new snapshot cache to test if the value can be seen from the new snapshot cache + var snapshotCache2 = new SnapshotCache(_snapshot); + Assert.IsNull(snapshotCache2.TryGet(key1)); + Assert.IsFalse(_snapshot.Contains(key1.ToArray())); + + // Test delete + + // Reset the snapshot to make it accessible to the new value. + _snapshot = _memoryStore.GetSnapshot() as MemorySnapshot; + _snapshotCache = new SnapshotCache(_snapshot); + + // Delete value to the snapshot cache will affect the snapshot + // But the snapshot and store itself can still see the item. + _snapshotCache.Delete(key1); + + // Commiting the snapshot cache will change the store, but the existing snapshot remains same. + _snapshotCache.Commit(); + + // reset the snapshotcache2 to snapshot + snapshotCache2 = new SnapshotCache(_snapshot); + // Value is removed from the store, but the snapshot remains the same. + // thus the snapshot cache from the snapshot will remain the same. + Assert.IsNotNull(snapshotCache2.TryGet(key1)); + Assert.IsNull(_memoryStore.TryGet(key1.ToArray())); + } +} diff --git a/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs b/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs index 4cddf07e65..133d9eb66c 100644 --- a/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs +++ b/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs @@ -91,7 +91,7 @@ public void NeoSystemStoreViewTest() [TestMethod] public void NeoSystemStoreAddTest() { - var storeCache = _neoSystem.GetSnapshot(); + var storeCache = _neoSystem.GetSnapshotCache(); var key = new KeyBuilder(0, 0); storeCache.Add(key, new StorageItem(UInt256.Zero.ToArray())); storeCache.Commit(); @@ -102,7 +102,7 @@ public void NeoSystemStoreAddTest() [TestMethod] public void NeoSystemStoreGetAndChange() { - var storeView = _neoSystem.GetSnapshot(); + var storeView = _neoSystem.GetSnapshotCache(); var key = new KeyBuilder(1, 1); var item = new StorageItem([1, 2, 3]); storeView.Delete(key); diff --git a/tests/Neo.UnitTests/Plugins/TestPlugin.cs b/tests/Neo.UnitTests/Plugins/TestPlugin.cs index 5e51220c7f..dde500b927 100644 --- a/tests/Neo.UnitTests/Plugins/TestPlugin.cs +++ b/tests/Neo.UnitTests/Plugins/TestPlugin.cs @@ -10,60 +10,15 @@ // modifications are permitted. using Microsoft.Extensions.Configuration; -using Neo.Ledger; -using Neo.Network.P2P.Payloads; -using Neo.Persistence; using Neo.Plugins; -using System; -using System.Collections.Generic; namespace Neo.UnitTests.Plugins { - - internal class TestPluginSettings(IConfigurationSection section) : PluginSettings(section) - { - public static TestPluginSettings Default { get; private set; } - public static void Load(IConfigurationSection section) - { - Default = new TestPluginSettings(section); - } - } - internal class TestNonPlugin - { - public TestNonPlugin() - { - Blockchain.Committing += OnCommitting; - Blockchain.Committed += OnCommitted; - } - - private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) - { - throw new NotImplementedException("Test exception from OnCommitting"); - } - - private void OnCommitted(NeoSystem system, Block block) - { - throw new NotImplementedException("Test exception from OnCommitted"); - } - } - - - internal class TestPlugin : Plugin + public class TestPlugin : Plugin { - private readonly UnhandledExceptionPolicy _exceptionPolicy; - protected internal override UnhandledExceptionPolicy ExceptionPolicy => _exceptionPolicy; + public TestPlugin() : base() { } - public TestPlugin(UnhandledExceptionPolicy exceptionPolicy = UnhandledExceptionPolicy.StopPlugin) : base() - { - Blockchain.Committing += OnCommitting; - Blockchain.Committed += OnCommitted; - _exceptionPolicy = exceptionPolicy; - } - - protected override void Configure() - { - TestPluginSettings.Load(GetConfiguration()); - } + protected override void Configure() { } public void LogMessage(string message) { @@ -81,15 +36,5 @@ public IConfigurationSection TestGetConfiguration() } protected override bool OnMessage(object message) => true; - - private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) - { - throw new NotImplementedException(); - } - - private void OnCommitted(NeoSystem system, Block block) - { - throw new NotImplementedException(); - } } } diff --git a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs index c48d32563f..e5e8cb6e49 100644 --- a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs +++ b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs @@ -11,11 +11,8 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Neo.Ledger; using Neo.Plugins; using System; -using System.Reflection; -using System.Threading.Tasks; namespace Neo.UnitTests.Plugins { @@ -24,50 +21,6 @@ public class UT_Plugin { private static readonly object locker = new(); - [TestInitialize] - public void TestInitialize() - { - ClearEventHandlers(); - } - - [TestCleanup] - public void TestCleanup() - { - ClearEventHandlers(); - } - - private static void ClearEventHandlers() - { - ClearEventHandler("Committing"); - ClearEventHandler("Committed"); - } - - private static void ClearEventHandler(string eventName) - { - var eventInfo = typeof(Blockchain).GetEvent(eventName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); - if (eventInfo == null) - { - return; - } - - var fields = typeof(Blockchain).GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public); - foreach (var field in fields) - { - if (field.FieldType == typeof(MulticastDelegate) || field.FieldType.BaseType == typeof(MulticastDelegate)) - { - var eventDelegate = (MulticastDelegate)field.GetValue(null); - if (eventDelegate != null && field.Name.Contains(eventName)) - { - foreach (var handler in eventDelegate.GetInvocationList()) - { - eventInfo.RemoveEventHandler(null, handler); - } - break; - } - } - } - } - [TestMethod] public void TestGetConfigFile() { @@ -110,109 +63,5 @@ public void TestGetConfiguration() var pp = new TestPlugin(); pp.TestGetConfiguration().Key.Should().Be("PluginConfiguration"); } - - [TestMethod] - public async Task TestOnException() - { - _ = new TestPlugin(); - // Ensure no exception is thrown - try - { - await Blockchain.InvokeCommittingAsync(null, null, null, null); - await Blockchain.InvokeCommittedAsync(null, null); - } - catch (Exception ex) - { - Assert.Fail($"InvokeCommitting or InvokeCommitted threw an exception: {ex.Message}"); - } - - // Register TestNonPlugin that throws exceptions - _ = new TestNonPlugin(); - - // Ensure exception is thrown - await Assert.ThrowsExceptionAsync(async () => - { - await Blockchain.InvokeCommittingAsync(null, null, null, null); - }); - - await Assert.ThrowsExceptionAsync(async () => - { - await Blockchain.InvokeCommittedAsync(null, null); - }); - } - - [TestMethod] - public async Task TestOnPluginStopped() - { - var pp = new TestPlugin(); - Assert.AreEqual(false, pp.IsStopped); - // Ensure no exception is thrown - try - { - await Blockchain.InvokeCommittingAsync(null, null, null, null); - await Blockchain.InvokeCommittedAsync(null, null); - } - catch (Exception ex) - { - Assert.Fail($"InvokeCommitting or InvokeCommitted threw an exception: {ex.Message}"); - } - - Assert.AreEqual(true, pp.IsStopped); - } - - [TestMethod] - public async Task TestOnPluginStopOnException() - { - // pp will stop on exception. - var pp = new TestPlugin(); - Assert.AreEqual(false, pp.IsStopped); - // Ensure no exception is thrown - try - { - await Blockchain.InvokeCommittingAsync(null, null, null, null); - await Blockchain.InvokeCommittedAsync(null, null); - } - catch (Exception ex) - { - Assert.Fail($"InvokeCommitting or InvokeCommitted threw an exception: {ex.Message}"); - } - - Assert.AreEqual(true, pp.IsStopped); - - // pp2 will not stop on exception. - var pp2 = new TestPlugin(UnhandledExceptionPolicy.Ignore); - Assert.AreEqual(false, pp2.IsStopped); - // Ensure no exception is thrown - try - { - await Blockchain.InvokeCommittingAsync(null, null, null, null); - await Blockchain.InvokeCommittedAsync(null, null); - } - catch (Exception ex) - { - Assert.Fail($"InvokeCommitting or InvokeCommitted threw an exception: {ex.Message}"); - } - - Assert.AreEqual(false, pp2.IsStopped); - } - - [TestMethod] - public async Task TestOnNodeStopOnPluginException() - { - // node will stop on pp exception. - var pp = new TestPlugin(UnhandledExceptionPolicy.StopNode); - Assert.AreEqual(false, pp.IsStopped); - await Assert.ThrowsExceptionAsync(async () => - { - await Blockchain.InvokeCommittingAsync(null, null, null, null); - }); - - await Assert.ThrowsExceptionAsync(async () => - { - await Blockchain.InvokeCommittedAsync(null, null); - }); - - Assert.AreEqual(false, pp.IsStopped); - } } } diff --git a/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleContractCall.manifest.json b/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleContractCall.manifest.json new file mode 100644 index 0000000000..bfbee88473 --- /dev/null +++ b/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleContractCall.manifest.json @@ -0,0 +1 @@ +{"name":"SampleContractCall","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"onNEP17Payment","parameters":[{"name":"from","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Integer"}],"returntype":"Void","offset":0,"safe":false},{"name":"_initialize","parameters":[],"returntype":"Void","offset":91,"safe":false}],"events":[]},"permissions":[],"trusts":[],"extra":{"Author":"core-dev","Version":"0.0.1","Description":"A sample contract to demonstrate how to call a contract","Sourcecode":"https://github.com/neo-project/neo-devpack-dotnet/tree/master/examples/"}} \ No newline at end of file diff --git a/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleEvent.manifest.json b/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleEvent.manifest.json new file mode 100644 index 0000000000..7e71a2f2a5 --- /dev/null +++ b/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleEvent.manifest.json @@ -0,0 +1 @@ +{"name":"SampleEvent","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"main","parameters":[],"returntype":"Boolean","offset":0,"safe":false}],"events":[{"name":"new_event_name","parameters":[{"name":"arg1","type":"ByteArray"},{"name":"arg2","type":"String"},{"name":"arg3","type":"Integer"}]},{"name":"event2","parameters":[{"name":"arg1","type":"ByteArray"},{"name":"arg2","type":"Integer"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":{"Author":"code-dev","Description":"A sample contract that demonstrates how to use Events","Version":"0.0.1","Sourcecode":"https://github.com/neo-project/neo-devpack-dotnet/tree/master/examples/"}} \ No newline at end of file diff --git a/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleException.manifest.json b/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleException.manifest.json new file mode 100644 index 0000000000..5d916af0f3 --- /dev/null +++ b/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleException.manifest.json @@ -0,0 +1 @@ +{"name":"SampleException","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"try01","parameters":[],"returntype":"Any","offset":0,"safe":false},{"name":"try02","parameters":[],"returntype":"Any","offset":77,"safe":false},{"name":"try03","parameters":[],"returntype":"Any","offset":166,"safe":false},{"name":"tryNest","parameters":[],"returntype":"Any","offset":259,"safe":false},{"name":"tryFinally","parameters":[],"returntype":"Any","offset":404,"safe":false},{"name":"tryFinallyAndRethrow","parameters":[],"returntype":"Any","offset":474,"safe":false},{"name":"tryCatch","parameters":[],"returntype":"Any","offset":550,"safe":false},{"name":"tryWithTwoFinally","parameters":[],"returntype":"Any","offset":628,"safe":false},{"name":"tryecpointCast","parameters":[],"returntype":"Any","offset":920,"safe":false},{"name":"tryvalidByteString2Ecpoint","parameters":[],"returntype":"Any","offset":1010,"safe":false},{"name":"tryinvalidByteArray2UInt160","parameters":[],"returntype":"Any","offset":1100,"safe":false},{"name":"tryvalidByteArray2UInt160","parameters":[],"returntype":"Any","offset":1190,"safe":false},{"name":"tryinvalidByteArray2UInt256","parameters":[],"returntype":"Any","offset":1280,"safe":false},{"name":"tryvalidByteArray2UInt256","parameters":[],"returntype":"Any","offset":1370,"safe":false},{"name":"tryNULL2Ecpoint_1","parameters":[],"returntype":"Array","offset":1476,"safe":false},{"name":"tryNULL2Uint160_1","parameters":[],"returntype":"Array","offset":1652,"safe":false},{"name":"tryNULL2Uint256_1","parameters":[],"returntype":"Array","offset":1828,"safe":false},{"name":"tryNULL2Bytestring_1","parameters":[],"returntype":"Array","offset":1990,"safe":false},{"name":"tryUncatchableException","parameters":[],"returntype":"Any","offset":2141,"safe":false},{"name":"_initialize","parameters":[],"returntype":"Void","offset":2219,"safe":false}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":{"Author":"core-dev","Description":"A sample contract to demonstrate how to handle exception","Version":"0.0.1","Sourcecode":"https://github.com/neo-project/neo-devpack-dotnet/tree/master/examples/"}} \ No newline at end of file diff --git a/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleHelloWorld.manifest.json b/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleHelloWorld.manifest.json new file mode 100644 index 0000000000..4c7c4b9605 --- /dev/null +++ b/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleHelloWorld.manifest.json @@ -0,0 +1 @@ +{"name":"SampleHelloWorld","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"sayHello","parameters":[],"returntype":"String","offset":0,"safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":{"Description":"A simple \u0060hello world\u0060 contract","E-mail":"dev@neo.org","Version":"0.0.1","Sourcecode":"https://github.com/neo-project/neo-devpack-dotnet/tree/master/examples/"}} \ No newline at end of file diff --git a/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleNep17Token.manifest.json b/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleNep17Token.manifest.json new file mode 100644 index 0000000000..bb9cf1682f --- /dev/null +++ b/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleNep17Token.manifest.json @@ -0,0 +1 @@ +{"name":"SampleNep17Token","groups":[],"features":{},"supportedstandards":["NEP-17"],"abi":{"methods":[{"name":"symbol","parameters":[],"returntype":"String","offset":1333,"safe":true},{"name":"decimals","parameters":[],"returntype":"Integer","offset":1348,"safe":true},{"name":"totalSupply","parameters":[],"returntype":"Integer","offset":52,"safe":true},{"name":"balanceOf","parameters":[{"name":"owner","type":"Hash160"}],"returntype":"Integer","offset":98,"safe":true},{"name":"transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","offset":362,"safe":false},{"name":"getOwner","parameters":[],"returntype":"Hash160","offset":808,"safe":true},{"name":"setOwner","parameters":[{"name":"newOwner","type":"Any"}],"returntype":"Void","offset":877,"safe":false},{"name":"getMinter","parameters":[],"returntype":"Hash160","offset":980,"safe":true},{"name":"setMinter","parameters":[{"name":"newMinter","type":"Hash160"}],"returntype":"Void","offset":1025,"safe":false},{"name":"mint","parameters":[{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Void","offset":1103,"safe":false},{"name":"burn","parameters":[{"name":"account","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Void","offset":1158,"safe":false},{"name":"verify","parameters":[],"returntype":"Boolean","offset":1216,"safe":true},{"name":"update","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"String"}],"returntype":"Boolean","offset":1222,"safe":false},{"name":"_initialize","parameters":[],"returntype":"Void","offset":1271,"safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]},{"name":"SetOwner","parameters":[{"name":"newOwner","type":"Hash160"}]},{"name":"SetMinter","parameters":[{"name":"newMinter","type":"Hash160"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":{"Author":"core-dev","Version":"0.0.1","Description":"A sample NEP-17 token","Sourcecode":"https://github.com/neo-project/neo-devpack-dotnet/tree/master/examples/"}} \ No newline at end of file diff --git a/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleOracle.manifest.json b/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleOracle.manifest.json new file mode 100644 index 0000000000..8570ccb3fb --- /dev/null +++ b/tests/Neo.UnitTests/SmartContract/Manifest/TestFile/SampleOracle.manifest.json @@ -0,0 +1 @@ +{"name":"SampleOracle","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"getResponse","parameters":[],"returntype":"String","offset":0,"safe":true},{"name":"doRequest","parameters":[],"returntype":"Void","offset":35,"safe":false},{"name":"onOracleResponse","parameters":[{"name":"requestedUrl","type":"String"},{"name":"userData","type":"Any"},{"name":"oracleResponse","type":"Integer"},{"name":"jsonString","type":"String"}],"returntype":"Void","offset":333,"safe":false}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":{"Author":"code-dev","Description":"A sample contract to demonstrate how to use Example.SmartContract.Oracle Service","Version":"0.0.1","Sourcecode":"https://github.com/neo-project/neo-devpack-dotnet/tree/master/examples/"}} \ No newline at end of file diff --git a/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractPermissionDescriptor.cs b/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractPermissionDescriptor.cs index 0dee8b0f67..cfcd9baf1e 100644 --- a/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractPermissionDescriptor.cs +++ b/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractPermissionDescriptor.cs @@ -9,9 +9,13 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Json; using Neo.SmartContract.Manifest; +using Neo.SmartContract.Native; using Neo.Wallets; +using System; using System.Security.Cryptography; namespace Neo.UnitTests.SmartContract.Manifest @@ -33,7 +37,7 @@ public void TestCreateByECPointAndIsWildcard() } [TestMethod] - public void TestFromAndToJson() + public void TestContractPermissionDescriptorFromAndToJson() { byte[] privateKey = new byte[32]; RandomNumberGenerator rng = RandomNumberGenerator.Create(); @@ -43,6 +47,33 @@ public void TestFromAndToJson() ContractPermissionDescriptor result = ContractPermissionDescriptor.FromJson(temp.ToJson()); Assert.AreEqual(null, result.Hash); Assert.AreEqual(result.Group, result.Group); + Assert.ThrowsException(() => ContractPermissionDescriptor.FromJson(string.Empty)); + } + + [TestMethod] + public void TestContractManifestFromJson() + { + Assert.ThrowsException(() => ContractManifest.FromJson(new Json.JObject())); + var jsonFiles = System.IO.Directory.GetFiles(System.IO.Path.Combine("SmartContract", "Manifest", "TestFile")); + foreach (var item in jsonFiles) + { + var json = JObject.Parse(System.IO.File.ReadAllText(item)) as JObject; + var manifest = ContractManifest.FromJson(json); + manifest.ToJson().ToString().Should().Be(json.ToString()); + } + } + + [TestMethod] + public void TestEquals() + { + var descriptor1 = ContractPermissionDescriptor.CreateWildcard(); + var descriptor2 = ContractPermissionDescriptor.Create(LedgerContract.NEO.Hash); + + Assert.AreNotEqual(descriptor1, descriptor2); + + var descriptor3 = ContractPermissionDescriptor.Create(LedgerContract.NEO.Hash); + + Assert.AreEqual(descriptor2, descriptor3); } } } diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs index 51617b6744..1557f9778d 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs @@ -49,7 +49,7 @@ public void TestSetup() [TestMethod] public async Task Check_BalanceOfTransferAndBurn() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; byte[] from = Contract.GetBFTAddress(TestProtocolSettings.Default.StandbyValidators).ToArray(); byte[] to = new byte[20]; diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs index 1989b4323b..707f8e6754 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs @@ -58,6 +58,28 @@ public void Clean() TestBlockchain.ResetStore(); } + class active : IHardforkActivable + { + public Hardfork? ActiveIn { get; init; } + public Hardfork? DeprecatedIn { get; init; } + } + + [TestMethod] + public void TestActiveDeprecatedIn() + { + string json = UT_ProtocolSettings.CreateHFSettings("\"HF_Cockatrice\": 20"); + var file = Path.GetTempFileName(); + File.WriteAllText(file, json); + ProtocolSettings settings = ProtocolSettings.Load(file, false); + File.Delete(file); + + Assert.IsFalse(NativeContract.IsActive(new active() { ActiveIn = Hardfork.HF_Cockatrice, DeprecatedIn = null }, settings.IsHardforkEnabled, 1)); + Assert.IsTrue(NativeContract.IsActive(new active() { ActiveIn = Hardfork.HF_Cockatrice, DeprecatedIn = null }, settings.IsHardforkEnabled, 20)); + + Assert.IsTrue(NativeContract.IsActive(new active() { ActiveIn = null, DeprecatedIn = Hardfork.HF_Cockatrice }, settings.IsHardforkEnabled, 1)); + Assert.IsFalse(NativeContract.IsActive(new active() { ActiveIn = null, DeprecatedIn = Hardfork.HF_Cockatrice }, settings.IsHardforkEnabled, 20)); + } + [TestMethod] public void TestGetContract() { @@ -67,7 +89,7 @@ public void TestGetContract() [TestMethod] public void TestIsInitializeBlock() { - string json = UT_ProtocolSettings.CreateHKSettings("\"HF_Cockatrice\": 20"); + string json = UT_ProtocolSettings.CreateHFSettings("\"HF_Cockatrice\": 20"); var file = Path.GetTempFileName(); File.WriteAllText(file, json); @@ -101,7 +123,7 @@ public void TestGenesisNEP17Manifest() }, Transactions = [] }; - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); // Ensure that native NEP17 contracts contain proper supported standards and events declared // in the manifest constructed for all hardforks enabled. Ref. https://github.com/neo-project/neo/pull/3195. @@ -128,7 +150,7 @@ public void TestGenesisNativeState() }, Transactions = [] }; - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); // Ensure that all native contracts have proper state generated with an assumption that // all hardforks enabled. diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs index 0a19314153..92f1e0e5f2 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs @@ -56,7 +56,7 @@ public void TestSetup() [TestMethod] public void Check_Vote() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); @@ -114,7 +114,7 @@ public void Check_Vote() [TestMethod] public void Check_Vote_Sameaccounts() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); @@ -147,7 +147,7 @@ public void Check_Vote_Sameaccounts() [TestMethod] public void Check_Vote_ChangeVote() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); @@ -182,7 +182,7 @@ public void Check_Vote_ChangeVote() [TestMethod] public void Check_Vote_VoteToNull() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); @@ -219,7 +219,7 @@ public void Check_Vote_VoteToNull() [TestMethod] public void Check_UnclaimedGas() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); @@ -239,7 +239,7 @@ public void Check_UnclaimedGas() [TestMethod] public void Check_RegisterValidator() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); var keyCount = snapshot.GetChangeSet().Count(); var point = TestProtocolSettings.Default.StandbyValidators[0].EncodePoint(true).Clone() as byte[]; @@ -267,7 +267,7 @@ public void Check_RegisterValidator() [TestMethod] public void Check_UnregisterCandidate() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); _persistingBlock.Header.Index = 1; var keyCount = snapshot.GetChangeSet().Count(); var point = TestProtocolSettings.Default.StandbyValidators[0].EncodePoint(true); @@ -328,7 +328,7 @@ public void Check_UnregisterCandidate() [TestMethod] public void Check_GetCommittee() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); var keyCount = snapshot.GetChangeSet().Count(); var point = TestProtocolSettings.Default.StandbyValidators[0].EncodePoint(true); var persistingBlock = _persistingBlock; @@ -388,7 +388,7 @@ public void Check_GetCommittee() [TestMethod] public void Check_Transfer() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; byte[] from = Contract.GetBFTAddress(TestProtocolSettings.Default.StandbyValidators).ToArray(); @@ -447,7 +447,7 @@ public void Check_Transfer() [TestMethod] public void Check_BalanceOf() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); byte[] account = Contract.GetBFTAddress(TestProtocolSettings.Default.StandbyValidators).ToArray(); NativeContract.NEO.BalanceOf(snapshot, account).Should().Be(100_000_000); @@ -460,7 +460,7 @@ public void Check_BalanceOf() [TestMethod] public void Check_CommitteeBonus() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); var persistingBlock = new Block { Header = new Header @@ -485,7 +485,7 @@ public void Check_CommitteeBonus() [TestMethod] public void Check_Initialize() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); // StandbyValidators @@ -495,7 +495,7 @@ public void Check_Initialize() [TestMethod] public void TestCalculateBonus() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); var persistingBlock = new Block(); StorageKey key = CreateStorageKey(20, UInt160.Zero.ToArray()); @@ -584,7 +584,7 @@ public void TestGetNextBlockValidators1() [TestMethod] public void TestGetNextBlockValidators2() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); var result = NativeContract.NEO.GetNextBlockValidators(snapshot, 7); result.Length.Should().Be(7); result[0].ToArray().ToHexString().Should().Be("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70"); @@ -607,7 +607,7 @@ public void TestGetCandidates1() [TestMethod] public void TestGetCandidates2() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); var result = NativeContract.NEO.GetCandidatesInternal(snapshot); result.Count().Should().Be(0); @@ -619,7 +619,7 @@ public void TestGetCandidates2() [TestMethod] public void TestCheckCandidate() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); var committee = NativeContract.NEO.GetCommittee(snapshot); var point = committee[0].EncodePoint(true); @@ -700,7 +700,7 @@ public void TestGetCommittee() [TestMethod] public void TestGetValidators() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); var result = NativeContract.NEO.ComputeNextBlockValidators(snapshot, TestProtocolSettings.Default); result[0].ToArray().ToHexString().Should().Be("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70"); result[1].ToArray().ToHexString().Should().Be("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d"); @@ -730,7 +730,7 @@ public void TestOnBalanceChanging() [TestMethod] public void TestTotalSupply() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); NativeContract.NEO.TotalSupply(snapshot).Should().Be(new BigInteger(100000000)); } @@ -738,7 +738,7 @@ public void TestTotalSupply() public void TestEconomicParameter() { const byte Prefix_CurrentBlock = 12; - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); var persistingBlock = new Block { Header = new Header() }; (BigInteger, bool) result = Check_GetGasPerBlock(snapshot, persistingBlock); @@ -768,7 +768,7 @@ public void TestEconomicParameter() [TestMethod] public void TestClaimGas() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); // Initialize block snapshot.Add(CreateStorageKey(1), new StorageItem(new BigInteger(30000000))); @@ -877,7 +877,7 @@ public void TestClaimGas() [TestMethod] public void TestUnclaimedGas() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 10).Should().Be(new BigInteger(0)); snapshot.Add(CreateStorageKey(20, UInt160.Zero.ToArray()), new StorageItem(new NeoAccountState())); NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 10).Should().Be(new BigInteger(0)); @@ -886,7 +886,7 @@ public void TestUnclaimedGas() [TestMethod] public void TestVote() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); UInt160 account = UInt160.Parse("01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4"); StorageKey keyAccount = CreateStorageKey(20, account.ToArray()); StorageKey keyValidator = CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray()); @@ -924,7 +924,7 @@ public void TestVote() internal (bool State, bool Result) Transfer4TesingOnBalanceChanging(BigInteger amount, bool addVotes) { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); _persistingBlock.Header.Index = 1; var engine = ApplicationEngine.Create(TriggerType.Application, TestBlockchain.TheNeoSystem.GenesisBlock, snapshot, _persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); ScriptBuilder sb = new(); diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs index 703253364c..a8d9495b5c 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs @@ -40,7 +40,7 @@ public void TestSetup() [TestMethod] public void Check_Default() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); var ret = NativeContract.Policy.Call(snapshot, "getFeePerByte"); ret.Should().BeOfType(); @@ -56,7 +56,7 @@ public void Check_Default() [TestMethod] public void Check_SetAttributeFee() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); // Fake blockchain Block block = new() @@ -115,7 +115,7 @@ public void Check_SetAttributeFee() [TestMethod] public void Check_SetFeePerByte() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); // Fake blockchain @@ -154,7 +154,7 @@ public void Check_SetFeePerByte() [TestMethod] public void Check_SetBaseExecFee() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); // Fake blockchain @@ -204,7 +204,7 @@ public void Check_SetBaseExecFee() [TestMethod] public void Check_SetStoragePrice() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); // Fake blockchain @@ -254,7 +254,7 @@ public void Check_SetStoragePrice() [TestMethod] public void Check_BlockAccount() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); // Fake blockchain @@ -310,7 +310,7 @@ public void Check_BlockAccount() [TestMethod] public void Check_Block_UnblockAccount() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshot.CloneCache(); // Fake blockchain diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_RoleManagement.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_RoleManagement.cs index 25ca7ee0d6..7db5ce4e01 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_RoleManagement.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_RoleManagement.cs @@ -62,7 +62,7 @@ public void TestSetAndGet() List roles = new List() { Role.StateValidator, Role.Oracle, Role.NeoFSAlphabetNode, Role.P2PNotary }; foreach (var role in roles) { - var snapshot1 = _snapshot.CreateSnapshot(); + var snapshot1 = _snapshot.CloneCache(); UInt160 committeeMultiSigAddr = NativeContract.NEO.GetCommitteeAddress(snapshot1); List notifications = new List(); EventHandler ev = (o, e) => notifications.Add(e); @@ -79,7 +79,7 @@ public void TestSetAndGet() ApplicationEngine.Notify -= ev; notifications.Count.Should().Be(1); notifications[0].EventName.Should().Be("Designation"); - var snapshot2 = _snapshot.CreateSnapshot(); + var snapshot2 = _snapshot.CloneCache(); ret = NativeContract.RoleManagement.Call( snapshot2, "getDesignatedByRole", diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs index f36fa2cfa4..b5c9c198ec 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; using Neo.SmartContract.Native; @@ -53,6 +54,9 @@ public void TestItoaAtoi() Assert.ThrowsException(() => StdLib.Atoi("a", 10)); Assert.ThrowsException(() => StdLib.Atoi("g", 16)); Assert.ThrowsException(() => StdLib.Atoi("a", 11)); + + StdLib.Atoi(StdLib.Itoa(BigInteger.One, 10)).Should().Be(BigInteger.One); + StdLib.Atoi(StdLib.Itoa(BigInteger.MinusOne, 10)).Should().Be(BigInteger.MinusOne); } [TestMethod] diff --git a/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs b/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs index 51f26d22eb..1edcfbd9ca 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Akka.TestKit.Xunit2; +using Akka.Util.Internal; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; @@ -476,7 +477,7 @@ public void TestBlockchain_GetTransactionHeight() BlockIndex = 0, Transaction = TestUtils.CreateRandomHashTransaction() }; - UT_SmartContractHelper.TransactionAdd(engine.Snapshot, state); + TestUtils.TransactionAdd(engine.Snapshot, state); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.Ledger.Hash, "getTransactionHeight", state.Transaction.Hash); @@ -507,6 +508,41 @@ public void TestBlockchain_GetContract() NativeContract.ContractManagement.GetContract(engine.Snapshot, state.Hash).Hash.Should().Be(state.Hash); } + [TestMethod] + public void TestBlockchain_GetContractById() + { + var engine = GetEngine(true, true); + var contract = NativeContract.ContractManagement.GetContractById(engine.Snapshot, -1); + contract.Id.Should().Be(-1); + contract.Manifest.Name.Should().Be(nameof(ContractManagement)); + } + + [TestMethod] + public void TestBlockchain_HasMethod() + { + var engine = GetEngine(true, true); + NativeContract.ContractManagement.HasMethod(engine.Snapshot, NativeContract.NEO.Hash, "symbol", 0).Should().Be(true); + NativeContract.ContractManagement.HasMethod(engine.Snapshot, NativeContract.NEO.Hash, "transfer", 4).Should().Be(true); + } + + [TestMethod] + public void TestBlockchain_ListContracts() + { + var engine = GetEngine(true, true); + var list = NativeContract.ContractManagement.ListContracts(engine.Snapshot); + list.ForEach(p => p.Id.Should().BeLessThan(0)); + + var snapshot = TestBlockchain.GetTestSnapshot(); + var state = TestUtils.GetContract(); + snapshot.AddContract(state.Hash, state); + engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + engine.LoadScript(new byte[] { 0x01 }); + NativeContract.ContractManagement.GetContract(engine.Snapshot, state.Hash).Hash.Should().Be(state.Hash); + + var list2 = NativeContract.ContractManagement.ListContracts(engine.Snapshot); + list2.Count().Should().Be(list.Count() + 1); + } + [TestMethod] public void TestStorage_GetContext() { @@ -739,5 +775,92 @@ private static ApplicationEngine GetEngine(bool hasContainer = false, bool hasSn if (addScript) engine.LoadScript(new byte[] { 0x01 }); return engine; } + + [TestMethod] + public void TestVerifyWithECDsaV0() + { + var privateKey = new byte[32]; + using var rng = System.Security.Cryptography.RandomNumberGenerator.Create(); + rng.GetBytes(privateKey); + var publicKeyR1 = new KeyPair(privateKey).PublicKey.ToArray(); + var publicKeyK1 = (Neo.Cryptography.ECC.ECCurve.Secp256k1.G * privateKey).ToArray(); + var hexMessage = "Hello, world!"u8.ToArray(); + var signatureR1 = Crypto.Sign(hexMessage, privateKey, Neo.Cryptography.ECC.ECCurve.Secp256r1); + var signatureK1 = Crypto.Sign(hexMessage, privateKey, Neo.Cryptography.ECC.ECCurve.Secp256k1); + + var result = CryptoLib.VerifyWithECDsaV0(hexMessage, publicKeyR1, signatureR1, NamedCurveHash.secp256r1SHA256); + result.Should().BeTrue(); + result = CryptoLib.VerifyWithECDsaV0(hexMessage, publicKeyK1, signatureK1, NamedCurveHash.secp256k1SHA256); + result.Should().BeTrue(); + result = CryptoLib.VerifyWithECDsaV0(hexMessage, publicKeyK1, new byte[0], NamedCurveHash.secp256k1SHA256); + result.Should().BeFalse(); + Assert.ThrowsException(() => CryptoLib.VerifyWithECDsaV0(hexMessage, publicKeyK1, new byte[64], NamedCurveHash.secp256r1Keccak256)); + } + + [TestMethod] + public void TestSha256() + { + var input = "Hello, world!"u8.ToArray(); + var actualHash = CryptoLib.Sha256(input); + var expectedHash = "315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3"; + actualHash.ToHexString().Should().Be(expectedHash); + } + + [TestMethod] + public void TestRIPEMD160() + { + var input = "Hello, world!"u8.ToArray(); + var actualHash = CryptoLib.RIPEMD160(input); + var expectedHash = "58262d1fbdbe4530d8865d3518c6d6e41002610f"; + actualHash.ToHexString().Should().Be(expectedHash); + } + + [TestMethod] + public void TestMurmur32() + { + var input = "Hello, world!"u8.ToArray(); + var actualHash = CryptoLib.Murmur32(input, 0); + var expectedHash = "433e36c0"; + actualHash.ToHexString().Should().Be(expectedHash); + } + + [TestMethod] + public void TestGetBlockHash() + { + var snapshot = GetEngine(true, true).Snapshot; + var hash = LedgerContract.Ledger.GetBlockHash(snapshot, 0); + var hash2 = LedgerContract.Ledger.GetBlock(snapshot, 0).Hash; + var hash3 = LedgerContract.Ledger.GetHeader(snapshot, 0).Hash; + hash.ToString().Should().Be(hash2.ToString()); + hash.ToString().Should().Be(hash3.ToString()); + hash.ToString().Should().Be("0x1f4d1defa46faa5e7b9b8d3f79a06bec777d7c26c4aa5f6f5899a291daa87c15"); + LedgerContract.Ledger.ContainsBlock(snapshot, hash).Should().BeTrue(); + } + + [TestMethod] + public void TestGetCandidateVote() + { + var snapshot = GetEngine(true, true).Snapshot; + var vote = LedgerContract.NEO.GetCandidateVote(snapshot, new ECPoint()); + vote.Should().Be(-1); + } + + [TestMethod] + public void TestContractPermissionDescriptorEquals() + { + var descriptor1 = ContractPermissionDescriptor.CreateWildcard(); + descriptor1.Equals(null).Should().BeFalse(); + descriptor1.Equals(null as object).Should().BeFalse(); + var descriptor2 = ContractPermissionDescriptor.Create(LedgerContract.NEO.Hash); + var descriptor3 = ContractPermissionDescriptor.Create(hash: null); + descriptor1.Equals(descriptor3).Should().BeTrue(); + descriptor1.Equals(descriptor3 as object).Should().BeTrue(); + var descriptor4 = ContractPermissionDescriptor.Create(group: null); + var descriptor5 = ContractPermissionDescriptor.Create(group: new ECPoint()); + descriptor1.Equals(descriptor4).Should().BeTrue(); + descriptor2.Equals(descriptor3).Should().BeFalse(); + descriptor5.Equals(descriptor3).Should().BeFalse(); + descriptor5.Equals(descriptor5).Should().BeTrue(); + } } } diff --git a/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs b/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs index c14b15c241..f864fbe749 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs @@ -27,9 +27,6 @@ namespace Neo.UnitTests.SmartContract [TestClass] public class UT_SmartContractHelper { - const byte Prefix_Block = 5; - const byte Prefix_BlockHash = 9; - const byte Prefix_Transaction = 11; [TestMethod] public void TestIsMultiSigContract() @@ -127,9 +124,9 @@ public void TestIsStandardContract() [TestMethod] public void TestVerifyWitnesses() { - var snapshot1 = TestBlockchain.GetTestSnapshot().CreateSnapshot(); + var snapshot1 = TestBlockchain.GetTestSnapshot().CloneCache(); UInt256 index1 = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"); - BlocksAdd(snapshot1, index1, new TrimmedBlock() + TestUtils.BlocksAdd(snapshot1, index1, new TrimmedBlock() { Header = new Header { @@ -141,7 +138,7 @@ public void TestVerifyWitnesses() }, Hashes = new UInt256[1] { UInt256.Zero }, }); - BlocksDelete(snapshot1, index1); + TestUtils.BlocksDelete(snapshot1, index1); Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(new Header() { PrevHash = index1 }, TestProtocolSettings.Default, snapshot1, 100)); var snapshot2 = TestBlockchain.GetTestSnapshot(); @@ -158,7 +155,7 @@ public void TestVerifyWitnesses() }, Hashes = new UInt256[1] { UInt256.Zero }, }; - BlocksAdd(snapshot2, index2, block2); + TestUtils.BlocksAdd(snapshot2, index2, block2); Header header2 = new() { PrevHash = index2, Witness = new Witness { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } }; snapshot2.AddContract(UInt160.Zero, new ContractState()); @@ -179,7 +176,7 @@ public void TestVerifyWitnesses() }, Hashes = new UInt256[1] { UInt256.Zero }, }; - BlocksAdd(snapshot3, index3, block3); + TestUtils.BlocksAdd(snapshot3, index3, block3); Header header3 = new() { PrevHash = index3, @@ -213,25 +210,5 @@ public void TestVerifyWitnesses() Assert.AreEqual(true, Neo.SmartContract.Helper.VerifyWitnesses(tx, TestProtocolSettings.Default, snapshot3, 1000)); } - - private static void BlocksDelete(DataCache snapshot, UInt256 hash) - { - snapshot.Delete(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, hash)); - snapshot.Delete(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash)); - } - - public static void TransactionAdd(DataCache snapshot, params TransactionState[] txs) - { - foreach (TransactionState tx in txs) - { - snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, tx.Transaction.Hash), new StorageItem(tx)); - } - } - - public static void BlocksAdd(DataCache snapshot, UInt256 hash, TrimmedBlock block) - { - snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, block.Index), new StorageItem(hash.ToArray())); - snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash), new StorageItem(block.ToArray())); - } } } diff --git a/tests/Neo.UnitTests/SmartContract/UT_Storage.cs b/tests/Neo.UnitTests/SmartContract/UT_Storage.cs new file mode 100644 index 0000000000..b52ea3046b --- /dev/null +++ b/tests/Neo.UnitTests/SmartContract/UT_Storage.cs @@ -0,0 +1,74 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Storage.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.SmartContract; +using Neo.VM; +using Neo.VM.Types; +using System; +using System.Linq; +using System.Numerics; + +namespace Neo.UnitTests.SmartContract +{ + [TestClass] + public class UT_Storage + { + [TestMethod] + public void TestStorageKey() + { + // Test data + byte[] keyData = [0x00, 0x00, 0x00, 0x00, 0x12]; + var keyMemory = new ReadOnlyMemory(keyData); + + // Test implicit conversion from byte[] to StorageKey + StorageKey storageKeyFromArray = keyData; + Assert.AreEqual(0, storageKeyFromArray.Id); + Assert.IsTrue(keyMemory.Span.ToArray().Skip(sizeof(int)).SequenceEqual(storageKeyFromArray.Key.Span.ToArray())); + + // Test implicit conversion from ReadOnlyMemory to StorageKey + StorageKey storageKeyFromMemory = keyMemory; + Assert.AreEqual(0, storageKeyFromMemory.Id); + Assert.IsTrue(keyMemory.Span.ToArray().Skip(sizeof(int)).SequenceEqual(storageKeyFromMemory.Key.Span.ToArray())); + + // Test CreateSearchPrefix method + byte[] prefix = { 0xAA }; + var searchPrefix = StorageKey.CreateSearchPrefix(0, prefix); + var expectedPrefix = BitConverter.GetBytes(0).Concat(prefix).ToArray(); + Assert.IsTrue(expectedPrefix.SequenceEqual(searchPrefix)); + + // Test Equals method + var storageKey1 = new StorageKey { Id = 0, Key = keyMemory }; + var storageKey2 = new StorageKey { Id = 0, Key = keyMemory }; + var storageKeyDifferentId = new StorageKey { Id = 0 + 1, Key = keyMemory }; + var storageKeyDifferentKey = new StorageKey { Id = 0, Key = new ReadOnlyMemory([0x04]) }; + Assert.AreEqual(storageKey1, storageKey2); + Assert.AreNotEqual(storageKey1, storageKeyDifferentId); + Assert.AreNotEqual(storageKey1, storageKeyDifferentKey); + } + + [TestMethod] + public void TestStorageItem() + { + // Test data + byte[] keyData = [0x00, 0x00, 0x00, 0x00, 0x12]; + BigInteger bigInteger = new BigInteger(1234567890); + + // Test implicit conversion from byte[] to StorageItem + StorageItem storageItemFromArray = keyData; + Assert.IsTrue(keyData.SequenceEqual(storageItemFromArray.Value.Span.ToArray())); + + // Test implicit conversion from BigInteger to StorageItem + StorageItem storageItemFromBigInteger = bigInteger; + Assert.AreEqual(bigInteger, (BigInteger)storageItemFromBigInteger); + } + } +} diff --git a/tests/Neo.UnitTests/SmartContract/UT_Syscalls.cs b/tests/Neo.UnitTests/SmartContract/UT_Syscalls.cs index 7791b8acef..e027fa4d88 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_Syscalls.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_Syscalls.cs @@ -81,10 +81,11 @@ public void System_Blockchain_GetBlock() const byte Prefix_Transaction = 11; const byte Prefix_CurrentBlock = 12; + TestUtils.BlocksAdd(snapshot, block.Hash, block); + var height = snapshot[NativeContract.Ledger.CreateStorageKey(Prefix_CurrentBlock)].GetInteroperable(); height.Index = block.Index + TestProtocolSettings.Default.MaxTraceableBlocks; - UT_SmartContractHelper.BlocksAdd(snapshot, block.Hash, block); snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, tx.Hash), new StorageItem(new TransactionState { BlockIndex = block.Index, diff --git a/tests/Neo.UnitTests/TestBlockchain.cs b/tests/Neo.UnitTests/TestBlockchain.cs index f7e06d0595..7bf26edbd7 100644 --- a/tests/Neo.UnitTests/TestBlockchain.cs +++ b/tests/Neo.UnitTests/TestBlockchain.cs @@ -43,7 +43,7 @@ internal static void ResetStore() internal static DataCache GetTestSnapshot() { - return TheNeoSystem.GetSnapshot().CreateSnapshot(); + return TheNeoSystem.GetSnapshotCache().CloneCache(); } } } diff --git a/tests/Neo.UnitTests/TestProtocolSettings.cs b/tests/Neo.UnitTests/TestProtocolSettings.cs index b12f5c9a85..3f89fdd5f9 100644 --- a/tests/Neo.UnitTests/TestProtocolSettings.cs +++ b/tests/Neo.UnitTests/TestProtocolSettings.cs @@ -15,12 +15,12 @@ namespace Neo.UnitTests { public static class TestProtocolSettings { - public static ProtocolSettings Default = new() + public static readonly ProtocolSettings Default = new() { Network = 0x334F454Eu, AddressVersion = ProtocolSettings.Default.AddressVersion, - StandbyCommittee = new[] - { + StandbyCommittee = + [ //Validators ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", ECCurve.Secp256r1), ECPoint.Parse("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", ECCurve.Secp256r1), @@ -44,16 +44,42 @@ public static class TestProtocolSettings ECPoint.Parse("0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d", ECCurve.Secp256r1), ECPoint.Parse("03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc", ECCurve.Secp256r1), ECPoint.Parse("02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a", ECCurve.Secp256r1) - }, + ], ValidatorsCount = 7, - SeedList = new[] - { + SeedList = + [ "seed1.neo.org:10333", "seed2.neo.org:10333", "seed3.neo.org:10333", "seed4.neo.org:10333", "seed5.neo.org:10333" - }, + ], + MillisecondsPerBlock = ProtocolSettings.Default.MillisecondsPerBlock, + MaxTransactionsPerBlock = ProtocolSettings.Default.MaxTransactionsPerBlock, + MemoryPoolMaxTransactions = ProtocolSettings.Default.MemoryPoolMaxTransactions, + MaxTraceableBlocks = ProtocolSettings.Default.MaxTraceableBlocks, + InitialGasDistribution = ProtocolSettings.Default.InitialGasDistribution, + Hardforks = ProtocolSettings.Default.Hardforks + }; + + public static readonly ProtocolSettings SoleNode = new() + { + Network = 0x334F454Eu, + AddressVersion = ProtocolSettings.Default.AddressVersion, + StandbyCommittee = + [ + //Validators + ECPoint.Parse("0278ed78c917797b637a7ed6e7a9d94e8c408444c41ee4c0a0f310a256b9271eda", ECCurve.Secp256r1) + ], + ValidatorsCount = 1, + SeedList = + [ + "seed1.neo.org:10333", + "seed2.neo.org:10333", + "seed3.neo.org:10333", + "seed4.neo.org:10333", + "seed5.neo.org:10333" + ], MillisecondsPerBlock = ProtocolSettings.Default.MillisecondsPerBlock, MaxTransactionsPerBlock = ProtocolSettings.Default.MaxTransactionsPerBlock, MemoryPoolMaxTransactions = ProtocolSettings.Default.MemoryPoolMaxTransactions, diff --git a/tests/Neo.UnitTests/TestUtils.Block.cs b/tests/Neo.UnitTests/TestUtils.Block.cs new file mode 100644 index 0000000000..bed85131e6 --- /dev/null +++ b/tests/Neo.UnitTests/TestUtils.Block.cs @@ -0,0 +1,198 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestUtils.Block.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.Util.Internal; +using Neo.Cryptography; +using Neo.IO; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using Neo.Wallets.NEP6; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.UnitTests; + +public partial class TestUtils +{ + const byte Prefix_Block = 5; + const byte Prefix_BlockHash = 9; + const byte Prefix_Transaction = 11; + const byte Prefix_CurrentBlock = 12; + + /// + /// Test Util function SetupHeaderWithValues + /// + /// The snapshot of the current storage provider. Can be null. + /// The header to be assigned + /// PrevHash + /// MerkleRoot + /// NextConsensus + /// Timestamp + /// Index + /// Nonce + /// Witness + public static void SetupHeaderWithValues(DataCache snapshot, Header header, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out ulong nonceVal, out uint indexVal, out Witness scriptVal) + { + header.PrevHash = val256; + header.MerkleRoot = merkRootVal = UInt256.Parse("0x6226416a0e5aca42b5566f5a19ab467692688ba9d47986f6981a7f747bba2772"); + header.Timestamp = timestampVal = new DateTime(2024, 06, 05, 0, 33, 1, 001, DateTimeKind.Utc).ToTimestampMS(); + if (snapshot != null) + header.Index = indexVal = NativeContract.Ledger.CurrentIndex(snapshot) + 1; + else + header.Index = indexVal = 0; + header.Nonce = nonceVal = 0; + header.NextConsensus = val160 = UInt160.Zero; + header.Witness = scriptVal = new Witness + { + InvocationScript = Array.Empty(), + VerificationScript = new[] { (byte)OpCode.PUSH1 } + }; + } + + public static void SetupBlockWithValues(DataCache snapshot, Block block, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out ulong nonceVal, out uint indexVal, out Witness scriptVal, out Transaction[] transactionsVal, int numberOfTransactions) + { + Header header = new Header(); + SetupHeaderWithValues(snapshot, header, val256, out merkRootVal, out val160, out timestampVal, out nonceVal, out indexVal, out scriptVal); + + transactionsVal = new Transaction[numberOfTransactions]; + if (numberOfTransactions > 0) + { + for (int i = 0; i < numberOfTransactions; i++) + { + transactionsVal[i] = GetTransaction(UInt160.Zero); + } + } + + block.Header = header; + block.Transactions = transactionsVal; + + header.MerkleRoot = merkRootVal = MerkleTree.ComputeRoot(block.Transactions.Select(p => p.Hash).ToArray()); + } + + public static Block CreateBlockWithValidTransactions(DataCache snapshot, NEP6Wallet wallet, WalletAccount account, int numberOfTransactions) + { + var transactions = new List(); + for (var i = 0; i < numberOfTransactions; i++) + { + transactions.Add(CreateValidTx(snapshot, wallet, account)); + } + + return CreateBlockWithValidTransactions(snapshot, account, transactions.ToArray()); + } + + public static Block CreateBlockWithValidTransactions(DataCache snapshot, WalletAccount account, Transaction[] transactions) + { + var block = new Block(); + var header = new Header(); + var state = snapshot.TryGet(NativeContract.Ledger.CreateStorageKey(Prefix_CurrentBlock)).GetInteroperable(); + SetupHeaderWithValues(snapshot, header, state.Hash, out _, out _, out _, out _, out _, out _); + + block.Header = header; + block.Transactions = transactions; + + header.MerkleRoot = MerkleTree.ComputeRoot(block.Transactions.Select(p => p.Hash).ToArray()); + var contract = Contract.CreateMultiSigContract(1, TestProtocolSettings.SoleNode.StandbyCommittee); + var sc = new ContractParametersContext(snapshot, header, TestProtocolSettings.SoleNode.Network); + var signature = header.Sign(account.GetKey(), TestProtocolSettings.SoleNode.Network); + sc.AddSignature(contract, TestProtocolSettings.SoleNode.StandbyCommittee[0], signature.ToArray()); + block.Header.Witness = sc.GetWitnesses()[0]; + + return block; + } + + public static void BlocksDelete(DataCache snapshot, UInt256 hash) + { + snapshot.Delete(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, hash)); + snapshot.Delete(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash)); + } + + public static void TransactionAdd(DataCache snapshot, params TransactionState[] txs) + { + foreach (var tx in txs) + { + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, tx.Transaction.Hash), new StorageItem(tx)); + } + } + + public static void BlocksAdd(DataCache snapshot, UInt256 hash, TrimmedBlock block) + { + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, block.Index), new StorageItem(hash.ToArray())); + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash), new StorageItem(block.ToArray())); + + var state = snapshot.GetAndChange(NativeContract.Ledger.CreateStorageKey(Prefix_CurrentBlock), () => new StorageItem(new HashIndexState())).GetInteroperable(); + state.Hash = hash; + state.Index = block.Index; + } + + public static void BlocksAdd(DataCache snapshot, UInt256 hash, Block block) + { + + block.Transactions.ForEach(tx => + { + var state = new TransactionState + { + BlockIndex = block.Index, + Transaction = tx + }; + TransactionAdd(snapshot, state); + }); + + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, block.Index), new StorageItem(hash.ToArray())); + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash), new StorageItem(block.ToTrimmedBlock().ToArray())); + var state = snapshot.GetAndChange(NativeContract.Ledger.CreateStorageKey(Prefix_CurrentBlock), () => new StorageItem(new HashIndexState())).GetInteroperable(); + state.Hash = hash; + state.Index = block.Index; + } + + public static string CreateInvalidBlockFormat() + { + // Create a valid block + var validBlock = new Block + { + Header = new Header + { + Version = 0, + PrevHash = UInt256.Zero, + MerkleRoot = UInt256.Zero, + Timestamp = 0, + Index = 0, + NextConsensus = UInt160.Zero, + Witness = new Witness { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } + }, + Transactions = [] + }; + + // Serialize the valid block + byte[] validBlockBytes = validBlock.ToArray(); + + // Corrupt the serialized data + // For example, we can truncate the data by removing the last few bytes + byte[] invalidBlockBytes = new byte[validBlockBytes.Length - 5]; + Array.Copy(validBlockBytes, invalidBlockBytes, invalidBlockBytes.Length); + + // Convert the corrupted data to a Base64 string + return Convert.ToBase64String(invalidBlockBytes); + } + + public static TrimmedBlock ToTrimmedBlock(this Block block) + { + return new TrimmedBlock + { + Header = block.Header, + Hashes = block.Transactions.Select(p => p.Hash).ToArray() + }; + } +} diff --git a/tests/Neo.UnitTests/TestUtils.Transaction.cs b/tests/Neo.UnitTests/TestUtils.Transaction.cs new file mode 100644 index 0000000000..f96ac8ec74 --- /dev/null +++ b/tests/Neo.UnitTests/TestUtils.Transaction.cs @@ -0,0 +1,129 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestUtils.Transaction.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; +using Neo.IO; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using Neo.Wallets.NEP6; +using System; +using System.IO; +using System.Linq; + +namespace Neo.UnitTests; + +public partial class TestUtils +{ + public static Transaction CreateInvalidTransaction(DataCache snapshot, NEP6Wallet wallet, WalletAccount account, InvalidTransactionType type, UInt256 conflict = null) + { + var rand = new Random(); + var sender = account.ScriptHash; + + var tx = new Transaction + { + Version = 0, + Nonce = (uint)rand.Next(), + ValidUntilBlock = NativeContract.Ledger.CurrentIndex(snapshot) + wallet.ProtocolSettings.MaxValidUntilBlockIncrement, + Signers = [new Signer { Account = sender, Scopes = WitnessScope.CalledByEntry }], + Attributes = [], + Script = new[] { (byte)OpCode.RET } + }; + + switch (type) + { + case InvalidTransactionType.InsufficientBalance: + // Set an unrealistically high system fee + tx.SystemFee = long.MaxValue; + break; + case InvalidTransactionType.InvalidScript: + // Use an invalid script + tx.Script = new byte[] { 0xFF }; + break; + case InvalidTransactionType.InvalidAttribute: + // Add an invalid attribute + tx.Attributes = [new InvalidAttribute()]; + break; + case InvalidTransactionType.Oversized: + // Make the transaction oversized + tx.Script = new byte[Transaction.MaxTransactionSize]; + break; + case InvalidTransactionType.Expired: + // Set an expired ValidUntilBlock + tx.ValidUntilBlock = NativeContract.Ledger.CurrentIndex(snapshot) - 1; + break; + case InvalidTransactionType.Conflicting: + // To create a conflicting transaction, we'd need another valid transaction. + // For simplicity, we'll just add a Conflicts attribute with a random hash. + tx.Attributes = [new Conflicts { Hash = conflict }]; + break; + } + + var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + Assert.IsNull(data.GetSignatures(tx.Sender)); + Assert.IsTrue(wallet.Sign(data)); + Assert.IsTrue(data.Completed); + Assert.AreEqual(1, data.GetSignatures(tx.Sender).Count); + tx.Witnesses = data.GetWitnesses(); + if (type == InvalidTransactionType.InvalidSignature) + { + tx.Witnesses[0] = new Witness + { + InvocationScript = new byte[] { (byte)OpCode.PUSHDATA1, 64 }.Concat(new byte[64]).ToArray(), + VerificationScript = data.GetWitnesses()[0].VerificationScript + }; + } + + return tx; + } + + public enum InvalidTransactionType + { + InsufficientBalance, + InvalidSignature, + InvalidScript, + InvalidAttribute, + Oversized, + Expired, + Conflicting + } + + class InvalidAttribute : TransactionAttribute + { + public override TransactionAttributeType Type => (TransactionAttributeType)0xFF; + public override bool AllowMultiple { get; } + protected override void DeserializeWithoutType(ref MemoryReader reader) { } + protected override void SerializeWithoutType(BinaryWriter writer) { } + } + + public static void AddTransactionToBlockchain(DataCache snapshot, Transaction tx) + { + var block = new Block + { + Header = new Header + { + Index = NativeContract.Ledger.CurrentIndex(snapshot) + 1, + PrevHash = NativeContract.Ledger.CurrentHash(snapshot), + MerkleRoot = new UInt256(Crypto.Hash256(tx.Hash.ToArray())), + Timestamp = TimeProvider.Current.UtcNow.ToTimestampMS(), + NextConsensus = UInt160.Zero, + Witness = new Witness { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } + }, + Transactions = [tx] + }; + + BlocksAdd(snapshot, block.Hash, block); + } +} diff --git a/tests/Neo.UnitTests/TestUtils.cs b/tests/Neo.UnitTests/TestUtils.cs index f8f63de53a..c1694347a7 100644 --- a/tests/Neo.UnitTests/TestUtils.cs +++ b/tests/Neo.UnitTests/TestUtils.cs @@ -10,25 +10,44 @@ // modifications are permitted. using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; using Neo.Cryptography.ECC; using Neo.IO; using Neo.Json; using Neo.Network.P2P.Payloads; +using Neo.Persistence; using Neo.SmartContract; using Neo.SmartContract.Manifest; using Neo.SmartContract.Native; using Neo.VM; +using Neo.Wallets; using Neo.Wallets.NEP6; using System; +using System.Collections.Generic; using System.Linq; +using System.Numerics; namespace Neo.UnitTests { - public static class TestUtils + public static partial class TestUtils { public static readonly Random TestRandom = new Random(1337); // use fixed seed for guaranteed determinism + public static UInt256 RandomUInt256() + { + byte[] data = new byte[32]; + TestRandom.NextBytes(data); + return new UInt256(data); + } + + public static UInt160 RandomUInt160() + { + byte[] data = new byte[20]; + TestRandom.NextBytes(data); + return new UInt160(data); + } + public static ContractManifest CreateDefaultManifest() { return new ContractManifest() @@ -111,6 +130,58 @@ public static NEP6Wallet GenerateTestWallet(string password) return new NEP6Wallet(null, password, TestProtocolSettings.Default, wallet); } + public static Transaction CreateValidTx(DataCache snapshot, NEP6Wallet wallet, WalletAccount account) + { + return CreateValidTx(snapshot, wallet, account.ScriptHash, (uint)new Random().Next()); + } + + public static Transaction CreateValidTx(DataCache snapshot, NEP6Wallet wallet, UInt160 account, uint nonce) + { + var tx = wallet.MakeTransaction(snapshot, [ + new TransferOutput + { + AssetId = NativeContract.GAS.Hash, + ScriptHash = account, + Value = new BigDecimal(BigInteger.One, 8) + } + ], + account); + + tx.Nonce = nonce; + + var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + Assert.IsNull(data.GetSignatures(tx.Sender)); + Assert.IsTrue(wallet.Sign(data)); + Assert.IsTrue(data.Completed); + Assert.AreEqual(1, data.GetSignatures(tx.Sender).Count()); + + tx.Witnesses = data.GetWitnesses(); + return tx; + } + + public static Transaction CreateValidTx(DataCache snapshot, NEP6Wallet wallet, UInt160 account, uint nonce, UInt256[] conflicts) + { + var tx = wallet.MakeTransaction(snapshot, [ + new TransferOutput + { + AssetId = NativeContract.GAS.Hash, + ScriptHash = account, + Value = new BigDecimal(BigInteger.One, 8) + } + ], + account); + tx.Attributes = conflicts.Select(conflict => new Conflicts { Hash = conflict }).ToArray(); + tx.Nonce = nonce; + + var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + Assert.IsNull(data.GetSignatures(tx.Sender)); + Assert.IsTrue(wallet.Sign(data)); + Assert.IsTrue(data.Completed); + Assert.AreEqual(1, data.GetSignatures(tx.Sender).Count); + tx.Witnesses = data.GetWitnesses(); + return tx; + } + public static Transaction GetTransaction(UInt160 sender) { return new Transaction @@ -133,7 +204,7 @@ public static Transaction GetTransaction(UInt160 sender) }; } - internal static ContractState GetContract(string method = "test", int parametersCount = 0) + public static ContractState GetContract(string method = "test", int parametersCount = 0) { NefFile nef = new() { @@ -188,50 +259,13 @@ internal static StorageKey GetStorageKey(int id, byte[] keyValue) }; } - /// - /// Test Util function SetupHeaderWithValues - /// - /// The header to be assigned - /// PrevHash - /// MerkleRoot - /// NextConsensus - /// Timestamp - /// Index - /// Nonce - /// Witness - public static void SetupHeaderWithValues(Header header, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out ulong nonceVal, out uint indexVal, out Witness scriptVal) - { - header.PrevHash = val256; - header.MerkleRoot = merkRootVal = UInt256.Parse("0x6226416a0e5aca42b5566f5a19ab467692688ba9d47986f6981a7f747bba2772"); - header.Timestamp = timestampVal = new DateTime(1980, 06, 01, 0, 0, 1, 001, DateTimeKind.Utc).ToTimestampMS(); // GMT: Sunday, June 1, 1980 12:00:01.001 AM - header.Index = indexVal = 0; - header.Nonce = nonceVal = 0; - header.NextConsensus = val160 = UInt160.Zero; - header.Witness = scriptVal = new Witness - { - InvocationScript = new byte[0], - VerificationScript = new[] { (byte)OpCode.PUSH1 } - }; - } - - public static void SetupBlockWithValues(Block block, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out ulong nonceVal, out uint indexVal, out Witness scriptVal, out Transaction[] transactionsVal, int numberOfTransactions) + public static void StorageItemAdd(DataCache snapshot, int id, byte[] keyValue, byte[] value) { - Header header = new Header(); - SetupHeaderWithValues(header, val256, out merkRootVal, out val160, out timestampVal, out nonceVal, out indexVal, out scriptVal); - - transactionsVal = new Transaction[numberOfTransactions]; - if (numberOfTransactions > 0) + snapshot.Add(new StorageKey { - for (int i = 0; i < numberOfTransactions; i++) - { - transactionsVal[i] = GetTransaction(UInt160.Zero); - } - } - - block.Header = header; - block.Transactions = transactionsVal; - - header.MerkleRoot = merkRootVal = MerkleTree.ComputeRoot(block.Transactions.Select(p => p.Hash).ToArray()); + Id = id, + Key = keyValue + }, new StorageItem(value)); } public static Transaction CreateRandomHashTransaction() @@ -254,6 +288,15 @@ public static Transaction CreateRandomHashTransaction() }; } + public static void FillMemoryPool(DataCache snapshot, NeoSystem system, NEP6Wallet wallet, WalletAccount account) + { + for (int i = 0; i < system.Settings.MemoryPoolMaxTransactions; i++) + { + var tx = CreateValidTx(snapshot, wallet, account); + system.MemPool.TryAdd(tx, snapshot); + } + } + public static T CopyMsgBySerialization(T serializableObj, T newObj) where T : ISerializable { MemoryReader reader = new(serializableObj.ToArray()); diff --git a/tests/Neo.UnitTests/UT_DataCache.cs b/tests/Neo.UnitTests/UT_DataCache.cs index 008f166b6f..01bd84c949 100644 --- a/tests/Neo.UnitTests/UT_DataCache.cs +++ b/tests/Neo.UnitTests/UT_DataCache.cs @@ -23,7 +23,7 @@ public class UT_DataCache public void TestCachedFind_Between() { var snapshot = TestBlockchain.GetTestSnapshot(); - var storages = snapshot.CreateSnapshot(); + var storages = snapshot.CloneCache(); var cache = new ClonedCache(storages); storages.Add @@ -62,7 +62,7 @@ public void TestCachedFind_Between() public void TestCachedFind_Last() { var snapshot = TestBlockchain.GetTestSnapshot(); - var storages = snapshot.CreateSnapshot(); + var storages = snapshot.CloneCache(); var cache = new ClonedCache(storages); storages.Add @@ -94,7 +94,7 @@ public void TestCachedFind_Last() public void TestCachedFind_Empty() { var snapshot = TestBlockchain.GetTestSnapshot(); - var storages = snapshot.CreateSnapshot(); + var storages = snapshot.CloneCache(); var cache = new ClonedCache(storages); cache.Add diff --git a/tests/Neo.UnitTests/UT_Helper.cs b/tests/Neo.UnitTests/UT_Helper.cs index b7dc4f1681..7fa3902cba 100644 --- a/tests/Neo.UnitTests/UT_Helper.cs +++ b/tests/Neo.UnitTests/UT_Helper.cs @@ -11,6 +11,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.IO.Caching; using Neo.Network.P2P; using Neo.SmartContract; @@ -190,16 +191,6 @@ public void TestGetVersion() version.Should().Be("0.0.0"); } - [TestMethod] - public void TestToByteArrayStandard() - { - BigInteger number = BigInteger.Zero; - Assert.AreEqual("", number.ToByteArrayStandard().ToHexString()); - - number = BigInteger.One; - Assert.AreEqual("01", number.ToByteArrayStandard().ToHexString()); - } - [TestMethod] public void TestNextBigIntegerForRandom() { diff --git a/tests/Neo.UnitTests/UT_ProtocolSettings.cs b/tests/Neo.UnitTests/UT_ProtocolSettings.cs index e5d829ef8b..97a36e5907 100644 --- a/tests/Neo.UnitTests/UT_ProtocolSettings.cs +++ b/tests/Neo.UnitTests/UT_ProtocolSettings.cs @@ -52,7 +52,7 @@ public void TestGetMillisecondsPerBlock() [TestMethod] public void HardForkTestBAndNotA() { - string json = CreateHKSettings("\"HF_Basilisk\": 4120000"); + string json = CreateHFSettings("\"HF_Basilisk\": 4120000"); var file = Path.GetTempFileName(); File.WriteAllText(file, json); @@ -74,7 +74,7 @@ public void HardForkTestBAndNotA() [TestMethod] public void HardForkTestAAndNotB() { - string json = CreateHKSettings("\"HF_Aspidochelone\": 0"); + string json = CreateHFSettings("\"HF_Aspidochelone\": 0"); var file = Path.GetTempFileName(); File.WriteAllText(file, json); @@ -96,7 +96,7 @@ public void HardForkTestAAndNotB() [TestMethod] public void HardForkTestNone() { - string json = CreateHKSettings(""); + string json = CreateHFSettings(""); var file = Path.GetTempFileName(); File.WriteAllText(file, json); @@ -117,14 +117,14 @@ public void HardForkTestNone() [TestMethod] public void HardForkTestAMoreThanB() { - string json = CreateHKSettings("\"HF_Aspidochelone\": 4120001, \"HF_Basilisk\": 4120000"); + string json = CreateHFSettings("\"HF_Aspidochelone\": 4120001, \"HF_Basilisk\": 4120000"); var file = Path.GetTempFileName(); File.WriteAllText(file, json); Assert.ThrowsException(() => ProtocolSettings.Load(file, false)); File.Delete(file); } - internal static string CreateHKSettings(string hf) + internal static string CreateHFSettings(string hf) { return @" {