diff --git a/.editorconfig b/.editorconfig index c8247e5496..ccbd29fd03 100644 --- a/.editorconfig +++ b/.editorconfig @@ -48,6 +48,11 @@ indent_size = 2 end_of_line = lf indent_size = 2 +# YAML files +[*.yml] +end_of_line = lf +indent_size = 2 + # Dotnet code style settings: [*.{cs,vb}] # Member can be made 'readonly' diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000..3d8e7d0d6c --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,77 @@ +name: Publish (docker-image) + +on: + release: + types: [published] + +env: + DOTNET_VERSION: 8.0.x + DIST_DIR: ./dist + +jobs: + neo-cli-build: + runs-on: ubuntu-latest + + steps: + - 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: Checkout (GitHub) + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Build (neo-cli) + run: | + dotnet publish ./src/Neo.CLI \ + --framework net8.0 \ + --configuration Release \ + --runtime linux-x64 \ + --self-contained true \ + --output ${{ env.DIST_DIR }} \ + --verbosity normal \ + -p:VersionPrefix=${{ env.APP_VERSION }} \ + -p:RuntimeIdentifier=linux-x64 \ + -p:SelfContained=true \ + -p:IncludeNativeLibrariesForSelfExtract=false \ + -p:PublishTrimmed=false \ + -p:PublishSingleFile=true \ + -p:PublishReadyToRun=true \ + -p:EnableCompressionInSingleFile=true \ + -p:DebugType=embedded \ + -p:ServerGarbageCollection=true + + - name: Build (LevelDbStore) + run: | + dotnet build ./src/Plugins/LevelDBStore \ + --framework net8.0 \ + --configuration Release \ + --output ${{ env.DIST_DIR }}/Plugins/LevelDBStore \ + --verbosity normal \ + -p:VersionPrefix=${{ env.APP_VERSION }} + + - name: Remove (junk) + run: | + rm -v ${{ env.DIST_DIR }}/Plugins/LevelDBStore/Neo* + rm -v ${{ env.DIST_DIR }}/Plugins/LevelDBStore/*.pdb + rm -v ${{ env.DIST_DIR }}/Plugins/LevelDBStore/*.xml + rm -v ${{ env.DIST_DIR }}/*.xml + + - name: Docker Login + run: | + docker login ghcr.io \ + --username ${{ github.repository_owner }} \ + --password ${{ secrets.GITHUB_TOKEN }} + + - name: Docker Build + run: | + docker build . \ + --file ./.neo/docker/neo-cli/Dockerfile \ + --tag ghcr.io/${{ github.repository_owner }}/neo-cli:latest \ + --tag ghcr.io/${{ github.repository_owner }}/neo-cli:${{ env.APP_VERSION }} \ + --push diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5294cd09f4..22cf4d08df 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 @@ -87,6 +89,7 @@ jobs: dotnet test ./tests/Neo.Plugins.OracleService.Tests --output ./bin/tests/Neo.Plugins.OracleService.Tests dotnet test ./tests/Neo.Plugins.RpcServer.Tests --output ./bin/tests/Neo.Plugins.RpcServer.Tests dotnet test ./tests/Neo.Plugins.Storage.Tests --output ./bin/tests/Neo.Plugins.Storage.Tests + dotnet test ./tests/Neo.Plugins.ApplicationLogs.Tests --output ./bin/tests/Neo.Plugins.ApplicationLogs.Tests - name: Coveralls if: matrix.os == 'ubuntu-latest' @@ -104,6 +107,8 @@ 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.Plugins.ApplicationLogs.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 +128,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/pkgs-delete.yml b/.github/workflows/pkgs-delete.yml index cf3471b551..75bbd9455e 100644 --- a/.github/workflows/pkgs-delete.yml +++ b/.github/workflows/pkgs-delete.yml @@ -61,67 +61,53 @@ jobs: shell: python - delete-git-pkgs: - name: Delete Old Nuget Packages + delete-git-docker-pkgs: + name: Delete Old Docker Images runs-on: ubuntu-latest steps: - - name: Delete Neo.Cryptography.BLS12_381 Package + - name: Delete Neo Package (docker) uses: actions/delete-package-versions@v4 + continue-on-error: true with: - package-name: Neo.Cryptography.BLS12_381 - package-type: nuget - min-versions-to-keep: 3 - delete-only-pre-release-versions: "true" - token: "${{ secrets.GITHUB_TOKEN }}" - - - name: Delete Neo.VM Package - uses: actions/delete-package-versions@v4 - with: - package-name: Neo.VM - package-type: nuget - min-versions-to-keep: 3 - delete-only-pre-release-versions: "true" - token: "${{ secrets.GITHUB_TOKEN }}" - - - name: Delete Neo.Json Package - uses: actions/delete-package-versions@v4 - with: - package-name: Neo.Json - package-type: nuget - min-versions-to-keep: 3 - delete-only-pre-release-versions: "true" + package-name: Neo + package-type: docker + min-versions-to-keep: 1 token: "${{ secrets.GITHUB_TOKEN }}" - - name: Delete Neo.IO Package - uses: actions/delete-package-versions@v4 - with: - package-name: Neo.IO - package-type: nuget - min-versions-to-keep: 3 - delete-only-pre-release-versions: "true" - token: "${{ secrets.GITHUB_TOKEN }}" + delete-git-nuget-pkgs: + name: Delete Old Nuget Packages + strategy: + matrix: + pkgs: + - "Neo.Plugins.StatesDumper" + - "Neo.Plugins.StateService" + - "Neo.Plugins.Storage.LevelDBStore" + - "Neo.Plugins.Storage.RocksDBStore" + - "Neo.Plugins.StorageDumper" + - "Neo.Plugins.TokensTracker" + - "Neo.Wallets.SQLite" + - "Neo.Consensus.DBFT" + - "Neo.ConsoleService" + - "Neo.Cryptography.MPT" + - "Neo.Extensions" + - "Neo.Network.RPC.RpcClient" + - "Neo.Plugins.ApplicationLogs" + - "Neo.Plugins.OracleService" + - "Neo.Plugins.RpcServer" + - "Neo.Cryptography.BLS12_381" + - "Neo.VM" + - "Neo.Json" + - "Neo.IO" + - "Neo" + runs-on: ubuntu-latest - - name: Delete Neo Package - uses: actions/delete-package-versions@v4 - with: - package-name: Neo - package-type: nuget - min-versions-to-keep: 3 - delete-only-pre-release-versions: "true" - token: "${{ secrets.GITHUB_TOKEN }}" - - name: Delete Neo.ConsoleService Package - uses: actions/delete-package-versions@v4 - with: - package-name: Neo.ConsoleService - package-type: nuget - min-versions-to-keep: 3 - delete-only-pre-release-versions: "true" - token: "${{ secrets.GITHUB_TOKEN }}" - - name: Delete Neo.Extensions Package + steps: + - name: Delete ${{ matrix.pkgs }} Package uses: actions/delete-package-versions@v4 + continue-on-error: true with: - package-name: Neo.Extensions + package-name: ${{ matrix.pkgs }} package-type: nuget min-versions-to-keep: 3 delete-only-pre-release-versions: "true" 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/.neo/docker/neo-cli/Dockerfile b/.neo/docker/neo-cli/Dockerfile new file mode 100644 index 0000000000..afdd97e597 --- /dev/null +++ b/.neo/docker/neo-cli/Dockerfile @@ -0,0 +1,8 @@ +FROM debian:stable-slim + +# Install the apt-get packages +RUN apt-get update +RUN apt-get install -y libicu-dev libleveldb-dev screen + +COPY ./dist /opt/neo-cli +RUN ln -s /opt/neo-cli/neo-cli /usr/bin diff --git a/README.md b/README.md index 407bd6a837..cc1c9b384a 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@

A modern distributed network for the Smart Economy.
- Documentation » + Documentation »

Neo diff --git a/benchmarks/Neo.Benchmarks/Benchmarks.cs b/benchmarks/Neo.Benchmarks/Benchmarks.POC.cs similarity index 87% rename from benchmarks/Neo.Benchmarks/Benchmarks.cs rename to benchmarks/Neo.Benchmarks/Benchmarks.POC.cs index 081f806622..f073c543a9 100644 --- a/benchmarks/Neo.Benchmarks/Benchmarks.cs +++ b/benchmarks/Neo.Benchmarks/Benchmarks.POC.cs @@ -1,6 +1,6 @@ // Copyright (C) 2015-2024 The Neo Project. // -// Benchmarks.cs file belongs to the neo project and is free +// Benchmarks.POC.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,19 +9,21 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using BenchmarkDotNet.Attributes; using Neo.Network.P2P.Payloads; using Neo.SmartContract; using Neo.VM; using System.Diagnostics; -namespace Neo; +namespace Neo.Benchmark; -static class Benchmarks +public class Benchmarks_PoCs { private static readonly ProtocolSettings protocol = ProtocolSettings.Load("config.json"); private static readonly NeoSystem system = new(protocol, (string)null); - public static void NeoIssue2725() + [Benchmark] + public void NeoIssue2725() { // https://github.com/neo-project/neo/issues/2725 // L00: INITSSLOT 1 @@ -67,13 +69,10 @@ 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(); engine.Execute(); - stopwatch.Stop(); Debug.Assert(engine.State == VMState.FAULT); - Console.WriteLine($"Benchmark: {name},\tTime: {stopwatch.Elapsed}"); } } diff --git a/benchmarks/Neo.Benchmarks/Benchmarks.UInt160.cs b/benchmarks/Neo.Benchmarks/Benchmarks.UInt160.cs new file mode 100644 index 0000000000..37fa701ff9 --- /dev/null +++ b/benchmarks/Neo.Benchmarks/Benchmarks.UInt160.cs @@ -0,0 +1,156 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Benchmarks.UInt160.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 BenchmarkDotNet.Attributes; + +namespace Neo.Benchmark; + +public class Benchmarks_UInt160 +{ + static readonly OldUInt160 s_oldUInt160 = new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + static readonly UInt160 s_newUInt160 = new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]); + + [Benchmark] + public void TestOldUInt160Gernerator1() + { + _ = new OldUInt160(); + } + + [Benchmark] + public void TestOldUInt160Gernerator2() + { + _ = new OldUInt160(new byte[20]); + } + + [Benchmark] + public void TestOldUInt160CompareTo() + { + OldUInt160.Zero.CompareTo(OldUInt160.Zero); + OldUInt160.Zero.CompareTo(s_oldUInt160); + s_oldUInt160.CompareTo(OldUInt160.Zero); + } + + [Benchmark] + public void TestOldUInt160Equals() + { + OldUInt160.Zero.Equals(OldUInt160.Zero); + OldUInt160.Zero.Equals(s_oldUInt160); + s_oldUInt160.Equals(null); + } + + [Benchmark] + public void TestOldUInt160Parse() + { + _ = OldUInt160.Parse("0x0000000000000000000000000000000000000000"); + _ = OldUInt160.Parse("0000000000000000000000000000000000000000"); + } + + [Benchmark] + public void TestOldUInt160TryParse() + { + OldUInt160.TryParse(null, out _); + OldUInt160.TryParse("0x0000000000000000000000000000000000000000", out var temp); + OldUInt160.TryParse("0x1230000000000000000000000000000000000000", out temp); + OldUInt160.TryParse("000000000000000000000000000000000000000", out _); + } + + [Benchmark] + public void TestOldUInt160OperatorLarger() + { + _ = s_oldUInt160 > OldUInt160.Zero; + } + + [Benchmark] + public void TestOldUInt160OperatorLargerAndEqual() + { + _ = s_oldUInt160 >= OldUInt160.Zero; + } + + [Benchmark] + public void TestOldUInt160OperatorSmaller() + { + _ = s_oldUInt160 < OldUInt160.Zero; + } + + [Benchmark] + public void TestOldUInt160OperatorSmallerAndEqual() + { + _ = s_oldUInt160 <= OldUInt160.Zero; + } + + [Benchmark] + public void TestGernerator1() + { + _ = new UInt160(); + } + + [Benchmark] + public void TestGernerator2() + { + _ = new UInt160(new byte[20]); + } + + [Benchmark] + public void TestCompareTo() + { + UInt160.Zero.CompareTo(UInt160.Zero); + UInt160.Zero.CompareTo(s_newUInt160); + s_newUInt160.CompareTo(UInt160.Zero); + } + + [Benchmark] + public void TestEquals() + { + UInt160.Zero.Equals(UInt160.Zero); + UInt160.Zero.Equals(s_newUInt160); + s_newUInt160.Equals(null); + } + + [Benchmark] + public void TestParse() + { + _ = UInt160.Parse("0x0000000000000000000000000000000000000000"); + _ = UInt160.Parse("0000000000000000000000000000000000000000"); + } + + [Benchmark] + public void TestTryParse() + { + UInt160.TryParse(null, out _); + UInt160.TryParse("0x0000000000000000000000000000000000000000", out var temp); + UInt160.TryParse("0x1230000000000000000000000000000000000000", out temp); + UInt160.TryParse("000000000000000000000000000000000000000", out _); + } + + [Benchmark] + public void TestOperatorLarger() + { + _ = s_newUInt160 > UInt160.Zero; + } + + [Benchmark] + public void TestOperatorLargerAndEqual() + { + _ = s_newUInt160 >= UInt160.Zero; + } + + [Benchmark] + public void TestOperatorSmaller() + { + _ = s_newUInt160 < UInt160.Zero; + } + + [Benchmark] + public void TestOperatorSmallerAndEqual() + { + _ = s_newUInt160 <= UInt160.Zero; + } +} diff --git a/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj b/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj index 22b90aeb03..a59fc6e728 100644 --- a/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj +++ b/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj @@ -5,10 +5,12 @@ net8.0 Neo enable + true + diff --git a/benchmarks/Neo.Benchmarks/OldUInt160.cs b/benchmarks/Neo.Benchmarks/OldUInt160.cs new file mode 100644 index 0000000000..965ac985ea --- /dev/null +++ b/benchmarks/Neo.Benchmarks/OldUInt160.cs @@ -0,0 +1,184 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// OldUInt160.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.Extensions; +using Neo.IO; +using System.Globalization; +using System.Runtime.InteropServices; + +namespace Neo +{ + ///

+ /// Represents a 160-bit unsigned integer. + /// + [StructLayout(LayoutKind.Explicit, Size = 20)] + public class OldUInt160 : IComparable, IEquatable, ISerializable + { + /// + /// The length of values. + /// + public const int Length = 20; + + /// + /// Represents 0. + /// + public static readonly OldUInt160 Zero = new(); + + [FieldOffset(0)] private ulong value1; + [FieldOffset(8)] private ulong value2; + [FieldOffset(16)] private uint value3; + + public int Size => Length; + + /// + /// Initializes a new instance of the class. + /// + public OldUInt160() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The value of the . + public unsafe OldUInt160(ReadOnlySpan value) + { + if (value.Length != Length) throw new FormatException(); + fixed (ulong* p = &value1) + { + Span dst = new(p, Length); + value[..Length].CopyTo(dst); + } + } + + public int CompareTo(OldUInt160 other) + { + int result = value3.CompareTo(other.value3); + if (result != 0) return result; + result = value2.CompareTo(other.value2); + if (result != 0) return result; + return value1.CompareTo(other.value1); + } + + public void Deserialize(ref MemoryReader reader) + { + value1 = reader.ReadUInt64(); + value2 = reader.ReadUInt64(); + value3 = reader.ReadUInt32(); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(obj, this)) return true; + return Equals(obj as OldUInt160); + } + + public bool Equals(OldUInt160 other) + { + if (other is null) return false; + return value1 == other.value1 + && value2 == other.value2 + && value3 == other.value3; + } + + public override int GetHashCode() + { + return (int)value1; + } + + /// + /// Parses an from the specified . + /// + /// An represented by a . + /// The parsed . + /// is not in the correct format. + public static OldUInt160 Parse(string value) + { + if (!TryParse(value, out var result)) throw new FormatException(); + return result; + } + + public void Serialize(BinaryWriter writer) + { + writer.Write(value1); + writer.Write(value2); + writer.Write(value3); + } + + public override string ToString() + { + return "0x" + this.ToArray().ToHexString(reverse: true); + } + + /// + /// Parses an from the specified . + /// + /// An represented by a . + /// The parsed . + /// if an is successfully parsed; otherwise, . + public static bool TryParse(string s, out OldUInt160 result) + { + if (s == null) + { + result = null; + return false; + } + if (s.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) + s = s[2..]; + if (s.Length != Length * 2) + { + result = null; + return false; + } + byte[] data = new byte[Length]; + for (int i = 0; i < Length; i++) + if (!byte.TryParse(s.Substring(i * 2, 2), NumberStyles.AllowHexSpecifier, null, out data[Length - i - 1])) + { + result = null; + return false; + } + result = new OldUInt160(data); + return true; + } + + public static bool operator ==(OldUInt160 left, OldUInt160 right) + { + if (ReferenceEquals(left, right)) return true; + if (left is null || right is null) return false; + return left.Equals(right); + } + + public static bool operator !=(OldUInt160 left, OldUInt160 right) + { + return !(left == right); + } + + public static bool operator >(OldUInt160 left, OldUInt160 right) + { + return left.CompareTo(right) > 0; + } + + public static bool operator >=(OldUInt160 left, OldUInt160 right) + { + return left.CompareTo(right) >= 0; + } + + public static bool operator <(OldUInt160 left, OldUInt160 right) + { + return left.CompareTo(right) < 0; + } + + public static bool operator <=(OldUInt160 left, OldUInt160 right) + { + return left.CompareTo(right) <= 0; + } + } +} diff --git a/benchmarks/Neo.Benchmarks/Program.cs b/benchmarks/Neo.Benchmarks/Program.cs index 9d4125bb9f..c44b76f839 100644 --- a/benchmarks/Neo.Benchmarks/Program.cs +++ b/benchmarks/Neo.Benchmarks/Program.cs @@ -9,10 +9,8 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -using Neo; -using System.Reflection; +using BenchmarkDotNet.Running; +using Neo.Benchmark; -foreach (var method in typeof(Benchmarks).GetMethods(BindingFlags.Public | BindingFlags.Static)) -{ - method.CreateDelegate().Invoke(); -} +// BenchmarkRunner.Run(); +BenchmarkRunner.Run(); diff --git a/benchmarks/Neo.VM.Benchmarks/Benchmarks.POC.cs b/benchmarks/Neo.VM.Benchmarks/Benchmarks.POC.cs new file mode 100644 index 0000000000..2eede59fc3 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/Benchmarks.POC.cs @@ -0,0 +1,397 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Benchmarks.POC.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 BenchmarkDotNet.Attributes; +using System.Diagnostics; + +namespace Neo.VM.Benchmark +{ + public class Benchmarks_PoCs + { + [Benchmark] + public void NeoIssue2528() + { + // https://github.com/neo-project/neo/issues/2528 + // L01: INITSLOT 1, 0 + // L02: NEWARRAY0 + // L03: DUP + // L04: DUP + // L05: PUSHINT16 2043 + // L06: STLOC 0 + // L07: PUSH1 + // L08: PACK + // L09: LDLOC 0 + // L10: DEC + // L11: STLOC 0 + // L12: LDLOC 0 + // L13: JMPIF_L L07 + // L14: PUSH1 + // L15: PACK + // L16: APPEND + // L17: PUSHINT32 38000 + // L18: STLOC 0 + // L19: PUSH0 + // L20: PICKITEM + // L21: LDLOC 0 + // L22: DEC + // L23: STLOC 0 + // L24: LDLOC 0 + // L25: JMPIF_L L19 + // L26: DROP + Run("VwEAwkpKAfsHdwARwG8AnXcAbwAl9////xHAzwJwlAAAdwAQzm8AnXcAbwAl9////0U="); + } + + [Benchmark] + public void NeoVMIssue418() + { + // https://github.com/neo-project/neo-vm/issues/418 + // L00: NEWARRAY0 + // L01: PUSH0 + // L02: PICK + // L03: PUSH1 + // L04: PACK + // L05: PUSH1 + // L06: PICK + // L07: PUSH1 + // L08: PACK + // L09: INITSSLOT 1 + // L10: PUSHINT16 510 + // L11: DEC + // L12: STSFLD0 + // L13: PUSH1 + // L14: PICK + // L15: PUSH1 + // L16: PICK + // L17: PUSH2 + // L18: PACK + // L19: REVERSE3 + // L20: PUSH2 + // L21: PACK + // L22: LDSFLD0 + // L23: DUP + // L24: JMPIF L11 + // L25: DROP + // L26: ROT + // L27: DROP + Run("whBNEcARTRHAVgEB/gGdYBFNEU0SwFMSwFhKJPNFUUU="); + } + + [Benchmark] + public void NeoIssue2723() + { + // L00: INITSSLOT 1 + // L01: PUSHINT32 130000 + // L02: STSFLD 0 + // L03: PUSHINT32 1048576 + // L04: NEWBUFFER + // L05: DROP + // L06: LDSFLD 0 + // L07: DEC + // L08: DUP + // L09: STSFLD 0 + // L10: JMPIF L03 + Run("VgEC0PsBAGcAAgAAEACIRV8AnUpnACTz"); + } + + // Below are PoCs from issue https://github.com/neo-project/neo/issues/2723 by @dusmart + [Benchmark] + public void PoC_NewBuffer() + { + // INITSLOT 0100 + // PUSHINT32 23000000 + // STLOC 00 + // PUSHINT32 1048576 + // NEWBUFFER + // DROP + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f2ffffff + // CLEAR + // RET + Run("VwEAAsDzXgF3AAIAABAAiEVvAJ13AG8AJfL///9JQA=="); + } + + [Benchmark] + public void PoC_Cat() + { + // INITSLOT 0100 + // PUSHINT32 1048575 + // NEWBUFFER + // PUSH1 + // NEWBUFFER + // PUSHINT32 133333337 + // STLOC 00 + // OVER + // OVER + // CAT + // DROP + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f5ffffff + // CLEAR + // RET + Run("VwEAAv//DwCIEYgCWYHyB3cAS0uLRW8AnXcAbwAl9f///0lA"); + } + + [Benchmark] + public void PoC_Left() + { + // INITSLOT 0100 + // PUSHINT32 1048576 + // NEWBUFFER + // PUSHINT32 133333337 + // STLOC 00 + // DUP + // PUSHINT32 1048576 + // LEFT + // DROP + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f1ffffff + // CLEAR + // RET + Run("VwEAAgAAEACIAlmB8gd3AEoCAAAQAI1FbwCddwBvACXx////SUA="); + } + + [Benchmark] + public void PoC_Right() + { + // INITSLOT 0100 + // PUSHINT32 1048576 + // NEWBUFFER + // PUSHINT32 133333337 + // STLOC 00 + // DUP + // PUSHINT32 1048576 + // RIGHT + // DROP + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f1ffffff + // CLEAR + // RET + Run("VwEAAgAAEACIAlmB8gd3AEoCAAAQAI5FbwCddwBvACXx////SUA="); + } + + [Benchmark] + public void PoC_ReverseN() + { + // INITSLOT 0100 + // PUSHINT16 2040 + // STLOC 00 + // PUSHDATA1 aaabbbbbbbbbcccccccdddddddeeeeeeefffffff + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L cfffffff + // PUSHINT32 23000000 + // STLOC 00 + // PUSHINT16 2040 + // REVERSEN + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f5ffffff + // CLEAR + // RET + Run("VwEAAfgHdwAMKGFhYWJiYmJiYmJiYmNjY2NjY2NkZGRkZGRkZWVlZWVlZWZmZmZmZmZvAJ13AG8AJc////8CwPNeAXcAAfgHVW8AnXcAbwAl9f///0lA"); + } + + [Benchmark] + public void PoC_Substr() + { + // INITSLOT 0100 + // PUSHINT32 1048576 + // NEWBUFFER + // PUSHINT32 133333337 + // STLOC 00 + // DUP + // PUSH0 + // PUSHINT32 1048576 + // SUBSTR + // DROP + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f0ffffff + // CLEAR + // RET + Run("VwEAAgAAEACIAlmB8gd3AEoQAgAAEACMRW8AnXcAbwAl8P///0lA"); + } + + [Benchmark] + public void PoC_NewArray() + { + // INITSLOT 0100 + // PUSHINT32 1333333337 + // STLOC 00 + // PUSHINT16 2040 + // NEWARRAY + // DROP + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f4ffffff + // RET + Run("VwEAAlkNeU93AAH4B8NFbwCddwBvACX0////QA=="); + } + + [Benchmark] + public void PoC_NewStruct() + { + // INITSLOT 0100 + // PUSHINT32 1333333337 + // STLOC 00 + // PUSHINT16 2040 + // NEWSTRUCT + // DROP + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f4ffffff + // RET + Run("VwEAAlkNeU93AAH4B8ZFbwCddwBvACX0////QA=="); + } + + [Benchmark] + public void PoC_Roll() + { + // INITSLOT 0100 + // PUSHINT16 2040 + // STLOC 00 + // PUSHDATA1 aaabbbbbbbbbcccccccdddddddeeeeeeefffffff + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L cfffffff + // PUSHINT32 23000000 + // STLOC 00 + // PUSHINT16 2039 + // ROLL + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f5ffffff + // CLEAR + // RET + Run("VwEAAfgHdwAMKGFhYWJiYmJiYmJiYmNjY2NjY2NkZGRkZGRkZWVlZWVlZWZmZmZmZmZvAJ13AG8AJc////8CwPNeAXcAAfcHUm8AnXcAbwAl9f///0lA"); + } + + [Benchmark] + public void PoC_XDrop() + { + // INITSLOT 0100 + // PUSHINT16 2040 + // STLOC 00 + // PUSHDATA1 aaabbbbbbbbbcccccccdddddddeeeeeeefffffff + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L cfffffff + // PUSHINT32 23000000 + // STLOC 00 + // PUSHINT16 2039 + // XDROP + // DUP + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f4ffffff + // CLEAR + // RET + Run("VwEAAfgHdwAMKGFhYWJiYmJiYmJiYmNjY2NjY2NkZGRkZGRkZWVlZWVlZWZmZmZmZmZvAJ13AG8AJc////8CwPNeAXcAAfcHSEpvAJ13AG8AJfT///9JQA=="); + } + + [Benchmark] + public void PoC_MemCpy() + { + // INITSLOT 0100 + // PUSHINT32 1048576 + // NEWBUFFER + // PUSHINT32 1048576 + // NEWBUFFER + // PUSHINT32 133333337 + // STLOC 00 + // OVER + // PUSH0 + // PUSH2 + // PICK + // PUSH0 + // PUSHINT32 1048576 + // MEMCPY + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L eeffffff + // CLEAR + // RET + Run("VwEAAgAAEACIAgAAEACIAlmB8gd3AEsQEk0QAgAAEACJbwCddwBvACXu////SUA="); + } + + [Benchmark] + public void PoC_Unpack() + { + // INITSLOT 0200 + // PUSHINT16 1010 + // NEWARRAY + // STLOC 01 + // PUSHINT32 1333333337 + // STLOC 00 + // LDLOC 01 + // UNPACK + // CLEAR + // LDLOC 00 + // DEC + // STLOC 00 + // LDLOC 00 + // JMPIF_L f5ffffff + // RET + Run("VwIAAfIDw3cBAlkNeU93AG8BwUlvAJ13AG8AJfX///9A"); + } + + [Benchmark] + public void PoC_GetScriptContainer() + { + // SYSCALL System.Runtime.GetScriptContainer + // DROP + // JMP fa + Run("QS1RCDBFIvo="); + } + + private static void Run(string poc) + { + byte[] script = Convert.FromBase64String(poc); + using ExecutionEngine engine = new(); + engine.LoadScript(script); + engine.Execute(); + + Debug.Assert(engine.State == VMState.HALT); + } + } +} diff --git a/benchmarks/Neo.VM.Benchmarks/Benchmarks.cs b/benchmarks/Neo.VM.Benchmarks/Benchmarks.cs deleted file mode 100644 index 6eab691a7d..0000000000 --- a/benchmarks/Neo.VM.Benchmarks/Benchmarks.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (C) 2015-2024 The Neo Project. -// -// Benchmarks.cs file belongs to the neo project and is free -// software distributed under the MIT software license, see the -// accompanying file LICENSE in the main directory of the -// repository or http://www.opensource.org/licenses/mit-license.php -// for more details. -// -// Redistribution and use in source and binary forms with or without -// modifications are permitted. - -using System.Diagnostics; - -namespace Neo.VM -{ - public static class Benchmarks - { - public static void NeoIssue2528() - { - // https://github.com/neo-project/neo/issues/2528 - // L01: INITSLOT 1, 0 - // L02: NEWARRAY0 - // L03: DUP - // L04: DUP - // L05: PUSHINT16 2043 - // L06: STLOC 0 - // L07: PUSH1 - // L08: PACK - // L09: LDLOC 0 - // L10: DEC - // L11: STLOC 0 - // L12: LDLOC 0 - // L13: JMPIF_L L07 - // L14: PUSH1 - // L15: PACK - // L16: APPEND - // L17: PUSHINT32 38000 - // L18: STLOC 0 - // L19: PUSH0 - // L20: PICKITEM - // L21: LDLOC 0 - // L22: DEC - // L23: STLOC 0 - // L24: LDLOC 0 - // L25: JMPIF_L L19 - // L26: DROP - Run(nameof(NeoIssue2528), "VwEAwkpKAfsHdwARwG8AnXcAbwAl9////xHAzwJwlAAAdwAQzm8AnXcAbwAl9////0U="); - } - - public static void NeoVMIssue418() - { - // https://github.com/neo-project/neo-vm/issues/418 - // L00: NEWARRAY0 - // L01: PUSH0 - // L02: PICK - // L03: PUSH1 - // L04: PACK - // L05: PUSH1 - // L06: PICK - // L07: PUSH1 - // L08: PACK - // L09: INITSSLOT 1 - // L10: PUSHINT16 510 - // L11: DEC - // L12: STSFLD0 - // L13: PUSH1 - // L14: PICK - // L15: PUSH1 - // L16: PICK - // L17: PUSH2 - // L18: PACK - // L19: REVERSE3 - // L20: PUSH2 - // L21: PACK - // L22: LDSFLD0 - // L23: DUP - // L24: JMPIF L11 - // L25: DROP - // L26: ROT - // L27: DROP - Run(nameof(NeoVMIssue418), "whBNEcARTRHAVgEB/gGdYBFNEU0SwFMSwFhKJPNFUUU="); - } - - public static void NeoIssue2723() - { - // L00: INITSSLOT 1 - // L01: PUSHINT32 130000 - // L02: STSFLD 0 - // L03: PUSHINT32 1048576 - // L04: NEWBUFFER - // L05: DROP - // L06: LDSFLD 0 - // L07: DEC - // L08: DUP - // L09: STSFLD 0 - // L10: JMPIF L03 - Run(nameof(NeoIssue2723), "VgEC0PsBAGcAAgAAEACIRV8AnUpnACTz"); - } - - private static void Run(string name, string poc) - { - byte[] script = Convert.FromBase64String(poc); - using ExecutionEngine engine = new(); - engine.LoadScript(script); - Stopwatch stopwatch = Stopwatch.StartNew(); - engine.Execute(); - stopwatch.Stop(); - Debug.Assert(engine.State == VMState.HALT); - Console.WriteLine($"Benchmark: {name},\tTime: {stopwatch.Elapsed}"); - } - } -} diff --git a/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/Helper.cs b/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/Helper.cs new file mode 100644 index 0000000000..f5ea579e6f --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/Helper.cs @@ -0,0 +1,72 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Helper.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.Buffers.Binary; + +namespace Neo.VM.Benchmark; + +public static class Helper +{ + public static void RebuildOffsets(this IReadOnlyList instructions) + { + var offset = 0; + foreach (var instruction in instructions) + { + instruction._offset = offset; + offset += instruction.Size; + } + } + + public static void RebuildOperands(this IReadOnlyList instructions) + { + foreach (var instruction in instructions) + { + if (instruction._target is null) continue; + bool isLong; + if (instruction._opCode >= VM.OpCode.JMP && instruction._opCode <= VM.OpCode.CALL_L) + isLong = (instruction._opCode - VM.OpCode.JMP) % 2 != 0; + else + isLong = instruction._opCode == VM.OpCode.PUSHA || instruction._opCode == VM.OpCode.CALLA || instruction._opCode == VM.OpCode.TRY_L || instruction._opCode == VM.OpCode.ENDTRY_L; + if (instruction._opCode == VM.OpCode.TRY || instruction._opCode == VM.OpCode.TRY_L) + { + var offset1 = (instruction._target._instruction?._offset - instruction._offset) ?? 0; + var offset2 = (instruction._target2!._instruction?._offset - instruction._offset) ?? 0; + if (isLong) + { + instruction._operand = new byte[sizeof(int) + sizeof(int)]; + BinaryPrimitives.WriteInt32LittleEndian(instruction._operand, offset1); + BinaryPrimitives.WriteInt32LittleEndian(instruction._operand.AsSpan(sizeof(int)), offset2); + } + else + { + instruction._operand = new byte[sizeof(sbyte) + sizeof(sbyte)]; + var sbyte1 = checked((sbyte)offset1); + var sbyte2 = checked((sbyte)offset2); + instruction._operand[0] = unchecked((byte)sbyte1); + instruction._operand[1] = unchecked((byte)sbyte2); + } + } + else + { + int offset = instruction._target._instruction!._offset - instruction._offset; + if (isLong) + { + instruction._operand = BitConverter.GetBytes(offset); + } + else + { + var sbyte1 = checked((sbyte)offset); + instruction._operand = [unchecked((byte)sbyte1)]; + } + } + } + } +} diff --git a/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/Instruction.cs b/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/Instruction.cs new file mode 100644 index 0000000000..5a30aeec10 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/Instruction.cs @@ -0,0 +1,58 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Instruction.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.CodeAnalysis; +using System.Diagnostics; +using System.Reflection; + +namespace Neo.VM.Benchmark; + +[DebuggerDisplay("{_opCode}")] +public class Instruction +{ + private static readonly int[] s_operandSizePrefixTable = new int[256]; + private static readonly int[] s_operandSizeTable = new int[256]; + + public VM.OpCode _opCode; + public byte[]? _operand; + public JumpTarget? _target; + public JumpTarget? _target2; + public int _offset; + + public int Size + { + get + { + int prefixSize = s_operandSizePrefixTable[(int)_opCode]; + return prefixSize > 0 + ? sizeof(VM.OpCode) + _operand!.Length + : sizeof(VM.OpCode) + s_operandSizeTable[(int)_opCode]; + } + } + + static Instruction() + { + foreach (var field in typeof(VM.OpCode).GetFields(BindingFlags.Public | BindingFlags.Static)) + { + var attribute = field.GetCustomAttribute(); + if (attribute is null) continue; + var index = (int)(VM.OpCode)field.GetValue(null)!; + s_operandSizePrefixTable[index] = attribute.SizePrefix; + s_operandSizeTable[index] = attribute.Size; + } + } + + public byte[] ToArray() + { + if (_operand is null) return [(byte)_opCode]; + return _operand.Prepend((byte)_opCode).ToArray(); + } +} diff --git a/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/InstructionBuilder.cs b/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/InstructionBuilder.cs new file mode 100644 index 0000000000..21d1b77de2 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/InstructionBuilder.cs @@ -0,0 +1,242 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// InstructionBuilder.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.Buffers.Binary; +using System.Numerics; + +namespace Neo.VM.Benchmark; + +internal class InstructionBuilder +{ + internal readonly List _instructions = new(); + + public InstructionBuilder() { } + + internal Instruction AddInstruction(Instruction instruction) + { + _instructions.Add(instruction); + return instruction; + } + + internal Instruction AddInstruction(VM.OpCode opcode) + { + return AddInstruction(new Instruction + { + _opCode = opcode + }); + } + + internal Instruction Jump(VM.OpCode opcode, JumpTarget target) + { + return AddInstruction(new Instruction + { + _opCode = opcode, + _target = target + }); + } + + internal void Push(bool value) + { + AddInstruction(value ? VM.OpCode.PUSHT : VM.OpCode.PUSHF); + } + + internal Instruction Ret() => AddInstruction(VM.OpCode.RET); + + internal Instruction Push(BigInteger number) + { + if (number >= -1 && number <= 16) return AddInstruction(number == -1 ? VM.OpCode.PUSHM1 : VM.OpCode.PUSH0 + (byte)(int)number); + Span buffer = stackalloc byte[32]; + if (!number.TryWriteBytes(buffer, out var bytesWritten, isUnsigned: false, isBigEndian: false)) + throw new ArgumentOutOfRangeException(nameof(number)); + var instruction = bytesWritten switch + { + 1 => new Instruction + { + _opCode = VM.OpCode.PUSHINT8, + _operand = PadRight(buffer, bytesWritten, 1, number.Sign < 0).ToArray() + }, + 2 => new Instruction + { + _opCode = VM.OpCode.PUSHINT16, + _operand = PadRight(buffer, bytesWritten, 2, number.Sign < 0).ToArray() + }, + <= 4 => new Instruction + { + _opCode = VM.OpCode.PUSHINT32, + _operand = PadRight(buffer, bytesWritten, 4, number.Sign < 0).ToArray() + }, + <= 8 => new Instruction + { + _opCode = VM.OpCode.PUSHINT64, + _operand = PadRight(buffer, bytesWritten, 8, number.Sign < 0).ToArray() + }, + <= 16 => new Instruction + { + _opCode = VM.OpCode.PUSHINT128, + _operand = PadRight(buffer, bytesWritten, 16, number.Sign < 0).ToArray() + }, + <= 32 => new Instruction + { + _opCode = VM.OpCode.PUSHINT256, + _operand = PadRight(buffer, bytesWritten, 32, number.Sign < 0).ToArray() + }, + _ => throw new ArgumentOutOfRangeException($"Number too large: {bytesWritten}") + }; + AddInstruction(instruction); + return instruction; + } + + internal Instruction Push(string s) + { + return Push(Utility.StrictUTF8.GetBytes(s)); + } + + internal Instruction Push(byte[] data) + { + VM.OpCode opcode; + byte[] buffer; + switch (data.Length) + { + case <= byte.MaxValue: + opcode = VM.OpCode.PUSHDATA1; + buffer = new byte[sizeof(byte) + data.Length]; + buffer[0] = (byte)data.Length; + Buffer.BlockCopy(data, 0, buffer, sizeof(byte), data.Length); + break; + case <= ushort.MaxValue: + opcode = VM.OpCode.PUSHDATA2; + buffer = new byte[sizeof(ushort) + data.Length]; + BinaryPrimitives.WriteUInt16LittleEndian(buffer, (ushort)data.Length); + Buffer.BlockCopy(data, 0, buffer, sizeof(ushort), data.Length); + break; + default: + opcode = VM.OpCode.PUSHDATA4; + buffer = new byte[sizeof(uint) + data.Length]; + BinaryPrimitives.WriteUInt32LittleEndian(buffer, (uint)data.Length); + Buffer.BlockCopy(data, 0, buffer, sizeof(uint), data.Length); + break; + } + return AddInstruction(new Instruction + { + _opCode = opcode, + _operand = buffer + }); + } + + internal void Push(object? obj) + { + switch (obj) + { + case bool data: + Push(data); + break; + case byte[] data: + Push(data); + break; + case string data: + Push(data); + break; + case BigInteger data: + Push(data); + break; + case char data: + Push((ushort)data); + break; + case sbyte data: + Push(data); + break; + case byte data: + Push(data); + break; + case short data: + Push(data); + break; + case ushort data: + Push(data); + break; + case int data: + Push(data); + break; + case uint data: + Push(data); + break; + case long data: + Push(data); + break; + case ulong data: + Push(data); + break; + case Enum data: + Push(BigInteger.Parse(data.ToString("d"))); + break; + case null: + AddInstruction(VM.OpCode.PUSHNULL); + break; + default: + throw new NotSupportedException($"Unsupported constant value: {obj}"); + } + } + + // Helper method to reverse stack items + internal void ReverseStackItems(int count) + { + switch (count) + { + case 2: + AddInstruction(VM.OpCode.SWAP); + break; + case 3: + AddInstruction(VM.OpCode.REVERSE3); + break; + case 4: + AddInstruction(VM.OpCode.REVERSE4); + break; + default: + Push(count); + AddInstruction(VM.OpCode.REVERSEN); + break; + } + } + + internal static ReadOnlySpan PadRight(Span buffer, int dataLength, int padLength, bool negative) + { + byte pad = negative ? (byte)0xff : (byte)0; + for (int x = dataLength; x < padLength; x++) + buffer[x] = pad; + return buffer[..padLength]; + } + + internal Instruction IsType(VM.Types.StackItemType type) + { + return AddInstruction(new Instruction + { + _opCode = VM.OpCode.ISTYPE, + _operand = [(byte)type] + }); + } + + internal Instruction ChangeType(VM.Types.StackItemType type) + { + return AddInstruction(new Instruction + { + _opCode = VM.OpCode.CONVERT, + _operand = [(byte)type] + }); + } + + internal byte[] ToArray() + { + var instructions = _instructions.ToArray(); + instructions.RebuildOffsets(); + instructions.RebuildOperands(); + return instructions.Select(p => p.ToArray()).SelectMany(p => p).ToArray(); + } +} diff --git a/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/JumpTarget.cs b/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/JumpTarget.cs new file mode 100644 index 0000000000..246b0e5884 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/InstructionBuilder/JumpTarget.cs @@ -0,0 +1,17 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// JumpTarget.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.VM.Benchmark; + +public class JumpTarget +{ + public Instruction? _instruction; +} diff --git a/benchmarks/Neo.VM.Benchmarks/Neo.VM.Benchmarks.csproj b/benchmarks/Neo.VM.Benchmarks/Neo.VM.Benchmarks.csproj index 4c3c3d6057..d5a7909a4b 100644 --- a/benchmarks/Neo.VM.Benchmarks/Neo.VM.Benchmarks.csproj +++ b/benchmarks/Neo.VM.Benchmarks/Neo.VM.Benchmarks.csproj @@ -9,7 +9,12 @@ + + + + + diff --git a/benchmarks/Neo.VM.Benchmarks/OpCode/Arrays/OpCode.ReverseN.cs b/benchmarks/Neo.VM.Benchmarks/OpCode/Arrays/OpCode.ReverseN.cs new file mode 100644 index 0000000000..515125ddc0 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/OpCode/Arrays/OpCode.ReverseN.cs @@ -0,0 +1,136 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// OpCode.ReverseN.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.VM.Benchmark.OpCode; + +public class OpCode_ReverseN : OpCodeBase +{ + protected override byte[] CreateScript(BenchmarkMode benchmarkMode) + { + var builder = new InstructionBuilder(); + var initBegin = new JumpTarget(); + builder.AddInstruction(new Instruction { _opCode = VM.OpCode.INITSLOT, _operand = [1, 0] }); + builder.Push(ItemCount); + builder.AddInstruction(VM.OpCode.STLOC0); + initBegin._instruction = builder.AddInstruction(VM.OpCode.NOP); + builder.Push(0); + builder.AddInstruction(VM.OpCode.LDLOC0); + builder.AddInstruction(VM.OpCode.DEC); + builder.AddInstruction(VM.OpCode.STLOC0); + builder.AddInstruction(VM.OpCode.LDLOC0); + builder.Jump(VM.OpCode.JMPIF, initBegin); + if (benchmarkMode == BenchmarkMode.BaseLine) + { + return builder.ToArray(); + } + builder.Push(ItemCount); + builder.AddInstruction(VM.OpCode.REVERSEN); + if (benchmarkMode == BenchmarkMode.OneGAS) + { + // just keep running until GAS is exhausted + var loopStart = new JumpTarget { _instruction = builder.AddInstruction(VM.OpCode.NOP) }; + builder.Push(ItemCount); + builder.AddInstruction(VM.OpCode.REVERSEN); + builder.Jump(VM.OpCode.JMP, loopStart); + } + + return builder.ToArray(); + } +} + +// for 0 + +// BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.4249/23H2/2023Update/SunValley3) +// Intel Core i9-14900HX, 1 CPU, 32 logical and 24 physical cores +// .NET SDK 8.0.205 +// [Host] : .NET 8.0.5 (8.0.524.21615), X64 RyuJIT AVX2 +// DefaultJob : .NET 8.0.5 (8.0.524.21615), X64 RyuJIT AVX2 +// +// +// | Method | ItemCount | Mean | Error | StdDev | Median | Ratio | RatioSD | +// |--------------------- |---------- |-----------------:|--------------:|---------------:|-----------------:|----------:|---------:| +// | Bench_ReverseN | 1 | 63.43 us | 0.466 us | 0.518 us | 63.45 us | 0.99 | 0.01 | +// | Bench_OneGasReverseN | 1 | 403,904.11 us | 6,492.511 us | 6,073.099 us | 402,932.40 us | 6,315.67 | 89.44 | +// | Bench_BaseLine | 1 | 63.96 us | 0.763 us | 0.714 us | 63.92 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 2 | 62.55 us | 0.988 us | 0.924 us | 62.46 us | 0.95 | 0.02 | +// | Bench_OneGasReverseN | 2 | 424,297.10 us | 8,453.137 us | 7,493.486 us | 423,912.90 us | 6,446.21 | 118.35 | +// | Bench_BaseLine | 2 | 65.83 us | 0.845 us | 0.749 us | 65.95 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 4 | 63.93 us | 0.418 us | 0.371 us | 63.89 us | 0.95 | 0.03 | +// | Bench_OneGasReverseN | 4 | 443,708.92 us | 6,689.013 us | 6,256.907 us | 444,636.60 us | 6,560.69 | 229.86 | +// | Bench_BaseLine | 4 | 67.64 us | 1.281 us | 1.524 us | 67.79 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 8 | 66.69 us | 0.757 us | 0.671 us | 66.69 us | 1.00 | 0.02 | +// | Bench_OneGasReverseN | 8 | 463,571.38 us | 6,614.687 us | 6,187.382 us | 465,568.00 us | 6,963.59 | 85.80 | +// | Bench_BaseLine | 8 | 66.64 us | 0.870 us | 0.771 us | 66.68 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 16 | 65.85 us | 0.994 us | 0.929 us | 65.61 us | 0.94 | 0.02 | +// | Bench_OneGasReverseN | 16 | 740,905.55 us | 71,090.901 us | 209,613.127 us | 653,644.75 us | 9,341.86 | 3,092.85 | +// | Bench_BaseLine | 16 | 70.08 us | 1.376 us | 1.638 us | 70.15 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 32 | 66.47 us | 0.928 us | 2.187 us | 65.77 us | 1.02 | 0.04 | +// | Bench_OneGasReverseN | 32 | 631,596.65 us | 11,060.847 us | 10,346.323 us | 629,654.10 us | 9,360.06 | 221.36 | +// | Bench_BaseLine | 32 | 67.49 us | 0.900 us | 0.842 us | 67.56 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 64 | 72.21 us | 0.921 us | 0.862 us | 72.05 us | 1.02 | 0.02 | +// | Bench_OneGasReverseN | 64 | 787,570.95 us | 6,915.746 us | 6,468.994 us | 786,778.70 us | 11,090.76 | 177.74 | +// | Bench_BaseLine | 64 | 71.02 us | 0.946 us | 0.884 us | 71.06 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 128 | 80.17 us | 0.723 us | 0.676 us | 80.19 us | 0.98 | 0.01 | +// | Bench_OneGasReverseN | 128 | 1,134,510.61 us | 14,991.714 us | 14,023.259 us | 1,133,177.90 us | 13,828.61 | 184.58 | +// | Bench_BaseLine | 128 | 81.90 us | 0.623 us | 0.553 us | 81.77 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 256 | 98.24 us | 1.140 us | 1.067 us | 98.05 us | 0.99 | 0.01 | +// | Bench_OneGasReverseN | 256 | 1,785,906.33 us | 13,785.746 us | 12,895.195 us | 1,788,819.30 us | 18,067.20 | 198.87 | +// | Bench_BaseLine | 256 | 98.85 us | 0.961 us | 0.899 us | 98.95 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 512 | 136.19 us | 1.614 us | 1.510 us | 136.34 us | 1.02 | 0.02 | +// | Bench_OneGasReverseN | 512 | 3,100,087.41 us | 16,564.249 us | 15,494.209 us | 3,097,066.60 us | 23,209.57 | 381.50 | +// | Bench_BaseLine | 512 | 133.60 us | 2.144 us | 2.006 us | 132.73 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 1024 | 207.06 us | 2.213 us | 2.070 us | 206.76 us | 1.01 | 0.01 | +// | Bench_OneGasReverseN | 1024 | 5,762,294.72 us | 20,289.404 us | 16,942.572 us | 5,764,133.80 us | 28,109.14 | 349.87 | +// | Bench_BaseLine | 1024 | 205.07 us | 2.360 us | 2.208 us | 205.07 us | 1.00 | 0.00 | +// | | | | | | | | | +// | Bench_ReverseN | 2040 | 345.09 us | 4.271 us | 3.995 us | 345.40 us | 0.97 | 0.01 | +// | Bench_OneGasReverseN | 2040 | 11,005,147.03 us | 37,306.455 us | 33,071.200 us | 11,003,479.70 us | 31,019.36 | 356.02 | +// | Bench_BaseLine | 2040 | 354.62 us | 4.623 us | 4.325 us | 353.32 us | 1.00 | 0.00 | + + + +// for StackItems that has a size of maximum stack size. +// | Method | ItemCount | Mean | Error | StdDev | +// |--------------------- |---------- |----------------:|--------------:|--------------:| +// | Bench_ReverseN | 1 | 104.0 us | 0.77 us | 0.72 us | +// | Bench_OneGasReverseN | 1 | 389,585.4 us | 4,740.18 us | 4,433.96 us | +// | Bench_ReverseN | 2 | 148.3 us | 2.25 us | 2.10 us | +// | Bench_OneGasReverseN | 2 | 417,831.5 us | 6,651.20 us | 6,221.53 us | +// | Bench_ReverseN | 4 | 231.8 us | 3.92 us | 3.67 us | +// | Bench_OneGasReverseN | 4 | 428,442.6 us | 8,034.41 us | 7,515.39 us | +// | Bench_ReverseN | 8 | 387.8 us | 5.23 us | 4.89 us | +// | Bench_OneGasReverseN | 8 | 448,046.9 us | 6,270.18 us | 5,235.89 us | +// | Bench_ReverseN | 16 | 240.0 us | 7.30 us | 21.53 us | +// | Bench_OneGasReverseN | 16 | 522,904.3 us | 7,157.93 us | 6,695.54 us | +// | Bench_ReverseN | 32 | 302.4 us | 9.53 us | 27.79 us | +// | Bench_OneGasReverseN | 32 | 626,536.6 us | 6,629.69 us | 6,201.42 us | +// | Bench_ReverseN | 64 | 1,728.3 us | 34.27 us | 58.19 us | +// | Bench_OneGasReverseN | 64 | 827,284.5 us | 15,943.00 us | 14,913.09 us | +// | Bench_ReverseN | 128 | 3,704.5 us | 73.98 us | 175.82 us | +// | Bench_OneGasReverseN | 128 | 1,125,104.6 us | 10,629.65 us | 9,942.98 us | +// | Bench_ReverseN | 256 | 6,381.1 us | 127.42 us | 290.21 us | +// | Bench_OneGasReverseN | 256 | 1,804,355.7 us | 9,690.50 us | 8,590.37 us | +// | Bench_ReverseN | 512 | 9,485.9 us | 184.52 us | 492.52 us | +// | Bench_OneGasReverseN | 512 | 3,159,411.1 us | 28,901.54 us | 27,034.52 us | +// | Bench_ReverseN | 1024 | 14,125.6 us | 282.51 us | 577.08 us | +// | Bench_OneGasReverseN | 1024 | 5,799,154.5 us | 33,817.93 us | 31,633.31 us | +// | Bench_ReverseN | 2040 | 22,868.0 us | 449.84 us | 929.00 us | +// | Bench_OneGasReverseN | 2040 | 11,100,853.9 us | 159,980.97 us | 141,818.97 us | diff --git a/benchmarks/Neo.VM.Benchmarks/OpCode/Benchmark.Opcode.cs b/benchmarks/Neo.VM.Benchmarks/OpCode/Benchmark.Opcode.cs new file mode 100644 index 0000000000..f55abae7ee --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/OpCode/Benchmark.Opcode.cs @@ -0,0 +1,234 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Benchmark.Opcode.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.VM.Benchmark.OpCode; + +public class Benchmark_Opcode +{ + internal static readonly long OneGasDatoshi = 1_0000_0000; + + public static readonly IReadOnlyDictionary OpCodePrices = new Dictionary + { + [VM.OpCode.PUSHINT8] = 1 << 0, + [VM.OpCode.PUSHINT16] = 1 << 0, + [VM.OpCode.PUSHINT32] = 1 << 0, + [VM.OpCode.PUSHINT64] = 1 << 0, + [VM.OpCode.PUSHINT128] = 1 << 2, + [VM.OpCode.PUSHINT256] = 1 << 2, + [VM.OpCode.PUSHT] = 1 << 0, + [VM.OpCode.PUSHF] = 1 << 0, + [VM.OpCode.PUSHA] = 1 << 2, + [VM.OpCode.PUSHNULL] = 1 << 0, + [VM.OpCode.PUSHDATA1] = 1 << 3, + [VM.OpCode.PUSHDATA2] = 1 << 9, + [VM.OpCode.PUSHDATA4] = 1 << 12, + [VM.OpCode.PUSHM1] = 1 << 0, + [VM.OpCode.PUSH0] = 1 << 0, + [VM.OpCode.PUSH1] = 1 << 0, + [VM.OpCode.PUSH2] = 1 << 0, + [VM.OpCode.PUSH3] = 1 << 0, + [VM.OpCode.PUSH4] = 1 << 0, + [VM.OpCode.PUSH5] = 1 << 0, + [VM.OpCode.PUSH6] = 1 << 0, + [VM.OpCode.PUSH7] = 1 << 0, + [VM.OpCode.PUSH8] = 1 << 0, + [VM.OpCode.PUSH9] = 1 << 0, + [VM.OpCode.PUSH10] = 1 << 0, + [VM.OpCode.PUSH11] = 1 << 0, + [VM.OpCode.PUSH12] = 1 << 0, + [VM.OpCode.PUSH13] = 1 << 0, + [VM.OpCode.PUSH14] = 1 << 0, + [VM.OpCode.PUSH15] = 1 << 0, + [VM.OpCode.PUSH16] = 1 << 0, + [VM.OpCode.NOP] = 1 << 0, + [VM.OpCode.JMP] = 1 << 1, + [VM.OpCode.JMP_L] = 1 << 1, + [VM.OpCode.JMPIF] = 1 << 1, + [VM.OpCode.JMPIF_L] = 1 << 1, + [VM.OpCode.JMPIFNOT] = 1 << 1, + [VM.OpCode.JMPIFNOT_L] = 1 << 1, + [VM.OpCode.JMPEQ] = 1 << 1, + [VM.OpCode.JMPEQ_L] = 1 << 1, + [VM.OpCode.JMPNE] = 1 << 1, + [VM.OpCode.JMPNE_L] = 1 << 1, + [VM.OpCode.JMPGT] = 1 << 1, + [VM.OpCode.JMPGT_L] = 1 << 1, + [VM.OpCode.JMPGE] = 1 << 1, + [VM.OpCode.JMPGE_L] = 1 << 1, + [VM.OpCode.JMPLT] = 1 << 1, + [VM.OpCode.JMPLT_L] = 1 << 1, + [VM.OpCode.JMPLE] = 1 << 1, + [VM.OpCode.JMPLE_L] = 1 << 1, + [VM.OpCode.CALL] = 1 << 9, + [VM.OpCode.CALL_L] = 1 << 9, + [VM.OpCode.CALLA] = 1 << 9, + [VM.OpCode.CALLT] = 1 << 15, + [VM.OpCode.ABORT] = 0, + [VM.OpCode.ABORTMSG] = 0, + [VM.OpCode.ASSERT] = 1 << 0, + [VM.OpCode.ASSERTMSG] = 1 << 0, + [VM.OpCode.THROW] = 1 << 9, + [VM.OpCode.TRY] = 1 << 2, + [VM.OpCode.TRY_L] = 1 << 2, + [VM.OpCode.ENDTRY] = 1 << 2, + [VM.OpCode.ENDTRY_L] = 1 << 2, + [VM.OpCode.ENDFINALLY] = 1 << 2, + [VM.OpCode.RET] = 0, + [VM.OpCode.SYSCALL] = 0, + [VM.OpCode.DEPTH] = 1 << 1, + [VM.OpCode.DROP] = 1 << 1, + [VM.OpCode.NIP] = 1 << 1, + [VM.OpCode.XDROP] = 1 << 4, + [VM.OpCode.CLEAR] = 1 << 4, + [VM.OpCode.DUP] = 1 << 1, + [VM.OpCode.OVER] = 1 << 1, + [VM.OpCode.PICK] = 1 << 1, + [VM.OpCode.TUCK] = 1 << 1, + [VM.OpCode.SWAP] = 1 << 1, + [VM.OpCode.ROT] = 1 << 1, + [VM.OpCode.ROLL] = 1 << 4, + [VM.OpCode.REVERSE3] = 1 << 1, + [VM.OpCode.REVERSE4] = 1 << 1, + [VM.OpCode.REVERSEN] = 1 << 4, + [VM.OpCode.INITSSLOT] = 1 << 4, + [VM.OpCode.INITSLOT] = 1 << 6, + [VM.OpCode.LDSFLD0] = 1 << 1, + [VM.OpCode.LDSFLD1] = 1 << 1, + [VM.OpCode.LDSFLD2] = 1 << 1, + [VM.OpCode.LDSFLD3] = 1 << 1, + [VM.OpCode.LDSFLD4] = 1 << 1, + [VM.OpCode.LDSFLD5] = 1 << 1, + [VM.OpCode.LDSFLD6] = 1 << 1, + [VM.OpCode.LDSFLD] = 1 << 1, + [VM.OpCode.STSFLD0] = 1 << 1, + [VM.OpCode.STSFLD1] = 1 << 1, + [VM.OpCode.STSFLD2] = 1 << 1, + [VM.OpCode.STSFLD3] = 1 << 1, + [VM.OpCode.STSFLD4] = 1 << 1, + [VM.OpCode.STSFLD5] = 1 << 1, + [VM.OpCode.STSFLD6] = 1 << 1, + [VM.OpCode.STSFLD] = 1 << 1, + [VM.OpCode.LDLOC0] = 1 << 1, + [VM.OpCode.LDLOC1] = 1 << 1, + [VM.OpCode.LDLOC2] = 1 << 1, + [VM.OpCode.LDLOC3] = 1 << 1, + [VM.OpCode.LDLOC4] = 1 << 1, + [VM.OpCode.LDLOC5] = 1 << 1, + [VM.OpCode.LDLOC6] = 1 << 1, + [VM.OpCode.LDLOC] = 1 << 1, + [VM.OpCode.STLOC0] = 1 << 1, + [VM.OpCode.STLOC1] = 1 << 1, + [VM.OpCode.STLOC2] = 1 << 1, + [VM.OpCode.STLOC3] = 1 << 1, + [VM.OpCode.STLOC4] = 1 << 1, + [VM.OpCode.STLOC5] = 1 << 1, + [VM.OpCode.STLOC6] = 1 << 1, + [VM.OpCode.STLOC] = 1 << 1, + [VM.OpCode.LDARG0] = 1 << 1, + [VM.OpCode.LDARG1] = 1 << 1, + [VM.OpCode.LDARG2] = 1 << 1, + [VM.OpCode.LDARG3] = 1 << 1, + [VM.OpCode.LDARG4] = 1 << 1, + [VM.OpCode.LDARG5] = 1 << 1, + [VM.OpCode.LDARG6] = 1 << 1, + [VM.OpCode.LDARG] = 1 << 1, + [VM.OpCode.STARG0] = 1 << 1, + [VM.OpCode.STARG1] = 1 << 1, + [VM.OpCode.STARG2] = 1 << 1, + [VM.OpCode.STARG3] = 1 << 1, + [VM.OpCode.STARG4] = 1 << 1, + [VM.OpCode.STARG5] = 1 << 1, + [VM.OpCode.STARG6] = 1 << 1, + [VM.OpCode.STARG] = 1 << 1, + [VM.OpCode.NEWBUFFER] = 1 << 8, + [VM.OpCode.MEMCPY] = 1 << 11, + [VM.OpCode.CAT] = 1 << 11, + [VM.OpCode.SUBSTR] = 1 << 11, + [VM.OpCode.LEFT] = 1 << 11, + [VM.OpCode.RIGHT] = 1 << 11, + [VM.OpCode.INVERT] = 1 << 2, + [VM.OpCode.AND] = 1 << 3, + [VM.OpCode.OR] = 1 << 3, + [VM.OpCode.XOR] = 1 << 3, + [VM.OpCode.EQUAL] = 1 << 5, + [VM.OpCode.NOTEQUAL] = 1 << 5, + [VM.OpCode.SIGN] = 1 << 2, + [VM.OpCode.ABS] = 1 << 2, + [VM.OpCode.NEGATE] = 1 << 2, + [VM.OpCode.INC] = 1 << 2, + [VM.OpCode.DEC] = 1 << 2, + [VM.OpCode.ADD] = 1 << 3, + [VM.OpCode.SUB] = 1 << 3, + [VM.OpCode.MUL] = 1 << 3, + [VM.OpCode.DIV] = 1 << 3, + [VM.OpCode.MOD] = 1 << 3, + [VM.OpCode.POW] = 1 << 6, + [VM.OpCode.SQRT] = 1 << 6, + [VM.OpCode.MODMUL] = 1 << 5, + [VM.OpCode.MODPOW] = 1 << 11, + [VM.OpCode.SHL] = 1 << 3, + [VM.OpCode.SHR] = 1 << 3, + [VM.OpCode.NOT] = 1 << 2, + [VM.OpCode.BOOLAND] = 1 << 3, + [VM.OpCode.BOOLOR] = 1 << 3, + [VM.OpCode.NZ] = 1 << 2, + [VM.OpCode.NUMEQUAL] = 1 << 3, + [VM.OpCode.NUMNOTEQUAL] = 1 << 3, + [VM.OpCode.LT] = 1 << 3, + [VM.OpCode.LE] = 1 << 3, + [VM.OpCode.GT] = 1 << 3, + [VM.OpCode.GE] = 1 << 3, + [VM.OpCode.MIN] = 1 << 3, + [VM.OpCode.MAX] = 1 << 3, + [VM.OpCode.WITHIN] = 1 << 3, + [VM.OpCode.PACKMAP] = 1 << 11, + [VM.OpCode.PACKSTRUCT] = 1 << 11, + [VM.OpCode.PACK] = 1 << 11, + [VM.OpCode.UNPACK] = 1 << 11, + [VM.OpCode.NEWARRAY0] = 1 << 4, + [VM.OpCode.NEWARRAY] = 1 << 9, + [VM.OpCode.NEWARRAY_T] = 1 << 9, + [VM.OpCode.NEWSTRUCT0] = 1 << 4, + [VM.OpCode.NEWSTRUCT] = 1 << 9, + [VM.OpCode.NEWMAP] = 1 << 3, + [VM.OpCode.SIZE] = 1 << 2, + [VM.OpCode.HASKEY] = 1 << 6, + [VM.OpCode.KEYS] = 1 << 4, + [VM.OpCode.VALUES] = 1 << 13, + [VM.OpCode.PICKITEM] = 1 << 6, + [VM.OpCode.APPEND] = 1 << 13, + [VM.OpCode.SETITEM] = 1 << 13, + [VM.OpCode.REVERSEITEMS] = 1 << 13, + [VM.OpCode.REMOVE] = 1 << 4, + [VM.OpCode.CLEARITEMS] = 1 << 4, + [VM.OpCode.POPITEM] = 1 << 4, + [VM.OpCode.ISNULL] = 1 << 1, + [VM.OpCode.ISTYPE] = 1 << 1, + [VM.OpCode.CONVERT] = 1 << 13, + }; + + internal static void RunScript(byte[] script) + { + LoadScript(script).ExecuteBenchmark(); + } + + internal static BenchmarkEngine RunScriptUntil(byte[] script, VM.OpCode opCode) + { + return LoadScript(script).ExecuteUntil(opCode); + } + + internal static BenchmarkEngine LoadScript(byte[] script) + { + var engine = new BenchmarkEngine(); + engine.LoadScript(script); + return engine; + } +} diff --git a/benchmarks/Neo.VM.Benchmarks/OpCode/BenchmarkEngine.cs b/benchmarks/Neo.VM.Benchmarks/OpCode/BenchmarkEngine.cs new file mode 100644 index 0000000000..50f80c57e9 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/OpCode/BenchmarkEngine.cs @@ -0,0 +1,190 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BenchmarkEngine.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.Test.Types; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Neo.VM.Benchmark.OpCode; + +/// +/// A simple benchmark engine for . +/// +public class BenchmarkEngine : TestEngine +{ + private readonly Dictionary _opcodeStats = new(); + private readonly Dictionary> _breakPoints = new(); + private long _gasConsumed = 0; + + /// + /// Add a breakpoint at the specified position of the specified script. The VM will break the execution when it reaches the breakpoint. + /// + /// The script to add the breakpoint. + /// The position of the breakpoint in the script. + public void AddBreakPoint(Script script, uint position) + { + if (!_breakPoints.TryGetValue(script, out var hashset)) + { + hashset = []; + _breakPoints.Add(script, hashset); + } + hashset.Add(position); + } + + /// + /// Start or continue execution of the VM. + /// + /// Returns the state of the VM after the execution. + public BenchmarkEngine ExecuteUntil(VM.OpCode opCode) + { + if (State == VMState.BREAK) + State = VMState.NONE; + while (State == VMState.NONE) + { + ExecuteNext(); + try + { + var instruction = CurrentContext!.CurrentInstruction!.OpCode; + if (instruction == opCode) break; + } + catch + { + break; + } + } + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ExecuteBenchmark() + { + while (State != VMState.HALT && State != VMState.FAULT) + { +#if DEBUG + var stopwatch = Stopwatch.StartNew(); +#endif + ExecuteNext(); +#if DEBUG + stopwatch.Stop(); + UpdateOpcodeStats(CurrentContext!.CurrentInstruction!.OpCode, stopwatch.Elapsed); +#endif + } +#if DEBUG + PrintOpcodeStats(); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ExecuteOneGASBenchmark() + { + while (State != VMState.HALT && State != VMState.FAULT) + { + var instruction = CurrentContext!.CurrentInstruction ?? VM.Instruction.RET; + _gasConsumed += Benchmark_Opcode.OpCodePrices[instruction.OpCode]; + if (_gasConsumed >= Benchmark_Opcode.OneGasDatoshi) + { + State = VMState.HALT; + } +#if DEBUG + var stopwatch = Stopwatch.StartNew(); +#endif + ExecuteNext(); +#if DEBUG + stopwatch.Stop(); + UpdateOpcodeStats(instruction.OpCode, stopwatch.Elapsed); +#endif + } +#if DEBUG + PrintOpcodeStats(); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ExecuteTwentyGASBenchmark() + { + while (State != VMState.HALT && State != VMState.FAULT) + { + var instruction = CurrentContext!.CurrentInstruction ?? VM.Instruction.RET; + _gasConsumed += Benchmark_Opcode.OpCodePrices[instruction.OpCode]; + if (_gasConsumed >= 20 * Benchmark_Opcode.OneGasDatoshi) + { + State = VMState.HALT; + } +#if DEBUG + var stopwatch = Stopwatch.StartNew(); +#endif + ExecuteNext(); +#if DEBUG + stopwatch.Stop(); + UpdateOpcodeStats(instruction.OpCode, stopwatch.Elapsed); +#endif + } +#if DEBUG + PrintOpcodeStats(); +#endif + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void ExecuteOpCodesBenchmark() + { + while (State != VMState.HALT && State != VMState.FAULT) + { + var instruction = CurrentContext!.CurrentInstruction ?? VM.Instruction.RET; + _gasConsumed += Benchmark_Opcode.OpCodePrices[instruction.OpCode]; + if (_gasConsumed >= Benchmark_Opcode.OneGasDatoshi) + { + State = VMState.HALT; + } +#if DEBUG + var stopwatch = Stopwatch.StartNew(); +#endif + ExecuteNext(); +#if DEBUG + stopwatch.Stop(); + UpdateOpcodeStats(instruction.OpCode, stopwatch.Elapsed); +#endif + } +#if DEBUG + PrintOpcodeStats(); +#endif + } + + protected override void OnFault(Exception ex) + { + base.OnFault(ex); + // throw ex; + } + + private void UpdateOpcodeStats(VM.OpCode opcode, TimeSpan elapsed) + { + if (!_opcodeStats.TryGetValue(opcode, out var value)) + { + _opcodeStats[opcode] = (1, elapsed); + } + else + { + var (count, totalTime) = value; + _opcodeStats[opcode] = (count + 1, totalTime + elapsed); + } + } + + private void PrintOpcodeStats() + { + Console.WriteLine("Opcode Statistics:"); + foreach (var kvp in _opcodeStats) + { + Console.WriteLine($"{kvp.Key,-15} " + + $"Count: {kvp.Value.Count,8} " + + $"Total Time: {kvp.Value.TotalTime.TotalMilliseconds * 1000,10:F2} μs " + + $"Avg Time: {kvp.Value.TotalTime.TotalMilliseconds * 1000 / kvp.Value.Count,10:F2} μs"); + } + } +} diff --git a/benchmarks/Neo.VM.Benchmarks/OpCode/BenchmarkMode.cs b/benchmarks/Neo.VM.Benchmarks/OpCode/BenchmarkMode.cs new file mode 100644 index 0000000000..792ff04f16 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/OpCode/BenchmarkMode.cs @@ -0,0 +1,19 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BenchmarkMode.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.VM.Benchmark.OpCode; + +public enum BenchmarkMode +{ + SimpleOpCode, + OneGAS, + BaseLine +} diff --git a/benchmarks/Neo.VM.Benchmarks/OpCode/OpCodeBase.cs b/benchmarks/Neo.VM.Benchmarks/OpCode/OpCodeBase.cs new file mode 100644 index 0000000000..78aa0dd24c --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/OpCode/OpCodeBase.cs @@ -0,0 +1,45 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// OpCodeBase.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 BenchmarkDotNet.Attributes; + +namespace Neo.VM.Benchmark.OpCode; + +public abstract class OpCodeBase +{ + [Params(1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2040)] + public int ItemCount { get; set; } = 10; + protected byte[] baseLineScript; + protected byte[] script; + protected byte[] multiScript; + + [GlobalSetup] + public void Setup() + { + script = CreateScript(BenchmarkMode.SimpleOpCode); + multiScript = CreateScript(BenchmarkMode.OneGAS); + baseLineScript = CreateScript(BenchmarkMode.BaseLine); + } + + [Benchmark(Baseline = true)] + public void Bench_BaseLine() => Benchmark_Opcode.RunScript(baseLineScript); + + [Benchmark] + public void Bench_OneOpCode() => Benchmark_Opcode.RunScript(script); + + /// + /// Benchmark how long 1 GAS can run. + /// + [Benchmark] + public void Bench_OneGAS() => Benchmark_Opcode.LoadScript(multiScript).ExecuteOneGASBenchmark(); + + protected abstract byte[] CreateScript(BenchmarkMode benchmarkMode); +} diff --git a/benchmarks/Neo.VM.Benchmarks/Program.cs b/benchmarks/Neo.VM.Benchmarks/Program.cs index a9c86f405d..09523ecd7c 100644 --- a/benchmarks/Neo.VM.Benchmarks/Program.cs +++ b/benchmarks/Neo.VM.Benchmarks/Program.cs @@ -9,10 +9,62 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -using Neo.VM; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Running; +using Neo.VM.Benchmark; using System.Reflection; -foreach (var method in typeof(Benchmarks).GetMethods(BindingFlags.Public | BindingFlags.Static)) +// Flag to determine if running benchmark or running methods +// If `NEO_VM_BENCHMARK` environment variable is set, run benchmark no matter. +var runBenchmark = true; + +// Define the benchmark or execute class +var benchmarkType = typeof(Benchmarks_PoCs); + +/* + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + | DO NOT MODIFY THE CODE BELOW | + | | + | All configuration should be done above this line | + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ + +// Explanation: +// Benchmark methods must contain no parameters to be valid. +// This is because we need to be able to invoke these methods repeatedly +// without any external input. All necessary data should be set up in the Setup method +// or as properties of the benchmark class. + +// Example: + +// [Benchmark] +// public void BenchmarkMethod() +// { +// // Benchmark code here +// } +if (Environment.GetEnvironmentVariable("NEO_VM_BENCHMARK") != null || runBenchmark) { - method.CreateDelegate().Invoke(); + BenchmarkRunner.Run(benchmarkType); +} +else +{ + var instance = Activator.CreateInstance(benchmarkType); + var setupMethod = benchmarkType.GetMethods(BindingFlags.Public | BindingFlags.Instance) + .FirstOrDefault(m => m.GetCustomAttribute() != null); + if (setupMethod != null) + { + setupMethod.Invoke(instance, null); + } + + var methods = benchmarkType.GetMethods(BindingFlags.Public | BindingFlags.Instance); + + foreach (var method in methods) + { + if (method.DeclaringType == benchmarkType && !method.GetCustomAttributes().Any()) + { + method.Invoke(instance, null); + } + } } diff --git a/benchmarks/Neo.VM.Benchmarks/TestArray.cs b/benchmarks/Neo.VM.Benchmarks/TestArray.cs new file mode 100644 index 0000000000..0799040e52 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/TestArray.cs @@ -0,0 +1,141 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestArray.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; +using System.Collections; + +namespace Neo.VM.Benchmark; + +public class TestArray : CompoundType, IReadOnlyList +{ + protected readonly List _array; + + /// + /// Get or set item in the array. + /// + /// The index of the item in the array. + /// The item at the specified index. + public StackItem this[int index] + { + get => _array[index]; + set + { + if (IsReadOnly) throw new InvalidOperationException("The object is readonly."); + ReferenceCounter?.RemoveReference(_array[index], this); + _array[index] = value; + ReferenceCounter?.AddReference(value, this); + } + } + + /// + /// The number of items in the array. + /// + public override int Count => _array.Count; + public override IEnumerable SubItems => _array; + public override int SubItemsCount => _array.Count; + public override StackItemType Type => StackItemType.Array; + + /// + /// Create an array containing the specified items. + /// + /// The items to be included in the array. + public TestArray(IEnumerable? items = null) + : this(null, items) + { + } + + /// + /// Create an array containing the specified items. And make the array use the specified . + /// + /// The to be used by this array. + /// The items to be included in the array. + public TestArray(IReferenceCounter? referenceCounter, IEnumerable? items = null) + : base(referenceCounter) + { + _array = items switch + { + null => new List(), + List list => list, + _ => new List(items) + }; + if (referenceCounter != null) + foreach (StackItem item in _array) + referenceCounter.AddReference(item, this); + } + + /// + /// Add a new item at the end of the array. + /// + /// The item to be added. + public void Add(StackItem item) + { + if (IsReadOnly) throw new InvalidOperationException("The object is readonly."); + _array.Add(item); + ReferenceCounter?.AddReference(item, this); + } + + public override void Clear() + { + if (IsReadOnly) throw new InvalidOperationException("The object is readonly."); + if (ReferenceCounter != null) + foreach (StackItem item in _array) + ReferenceCounter.RemoveReference(item, this); + _array.Clear(); + } + + public override StackItem ConvertTo(StackItemType type) + { + if (Type == StackItemType.Array && type == StackItemType.Struct) + return new Struct(ReferenceCounter, new List(_array)); + return base.ConvertTo(type); + } + + internal sealed override StackItem DeepCopy(Dictionary refMap, bool asImmutable) + { + if (refMap.TryGetValue(this, out StackItem? mappedItem)) return mappedItem; + var result = this is TestStruct ? new TestStruct(ReferenceCounter) : new TestArray(ReferenceCounter); + refMap.Add(this, result); + foreach (StackItem item in _array) + result.Add(item.DeepCopy(refMap, asImmutable)); + result.IsReadOnly = true; + return result; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public IEnumerator GetEnumerator() + { + return _array.GetEnumerator(); + } + + /// + /// Remove the item at the specified index. + /// + /// The index of the item to be removed. + public void RemoveAt(int index) + { + if (IsReadOnly) throw new InvalidOperationException("The object is readonly."); + ReferenceCounter?.RemoveReference(_array[index], this); + _array.RemoveAt(index); + } + + /// + /// Reverse all items in the array. + /// + public void Reverse() + { + if (IsReadOnly) throw new InvalidOperationException("The object is readonly."); + _array.Reverse(); + } +} diff --git a/benchmarks/Neo.VM.Benchmarks/TestStruct.cs b/benchmarks/Neo.VM.Benchmarks/TestStruct.cs new file mode 100644 index 0000000000..f7dc2fcb64 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/TestStruct.cs @@ -0,0 +1,129 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestStruct.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; + +namespace Neo.VM.Benchmark; + +public class TestStruct : TestArray +{ + public override StackItemType Type => StackItemType.Struct; + + /// + /// Create a structure with the specified fields. + /// + /// The fields to be included in the structure. + public TestStruct(IEnumerable? fields = null) + : this(null, fields) + { + } + + /// + /// Create a structure with the specified fields. And make the structure use the specified . + /// + /// The to be used by this structure. + /// The fields to be included in the structure. + public TestStruct(IReferenceCounter? referenceCounter, IEnumerable? fields = null) + : base(referenceCounter, fields) + { + } + + /// + /// Create a new structure with the same content as this structure. All nested structures will be copied by value. + /// + /// Execution engine limits + /// The copied structure. + public TestStruct Clone(ExecutionEngineLimits limits) + { + int count = (int)(limits.MaxStackSize - 1); + TestStruct result = new(ReferenceCounter); + Queue queue = new(); + queue.Enqueue(result); + queue.Enqueue(this); + while (queue.Count > 0) + { + TestStruct a = queue.Dequeue(); + TestStruct b = queue.Dequeue(); + foreach (StackItem item in b) + { + count--; + if (count < 0) throw new InvalidOperationException("Beyond clone limits!"); + if (item is TestStruct sb) + { + TestStruct sa = new(ReferenceCounter); + a.Add(sa); + queue.Enqueue(sa); + queue.Enqueue(sb); + } + else + { + a.Add(item); + } + } + } + return result; + } + + public override StackItem ConvertTo(StackItemType type) + { + if (type == StackItemType.Array) + return new TestArray(ReferenceCounter, new List(_array)); + return base.ConvertTo(type); + } + + public override bool Equals(StackItem? other) + { + throw new NotSupportedException(); + } + + internal override bool Equals(StackItem? other, ExecutionEngineLimits limits) + { + if (other is not TestStruct s) return false; + Stack stack1 = new(); + Stack stack2 = new(); + stack1.Push(this); + stack2.Push(s); + uint count = limits.MaxStackSize; + uint maxComparableSize = limits.MaxComparableSize; + while (stack1.Count > 0) + { + if (count-- == 0) + throw new InvalidOperationException("Too many struct items to compare."); + StackItem a = stack1.Pop(); + StackItem b = stack2.Pop(); + if (a is ByteString byteString) + { + if (!byteString.Equals(b, ref maxComparableSize)) return false; + } + else + { + if (maxComparableSize == 0) + throw new InvalidOperationException("The operand exceeds the maximum comparable size."); + maxComparableSize -= 1; + if (a is TestStruct sa) + { + if (ReferenceEquals(a, b)) continue; + if (b is not TestStruct sb) return false; + if (sa.Count != sb.Count) return false; + foreach (StackItem item in sa) + stack1.Push(item); + foreach (StackItem item in sb) + stack2.Push(item); + } + else + { + if (!a.Equals(b)) return false; + } + } + } + return true; + } +} diff --git a/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_Convert.cs b/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_Convert.cs new file mode 100644 index 0000000000..91c660ff48 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_Convert.cs @@ -0,0 +1,216 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Benchmarks_Convert.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 BenchmarkDotNet.Attributes; +using Neo.VM.Types; +using Array = Neo.VM.Types.Array; +using Buffer = Neo.VM.Types.Buffer; + +namespace Neo.VM.Benchmark; + +public class Benchmarks_Convert +{ + private Dictionary> testItemsByType; + + [GlobalSetup] + public void Setup() + { + testItemsByType = CreateTestItemsByType(); + } + + [Benchmark] + [ArgumentsSource(nameof(GetTypeConversionPairs))] + public void BenchConvertTo(StackItemType fromType, StackItemType toType) + { + foreach (var item in testItemsByType[fromType]) + { + try + { + _ = item.ConvertTo(toType); + } + catch (Exception) + { + // Ignore invalid casts as they're expected for some conversions + } + } + } + + public IEnumerable GetTypeConversionPairs() + { + var types = (StackItemType[])Enum.GetValues(typeof(StackItemType)); + foreach (var fromType in types) + { + foreach (var toType in types) + { + yield return new object[] { fromType, toType }; + } + } + } + + private Dictionary> CreateTestItemsByType() + { + var referenceCounter = new ReferenceCounter(); + var result = new Dictionary>(); + + foreach (StackItemType type in Enum.GetValues(typeof(StackItemType))) + { + result[type] = new List(); + } + + result[StackItemType.Boolean].Add(StackItem.True); + result[StackItemType.Boolean].Add(StackItem.False); + + result[StackItemType.Integer].Add(new Integer(42)); + result[StackItemType.Integer].Add(new Integer(-1)); + + result[StackItemType.ByteString].Add(new ByteString(new byte[] { 1, 2, 3 })); + result[StackItemType.ByteString].Add(new ByteString(new byte[] { 255, 0, 128 })); + + // Create a 128-byte buffer + var longBuffer = new byte[128]; + for (int i = 0; i < 128; i++) longBuffer[i] = (byte)(i % 256); + result[StackItemType.Buffer].Add(new Buffer(longBuffer)); + result[StackItemType.Buffer].Add(new Buffer(new byte[128])); // Another 128-byte buffer, all zeros + + // Create an array with 10 items + var longArray = new Array(referenceCounter); + for (int i = 0; i < 10; i++) longArray.Add(new Integer(i)); + result[StackItemType.Array].Add(longArray); + result[StackItemType.Array].Add(new Array(referenceCounter) { StackItem.True, new ByteString(new byte[] { 3, 4, 5 }) }); + + // Create a struct with 10 items + var longStruct = new Struct(referenceCounter); + for (int i = 0; i < 10; i++) longStruct.Add(new Integer(i * 10)); + result[StackItemType.Struct].Add(longStruct); + result[StackItemType.Struct].Add(new Struct(referenceCounter) { StackItem.False, new Buffer(new byte[] { 6, 7, 8 }) }); + + // Create a map with 10 items + var longMap = new Map(referenceCounter); + for (int i = 0; i < 10; i++) longMap[new Integer(i)] = new ByteString(new byte[] { (byte)(i * 20) }); + result[StackItemType.Map].Add(longMap); + result[StackItemType.Map].Add(new Map(referenceCounter) { [new ByteString(new byte[] { 9 })] = StackItem.True }); + + result[StackItemType.InteropInterface].Add(new InteropInterface(new object())); + result[StackItemType.InteropInterface].Add(new InteropInterface("test string")); + + return result; + } +} + +// BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.4249/23H2/2023Update/SunValley3) +// Intel Core i9-14900HX, 1 CPU, 32 logical and 24 physical cores +// .NET SDK 8.0.205 +// [Host] : .NET 8.0.5 (8.0.524.21615), X64 RyuJIT AVX2 +// DefaultJob : .NET 8.0.5 (8.0.524.21615), X64 RyuJIT AVX2 +// +// +// | Method | fromType | toType | Mean | Error | StdDev | +// |--------------- |----------------- |----------------- |-------------:|------------:|------------:| +// | BenchConvertTo | Any | Any | 1.762 ns | 0.0195 ns | 0.0182 ns | +// | BenchConvertTo | Any | Pointer | 1.791 ns | 0.0196 ns | 0.0183 ns | +// | BenchConvertTo | Any | Boolean | 1.774 ns | 0.0245 ns | 0.0229 ns | +// | BenchConvertTo | Any | Integer | 1.781 ns | 0.0236 ns | 0.0220 ns | +// | BenchConvertTo | Any | ByteString | 1.767 ns | 0.0255 ns | 0.0226 ns | +// | BenchConvertTo | Any | Buffer | 1.774 ns | 0.0217 ns | 0.0203 ns | +// | BenchConvertTo | Any | Array | 1.770 ns | 0.0412 ns | 0.0385 ns | +// | BenchConvertTo | Any | Struct | 1.787 ns | 0.0227 ns | 0.0212 ns | +// | BenchConvertTo | Any | Map | 1.796 ns | 0.0292 ns | 0.0273 ns | +// | BenchConvertTo | Any | InteropInterface | 1.820 ns | 0.0549 ns | 0.0675 ns | +// | BenchConvertTo | Pointer | Any | 2.312 ns | 0.0210 ns | 0.0175 ns | +// | BenchConvertTo | Pointer | Pointer | 2.337 ns | 0.0157 ns | 0.0146 ns | +// | BenchConvertTo | Pointer | Boolean | 2.352 ns | 0.0190 ns | 0.0169 ns | +// | BenchConvertTo | Pointer | Integer | 2.334 ns | 0.0231 ns | 0.0216 ns | +// | BenchConvertTo | Pointer | ByteString | 2.317 ns | 0.0298 ns | 0.0279 ns | +// | BenchConvertTo | Pointer | Buffer | 2.329 ns | 0.0274 ns | 0.0256 ns | +// | BenchConvertTo | Pointer | Array | 2.338 ns | 0.0257 ns | 0.0241 ns | +// | BenchConvertTo | Pointer | Struct | 2.336 ns | 0.0318 ns | 0.0298 ns | +// | BenchConvertTo | Pointer | Map | 2.351 ns | 0.0676 ns | 0.0903 ns | +// | BenchConvertTo | Pointer | InteropInterface | 2.281 ns | 0.0133 ns | 0.0125 ns | +// | BenchConvertTo | Boolean | Any | 5,926.451 ns | 118.1195 ns | 136.0266 ns | +// | BenchConvertTo | Boolean | Pointer | 6,001.282 ns | 15.3048 ns | 12.7802 ns | +// | BenchConvertTo | Boolean | Boolean | 4.459 ns | 0.0151 ns | 0.0133 ns | +// | BenchConvertTo | Boolean | Integer | 14.104 ns | 0.1526 ns | 0.1428 ns | +// | BenchConvertTo | Boolean | ByteString | 11.650 ns | 0.0539 ns | 0.0450 ns | +// | BenchConvertTo | Boolean | Buffer | 26.106 ns | 0.1549 ns | 0.1449 ns | +// | BenchConvertTo | Boolean | Array | 5,813.116 ns | 28.1911 ns | 26.3700 ns | +// | BenchConvertTo | Boolean | Struct | 5,809.844 ns | 19.1249 ns | 15.9702 ns | +// | BenchConvertTo | Boolean | Map | 6,061.558 ns | 29.3991 ns | 27.4999 ns | +// | BenchConvertTo | Boolean | InteropInterface | 5,924.682 ns | 80.5533 ns | 75.3496 ns | +// | BenchConvertTo | Integer | Any | 5,240.903 ns | 41.0628 ns | 38.4102 ns | +// | BenchConvertTo | Integer | Pointer | 5,479.116 ns | 75.8232 ns | 70.9251 ns | +// | BenchConvertTo | Integer | Boolean | 5.981 ns | 0.0445 ns | 0.0416 ns | +// | BenchConvertTo | Integer | Integer | 4.277 ns | 0.0177 ns | 0.0166 ns | +// | BenchConvertTo | Integer | ByteString | 19.053 ns | 0.2125 ns | 0.1883 ns | +// | BenchConvertTo | Integer | Buffer | 32.782 ns | 0.1653 ns | 0.1380 ns | +// | BenchConvertTo | Integer | Array | 4,693.207 ns | 14.2446 ns | 12.6275 ns | +// | BenchConvertTo | Integer | Struct | 4,737.341 ns | 60.1813 ns | 56.2936 ns | +// | BenchConvertTo | Integer | Map | 4,808.431 ns | 23.5380 ns | 22.0174 ns | +// | BenchConvertTo | Integer | InteropInterface | 4,684.409 ns | 24.7033 ns | 21.8989 ns | +// | BenchConvertTo | ByteString | Any | 5,833.857 ns | 20.1553 ns | 18.8533 ns | +// | BenchConvertTo | ByteString | Pointer | 5,807.973 ns | 11.7754 ns | 10.4386 ns | +// | BenchConvertTo | ByteString | Boolean | 33.007 ns | 0.1574 ns | 0.1472 ns | +// | BenchConvertTo | ByteString | Integer | 23.622 ns | 0.0755 ns | 0.0669 ns | +// | BenchConvertTo | ByteString | ByteString | 4.288 ns | 0.0152 ns | 0.0142 ns | +// | BenchConvertTo | ByteString | Buffer | 24.881 ns | 0.0889 ns | 0.0788 ns | +// | BenchConvertTo | ByteString | Array | 6,030.813 ns | 19.9562 ns | 18.6670 ns | +// | BenchConvertTo | ByteString | Struct | 5,811.185 ns | 24.0781 ns | 22.5226 ns | +// | BenchConvertTo | ByteString | Map | 5,866.820 ns | 17.0315 ns | 15.0980 ns | +// | BenchConvertTo | ByteString | InteropInterface | 5,757.124 ns | 16.3184 ns | 14.4658 ns | +// | BenchConvertTo | Buffer | Any | 4,886.279 ns | 17.1370 ns | 14.3102 ns | +// | BenchConvertTo | Buffer | Pointer | 4,698.364 ns | 14.5491 ns | 12.1492 ns | +// | BenchConvertTo | Buffer | Boolean | 6.130 ns | 0.0323 ns | 0.0302 ns | +// | BenchConvertTo | Buffer | Integer | 4,645.764 ns | 15.8146 ns | 14.7930 ns | +// | BenchConvertTo | Buffer | ByteString | 29.874 ns | 0.1518 ns | 0.1268 ns | +// | BenchConvertTo | Buffer | Buffer | 4.939 ns | 0.0190 ns | 0.0178 ns | +// | BenchConvertTo | Buffer | Array | 4,683.427 ns | 21.3813 ns | 20.0001 ns | +// | BenchConvertTo | Buffer | Struct | 4,680.762 ns | 15.7220 ns | 13.9371 ns | +// | BenchConvertTo | Buffer | Map | 4,706.510 ns | 14.2061 ns | 12.5934 ns | +// | BenchConvertTo | Buffer | InteropInterface | 4,703.050 ns | 15.8002 ns | 14.0064 ns | +// | BenchConvertTo | Array | Any | 4,652.710 ns | 23.2061 ns | 20.5716 ns | +// | BenchConvertTo | Array | Pointer | 4,625.049 ns | 12.4455 ns | 11.6415 ns | +// | BenchConvertTo | Array | Boolean | 5.568 ns | 0.0181 ns | 0.0169 ns | +// | BenchConvertTo | Array | Integer | 4,659.897 ns | 19.8036 ns | 18.5243 ns | +// | BenchConvertTo | Array | ByteString | 4,663.020 ns | 12.4988 ns | 11.6914 ns | +// | BenchConvertTo | Array | Buffer | 4,680.281 ns | 14.9748 ns | 13.2748 ns | +// | BenchConvertTo | Array | Array | 4.246 ns | 0.0124 ns | 0.0110 ns | +// | BenchConvertTo | Array | Struct | 1,193.106 ns | 98.5374 ns | 285.8748 ns | +// | BenchConvertTo | Array | Map | 4,742.631 ns | 35.5855 ns | 33.2867 ns | +// | BenchConvertTo | Array | InteropInterface | 4,670.743 ns | 9.3547 ns | 7.8116 ns | +// | BenchConvertTo | Struct | Any | 4,643.558 ns | 31.0451 ns | 29.0396 ns | +// | BenchConvertTo | Struct | Pointer | 4,867.925 ns | 22.2347 ns | 19.7105 ns | +// | BenchConvertTo | Struct | Boolean | 5.581 ns | 0.0251 ns | 0.0235 ns | +// | BenchConvertTo | Struct | Integer | 4,653.442 ns | 17.7417 ns | 16.5956 ns | +// | BenchConvertTo | Struct | ByteString | 4,646.242 ns | 13.7830 ns | 12.8926 ns | +// | BenchConvertTo | Struct | Buffer | 4,776.205 ns | 14.1918 ns | 13.2751 ns | +// | BenchConvertTo | Struct | Array | 1,622.573 ns | 144.8116 ns | 398.8532 ns | +// | BenchConvertTo | Struct | Struct | 4.195 ns | 0.0327 ns | 0.0290 ns | +// | BenchConvertTo | Struct | Map | 4,672.579 ns | 17.6257 ns | 16.4871 ns | +// | BenchConvertTo | Struct | InteropInterface | 4,653.476 ns | 8.2047 ns | 7.6747 ns | +// | BenchConvertTo | Map | Any | 4,676.540 ns | 15.2010 ns | 13.4753 ns | +// | BenchConvertTo | Map | Pointer | 4,663.489 ns | 13.7871 ns | 12.2219 ns | +// | BenchConvertTo | Map | Boolean | 5.535 ns | 0.0205 ns | 0.0192 ns | +// | BenchConvertTo | Map | Integer | 4,661.275 ns | 12.4402 ns | 11.6366 ns | +// | BenchConvertTo | Map | ByteString | 4,662.482 ns | 25.7111 ns | 24.0502 ns | +// | BenchConvertTo | Map | Buffer | 4,859.809 ns | 18.2981 ns | 16.2208 ns | +// | BenchConvertTo | Map | Array | 4,627.149 ns | 10.7487 ns | 9.5285 ns | +// | BenchConvertTo | Map | Struct | 4,646.504 ns | 22.4190 ns | 20.9707 ns | +// | BenchConvertTo | Map | Map | 4.160 ns | 0.0180 ns | 0.0169 ns | +// | BenchConvertTo | Map | InteropInterface | 4,667.024 ns | 14.1790 ns | 13.2630 ns | +// | BenchConvertTo | InteropInterface | Any | 4,700.511 ns | 17.4725 ns | 15.4889 ns | +// | BenchConvertTo | InteropInterface | Pointer | 4,705.819 ns | 25.2035 ns | 23.5754 ns | +// | BenchConvertTo | InteropInterface | Boolean | 5.557 ns | 0.0244 ns | 0.0228 ns | +// | BenchConvertTo | InteropInterface | Integer | 4,695.410 ns | 21.8674 ns | 20.4547 ns | +// | BenchConvertTo | InteropInterface | ByteString | 4,674.552 ns | 18.8705 ns | 17.6515 ns | +// | BenchConvertTo | InteropInterface | Buffer | 4,649.237 ns | 23.9084 ns | 22.3639 ns | +// | BenchConvertTo | InteropInterface | Array | 4,827.652 ns | 29.7153 ns | 27.7957 ns | +// | BenchConvertTo | InteropInterface | Struct | 4,624.202 ns | 10.3563 ns | 8.0855 ns | +// | BenchConvertTo | InteropInterface | Map | 4,695.310 ns | 23.1192 ns | 21.6257 ns | +// | BenchConvertTo | InteropInterface | InteropInterface | 4.137 ns | 0.0156 ns | 0.0138 ns | diff --git a/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_DeepCopy.cs b/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_DeepCopy.cs new file mode 100644 index 0000000000..1d9c267f5f --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/VMTypes/Benchmarks_DeepCopy.cs @@ -0,0 +1,122 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Benchmarks_DeepCopy.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 BenchmarkDotNet.Attributes; +using Array = Neo.VM.Types.Array; + +namespace Neo.VM.Benchmark; + +public class Benchmarks_DeepCopy +{ + public IEnumerable<(int Depth, int ElementsPerLevel)> ParamSource() + { + int[] depths = [2, 4]; + int[] elementsPerLevel = [2, 4, 6]; + + foreach (var depth in depths) + { + foreach (var elements in elementsPerLevel) + { + if (depth <= 8 || elements <= 2) + { + yield return (depth, elements); + } + } + } + } + + [ParamsSource(nameof(ParamSource))] + public (int Depth, int ElementsPerLevel) Params; + + [Benchmark] + public void BenchNestedArrayDeepCopy() + { + var root = new Array(new ReferenceCounter()); + CreateNestedArray(root, Params.Depth, Params.ElementsPerLevel); + _ = root.DeepCopy(); + } + + [Benchmark] + public void BenchNestedArrayDeepCopyWithReferenceCounter() + { + var referenceCounter = new ReferenceCounter(); + var root = new Array(referenceCounter); + CreateNestedArray(root, Params.Depth, Params.ElementsPerLevel, referenceCounter); + _ = root.DeepCopy(); + } + + [Benchmark] + public void BenchNestedTestArrayDeepCopy() + { + var root = new TestArray(new ReferenceCounter()); + CreateNestedTestArray(root, Params.Depth, Params.ElementsPerLevel); + _ = root.DeepCopy(); + } + + [Benchmark] + public void BenchNestedTestArrayDeepCopyWithReferenceCounter() + { + var referenceCounter = new ReferenceCounter(); + var root = new TestArray(referenceCounter); + CreateNestedTestArray(root, Params.Depth, Params.ElementsPerLevel, referenceCounter); + _ = root.DeepCopy(); + } + + private static void CreateNestedArray(Array? rootArray, int depth, int elementsPerLevel = 1, IReferenceCounter? referenceCounter = null) + { + if (depth < 0) + { + throw new ArgumentException("Depth must be non-negative", nameof(depth)); + } + + if (rootArray == null) + { + throw new ArgumentNullException(nameof(rootArray)); + } + + if (depth == 0) + { + return; + } + + for (var i = 0; i < elementsPerLevel; i++) + { + var childArray = new Array(referenceCounter); + rootArray.Add(childArray); + CreateNestedArray(childArray, depth - 1, elementsPerLevel, referenceCounter); + } + } + + private static void CreateNestedTestArray(TestArray rootArray, int depth, int elementsPerLevel = 1, IReferenceCounter referenceCounter = null) + { + if (depth < 0) + { + throw new ArgumentException("Depth must be non-negative", nameof(depth)); + } + + if (rootArray == null) + { + throw new ArgumentNullException(nameof(rootArray)); + } + + if (depth == 0) + { + return; + } + + for (var i = 0; i < elementsPerLevel; i++) + { + var childArray = new TestArray(referenceCounter); + rootArray.Add(childArray); + CreateNestedTestArray(childArray, depth - 1, elementsPerLevel, referenceCounter); + } + } +} diff --git a/src/Neo.VM/ReferenceCounter.md b/docs/ReferenceCounter.md similarity index 100% rename from src/Neo.VM/ReferenceCounter.md rename to docs/ReferenceCounter.md diff --git a/src/Neo.CLI/config.json.md b/docs/config.json.md similarity index 100% rename from src/Neo.CLI/config.json.md rename to docs/config.json.md diff --git a/src/Neo/IEventHandlers/handlers.md b/docs/handlers.md similarity index 100% rename from src/Neo/IEventHandlers/handlers.md rename to docs/handlers.md diff --git a/neo.sln b/neo.sln index b0de1c27b1..5e7f8c7dda 100644 --- a/neo.sln +++ b/neo.sln @@ -78,6 +78,10 @@ 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("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.Plugins.ApplicationLogs.Tests", "tests\Neo.Plugins.ApplicationLogs.Tests\Neo.Plugins.ApplicationLogs.Tests.csproj", "{8C866DC8-2E55-4399-9563-2F47FD4602EC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Extensions.Tests", "tests\Neo.Extensions.Tests\Neo.Extensions.Tests.csproj", "{77FDEE2E-9381-4BFC-B9E6-741EDBD6B90F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -216,6 +220,14 @@ 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 + {8C866DC8-2E55-4399-9563-2F47FD4602EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8C866DC8-2E55-4399-9563-2F47FD4602EC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8C866DC8-2E55-4399-9563-2F47FD4602EC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8C866DC8-2E55-4399-9563-2F47FD4602EC}.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 +267,8 @@ 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} + {8C866DC8-2E55-4399-9563-2F47FD4602EC} = {7F257712-D033-47FF-B439-9D4320D06599} + {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/Extensions.cs b/src/Neo.CLI/AssemblyExtensions.cs similarity index 88% rename from src/Neo.CLI/Extensions.cs rename to src/Neo.CLI/AssemblyExtensions.cs index b8331201be..42e49e13fc 100644 --- a/src/Neo.CLI/Extensions.cs +++ b/src/Neo.CLI/AssemblyExtensions.cs @@ -1,6 +1,6 @@ // Copyright (C) 2015-2024 The Neo Project. // -// Extensions.cs file belongs to the neo project and is free +// AssemblyExtensions.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 @@ -17,7 +17,7 @@ namespace Neo /// /// Extension methods /// - internal static class Extensions + internal static class AssemblyExtensions { public static string GetVersion(this Assembly assembly) { diff --git a/src/Neo.CLI/CLI/MainService.Blockchain.cs b/src/Neo.CLI/CLI/MainService.Blockchain.cs index 090939de49..2ac9c46ea4 100644 --- a/src/Neo.CLI/CLI/MainService.Blockchain.cs +++ b/src/Neo.CLI/CLI/MainService.Blockchain.cs @@ -82,7 +82,7 @@ private void OnShowBlockCommand(string indexOrHash) ConsoleHelper.Info("", " PrevHash: ", $"{block.PrevHash}"); ConsoleHelper.Info("", " NextConsensus: ", $"{block.NextConsensus}"); ConsoleHelper.Info("", " PrimaryIndex: ", $"{block.PrimaryIndex}"); - ConsoleHelper.Info("", " PrimaryPubKey: ", $"{NativeContract.NEO.GetCommittee(NeoSystem.GetSnapshot())[block.PrimaryIndex]}"); + ConsoleHelper.Info("", " PrimaryPubKey: ", $"{NativeContract.NEO.GetCommittee(NeoSystem.GetSnapshotCache())[block.PrimaryIndex]}"); ConsoleHelper.Info("", " Version: ", $"{block.Version}"); ConsoleHelper.Info("", " Size: ", $"{block.Size} Byte(s)"); ConsoleHelper.Info(); diff --git a/src/Neo.CLI/CLI/MainService.Network.cs b/src/Neo.CLI/CLI/MainService.Network.cs index 38d6fd1d28..49a056640e 100644 --- a/src/Neo.CLI/CLI/MainService.Network.cs +++ b/src/Neo.CLI/CLI/MainService.Network.cs @@ -11,6 +11,7 @@ using Akka.Actor; using Neo.ConsoleService; +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.Network.P2P; @@ -117,7 +118,7 @@ private void OnBroadcastInvCommand(InventoryType type, UInt256[] payload) [ConsoleCommand("broadcast transaction", Category = "Network Commands")] private void OnBroadcastTransactionCommand(UInt256 hash) { - if (NeoSystem.MemPool.TryGetValue(hash, out Transaction tx)) + if (NeoSystem.MemPool.TryGetValue(hash, out var tx)) OnBroadcastCommand(MessageCommand.Transaction, tx); } 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.Tools.cs b/src/Neo.CLI/CLI/MainService.Tools.cs index 66723d7df9..c000b48655 100644 --- a/src/Neo.CLI/CLI/MainService.Tools.cs +++ b/src/Neo.CLI/CLI/MainService.Tools.cs @@ -11,6 +11,7 @@ using Neo.ConsoleService; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.SmartContract; using Neo.VM; @@ -18,6 +19,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Numerics; using System.Reflection; @@ -56,7 +58,8 @@ private void OnParseCommand(string value) if (result != null) { - Console.WriteLine($"{pair.Key,-30}\t{result}"); + ConsoleHelper.Info("", "-----", pair.Key, "-----"); + ConsoleHelper.Info("", result, Environment.NewLine); any = true; } } @@ -417,62 +420,79 @@ private static string Base64Fixed(string str) [ParseFunction("Base64 Smart Contract Script Analysis")] private string? ScriptsToOpCode(string base64) { - Script script; try { - var scriptData = Convert.FromBase64String(base64); - script = new Script(scriptData.ToArray(), true); + var bytes = Convert.FromBase64String(base64); + var sb = new StringBuilder(); + var line = 0; + + foreach (var instruct in new VMInstruction(bytes)) + { + if (instruct.OperandSize == 0) + sb.AppendFormat("L{0:D04}:{1:X04} {2}{3}", line, instruct.Position, instruct.OpCode, Environment.NewLine); + else + sb.AppendFormat("L{0:D04}:{1:X04} {2,-10}{3}{4}", line, instruct.Position, instruct.OpCode, instruct.DecodeOperand(), Environment.NewLine); + line++; + } + + return sb.ToString(); } - catch (Exception) + catch { return null; } - return ScriptsToOpCode(script); } - private string ScriptsToOpCode(Script script) + /// + /// Base64 .nef file Analysis + /// + [ParseFunction("Base64 .nef file Analysis")] + private string? NefFileAnalyis(string base64) { - //Initialize all InteropService - var dic = new Dictionary(); - ApplicationEngine.Services.ToList().ForEach(p => dic.Add(p.Value.Hash, p.Value.Name)); - - //Analyzing Scripts - var ip = 0; - Instruction instruction; - var result = new List(); - while (ip < script.Length && (instruction = script.GetInstruction(ip)) != null) + byte[] nefData; + if (File.Exists(base64)) // extension name not considered + nefData = File.ReadAllBytes(base64); + else { - ip += instruction.Size; - - var op = instruction.OpCode; - - if (op.ToString().StartsWith("PUSHINT")) - { - var operand = instruction.Operand.ToArray(); - result.Add($"{op} {new BigInteger(operand)}"); - } - else if (op == OpCode.SYSCALL) + try { - var operand = instruction.Operand.ToArray(); - result.Add($"{op} {dic[BitConverter.ToUInt32(operand)]}"); - } - else - { - if (!instruction.Operand.IsEmpty && instruction.Operand.Length > 0) - { - var operand = instruction.Operand.ToArray(); - var ascii = Encoding.Default.GetString(operand); - ascii = ascii.Any(p => p < '0' || p > 'z') ? operand.ToHexString() : ascii; - - result.Add($"{op} {(operand.Length == 20 ? new UInt160(operand).ToString() : ascii)}"); - } - else - { - result.Add($"{op}"); - } + nefData = Convert.FromBase64String(base64); } + catch { return null; } + } + NefFile nef; + Script script; + bool verifyChecksum = false; + bool strictMode = false; + try + { + nef = NefFile.Parse(nefData, true); + verifyChecksum = true; } - return Environment.NewLine + string.Join("\r\n", result.ToArray()); + catch (FormatException) + { + nef = NefFile.Parse(nefData, false); + } + catch { return null; } + try + { + script = new Script(nef.Script, true); + strictMode = true; + } + catch (BadScriptException) + { + script = new Script(nef.Script, false); + } + catch { return null; } + string? result = ScriptsToOpCode(Convert.ToBase64String(nef.Script.ToArray())); + if (result == null) + return null; + string prefix = $"\r\n# Compiler: {nef.Compiler}"; + if (!verifyChecksum) + prefix += $"\r\n# Warning: Invalid .nef file checksum"; + if (!strictMode) + prefix += $"\r\n# Warning: Failed in {nameof(strictMode)}"; + return prefix + result; } /// diff --git a/src/Neo.CLI/CLI/MainService.Vote.cs b/src/Neo.CLI/CLI/MainService.Vote.cs index 12cd48b3a9..bec0bab91d 100644 --- a/src/Neo.CLI/CLI/MainService.Vote.cs +++ b/src/Neo.CLI/CLI/MainService.Vote.cs @@ -11,6 +11,7 @@ using Neo.ConsoleService; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.Json; using Neo.SmartContract; using Neo.SmartContract.Native; diff --git a/src/Neo.CLI/CLI/MainService.Wallet.cs b/src/Neo.CLI/CLI/MainService.Wallet.cs index db33e89564..54bd8dccab 100644 --- a/src/Neo.CLI/CLI/MainService.Wallet.cs +++ b/src/Neo.CLI/CLI/MainService.Wallet.cs @@ -12,6 +12,7 @@ using Akka.Actor; using Neo.ConsoleService; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.Json; using Neo.Network.P2P.Payloads; using Neo.Persistence; @@ -618,7 +619,7 @@ private void OnCancelCommand(UInt256 txid, UInt160? sender = null, UInt160[]? si return; } - if (NeoSystem.MemPool.TryGetValue(txid, out Transaction conflictTx)) + if (NeoSystem.MemPool.TryGetValue(txid, out var conflictTx)) { tx.NetworkFee = Math.Max(tx.NetworkFee, conflictTx.NetworkFee) + 1; } diff --git a/src/Neo.CLI/CLI/MainService.cs b/src/Neo.CLI/CLI/MainService.cs index 19ea5d9334..96db57a19d 100644 --- a/src/Neo.CLI/CLI/MainService.cs +++ b/src/Neo.CLI/CLI/MainService.cs @@ -12,6 +12,7 @@ using Akka.Actor; using Neo.ConsoleService; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.Ledger; @@ -33,7 +34,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 +383,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 +392,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.CLI/Neo.CLI.csproj b/src/Neo.CLI/Neo.CLI.csproj index eaa2cb3067..f2e1359194 100644 --- a/src/Neo.CLI/Neo.CLI.csproj +++ b/src/Neo.CLI/Neo.CLI.csproj @@ -31,6 +31,7 @@ + diff --git a/src/Neo.CLI/Tools/VMInstruction.cs b/src/Neo.CLI/Tools/VMInstruction.cs new file mode 100644 index 0000000000..9a4ba6a3d4 --- /dev/null +++ b/src/Neo.CLI/Tools/VMInstruction.cs @@ -0,0 +1,178 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// VMInstruction.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.SmartContract; +using Neo.VM; +using System; +using System.Buffers.Binary; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Numerics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Neo.CLI +{ + [DebuggerDisplay("OpCode={OpCode}, OperandSize={OperandSize}")] + internal sealed class VMInstruction : IEnumerable + { + private const int OpCodeSize = 1; + + public int Position { get; private init; } + public OpCode OpCode { get; private init; } + public ReadOnlyMemory Operand { get; private init; } + public int OperandSize { get; private init; } + public int OperandPrefixSize { get; private init; } + + private static readonly int[] s_operandSizeTable = new int[256]; + private static readonly int[] s_operandSizePrefixTable = new int[256]; + + private readonly ReadOnlyMemory _script; + + public VMInstruction(ReadOnlyMemory script, int start = 0) + { + if (script.IsEmpty) + throw new Exception("Bad Script."); + + var opcode = (OpCode)script.Span[start]; + + if (Enum.IsDefined(opcode) == false) + throw new InvalidDataException($"Invalid opcode at Position: {start}."); + + OperandPrefixSize = s_operandSizePrefixTable[(int)opcode]; + OperandSize = OperandPrefixSize switch + { + 0 => s_operandSizeTable[(int)opcode], + 1 => script.Span[start + 1], + 2 => BinaryPrimitives.ReadUInt16LittleEndian(script.Span[(start + 1)..]), + 4 => unchecked((int)BinaryPrimitives.ReadUInt32LittleEndian(script.Span[(start + 1)..])), + _ => throw new InvalidDataException($"Invalid opcode prefix at Position: {start}."), + }; + + OperandSize += OperandPrefixSize; + + if (start + OperandSize + OpCodeSize > script.Length) + throw new IndexOutOfRangeException("Operand size exceeds end of script."); + + Operand = script.Slice(start + OpCodeSize, OperandSize); + + _script = script; + OpCode = opcode; + Position = start; + } + + static VMInstruction() + { + foreach (var field in typeof(OpCode).GetFields(BindingFlags.Public | BindingFlags.Static)) + { + var attr = field.GetCustomAttribute(); + if (attr == null) continue; + + var index = (uint)(OpCode)field.GetValue(null)!; + s_operandSizeTable[index] = attr.Size; + s_operandSizePrefixTable[index] = attr.SizePrefix; + } + } + + public IEnumerator GetEnumerator() + { + var nip = Position + OperandSize + OpCodeSize; + yield return this; + + VMInstruction? instruct; + for (var ip = nip; ip < _script.Length; ip += instruct.OperandSize + OpCodeSize) + yield return instruct = new VMInstruction(_script, ip); + } + + IEnumerator IEnumerable.GetEnumerator() => + GetEnumerator(); + + public override string ToString() + { + var sb = new StringBuilder(); + sb.AppendFormat("{1:X04} {2,-10}{3}{4}", Position, OpCode, DecodeOperand()); + return sb.ToString(); + } + + public T AsToken(uint index = 0) + where T : unmanaged + { + var size = Unsafe.SizeOf(); + + if (size > OperandSize) + throw new ArgumentOutOfRangeException(nameof(T), $"SizeOf {typeof(T).FullName} is too big for operand. OpCode: {OpCode}."); + if (size + index > OperandSize) + throw new ArgumentOutOfRangeException(nameof(index), $"SizeOf {typeof(T).FullName} + {index} is too big for operand. OpCode: {OpCode}."); + + var bytes = Operand[..OperandSize].ToArray(); + return Unsafe.As(ref bytes[index]); + } + + public string DecodeOperand() + { + var operand = Operand[OperandPrefixSize..].ToArray(); + var asStr = Encoding.UTF8.GetString(operand); + var readable = asStr.All(char.IsAsciiLetterOrDigit); + + return OpCode switch + { + OpCode.JMP or + OpCode.JMPIF or + OpCode.JMPIFNOT or + OpCode.JMPEQ or + OpCode.JMPNE or + OpCode.JMPGT or + OpCode.JMPLT or + OpCode.CALL or + OpCode.ENDTRY => $"[{checked(Position + AsToken()):X08}]", + OpCode.JMP_L or + OpCode.JMPIF_L or + OpCode.PUSHA or + OpCode.JMPIFNOT_L or + OpCode.JMPEQ_L or + OpCode.JMPNE_L or + OpCode.JMPGT_L or + OpCode.JMPLT_L or + OpCode.CALL_L or + OpCode.ENDTRY_L => $"[{checked(Position + AsToken()):X08}]", + OpCode.TRY => $"[{AsToken():X02}, {AsToken(1):X02}]", + OpCode.INITSLOT => $"{AsToken()}, {AsToken(1)}", + OpCode.TRY_L => $"[{checked(Position + AsToken()):X08}, {checked(Position + AsToken()):X08}]", + OpCode.CALLT => $"[{checked(Position + AsToken()):X08}]", + OpCode.NEWARRAY_T or + OpCode.ISTYPE or + OpCode.CONVERT => $"{AsToken():X02}", + OpCode.STLOC or + OpCode.LDLOC or + OpCode.LDSFLD or + OpCode.STSFLD or + OpCode.LDARG or + OpCode.STARG or + OpCode.INITSSLOT => $"{AsToken()}", + OpCode.PUSHINT8 => $"{AsToken()}", + OpCode.PUSHINT16 => $"{AsToken()}", + OpCode.PUSHINT32 => $"{AsToken()}", + OpCode.PUSHINT64 => $"{AsToken()}", + OpCode.PUSHINT128 or + OpCode.PUSHINT256 => $"{new BigInteger(operand)}", + OpCode.SYSCALL => $"[{ApplicationEngine.Services[Unsafe.As(ref operand[0])].Name}]", + OpCode.PUSHDATA1 or + OpCode.PUSHDATA2 or + OpCode.PUSHDATA4 => readable ? $"{Convert.ToHexString(operand)} // {asStr}" : Convert.ToHexString(operand), + _ => readable ? $"\"{asStr}\"" : $"{Convert.ToHexString(operand)}", + }; + } + } +} diff --git a/src/Neo.CLI/config.fs.mainnet.json b/src/Neo.CLI/config.fs.mainnet.json index 3ec4eda871..6879232732 100644 --- a/src/Neo.CLI/config.fs.mainnet.json +++ b/src/Neo.CLI/config.fs.mainnet.json @@ -33,7 +33,7 @@ "MillisecondsPerBlock": 15000, "MaxTransactionsPerBlock": 512, "MemoryPoolMaxTransactions": 50000, - "MaxTraceableBlocks": 2102400, + "MaxTraceableBlocks": 17280, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, "Hardforks": { diff --git a/src/Neo.CLI/config.fs.testnet.json b/src/Neo.CLI/config.fs.testnet.json index 4e9468e2c6..9dc2d3b72f 100644 --- a/src/Neo.CLI/config.fs.testnet.json +++ b/src/Neo.CLI/config.fs.testnet.json @@ -33,7 +33,7 @@ "MillisecondsPerBlock": 15000, "MaxTransactionsPerBlock": 512, "MemoryPoolMaxTransactions": 50000, - "MaxTraceableBlocks": 2102400, + "MaxTraceableBlocks": 17280, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, "StandbyCommittee": [ diff --git a/src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs b/src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs index 70517edeb1..3bfc1226e6 100644 --- a/src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs +++ b/src/Neo.Cryptography.BLS12_381/ConstantTimeUtility.cs @@ -11,6 +11,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Security.Cryptography; namespace Neo.Cryptography.BLS12_381; @@ -18,16 +19,10 @@ public static class ConstantTimeUtility { public static bool ConstantTimeEq(in T a, in T b) where T : unmanaged { - ReadOnlySpan a_bytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in a), 1)); - ReadOnlySpan b_bytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in b), 1)); - ReadOnlySpan a_u64 = MemoryMarshal.Cast(a_bytes); - ReadOnlySpan b_u64 = MemoryMarshal.Cast(b_bytes); - ulong f = 0; - for (int i = 0; i < a_u64.Length; i++) - f |= a_u64[i] ^ b_u64[i]; - for (int i = a_u64.Length * sizeof(ulong); i < a_bytes.Length; i++) - f |= (ulong)a_bytes[i] ^ a_bytes[i]; - return f == 0; + var a_bytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in a), 1)); + var b_bytes = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in b), 1)); + + return CryptographicOperations.FixedTimeEquals(a_bytes, b_bytes); } public static T ConditionalSelect(in T a, in T b, bool choice) where T : unmanaged diff --git a/src/Neo.Extensions/AssemblyExtensions.cs b/src/Neo.Extensions/AssemblyExtensions.cs new file mode 100644 index 0000000000..4f9368c98b --- /dev/null +++ b/src/Neo.Extensions/AssemblyExtensions.cs @@ -0,0 +1,23 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// AssemblyExtensions.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.Reflection; + +namespace Neo.Extensions +{ + public static class AssemblyExtensions + { + public static string GetVersion(this Assembly assembly) + { + return assembly.GetName().Version!.ToString(3); + } + } +} diff --git a/src/Neo.Extensions/BigIntegerExtensions.cs b/src/Neo.Extensions/BigIntegerExtensions.cs new file mode 100644 index 0000000000..18667262e6 --- /dev/null +++ b/src/Neo.Extensions/BigIntegerExtensions.cs @@ -0,0 +1,96 @@ +// 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) + { + if (BigInteger.GreatestCommonDivisor(a, n) != 1) + { + throw new ArithmeticException("No modular inverse exists for the given inputs."); + } + + 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/ByteExtensions.cs b/src/Neo.Extensions/ByteExtensions.cs new file mode 100644 index 0000000000..013a8ef1cc --- /dev/null +++ b/src/Neo.Extensions/ByteExtensions.cs @@ -0,0 +1,59 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ByteExtensions.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.Text; + +namespace Neo.Extensions +{ + public static class ByteExtensions + { + /// + /// Converts a byte array to hex . + /// + /// The byte array to convert. + /// The converted hex . + public static string ToHexString(this byte[] value) + { + StringBuilder sb = new(); + foreach (var b in value) + sb.AppendFormat("{0:x2}", b); + return sb.ToString(); + } + + /// + /// Converts a byte array to hex . + /// + /// The byte array to convert. + /// Indicates whether it should be converted in the reversed byte order. + /// The converted hex . + public static string ToHexString(this byte[] value, bool reverse = false) + { + StringBuilder sb = new(); + for (var i = 0; i < value.Length; i++) + sb.AppendFormat("{0:x2}", value[reverse ? value.Length - i - 1 : i]); + return sb.ToString(); + } + + /// + /// Converts a byte array to hex . + /// + /// The byte array to convert. + /// The converted hex . + public static string ToHexString(this ReadOnlySpan value) + { + StringBuilder sb = new(); + foreach (var b in value) + sb.AppendFormat("{0:x2}", b); + return sb.ToString(); + } + } +} diff --git a/src/Neo.Extensions/Collections/HashSetExtensions.cs b/src/Neo.Extensions/Collections/HashSetExtensions.cs new file mode 100644 index 0000000000..9cf3f13b23 --- /dev/null +++ b/src/Neo.Extensions/Collections/HashSetExtensions.cs @@ -0,0 +1,42 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// HashSetExtensions.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.Collections.Generic; + +namespace Neo.Extensions +{ + public static class HashSetExtensions + { + public static void Remove(this HashSet set, ISet other) + { + if (set.Count > other.Count) + { + set.ExceptWith(other); + } + else + { + set.RemoveWhere(u => other.Contains(u)); + } + } + + public static void Remove(this HashSet set, IReadOnlyDictionary other) + { + if (set.Count > other.Count) + { + set.ExceptWith(other.Keys); + } + else + { + set.RemoveWhere(u => other.ContainsKey(u)); + } + } + } +} diff --git a/src/Neo.Extensions/DateTimeExtensions.cs b/src/Neo.Extensions/DateTimeExtensions.cs new file mode 100644 index 0000000000..130d09e78d --- /dev/null +++ b/src/Neo.Extensions/DateTimeExtensions.cs @@ -0,0 +1,38 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// DateTimeExtensions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.Extensions +{ + public static class DateTimeExtensions + { + /// + /// Converts a to timestamp. + /// + /// The to convert. + /// The converted timestamp. + public static uint ToTimestamp(this DateTime time) + { + return (uint)new DateTimeOffset(time.ToUniversalTime()).ToUnixTimeSeconds(); + } + + /// + /// Converts a to timestamp in milliseconds. + /// + /// The to convert. + /// The converted timestamp. + public static ulong ToTimestampMS(this DateTime time) + { + return (ulong)new DateTimeOffset(time.ToUniversalTime()).ToUnixTimeMilliseconds(); + } + } +} diff --git a/src/Neo.Extensions/Neo.Extensions.csproj b/src/Neo.Extensions/Neo.Extensions.csproj index a7f4870568..f424dd0809 100644 --- a/src/Neo.Extensions/Neo.Extensions.csproj +++ b/src/Neo.Extensions/Neo.Extensions.csproj @@ -3,19 +3,20 @@ netstandard2.1;net8.0 enable + true Neo.Extensions NEO;Blockchain;Extensions ../../bin/$(PackageId) - + - + diff --git a/src/Neo.Extensions/Net/IpAddressExtensions.cs b/src/Neo.Extensions/Net/IpAddressExtensions.cs new file mode 100644 index 0000000000..adafee142f --- /dev/null +++ b/src/Neo.Extensions/Net/IpAddressExtensions.cs @@ -0,0 +1,40 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// IpAddressExtensions.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.Net; + +namespace Neo.Extensions +{ + public static class IpAddressExtensions + { + /// + /// Checks if address is IPv4 Mapped to IPv6 format, if so, Map to IPv4. + /// Otherwise, return current address. + /// + public static IPAddress UnMap(this IPAddress address) + { + if (address.IsIPv4MappedToIPv6) + address = address.MapToIPv4(); + return address; + } + + /// + /// Checks if IPEndPoint is IPv4 Mapped to IPv6 format, if so, unmap to IPv4. + /// Otherwise, return current endpoint. + /// + public static IPEndPoint UnMap(this IPEndPoint endPoint) + { + if (!endPoint.Address.IsIPv4MappedToIPv6) + return endPoint; + return new IPEndPoint(endPoint.Address.UnMap(), endPoint.Port); + } + } +} diff --git a/src/Neo.Extensions/RandomExtensions.cs b/src/Neo.Extensions/RandomExtensions.cs new file mode 100644 index 0000000000..72bcac289f --- /dev/null +++ b/src/Neo.Extensions/RandomExtensions.cs @@ -0,0 +1,34 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RandomExtensions.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.Numerics; + +namespace Neo.Extensions +{ + public static class RandomExtensions + { + public static BigInteger NextBigInteger(this Random rand, int sizeInBits) + { + if (sizeInBits < 0) + throw new ArgumentException("sizeInBits must be non-negative"); + if (sizeInBits == 0) + return 0; + Span b = stackalloc byte[sizeInBits / 8 + 1]; + rand.NextBytes(b); + if (sizeInBits % 8 == 0) + b[^1] = 0; + else + b[^1] &= (byte)((1 << sizeInBits % 8) - 1); + return new BigInteger(b); + } + } +} diff --git a/src/Neo.Extensions/SecureStringExtensions.cs b/src/Neo.Extensions/SecureStringExtensions.cs new file mode 100644 index 0000000000..e4a24f05ee --- /dev/null +++ b/src/Neo.Extensions/SecureStringExtensions.cs @@ -0,0 +1,53 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// SecureStringExtensions.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.Runtime.InteropServices; +using System.Security; + +namespace Neo.Extensions +{ + public static class SecureStringExtensions + { + public static string? GetClearText(this SecureString secureString) + { + if (secureString is null) + throw new ArgumentNullException(nameof(secureString)); + + var unmanagedStringPtr = IntPtr.Zero; + + try + { + unmanagedStringPtr = Marshal.SecureStringToGlobalAllocUnicode(secureString); + return Marshal.PtrToStringUni(unmanagedStringPtr); + } + finally + { + Marshal.ZeroFreeGlobalAllocUnicode(unmanagedStringPtr); + } + } + + public static SecureString ToSecureString(this string value, bool asReadOnly = true) + { + unsafe + { + fixed (char* passwordChars = value) + { + var securePasswordString = new SecureString(passwordChars, value.Length); + + if (asReadOnly) + securePasswordString.MakeReadOnly(); + return securePasswordString; + } + } + } + } +} diff --git a/src/Neo.Extensions/StringExtensions.cs b/src/Neo.Extensions/StringExtensions.cs new file mode 100644 index 0000000000..b84869b2e1 --- /dev/null +++ b/src/Neo.Extensions/StringExtensions.cs @@ -0,0 +1,47 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// StringExtensions.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.Globalization; + +namespace Neo.Extensions +{ + public static class StringExtensions + { + /// + /// Converts a hex to byte array. + /// + /// The hex to convert. + /// The converted byte array. + public static byte[] HexToBytes(this string value) + { + if (value == null || value.Length == 0) + return []; + if (value.Length % 2 == 1) + throw new FormatException(); + var result = new byte[value.Length / 2]; + for (var i = 0; i < result.Length; i++) + result[i] = byte.Parse(value.Substring(i * 2, 2), NumberStyles.AllowHexSpecifier); + return result; + } + + /// + /// Gets the size of the specified encoded in variable-length encoding. + /// + /// The specified . + /// The size of the . + public static int GetVarSize(this string value) + { + var size = Utility.StrictUTF8.GetByteCount(value); + return UnsafeData.GetVarSize(size) + size; + } + } +} diff --git a/src/Neo.Extensions/UnsafeData.cs b/src/Neo.Extensions/UnsafeData.cs new file mode 100644 index 0000000000..6d4c9a6f8e --- /dev/null +++ b/src/Neo.Extensions/UnsafeData.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UnsafeData.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Extensions +{ + public static class UnsafeData + { + /// + /// Gets the size of variable-length of the data. + /// + /// The length of the data. + /// The size of variable-length of the data. + public static int GetVarSize(int value) + { + if (value < 0xFD) + return sizeof(byte); + else if (value <= 0xFFFF) + return sizeof(byte) + sizeof(ushort); + else + return sizeof(byte) + sizeof(uint); + } + } +} diff --git a/src/Neo.GUI/GUI/CreateMultiSigContractDialog.cs b/src/Neo.GUI/GUI/CreateMultiSigContractDialog.cs index 5dd10f858f..d28696f138 100644 --- a/src/Neo.GUI/GUI/CreateMultiSigContractDialog.cs +++ b/src/Neo.GUI/GUI/CreateMultiSigContractDialog.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.SmartContract; using Neo.Wallets; using System; diff --git a/src/Neo.GUI/GUI/DeployContractDialog.cs b/src/Neo.GUI/GUI/DeployContractDialog.cs index 31ca5e6636..0eaf9c1ae1 100644 --- a/src/Neo.GUI/GUI/DeployContractDialog.cs +++ b/src/Neo.GUI/GUI/DeployContractDialog.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.VM; diff --git a/src/Neo.GUI/GUI/ImportCustomContractDialog.cs b/src/Neo.GUI/GUI/ImportCustomContractDialog.cs index b8df534699..8faa23d593 100644 --- a/src/Neo.GUI/GUI/ImportCustomContractDialog.cs +++ b/src/Neo.GUI/GUI/ImportCustomContractDialog.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.SmartContract; using Neo.Wallets; using System; diff --git a/src/Neo.GUI/GUI/InvokeContractDialog.cs b/src/Neo.GUI/GUI/InvokeContractDialog.cs index d8a8a6b66f..e24b3c24ee 100644 --- a/src/Neo.GUI/GUI/InvokeContractDialog.cs +++ b/src/Neo.GUI/GUI/InvokeContractDialog.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.Json; using Neo.Network.P2P.Payloads; using Neo.Properties; 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.GUI/GUI/ParametersEditor.cs b/src/Neo.GUI/GUI/ParametersEditor.cs index 19eac82c31..f5156d6bfb 100644 --- a/src/Neo.GUI/GUI/ParametersEditor.cs +++ b/src/Neo.GUI/GUI/ParametersEditor.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.SmartContract; using System; using System.Collections.Generic; diff --git a/src/Neo.GUI/GUI/SigningDialog.cs b/src/Neo.GUI/GUI/SigningDialog.cs index 5b515a4365..e7e5b6bb2b 100644 --- a/src/Neo.GUI/GUI/SigningDialog.cs +++ b/src/Neo.GUI/GUI/SigningDialog.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography; +using Neo.Extensions; using Neo.Properties; using Neo.Wallets; using System; diff --git a/src/Neo.GUI/GUI/ViewContractDialog.cs b/src/Neo.GUI/GUI/ViewContractDialog.cs index 00ea963328..a97aa648e9 100644 --- a/src/Neo.GUI/GUI/ViewContractDialog.cs +++ b/src/Neo.GUI/GUI/ViewContractDialog.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.SmartContract; using Neo.Wallets; using System.Linq; diff --git a/src/Neo.GUI/GUI/ViewPrivateKeyDialog.cs b/src/Neo.GUI/GUI/ViewPrivateKeyDialog.cs index 7beaa56271..4233814df9 100644 --- a/src/Neo.GUI/GUI/ViewPrivateKeyDialog.cs +++ b/src/Neo.GUI/GUI/ViewPrivateKeyDialog.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.Wallets; using System.Windows.Forms; diff --git a/src/Neo.GUI/GUI/Wrappers/HexConverter.cs b/src/Neo.GUI/GUI/Wrappers/HexConverter.cs index 757bfd3b97..ecd1fd1e4a 100644 --- a/src/Neo.GUI/GUI/Wrappers/HexConverter.cs +++ b/src/Neo.GUI/GUI/Wrappers/HexConverter.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.ComponentModel; using System.Globalization; 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 74% rename from src/Neo/IO/Caching/HashSetCache.cs rename to src/Neo.IO/Caching/HashSetCache.cs index bdef1c5e3a..6e39f5fd8b 100644 --- a/src/Neo/IO/Caching/HashSetCache.cs +++ b/src/Neo.IO/Caching/HashSetCache.cs @@ -15,22 +15,22 @@ namespace Neo.IO.Caching { - class HashSetCache : IReadOnlyCollection where T : IEquatable + internal 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..b825739ddd 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 +internal 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.IO/Neo.IO.csproj b/src/Neo.IO/Neo.IO.csproj index a51ec1110a..deb98b053b 100644 --- a/src/Neo.IO/Neo.IO.csproj +++ b/src/Neo.IO/Neo.IO.csproj @@ -12,7 +12,8 @@ - + + diff --git a/src/Neo.Json/JArray.cs b/src/Neo.Json/JArray.cs index 903f29941b..5ac193e1eb 100644 --- a/src/Neo.Json/JArray.cs +++ b/src/Neo.Json/JArray.cs @@ -67,7 +67,7 @@ public void Add(JToken? item) public override string AsString() { - return string.Join(",", items.Select(p => p?.AsString())); + return ToString(); } public override void Clear() diff --git a/src/Neo.Json/Neo.Json.csproj b/src/Neo.Json/Neo.Json.csproj index 15924436e7..0a490a1934 100644 --- a/src/Neo.Json/Neo.Json.csproj +++ b/src/Neo.Json/Neo.Json.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/Neo.VM/EvaluationStack.cs b/src/Neo.VM/EvaluationStack.cs index 34517b2197..463df8d12a 100644 --- a/src/Neo.VM/EvaluationStack.cs +++ b/src/Neo.VM/EvaluationStack.cs @@ -23,10 +23,12 @@ namespace Neo.VM /// public sealed class EvaluationStack : IReadOnlyList { - private readonly List innerList = new(); - private readonly ReferenceCounter referenceCounter; + private readonly List innerList = []; + private readonly IReferenceCounter referenceCounter; - internal EvaluationStack(ReferenceCounter referenceCounter) + internal IReferenceCounter ReferenceCounter => referenceCounter; + + internal EvaluationStack(IReferenceCounter referenceCounter) { this.referenceCounter = referenceCounter; } diff --git a/src/Neo.VM/ExecutionContext.SharedStates.cs b/src/Neo.VM/ExecutionContext.SharedStates.cs index afa01d7995..2e9a702b9d 100644 --- a/src/Neo.VM/ExecutionContext.SharedStates.cs +++ b/src/Neo.VM/ExecutionContext.SharedStates.cs @@ -23,7 +23,7 @@ private class SharedStates public Slot? StaticFields; public readonly Dictionary States; - public SharedStates(Script script, ReferenceCounter referenceCounter) + public SharedStates(Script script, IReferenceCounter referenceCounter) { Script = script; EvaluationStack = new EvaluationStack(referenceCounter); diff --git a/src/Neo.VM/ExecutionContext.cs b/src/Neo.VM/ExecutionContext.cs index d1f9a54b1d..67e0889208 100644 --- a/src/Neo.VM/ExecutionContext.cs +++ b/src/Neo.VM/ExecutionContext.cs @@ -107,7 +107,7 @@ public Instruction? NextInstruction } } - internal ExecutionContext(Script script, int rvcount, ReferenceCounter referenceCounter) + internal ExecutionContext(Script script, int rvcount, IReferenceCounter referenceCounter) : this(new SharedStates(script, referenceCounter), rvcount, 0) { } diff --git a/src/Neo.VM/ExecutionEngine.cs b/src/Neo.VM/ExecutionEngine.cs index bec60c4348..8a3167b4c8 100644 --- a/src/Neo.VM/ExecutionEngine.cs +++ b/src/Neo.VM/ExecutionEngine.cs @@ -34,7 +34,7 @@ public class ExecutionEngine : IDisposable /// /// Used for reference counting of objects in the VM. /// - public ReferenceCounter ReferenceCounter { get; } + public IReferenceCounter ReferenceCounter { get; } /// /// The invocation stack of the VM. @@ -83,17 +83,18 @@ protected internal set /// /// Initializes a new instance of the class. /// + /// The jump table to be used. public ExecutionEngine(JumpTable? jumpTable = null) : this(jumpTable, new ReferenceCounter(), ExecutionEngineLimits.Default) { } /// - /// Initializes a new instance of the class with the specified and . + /// Initializes a new instance of the class with the specified and . /// /// The jump table to be used. /// The reference counter to be used. /// Restrictions on the VM. - protected ExecutionEngine(JumpTable? jumpTable, ReferenceCounter referenceCounter, ExecutionEngineLimits limits) + protected ExecutionEngine(JumpTable? jumpTable, IReferenceCounter referenceCounter, ExecutionEngineLimits limits) { JumpTable = jumpTable ?? JumpTable.Default; Limits = limits; diff --git a/src/Neo.VM/IReferenceCounter.cs b/src/Neo.VM/IReferenceCounter.cs new file mode 100644 index 0000000000..f451c0e316 --- /dev/null +++ b/src/Neo.VM/IReferenceCounter.cs @@ -0,0 +1,91 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// IReferenceCounter.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.VM.Types; + +namespace Neo.VM +{ + /// + /// Used for reference counting of objects in the VM. + /// + public interface IReferenceCounter + { + /// + /// Gets the count of references. + /// + int Count { get; } + + /// + /// Adds an item to the zero-referred list. + /// + /// This method is used when an item has no remaining references. + /// It adds the item to the zero-referred list to be checked for cleanup later. + /// + /// Use this method when you detect that an item has zero references and may need to be cleaned up. + /// + /// The item to add. + void AddZeroReferred(StackItem item); + + /// + /// Adds a reference to a specified item with a parent compound type. + /// + /// This method is used when an item gains a new reference through a parent compound type. + /// It increments the reference count and updates the tracking structures if necessary. + /// + /// Use this method when you need to add a reference from a compound type to a stack item. + /// + /// The item to add a reference to. + /// The parent compound type. + void AddReference(StackItem item, CompoundType parent); + + /// + /// Adds a stack reference to a specified item with a count. + /// + /// This method is used when an item gains a new stack reference, usually due to being pushed onto the evaluation stack. + /// It increments the reference count and updates the tracking structures if necessary. + /// + /// Use this method when you need to add one or more stack references to a stack item. + /// + /// The item to add a stack reference to. + /// The number of references to add. + void AddStackReference(StackItem item, int count = 1); + + /// + /// Removes a reference from a specified item with a parent compound type. + /// + /// This method is used when an item loses a reference from a parent compound type. + /// It decrements the reference count and updates the tracking structures if necessary. + /// + /// Use this method when you need to remove a reference from a compound type to a stack item. + /// + /// The item to remove a reference from. + /// The parent compound type. + void RemoveReference(StackItem item, CompoundType parent); + + /// + /// Removes a stack reference from a specified item. + /// + /// This method is used when an item loses a stack reference, usually due to being popped off the evaluation stack. + /// It decrements the reference count and updates the tracking structures if necessary. + /// + /// Use this method when you need to remove one or more stack references from a stack item. + /// + /// The item to remove a stack reference from. + void RemoveStackReference(StackItem item); + + /// + /// Checks and processes items that have zero references. + /// This method is used to check items in the zero-referred list and clean up those that are no longer needed. + /// + /// The current reference count. + int CheckZeroReferred(); + } +} diff --git a/src/Neo.VM/JumpTable/JumpTable.Compound.cs b/src/Neo.VM/JumpTable/JumpTable.Compound.cs index aeb2047825..2a81b213fe 100644 --- a/src/Neo.VM/JumpTable/JumpTable.Compound.cs +++ b/src/Neo.VM/JumpTable/JumpTable.Compound.cs @@ -14,6 +14,7 @@ using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; +using Array = System.Array; using VMArray = Neo.VM.Types.Array; namespace Neo.VM @@ -151,8 +152,9 @@ public virtual void NewArray(ExecutionEngine engine, Instruction instruction) var n = (int)engine.Pop().GetInteger(); if (n < 0 || n > engine.Limits.MaxStackSize) throw new InvalidOperationException($"MaxStackSize exceed: {n}"); - - engine.Push(new VMArray(engine.ReferenceCounter, Enumerable.Repeat(StackItem.Null, n))); + var nullArray = new StackItem[n]; + Array.Fill(nullArray, StackItem.Null); + engine.Push(new VMArray(engine.ReferenceCounter, nullArray)); } /// @@ -180,8 +182,9 @@ public virtual void NewArray_T(ExecutionEngine engine, Instruction instruction) (byte)StackItemType.ByteString => ByteString.Empty, _ => StackItem.Null }; - - engine.Push(new VMArray(engine.ReferenceCounter, Enumerable.Repeat(item, n))); + var itemArray = new StackItem[n]; + Array.Fill(itemArray, item); + engine.Push(new VMArray(engine.ReferenceCounter, itemArray)); } /// @@ -210,10 +213,10 @@ public virtual void NewStruct(ExecutionEngine engine, Instruction instruction) var n = (int)engine.Pop().GetInteger(); if (n < 0 || n > engine.Limits.MaxStackSize) throw new InvalidOperationException($"MaxStackSize exceed: {n}"); - Struct result = new(engine.ReferenceCounter); - for (var i = 0; i < n; i++) - result.Add(StackItem.Null); - engine.Push(result); + + var nullArray = new StackItem[n]; + Array.Fill(nullArray, StackItem.Null); + engine.Push(new Struct(engine.ReferenceCounter, nullArray)); } /// @@ -239,6 +242,7 @@ public virtual void NewMap(ExecutionEngine engine, Instruction instruction) [MethodImpl(MethodImplOptions.AggressiveInlining)] public virtual void Size(ExecutionEngine engine, Instruction instruction) { + // TODO: we should be able to optimize by using peek instead of dup and pop var x = engine.Pop(); switch (x) { @@ -410,7 +414,7 @@ public virtual void PickItem(ExecutionEngine engine, Instruction instruction) /// /// The execution engine. /// The instruction being executed. - /// Pop 1, Push 1 + /// Pop 2, Push 0 [MethodImpl(MethodImplOptions.AggressiveInlining)] public virtual void Append(ExecutionEngine engine, Instruction instruction) { @@ -539,7 +543,7 @@ public virtual void ClearItems(ExecutionEngine engine, Instruction instruction) /// /// The execution engine. /// The instruction being executed. - /// Pop 1, Push 0 + /// Pop 1, Push 1 [MethodImpl(MethodImplOptions.AggressiveInlining)] public virtual void PopItem(ExecutionEngine engine, Instruction instruction) { diff --git a/src/Neo.VM/JumpTable/JumpTable.Control.cs b/src/Neo.VM/JumpTable/JumpTable.Control.cs index 3c8448c050..1ef74fe4a5 100644 --- a/src/Neo.VM/JumpTable/JumpTable.Control.cs +++ b/src/Neo.VM/JumpTable/JumpTable.Control.cs @@ -539,7 +539,11 @@ public virtual void Ret(ExecutionEngine engine, Instruction instruction) if (context_pop.EvaluationStack != stack_eval) { if (context_pop.RVCount >= 0 && context_pop.EvaluationStack.Count != context_pop.RVCount) - throw new InvalidOperationException("RVCount doesn't match with EvaluationStack"); + // This exception indicates a mismatch between the expected and actual number of stack items. + // It typically occurs due to compilation errors caused by potential issues in the compiler, resulting in either too many or too few + // items left on the stack compared to what was anticipated by the return value count. + // When you run into this problem, try to reach core-devs at https://github.com/neo-project/neo for help. + throw new InvalidOperationException($"Return value count mismatch: expected {context_pop.RVCount}, but got {context_pop.EvaluationStack.Count} items on the evaluation stack"); context_pop.EvaluationStack.CopyTo(stack_eval); } if (engine.InvocationStack.Count == 0) diff --git a/src/Neo.VM/JumpTable/JumpTable.Numeric.cs b/src/Neo.VM/JumpTable/JumpTable.Numeric.cs index 989991a07c..06533e293a 100644 --- a/src/Neo.VM/JumpTable/JumpTable.Numeric.cs +++ b/src/Neo.VM/JumpTable/JumpTable.Numeric.cs @@ -155,7 +155,7 @@ public virtual void Div(ExecutionEngine engine, Instruction instruction) } /// - /// Computes the result of raising a number to the specified power. + /// Computes the remainder after dividing a by b. /// /// /// The execution engine. @@ -170,7 +170,7 @@ public virtual void Mod(ExecutionEngine engine, Instruction instruction) } /// - /// Computes the square root of the specified integer. + /// Computes the result of raising a number to the specified power. /// /// /// The execution engine. @@ -186,7 +186,7 @@ public virtual void Pow(ExecutionEngine engine, Instruction instruction) } /// - /// + /// Returns the square root of a specified number. /// /// /// The execution engine. diff --git a/src/Neo.VM/JumpTable/JumpTable.Splice.cs b/src/Neo.VM/JumpTable/JumpTable.Splice.cs index 2f49ea0107..eaa979a8b8 100644 --- a/src/Neo.VM/JumpTable/JumpTable.Splice.cs +++ b/src/Neo.VM/JumpTable/JumpTable.Splice.cs @@ -59,6 +59,7 @@ public virtual void Memcpy(ExecutionEngine engine, Instruction instruction) Types.Buffer dst = engine.Pop(); if (checked(di + count) > dst.Size) throw new InvalidOperationException($"The value {count} is out of range."); + // TODO: check if we can optimize the memcpy by using peek instead of dup then pop src.Slice(si, count).CopyTo(dst.InnerBuffer.Span[di..]); } @@ -84,7 +85,7 @@ public virtual void Cat(ExecutionEngine engine, Instruction instruction) /// /// Extracts a substring from the specified buffer and pushes it onto the evaluation stack. - /// + /// /// /// The execution engine. /// The instruction being executed. diff --git a/src/Neo.VM/JumpTable/JumpTable.Stack.cs b/src/Neo.VM/JumpTable/JumpTable.Stack.cs index 45ee5565a7..68094470fe 100644 --- a/src/Neo.VM/JumpTable/JumpTable.Stack.cs +++ b/src/Neo.VM/JumpTable/JumpTable.Stack.cs @@ -47,7 +47,7 @@ public virtual void Drop(ExecutionEngine engine, Instruction instruction) } /// - /// + /// Removes the second-to-top stack item. /// /// /// The execution engine. @@ -59,7 +59,7 @@ public virtual void Nip(ExecutionEngine engine, Instruction instruction) } /// - /// Removes the nth item from the top of the evaluation stack. + /// Removes the n-th item from the top of the evaluation stack. /// /// /// The execution engine. diff --git a/src/Neo.VM/Neo.VM.csproj b/src/Neo.VM/Neo.VM.csproj index eb137b623a..cc6fb4a3a9 100644 --- a/src/Neo.VM/Neo.VM.csproj +++ b/src/Neo.VM/Neo.VM.csproj @@ -11,6 +11,7 @@ + diff --git a/src/Neo.VM/OpCode.cs b/src/Neo.VM/OpCode.cs index dd5f1574ea..4a705a556c 100644 --- a/src/Neo.VM/OpCode.cs +++ b/src/Neo.VM/OpCode.cs @@ -22,136 +22,321 @@ public enum OpCode : byte /// /// Pushes a 1-byte signed integer onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// [OperandSize(Size = 1)] PUSHINT8 = 0x00, + /// /// Pushes a 2-bytes signed integer onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// [OperandSize(Size = 2)] PUSHINT16 = 0x01, + /// /// Pushes a 4-bytes signed integer onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// [OperandSize(Size = 4)] PUSHINT32 = 0x02, + /// - /// Pushes a 8-bytes signed integer onto the stack. + /// Pushes an 8-bytes signed integer onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// [OperandSize(Size = 8)] PUSHINT64 = 0x03, + /// /// Pushes a 16-bytes signed integer onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// [OperandSize(Size = 16)] PUSHINT128 = 0x04, + /// /// Pushes a 32-bytes signed integer onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// [OperandSize(Size = 32)] PUSHINT256 = 0x05, + /// /// Pushes the boolean value onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSHT = 0x08, + /// /// Pushes the boolean value onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSHF = 0x09, + /// /// Converts the 4-bytes offset to an , and pushes it onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// [OperandSize(Size = 4)] PUSHA = 0x0A, + /// /// The item is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSHNULL = 0x0B, + /// /// The next byte contains the number of bytes to be pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// [OperandSize(SizePrefix = 1)] PUSHDATA1 = 0x0C, + /// /// The next two bytes contain the number of bytes to be pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// [OperandSize(SizePrefix = 2)] PUSHDATA2 = 0x0D, + /// /// The next four bytes contain the number of bytes to be pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// [OperandSize(SizePrefix = 4)] PUSHDATA4 = 0x0E, + /// /// The number -1 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSHM1 = 0x0F, + /// /// The number 0 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSH0 = 0x10, + /// /// The number 1 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSH1 = 0x11, + /// /// The number 2 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSH2 = 0x12, + /// /// The number 3 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSH3 = 0x13, + /// /// The number 4 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSH4 = 0x14, + /// /// The number 5 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSH5 = 0x15, + /// /// The number 6 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSH6 = 0x16, + /// /// The number 7 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSH7 = 0x17, + /// /// The number 8 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSH8 = 0x18, + /// /// The number 9 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSH9 = 0x19, + /// /// The number 10 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSH10 = 0x1A, + /// /// The number 11 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSH11 = 0x1B, + /// /// The number 12 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSH12 = 0x1C, + /// /// The number 13 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSH13 = 0x1D, + /// /// The number 14 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSH14 = 0x1E, + /// /// The number 15 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSH15 = 0x1F, + /// /// The number 16 is pushed onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PUSH16 = 0x20, @@ -161,157 +346,299 @@ public enum OpCode : byte /// /// The operation does nothing. It is intended to fill in space if opcodes are patched. + /// + /// + /// Push: 0 item(s) + /// Pop: 0 item(s) + /// /// NOP = 0x21, + /// /// Unconditionally transfers control to a target instruction. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 0 item(s) + /// /// [OperandSize(Size = 1)] JMP = 0x22, + /// /// Unconditionally transfers control to a target instruction. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 0 item(s) + /// /// [OperandSize(Size = 4)] JMP_L = 0x23, + /// /// Transfers control to a target instruction if the value is , not , or non-zero. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// [OperandSize(Size = 1)] JMPIF = 0x24, + /// /// Transfers control to a target instruction if the value is , not , or non-zero. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// [OperandSize(Size = 4)] JMPIF_L = 0x25, + /// /// Transfers control to a target instruction if the value is , a reference, or zero. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// [OperandSize(Size = 1)] JMPIFNOT = 0x26, + /// /// Transfers control to a target instruction if the value is , a reference, or zero. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// [OperandSize(Size = 4)] JMPIFNOT_L = 0x27, + /// /// Transfers control to a target instruction if two values are equal. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 2 item(s) + /// /// [OperandSize(Size = 1)] JMPEQ = 0x28, + /// /// Transfers control to a target instruction if two values are equal. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 2 item(s) + /// /// [OperandSize(Size = 4)] JMPEQ_L = 0x29, + /// /// Transfers control to a target instruction when two values are not equal. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 2 item(s) + /// /// [OperandSize(Size = 1)] JMPNE = 0x2A, + /// /// Transfers control to a target instruction when two values are not equal. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 2 item(s) + /// /// [OperandSize(Size = 4)] JMPNE_L = 0x2B, + /// /// Transfers control to a target instruction if the first value is greater than the second value. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 2 item(s) + /// /// [OperandSize(Size = 1)] JMPGT = 0x2C, + /// /// Transfers control to a target instruction if the first value is greater than the second value. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 2 item(s) + /// /// [OperandSize(Size = 4)] JMPGT_L = 0x2D, + /// /// Transfers control to a target instruction if the first value is greater than or equal to the second value. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 2 item(s) + /// /// [OperandSize(Size = 1)] JMPGE = 0x2E, + /// /// Transfers control to a target instruction if the first value is greater than or equal to the second value. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 2 item(s) + /// /// [OperandSize(Size = 4)] JMPGE_L = 0x2F, + /// /// Transfers control to a target instruction if the first value is less than the second value. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 2 item(s) + /// /// [OperandSize(Size = 1)] JMPLT = 0x30, + /// /// Transfers control to a target instruction if the first value is less than the second value. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 2 item(s) + /// /// [OperandSize(Size = 4)] JMPLT_L = 0x31, + /// /// Transfers control to a target instruction if the first value is less than or equal to the second value. The target instruction is represented as a 1-byte signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 2 item(s) + /// /// [OperandSize(Size = 1)] JMPLE = 0x32, + /// /// Transfers control to a target instruction if the first value is less than or equal to the second value. The target instruction is represented as a 4-bytes signed offset from the beginning of the current instruction. + /// + /// + /// Push: 0 item(s) + /// Pop: 2 item(s) + /// /// [OperandSize(Size = 4)] JMPLE_L = 0x33, + /// /// Calls the function at the target address which is represented as a 1-byte signed offset from the beginning of the current instruction. /// [OperandSize(Size = 1)] CALL = 0x34, + /// /// Calls the function at the target address which is represented as a 4-bytes signed offset from the beginning of the current instruction. /// [OperandSize(Size = 4)] CALL_L = 0x35, + /// /// Pop the address of a function from the stack, and call the function. /// CALLA = 0x36, + /// /// Calls the function which is described by the token. /// [OperandSize(Size = 2)] CALLT = 0x37, + /// /// It turns the vm state to FAULT immediately, and cannot be caught. + /// + /// + /// Push: 0 item(s) + /// Pop: 0 item(s) + /// /// ABORT = 0x38, + /// /// Pop the top value of the stack. If it's false, exit vm execution and set vm state to FAULT. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// ASSERT = 0x39, + /// /// Pop the top value of the stack, and throw it. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// THROW = 0x3A, + /// /// TRY CatchOffset(sbyte) FinallyOffset(sbyte). If there's no catch body, set CatchOffset 0. If there's no finally body, set FinallyOffset 0. /// [OperandSize(Size = 2)] TRY = 0x3B, + /// /// TRY_L CatchOffset(int) FinallyOffset(int). If there's no catch body, set CatchOffset 0. If there's no finally body, set FinallyOffset 0. /// [OperandSize(Size = 8)] TRY_L = 0x3C, + /// /// Ensures that the appropriate surrounding finally blocks are executed. And then unconditionally transfers control to the specific target instruction, represented as a 1-byte signed offset from the beginning of the current instruction. /// [OperandSize(Size = 1)] ENDTRY = 0x3D, + /// /// Ensures that the appropriate surrounding finally blocks are executed. And then unconditionally transfers control to the specific target instruction, represented as a 4-byte signed offset from the beginning of the current instruction. /// [OperandSize(Size = 4)] ENDTRY_L = 0x3E, + /// - /// End finally, If no exception happen or be catched, vm will jump to the target instruction of ENDTRY/ENDTRY_L. Otherwise vm will rethrow the exception to upper layer. + /// End finally, If no exception happen or be catched, vm will jump to the target instruction of ENDTRY/ENDTRY_L. Otherwise, vm will rethrow the exception to upper layer. /// ENDFINALLY = 0x3F, + /// /// Returns from the current method. /// RET = 0x40, + /// /// Calls to an interop service. /// @@ -324,62 +651,174 @@ public enum OpCode : byte /// /// Puts the number of stack items onto the stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// DEPTH = 0x43, + /// /// Removes the top stack item. + /// + /// a b c -> a b + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// DROP = 0x45, + /// /// Removes the second-to-top stack item. + /// + /// a b c -> a c + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// NIP = 0x46, + /// /// The item n back in the main stack is removed. + /// + /// + /// Push: 0 item(s) + /// Pop: n+1 item(s) + /// /// XDROP = 0x48, + /// /// Clear the stack /// CLEAR = 0x49, + /// /// Duplicates the top stack item. + /// + /// a b c -> a b c c + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// DUP = 0x4A, + /// /// Copies the second-to-top stack item to the top. + /// + /// a b c -> a b c b + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// OVER = 0x4B, + /// /// The item n back in the stack is copied to the top. + /// + /// a b c d 2 -> a b c d b + /// index => 3[2]1 0 + /// + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// PICK = 0x4D, + /// /// The item at the top of the stack is copied and inserted before the second-to-top item. + /// + /// a b c -> a c b c + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// TUCK = 0x4E, + /// /// The top two items on the stack are swapped. + /// + /// a b -> b a + /// + /// + /// Push: 0 item(s) + /// Pop: 0 item(s) + /// /// SWAP = 0x50, + /// /// The top three items on the stack are rotated to the left. + /// + /// a b c -> b c a + /// + /// + /// Push: 0 item(s) + /// Pop: 0 item(s) + /// /// ROT = 0x51, + /// /// The item n back in the stack is moved to the top. + /// + /// a b c d 2 -> a c d b + /// index => 3[2]1 0 + /// + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// ROLL = 0x52, + /// /// Reverse the order of the top 3 items on the stack. + /// + /// a b c -> c b a + /// + /// + /// Push: 0 item(s) + /// Pop: 0 item(s) + /// /// REVERSE3 = 0x53, + /// /// Reverse the order of the top 4 items on the stack. + /// + /// a b c d -> d c b a + /// + /// + /// Push: 0 item(s) + /// Pop: 0 item(s) + /// /// REVERSE4 = 0x54, + /// /// Pop the number N on the stack, and reverse the order of the top N items on the stack. + /// + /// a b c d 3 -> a d c b + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// REVERSEN = 0x55, @@ -389,209 +828,503 @@ public enum OpCode : byte /// /// Initialize the static field list for the current execution context. + /// + /// + /// Push: 0 item(s) + /// Pop: 0 item(s) + /// /// [OperandSize(Size = 1)] INITSSLOT = 0x56, + /// /// Initialize the argument slot and the local variable list for the current execution context. /// [OperandSize(Size = 2)] INITSLOT = 0x57, + /// /// Loads the static field at index 0 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDSFLD0 = 0x58, + /// /// Loads the static field at index 1 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDSFLD1 = 0x59, + /// /// Loads the static field at index 2 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDSFLD2 = 0x5A, + /// /// Loads the static field at index 3 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDSFLD3 = 0x5B, + /// /// Loads the static field at index 4 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDSFLD4 = 0x5C, + /// /// Loads the static field at index 5 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDSFLD5 = 0x5D, + /// /// Loads the static field at index 6 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDSFLD6 = 0x5E, + /// /// Loads the static field at a specified index onto the evaluation stack. The index is represented as a 1-byte unsigned integer. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// [OperandSize(Size = 1)] LDSFLD = 0x5F, + /// /// Stores the value on top of the evaluation stack in the static field list at index 0. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STSFLD0 = 0x60, + /// /// Stores the value on top of the evaluation stack in the static field list at index 1. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STSFLD1 = 0x61, + /// /// Stores the value on top of the evaluation stack in the static field list at index 2. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STSFLD2 = 0x62, + /// /// Stores the value on top of the evaluation stack in the static field list at index 3. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STSFLD3 = 0x63, + /// /// Stores the value on top of the evaluation stack in the static field list at index 4. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STSFLD4 = 0x64, + /// /// Stores the value on top of the evaluation stack in the static field list at index 5. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STSFLD5 = 0x65, + /// /// Stores the value on top of the evaluation stack in the static field list at index 6. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STSFLD6 = 0x66, + /// /// Stores the value on top of the evaluation stack in the static field list at a specified index. The index is represented as a 1-byte unsigned integer. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// [OperandSize(Size = 1)] STSFLD = 0x67, + /// /// Loads the local variable at index 0 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDLOC0 = 0x68, + /// /// Loads the local variable at index 1 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDLOC1 = 0x69, + /// /// Loads the local variable at index 2 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDLOC2 = 0x6A, + /// /// Loads the local variable at index 3 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDLOC3 = 0x6B, + /// /// Loads the local variable at index 4 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDLOC4 = 0x6C, + /// /// Loads the local variable at index 5 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDLOC5 = 0x6D, + /// /// Loads the local variable at index 6 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDLOC6 = 0x6E, + /// /// Loads the local variable at a specified index onto the evaluation stack. The index is represented as a 1-byte unsigned integer. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// [OperandSize(Size = 1)] LDLOC = 0x6F, + /// /// Stores the value on top of the evaluation stack in the local variable list at index 0. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STLOC0 = 0x70, + /// /// Stores the value on top of the evaluation stack in the local variable list at index 1. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STLOC1 = 0x71, + /// /// Stores the value on top of the evaluation stack in the local variable list at index 2. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STLOC2 = 0x72, + /// /// Stores the value on top of the evaluation stack in the local variable list at index 3. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STLOC3 = 0x73, + /// /// Stores the value on top of the evaluation stack in the local variable list at index 4. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STLOC4 = 0x74, + /// /// Stores the value on top of the evaluation stack in the local variable list at index 5. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STLOC5 = 0x75, + /// /// Stores the value on top of the evaluation stack in the local variable list at index 6. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STLOC6 = 0x76, + /// /// Stores the value on top of the evaluation stack in the local variable list at a specified index. The index is represented as a 1-byte unsigned integer. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// [OperandSize(Size = 1)] STLOC = 0x77, + /// /// Loads the argument at index 0 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDARG0 = 0x78, + /// /// Loads the argument at index 1 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDARG1 = 0x79, + /// /// Loads the argument at index 2 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDARG2 = 0x7A, + /// /// Loads the argument at index 3 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDARG3 = 0x7B, + /// /// Loads the argument at index 4 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDARG4 = 0x7C, + /// /// Loads the argument at index 5 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDARG5 = 0x7D, + /// /// Loads the argument at index 6 onto the evaluation stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// LDARG6 = 0x7E, + /// /// Loads the argument at a specified index onto the evaluation stack. The index is represented as a 1-byte unsigned integer. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// [OperandSize(Size = 1)] LDARG = 0x7F, + /// /// Stores the value on top of the evaluation stack in the argument slot at index 0. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STARG0 = 0x80, + /// /// Stores the value on top of the evaluation stack in the argument slot at index 1. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STARG1 = 0x81, + /// /// Stores the value on top of the evaluation stack in the argument slot at index 2. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STARG2 = 0x82, + /// /// Stores the value on top of the evaluation stack in the argument slot at index 3. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STARG3 = 0x83, + /// /// Stores the value on top of the evaluation stack in the argument slot at index 4. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STARG4 = 0x84, + /// /// Stores the value on top of the evaluation stack in the argument slot at index 5. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STARG5 = 0x85, + /// /// Stores the value on top of the evaluation stack in the argument slot at index 6. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// STARG6 = 0x86, + /// /// Stores the value on top of the evaluation stack in the argument slot at a specified index. The index is represented as a 1-byte unsigned integer. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// [OperandSize(Size = 1)] STARG = 0x87, @@ -602,26 +1335,74 @@ public enum OpCode : byte /// /// Creates a new and pushes it onto the stack. + /// + /// new Buffer(a) + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// NEWBUFFER = 0x88, + /// /// Copies a range of bytes from one to another. + /// Using this opcode will require to dup the destination buffer. + /// + /// c.Slice(d, e).CopyTo(a.InnerBuffer.Span[b..]); + /// + /// + /// Push: 0 item(s) + /// Pop: 5 item(s) + /// /// MEMCPY = 0x89, + /// /// Concatenates two strings. + /// + /// a.concat(b) + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// CAT = 0x8B, + /// /// Returns a section of a string. + /// + /// a.Slice(b, c) + /// + /// + /// Push: 1 item(s) + /// Pop: 3 item(s) + /// /// SUBSTR = 0x8C, + /// /// Keeps only characters left of the specified point in a string. + /// + /// a[..b] + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// LEFT = 0x8D, + /// /// Keeps only characters right of the specified point in a string. + /// + /// a[^b..^0] + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// RIGHT = 0x8E, @@ -630,27 +1411,74 @@ public enum OpCode : byte #region Bitwise logic /// - /// Flips all of the bits in the input. + /// Flips all the bits in the input. + /// + /// ~a + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// INVERT = 0x90, + /// /// Boolean and between each bit in the inputs. + /// + /// a&b + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// AND = 0x91, + /// /// Boolean or between each bit in the inputs. + /// + /// a|b + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// OR = 0x92, + /// /// Boolean exclusive or between each bit in the inputs. + /// + /// a^b + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// XOR = 0x93, + /// /// Returns 1 if the inputs are exactly equal, 0 otherwise. + /// + /// a.Equals(b) + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// EQUAL = 0x97, + /// /// Returns 1 if the inputs are not equal, 0 otherwise. + /// + /// !a.Equals(b) + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// NOTEQUAL = 0x98, @@ -660,118 +1488,339 @@ public enum OpCode : byte /// /// Puts the sign of top stack item on top of the main stack. If value is negative, put -1; if positive, put 1; if value is zero, put 0. + /// + /// a.Sign + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// SIGN = 0x99, + /// /// The input is made positive. + /// + /// abs(a) + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// ABS = 0x9A, + /// /// The sign of the input is flipped. + /// + /// -a + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// NEGATE = 0x9B, + /// /// 1 is added to the input. + /// + /// a+1 + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// INC = 0x9C, + /// /// 1 is subtracted from the input. + /// + /// a-1 + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// DEC = 0x9D, + /// /// a is added to b. + /// + /// a+b + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// ADD = 0x9E, + /// /// b is subtracted from a. + /// + /// a-b + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// SUB = 0x9F, + /// /// a is multiplied by b. + /// + /// a*b + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// MUL = 0xA0, + /// /// a is divided by b. + /// + /// a/b + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// DIV = 0xA1, + /// /// Returns the remainder after dividing a by b. + /// + /// a%b + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// MOD = 0xA2, + /// /// The result of raising value to the exponent power. + /// + /// a^b + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// POW = 0xA3, + /// /// Returns the square root of a specified number. + /// + /// sqrt(a) + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// SQRT = 0xA4, + /// /// Performs modulus division on a number multiplied by another number. + /// + /// a*b%c + /// + /// + /// Push: 1 item(s) + /// Pop: 3 item(s) + /// /// MODMUL = 0xA5, + /// /// Performs modulus division on a number raised to the power of another number. If the exponent is -1, it will have the calculation of the modular inverse. + /// + /// modpow(a, b, c) + /// + /// + /// Push: 1 item(s) + /// Pop: 3 item(s) + /// /// MODPOW = 0xA6, + /// /// Shifts a left b bits, preserving sign. + /// + /// a<<b + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// SHL = 0xA8, + /// /// Shifts a right b bits, preserving sign. + /// + /// a>>b + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// SHR = 0xA9, + /// - /// If the input is 0 or 1, it is flipped. Otherwise the output will be 0. + /// If the input is 0 or 1, it is flipped. Otherwise, the output will be 0. + /// + /// !a + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// NOT = 0xAA, + /// - /// If both a and b are not 0, the output is 1. Otherwise 0. + /// If both a and b are not 0, the output is 1. Otherwise, 0. + /// + /// b && a + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// BOOLAND = 0xAB, + /// - /// If a or b is not 0, the output is 1. Otherwise 0. + /// If a or b is not 0, the output is 1. Otherwise, 0. + /// + /// b || a + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// BOOLOR = 0xAC, + /// /// Returns 0 if the input is 0. 1 otherwise. + /// + /// a != 0 + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// NZ = 0xB1, + /// /// Returns 1 if the numbers are equal, 0 otherwise. + /// + /// b == a + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// NUMEQUAL = 0xB3, + /// /// Returns 1 if the numbers are not equal, 0 otherwise. + /// + /// b != a + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// NUMNOTEQUAL = 0xB4, + /// /// Returns 1 if a is less than b, 0 otherwise. + /// + /// b>a + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// LT = 0xB5, + /// /// Returns 1 if a is less than or equal to b, 0 otherwise. + /// + /// b>=a + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// LE = 0xB6, + /// /// Returns 1 if a is greater than b, 0 otherwise. + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// GT = 0xB7, + /// /// Returns 1 if a is greater than or equal to b, 0 otherwise. + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// GE = 0xB8, + /// - /// Returns the smaller of a and b. + /// Returns the smallest of a and b. + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// MIN = 0xB9, + /// - /// Returns the larger of a and b. + /// Returns the largest of a and b. + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// MAX = 0xBA, + /// /// Returns 1 if x is within the specified range (left-inclusive), 0 otherwise. + /// + /// + /// Push: 1 item(s) + /// Pop: 3 item(s) + /// /// WITHIN = 0xBB, @@ -781,87 +1830,217 @@ public enum OpCode : byte /// /// A value n is taken from top of main stack. The next n*2 items on main stack are removed, put inside n-sized map and this map is put on top of the main stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 2n+1 item(s) + /// /// PACKMAP = 0xBE, + /// /// A value n is taken from top of main stack. The next n items on main stack are removed, put inside n-sized struct and this struct is put on top of the main stack. + /// + /// + /// Push: 1 item(s) + /// Pop: n+1 item(s) + /// /// PACKSTRUCT = 0xBF, + /// /// A value n is taken from top of main stack. The next n items on main stack are removed, put inside n-sized array and this array is put on top of the main stack. + /// + /// + /// Push: 1 item(s) + /// Pop: n+1 item(s) + /// /// PACK = 0xC0, + /// /// A collection is removed from top of the main stack. Its elements are put on top of the main stack (in reverse order) and the collection size is also put on main stack. + /// + /// + /// Push: 2n+1 or n+1 item(s) + /// Pop: 1 item(s) + /// /// UNPACK = 0xC1, + /// /// An empty array (with size 0) is put on top of the main stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// NEWARRAY0 = 0xC2, + /// /// A value n is taken from top of main stack. A null-filled array with size n is put on top of the main stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// NEWARRAY = 0xC3, + /// /// A value n is taken from top of main stack. An array of type T with size n is put on top of the main stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// [OperandSize(Size = 1)] NEWARRAY_T = 0xC4, + /// /// An empty struct (with size 0) is put on top of the main stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// NEWSTRUCT0 = 0xC5, + /// /// A value n is taken from top of main stack. A zero-filled struct with size n is put on top of the main stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// NEWSTRUCT = 0xC6, + /// /// A Map is created and put on top of the main stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 0 item(s) + /// /// NEWMAP = 0xC8, + /// /// An array is removed from top of the main stack. Its size is put on top of the main stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// SIZE = 0xCA, + /// /// An input index n (or key) and an array (or map) are removed from the top of the main stack. Puts True on top of main stack if array[n] (or map[n]) exist, and False otherwise. + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// HASKEY = 0xCB, + /// /// A map is taken from top of the main stack. The keys of this map are put on top of the main stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// KEYS = 0xCC, + /// /// A map is taken from top of the main stack. The values of this map are put on top of the main stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// VALUES = 0xCD, + /// /// An input index n (or key) and an array (or map) are taken from main stack. Element array[n] (or map[n]) is put on top of the main stack. + /// + /// + /// Push: 1 item(s) + /// Pop: 2 item(s) + /// /// PICKITEM = 0xCE, + /// /// The item on top of main stack is removed and appended to the second item on top of the main stack. + /// When we use this opcode, we should dup the second item on top of the main stack before using it. + /// + /// a a b -> a.concat(b) + /// + /// + /// Push: 0 item(s) + /// Pop: 2 item(s) + /// /// APPEND = 0xCF, + /// /// A value v, index n (or key) and an array (or map) are taken from main stack. Attribution array[n]=v (or map[n]=v) is performed. + /// + /// + /// Push: 1 item(s) + /// Pop: 3 item(s) + /// /// SETITEM = 0xD0, + /// /// An array is removed from the top of the main stack and its elements are reversed. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// REVERSEITEMS = 0xD1, + /// /// An input index n (or key) and an array (or map) are removed from the top of the main stack. Element array[n] (or map[n]) is removed. + /// + /// + /// Push: 0 item(s) + /// Pop: 2 item(s) + /// /// REMOVE = 0xD2, + /// /// Remove all the items from the compound-type. + /// Using this opcode will need to dup the compound-type before using it. + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// CLEARITEMS = 0xD3, + /// /// Remove the last element from an array, and push it onto the stack. + /// Using this opcode will need to dup the array before using it. + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// POPITEM = 0xD4, @@ -872,16 +2051,33 @@ public enum OpCode : byte /// /// Returns if the input is ; /// otherwise. + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// ISNULL = 0xD8, + /// /// Returns if the top item of the stack is of the specified type; /// otherwise. + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// [OperandSize(Size = 1)] ISTYPE = 0xD9, + /// /// Converts the top item of the stack to the specified type. + /// + /// + /// Push: 1 item(s) + /// Pop: 1 item(s) + /// /// [OperandSize(Size = 1)] CONVERT = 0xDB, @@ -893,11 +2089,24 @@ public enum OpCode : byte /// /// Pops the top stack item. Then, turns the vm state to FAULT immediately, and cannot be caught. The top stack /// value is used as reason. + /// + /// new Exception(a) + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// ABORTMSG = 0xE0, + /// /// Pops the top two stack items. If the second-to-top stack value is false, exits the vm execution and sets the /// vm state to FAULT. In this case, the top stack value is used as reason for the exit. Otherwise, it is ignored. + /// + /// + /// Push: 0 item(s) + /// Pop: 2 item(s) + /// /// ASSERTMSG = 0xE1 diff --git a/src/Neo.VM/ReferenceCounter.cs b/src/Neo.VM/ReferenceCounter.cs index f9ea08a2e2..2ba7d74063 100644 --- a/src/Neo.VM/ReferenceCounter.cs +++ b/src/Neo.VM/ReferenceCounter.cs @@ -20,7 +20,7 @@ namespace Neo.VM /// /// Used for reference counting of objects in the VM. /// - public sealed class ReferenceCounter + public sealed class ReferenceCounter : IReferenceCounter { // If set to true, all items will be tracked regardless of their type. private const bool TrackAllItems = false; @@ -38,9 +38,7 @@ public sealed class ReferenceCounter // Keeps the total count of references. private int _referencesCount = 0; - /// - /// Gets the count of references. - /// + /// public int Count => _referencesCount; /// @@ -61,17 +59,8 @@ private static bool NeedTrack(StackItem item) return false; } - /// - /// Adds a reference to a specified item with a parent compound type. - /// - /// This method is used when an item gains a new reference through a parent compound type. - /// It increments the reference count and updates the tracking structures if necessary. - /// - /// Use this method when you need to add a reference from a compound type to a stack item. - /// - /// The item to add a reference to. - /// The parent compound type. - internal void AddReference(StackItem item, CompoundType parent) + /// + public void AddReference(StackItem item, CompoundType parent) { // Increment the reference count. _referencesCount++; @@ -98,17 +87,8 @@ internal void AddReference(StackItem item, CompoundType parent) pEntry.References++; } - /// - /// Adds a stack reference to a specified item with a count. - /// - /// This method is used when an item gains a new stack reference, usually due to being pushed onto the evaluation stack. - /// It increments the reference count and updates the tracking structures if necessary. - /// - /// Use this method when you need to add one or more stack references to a stack item. - /// - /// The item to add a stack reference to. - /// The number of references to add. - internal void AddStackReference(StackItem item, int count = 1) + /// + public void AddStackReference(StackItem item, int count = 1) { // Increment the reference count by the specified count. _referencesCount += count; @@ -127,16 +107,8 @@ internal void AddStackReference(StackItem item, int count = 1) _zeroReferred.Remove(item); } - /// - /// Adds an item to the zero-referred list. - /// - /// This method is used when an item has no remaining references. - /// It adds the item to the zero-referred list to be checked for cleanup later. - /// - /// Use this method when you detect that an item has zero references and may need to be cleaned up. - /// - /// The item to add. - internal void AddZeroReferred(StackItem item) + /// + public void AddZeroReferred(StackItem item) { // Add the item to the _zeroReferred set. _zeroReferred.Add(item); @@ -158,7 +130,7 @@ internal void AddZeroReferred(StackItem item) /// Use this method periodically to clean up items with zero references and free up memory. /// /// The current reference count. - internal int CheckZeroReferred() + public int CheckZeroReferred() { // If there are items with zero references, process them. if (_zeroReferred.Count > 0) @@ -241,18 +213,8 @@ internal int CheckZeroReferred() return _referencesCount; } - - /// - /// Removes a reference from a specified item with a parent compound type. - /// - /// This method is used when an item loses a reference from a parent compound type. - /// It decrements the reference count and updates the tracking structures if necessary. - /// - /// Use this method when you need to remove a reference from a compound type to a stack item. - /// - /// The item to remove a reference from. - /// The parent compound type. - internal void RemoveReference(StackItem item, CompoundType parent) + /// + public void RemoveReference(StackItem item, CompoundType parent) { // Decrement the reference count. _referencesCount--; @@ -271,16 +233,8 @@ internal void RemoveReference(StackItem item, CompoundType parent) _zeroReferred.Add(item); } - /// - /// Removes a stack reference from a specified item. - /// - /// This method is used when an item loses a stack reference, usually due to being popped off the evaluation stack. - /// It decrements the reference count and updates the tracking structures if necessary. - /// - /// Use this method when you need to remove one or more stack references from a stack item. - /// - /// The item to remove a stack reference from. - internal void RemoveStackReference(StackItem item) + /// + public void RemoveStackReference(StackItem item) { // Decrement the reference count. _referencesCount--; diff --git a/src/Neo.VM/ScriptBuilder.cs b/src/Neo.VM/ScriptBuilder.cs index 3b2f83171a..5615f6b918 100644 --- a/src/Neo.VM/ScriptBuilder.cs +++ b/src/Neo.VM/ScriptBuilder.cs @@ -104,7 +104,8 @@ public ScriptBuilder EmitPush(BigInteger value) <= 4 => Emit(OpCode.PUSHINT32, PadRight(buffer, bytesWritten, 4, value.Sign < 0)), <= 8 => Emit(OpCode.PUSHINT64, PadRight(buffer, bytesWritten, 8, value.Sign < 0)), <= 16 => Emit(OpCode.PUSHINT128, PadRight(buffer, bytesWritten, 16, value.Sign < 0)), - _ => Emit(OpCode.PUSHINT256, PadRight(buffer, bytesWritten, 32, value.Sign < 0)), + <= 32 => Emit(OpCode.PUSHINT256, PadRight(buffer, bytesWritten, 32, value.Sign < 0)), + _ => throw new ArgumentOutOfRangeException(nameof(value), "Invalid value: BigInteger is too large"), }; } diff --git a/src/Neo.VM/Slot.cs b/src/Neo.VM/Slot.cs index 7812694ce6..30a1cb9a44 100644 --- a/src/Neo.VM/Slot.cs +++ b/src/Neo.VM/Slot.cs @@ -20,7 +20,7 @@ namespace Neo.VM /// public class Slot : IReadOnlyList { - private readonly ReferenceCounter referenceCounter; + private readonly IReferenceCounter referenceCounter; private readonly StackItem[] items; /// @@ -53,7 +53,7 @@ internal set /// /// The items to be contained. /// The reference counter to be used. - public Slot(StackItem[] items, ReferenceCounter referenceCounter) + public Slot(StackItem[] items, IReferenceCounter referenceCounter) { this.referenceCounter = referenceCounter; this.items = items; @@ -66,7 +66,7 @@ public Slot(StackItem[] items, ReferenceCounter referenceCounter) /// /// Indicates the number of items contained in the slot. /// The reference counter to be used. - public Slot(int count, ReferenceCounter referenceCounter) + public Slot(int count, IReferenceCounter referenceCounter) { this.referenceCounter = referenceCounter; items = new StackItem[count]; diff --git a/src/Neo.VM/Types/Array.cs b/src/Neo.VM/Types/Array.cs index e15358a881..903613c228 100644 --- a/src/Neo.VM/Types/Array.cs +++ b/src/Neo.VM/Types/Array.cs @@ -35,6 +35,11 @@ public StackItem this[int index] if (IsReadOnly) throw new InvalidOperationException("The object is readonly."); ReferenceCounter?.RemoveReference(_array[index], this); _array[index] = value; + if (ReferenceCounter != null && value is CompoundType { ReferenceCounter: null }) + { + throw new InvalidOperationException("Can not set a CompoundType without a ReferenceCounter."); + } + ReferenceCounter?.AddReference(value, this); } } @@ -57,11 +62,11 @@ public Array(IEnumerable? items = null) } /// - /// Create an array containing the specified items. And make the array use the specified . + /// Create an array containing the specified items. And make the array use the specified . /// - /// The to be used by this array. + /// The to be used by this array. /// The items to be included in the array. - public Array(ReferenceCounter? referenceCounter, IEnumerable? items = null) + public Array(IReferenceCounter? referenceCounter, IEnumerable? items = null) : base(referenceCounter) { _array = items switch @@ -70,9 +75,18 @@ public Array(ReferenceCounter? referenceCounter, IEnumerable? items = List list => list, _ => new List(items) }; - if (referenceCounter != null) - foreach (StackItem item in _array) - referenceCounter.AddReference(item, this); + + if (referenceCounter == null) return; + + foreach (var item in _array) + { + if (item is CompoundType { ReferenceCounter: null }) + { + throw new InvalidOperationException("Can not set a CompoundType without a ReferenceCounter."); + } + + referenceCounter.AddReference(item, this); + } } /// @@ -83,7 +97,14 @@ public void Add(StackItem item) { if (IsReadOnly) throw new InvalidOperationException("The object is readonly."); _array.Add(item); - ReferenceCounter?.AddReference(item, this); + + if (ReferenceCounter == null) return; + + if (item is CompoundType { ReferenceCounter: null }) + { + throw new InvalidOperationException("Can not set a CompoundType without a ReferenceCounter."); + } + ReferenceCounter.AddReference(item, this); } public override void Clear() diff --git a/src/Neo.VM/Types/CompoundType.cs b/src/Neo.VM/Types/CompoundType.cs index ede743b881..6b0da819ec 100644 --- a/src/Neo.VM/Types/CompoundType.cs +++ b/src/Neo.VM/Types/CompoundType.cs @@ -24,13 +24,13 @@ public abstract class CompoundType : StackItem /// /// The reference counter used to count the items in the VM object. /// - protected readonly ReferenceCounter? ReferenceCounter; + protected internal readonly IReferenceCounter? ReferenceCounter; /// /// Create a new with the specified reference counter. /// /// The reference counter to be used. - protected CompoundType(ReferenceCounter? referenceCounter) + protected CompoundType(IReferenceCounter? referenceCounter) { ReferenceCounter = referenceCounter; referenceCounter?.AddZeroReferred(this); diff --git a/src/Neo.VM/Types/Map.cs b/src/Neo.VM/Types/Map.cs index 5df66fb897..2986399d59 100644 --- a/src/Neo.VM/Types/Map.cs +++ b/src/Neo.VM/Types/Map.cs @@ -54,6 +54,10 @@ public StackItem this[PrimitiveType key] ReferenceCounter.RemoveReference(old_value, this); else ReferenceCounter.AddReference(key, this); + if (value is CompoundType { ReferenceCounter: null }) + { + throw new InvalidOperationException("Can not set a Map without a ReferenceCounter."); + } ReferenceCounter.AddReference(value, this); } dictionary[key] = value; @@ -82,7 +86,7 @@ public StackItem this[PrimitiveType key] /// Create a new map with the specified reference counter. /// /// The reference counter to be used. - public Map(ReferenceCounter? referenceCounter = null) + public Map(IReferenceCounter? referenceCounter = null) : base(referenceCounter) { } diff --git a/src/Neo.VM/Types/Struct.cs b/src/Neo.VM/Types/Struct.cs index 8170414363..344147b5ed 100644 --- a/src/Neo.VM/Types/Struct.cs +++ b/src/Neo.VM/Types/Struct.cs @@ -31,11 +31,11 @@ public Struct(IEnumerable? fields = null) } /// - /// Create a structure with the specified fields. And make the structure use the specified . + /// Create a structure with the specified fields. And make the structure use the specified . /// - /// The to be used by this structure. + /// The to be used by this structure. /// The fields to be included in the structure. - public Struct(ReferenceCounter? referenceCounter, IEnumerable? fields = null) + public Struct(IReferenceCounter? referenceCounter, IEnumerable? fields = null) : base(referenceCounter, fields) { } diff --git a/src/Neo/Builders/AndConditionBuilder.cs b/src/Neo/Builders/AndConditionBuilder.cs new file mode 100644 index 0000000000..a75a52227a --- /dev/null +++ b/src/Neo/Builders/AndConditionBuilder.cs @@ -0,0 +1,90 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// AndConditionBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads.Conditions; +using System; + +namespace Neo.Builders +{ + public sealed class AndConditionBuilder + { + private readonly AndCondition _condition = new() { Expressions = [] }; + + private AndConditionBuilder() { } + + public static AndConditionBuilder CreateEmpty() + { + return new AndConditionBuilder(); + } + + public AndConditionBuilder And(Action config) + { + var acb = new AndConditionBuilder(); + config(acb); + + _condition.Expressions = [.. _condition.Expressions, acb.Build()]; + + return this; + } + + public AndConditionBuilder Or(Action config) + { + var ocb = OrConditionBuilder.CreateEmpty(); + config(ocb); + + _condition.Expressions = [.. _condition.Expressions, ocb.Build()]; + + return this; + } + + public AndConditionBuilder Boolean(bool expression) + { + _condition.Expressions = [.. _condition.Expressions, new BooleanCondition { Expression = expression }]; + return this; + } + + public AndConditionBuilder CalledByContract(UInt160 hash) + { + _condition.Expressions = [.. _condition.Expressions, new CalledByContractCondition { Hash = hash }]; + return this; + } + + public AndConditionBuilder CalledByEntry() + { + _condition.Expressions = [.. _condition.Expressions, new CalledByEntryCondition()]; + return this; + } + + public AndConditionBuilder CalledByGroup(ECPoint publicKey) + { + _condition.Expressions = [.. _condition.Expressions, new CalledByGroupCondition { Group = publicKey }]; + return this; + } + + public AndConditionBuilder Group(ECPoint publicKey) + { + _condition.Expressions = [.. _condition.Expressions, new GroupCondition() { Group = publicKey }]; + return this; + } + + public AndConditionBuilder ScriptHash(UInt160 scriptHash) + { + _condition.Expressions = [.. _condition.Expressions, new ScriptHashCondition() { Hash = scriptHash }]; + return this; + } + + public AndCondition Build() + { + return _condition; + } + } +} diff --git a/src/Neo/Builders/OrConditionBuilder.cs b/src/Neo/Builders/OrConditionBuilder.cs new file mode 100644 index 0000000000..6b8ca2a0e1 --- /dev/null +++ b/src/Neo/Builders/OrConditionBuilder.cs @@ -0,0 +1,90 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// OrConditionBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads.Conditions; +using System; + +namespace Neo.Builders +{ + public sealed class OrConditionBuilder + { + private readonly OrCondition _condition = new() { Expressions = [] }; + + private OrConditionBuilder() { } + + public static OrConditionBuilder CreateEmpty() + { + return new OrConditionBuilder(); + } + + public OrConditionBuilder And(Action config) + { + var acb = AndConditionBuilder.CreateEmpty(); + config(acb); + + _condition.Expressions = [.. _condition.Expressions, acb.Build()]; + + return this; + } + + public OrConditionBuilder Or(Action config) + { + var acb = new OrConditionBuilder(); + config(acb); + + _condition.Expressions = [.. _condition.Expressions, acb.Build()]; + + return this; + } + + public OrConditionBuilder Boolean(bool expression) + { + _condition.Expressions = [.. _condition.Expressions, new BooleanCondition { Expression = expression }]; + return this; + } + + public OrConditionBuilder CalledByContract(UInt160 hash) + { + _condition.Expressions = [.. _condition.Expressions, new CalledByContractCondition { Hash = hash }]; + return this; + } + + public OrConditionBuilder CalledByEntry() + { + _condition.Expressions = [.. _condition.Expressions, new CalledByEntryCondition()]; + return this; + } + + public OrConditionBuilder CalledByGroup(ECPoint publicKey) + { + _condition.Expressions = [.. _condition.Expressions, new CalledByGroupCondition { Group = publicKey }]; + return this; + } + + public OrConditionBuilder Group(ECPoint publicKey) + { + _condition.Expressions = [.. _condition.Expressions, new GroupCondition() { Group = publicKey }]; + return this; + } + + public OrConditionBuilder ScriptHash(UInt160 scriptHash) + { + _condition.Expressions = [.. _condition.Expressions, new ScriptHashCondition() { Hash = scriptHash }]; + return this; + } + + public OrCondition Build() + { + return _condition; + } + } +} diff --git a/src/Neo/Builders/SignerBuilder.cs b/src/Neo/Builders/SignerBuilder.cs new file mode 100644 index 0000000000..76b70f1efe --- /dev/null +++ b/src/Neo/Builders/SignerBuilder.cs @@ -0,0 +1,73 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// SignerBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads; +using System; + +namespace Neo.Builders +{ + public sealed class SignerBuilder + { + private readonly Signer _signer = new Signer() + { + Account = UInt160.Zero, + AllowedContracts = [], + AllowedGroups = [], + Rules = [], + Scopes = WitnessScope.None, + }; + + private SignerBuilder() { } + + public static SignerBuilder CreateEmpty() + { + return new SignerBuilder(); + } + + public SignerBuilder Account(UInt160 scriptHash) + { + _signer.Account = scriptHash; + return this; + } + + public SignerBuilder AllowContract(UInt160 contractHash) + { + _signer.AllowedContracts = [.. _signer.AllowedContracts, contractHash]; + return this; + } + + public SignerBuilder AllowGroup(ECPoint publicKey) + { + _signer.AllowedGroups = [.. _signer.AllowedGroups, publicKey]; + return this; + } + + public SignerBuilder AddWitnessScope(WitnessScope scope) + { + _signer.Scopes |= scope; + return this; + } + + public SignerBuilder AddWitnessRule(WitnessRuleAction action, Action config) + { + var rb = WitnessRuleBuilder.Create(action); + config(rb); + _signer.Rules = [.. _signer.Rules, rb.Build()]; + return this; + } + + public Signer Build() + { + return _signer; + } + } +} diff --git a/src/Neo/Builders/TransactionAttributesBuilder.cs b/src/Neo/Builders/TransactionAttributesBuilder.cs new file mode 100644 index 0000000000..9eb4ae99e3 --- /dev/null +++ b/src/Neo/Builders/TransactionAttributesBuilder.cs @@ -0,0 +1,74 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionAttributesBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using System; +using System.Linq; + +namespace Neo.Builders +{ + public sealed class TransactionAttributesBuilder + { + private TransactionAttribute[] _attributes = []; + + private TransactionAttributesBuilder() { } + + public static TransactionAttributesBuilder CreateEmpty() + { + return new TransactionAttributesBuilder(); + } + + public TransactionAttributesBuilder AddConflict(Action config) + { + var conflicts = new Conflicts(); + config(conflicts); + _attributes = [.. _attributes, conflicts]; + return this; + } + + public TransactionAttributesBuilder AddOracleResponse(Action config) + { + var oracleResponse = new OracleResponse(); + config(oracleResponse); + _attributes = [.. _attributes, oracleResponse]; + return this; + } + + public TransactionAttributesBuilder AddHighPriority() + { + if (_attributes.Any(a => a is HighPriorityAttribute)) + throw new InvalidOperationException("HighPriority already exists in the attributes."); + + var highPriority = new HighPriorityAttribute(); + _attributes = [.. _attributes, highPriority]; + return this; + } + + public TransactionAttributesBuilder AddNotValidBefore(uint block) + { + if (_attributes.Any(a => a is NotValidBefore b && b.Height == block)) + throw new InvalidOperationException($"Block {block} already exists in the attributes."); + + var validUntilBlock = new NotValidBefore() + { + Height = block + }; + + _attributes = [.. _attributes, validUntilBlock]; + return this; + } + + public TransactionAttribute[] Build() + { + return _attributes; + } + } +} diff --git a/src/Neo/Builders/TransactionBuilder.cs b/src/Neo/Builders/TransactionBuilder.cs new file mode 100644 index 0000000000..2a1a7a9424 --- /dev/null +++ b/src/Neo/Builders/TransactionBuilder.cs @@ -0,0 +1,116 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using Neo.VM; +using System; + +namespace Neo.Builders +{ + public sealed class TransactionBuilder + { + private readonly Transaction _tx = new() + { + Script = new[] { (byte)OpCode.RET }, + Attributes = [], + Signers = [], + Witnesses = [], + }; + + private TransactionBuilder() { } + + public static TransactionBuilder CreateEmpty() + { + return new TransactionBuilder(); + } + + public TransactionBuilder Version(byte version) + { + _tx.Version = version; + return this; + } + + public TransactionBuilder Nonce(uint nonce) + { + _tx.Nonce = nonce; + return this; + } + + public TransactionBuilder SystemFee(uint systemFee) + { + _tx.SystemFee = systemFee; + return this; + } + + public TransactionBuilder NetworkFee(uint networkFee) + { + _tx.NetworkFee = networkFee; + return this; + } + + public TransactionBuilder ValidUntil(uint blockIndex) + { + _tx.ValidUntilBlock = blockIndex; + return this; + } + + public TransactionBuilder AttachSystem(Action config) + { + using var sb = new ScriptBuilder(); + config(sb); + _tx.Script = sb.ToArray(); + return this; + } + + public TransactionBuilder AttachSystem(byte[] script) + { + _tx.Script = script; + return this; + } + + public TransactionBuilder AddAttributes(Action config) + { + var ab = TransactionAttributesBuilder.CreateEmpty(); + config(ab); + _tx.Attributes = ab.Build(); + return this; + } + + public TransactionBuilder AddWitness(Action config) + { + var wb = WitnessBuilder.CreateEmpty(); + config(wb); + _tx.Witnesses = [.. _tx.Witnesses, wb.Build()]; + return this; + } + + public TransactionBuilder AddWitness(Action config) + { + var wb = WitnessBuilder.CreateEmpty(); + config(wb, _tx); + _tx.Witnesses = [.. _tx.Witnesses, wb.Build()]; + return this; + } + + public TransactionBuilder AddSigner(Action config) + { + var wb = SignerBuilder.CreateEmpty(); + config(wb, _tx); + _tx.Signers = [.. _tx.Signers, wb.Build()]; + return this; + } + + public Transaction Build() + { + return _tx; + } + } +} diff --git a/src/Neo/Builders/WitnessBuilder.cs b/src/Neo/Builders/WitnessBuilder.cs new file mode 100644 index 0000000000..e555195688 --- /dev/null +++ b/src/Neo/Builders/WitnessBuilder.cs @@ -0,0 +1,79 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using Neo.VM; +using System; + +namespace Neo.Builders +{ + public sealed class WitnessBuilder + { + private byte[] _invocationScript = []; + private byte[] _verificationScript = []; + + private WitnessBuilder() { } + + public static WitnessBuilder CreateEmpty() + { + return new WitnessBuilder(); + } + + public WitnessBuilder AddInvocation(Action config) + { + if (_invocationScript.Length > 0) + throw new InvalidOperationException("Invocation script already exists."); + + using var sb = new ScriptBuilder(); + config(sb); + _invocationScript = sb.ToArray(); + return this; + } + + public WitnessBuilder AddInvocation(byte[] bytes) + { + if (_invocationScript.Length > 0) + throw new InvalidOperationException("Invocation script already exists."); + + _invocationScript = bytes; + return this; + } + + public WitnessBuilder AddVerification(Action config) + { + if (_verificationScript.Length > 0) + throw new InvalidOperationException("Verification script already exists."); + + using var sb = new ScriptBuilder(); + config(sb); + _verificationScript = sb.ToArray(); + return this; + } + + public WitnessBuilder AddVerification(byte[] bytes) + { + if (_verificationScript.Length > 0) + throw new InvalidOperationException("Verification script already exists."); + + _verificationScript = bytes; + return this; + } + + public Witness Build() + { + return new Witness() + { + InvocationScript = _invocationScript, + VerificationScript = _verificationScript, + }; + } + } +} diff --git a/src/Neo/Builders/WitnessConditionBuilder.cs b/src/Neo/Builders/WitnessConditionBuilder.cs new file mode 100644 index 0000000000..41cfc05163 --- /dev/null +++ b/src/Neo/Builders/WitnessConditionBuilder.cs @@ -0,0 +1,131 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessConditionBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads.Conditions; +using System; + +namespace Neo.Builders +{ + public sealed class WitnessConditionBuilder + { + WitnessCondition _condition; + + private WitnessConditionBuilder() { } + + private WitnessConditionBuilder(WitnessCondition condition) + { + _condition = condition; + } + + public static WitnessConditionBuilder Create() + { + return new WitnessConditionBuilder(); + } + + public WitnessConditionBuilder And(Action config) + { + var acb = AndConditionBuilder.CreateEmpty(); + config(acb); + + _condition = acb.Build(); + + return this; + } + + public WitnessConditionBuilder Boolean(bool expression) + { + var condition = new BooleanCondition() { Expression = expression }; + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder CalledByContract(UInt160 hash) + { + var condition = new CalledByContractCondition() { Hash = hash }; + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder CalledByEntry() + { + var condition = new CalledByEntryCondition(); + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder CalledByGroup(ECPoint publicKey) + { + var condition = new CalledByGroupCondition() { Group = publicKey }; + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder Group(ECPoint publicKey) + { + var condition = new GroupCondition() { Group = publicKey }; + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder Not(Action config) + { + var wcb = new WitnessConditionBuilder(); + config(wcb); + + var condition = new NotCondition() + { + Expression = wcb.Build() + }; + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder Or(Action config) + { + var ocb = OrConditionBuilder.CreateEmpty(); + config(ocb); + + _condition = ocb.Build(); + + return this; + } + + public WitnessConditionBuilder ScriptHash(UInt160 scriptHash) + { + var condition = new ScriptHashCondition() { Hash = scriptHash }; + + _condition = condition; + + return this; + } + + public WitnessCondition Build() + { + if (_condition is null) + return new BooleanCondition() { Expression = true }; + + return _condition; + } + } +} diff --git a/src/Neo/Builders/WitnessRuleBuilder.cs b/src/Neo/Builders/WitnessRuleBuilder.cs new file mode 100644 index 0000000000..7c05a524d6 --- /dev/null +++ b/src/Neo/Builders/WitnessRuleBuilder.cs @@ -0,0 +1,44 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessRuleBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using System; + +namespace Neo.Builders +{ + public sealed class WitnessRuleBuilder + { + private readonly WitnessRule _rule = new(); + + private WitnessRuleBuilder(WitnessRuleAction action) + { + _rule.Action = action; + } + + public static WitnessRuleBuilder Create(WitnessRuleAction action) + { + return new WitnessRuleBuilder(action); + } + + public WitnessRuleBuilder AddCondition(Action config) + { + var cb = WitnessConditionBuilder.Create(); + config(cb); + _rule.Condition = cb.Build(); + return this; + } + + public WitnessRule Build() + { + return _rule; + } + } +} diff --git a/src/Neo/Cryptography/Base58.cs b/src/Neo/Cryptography/Base58.cs index adecc8b631..a2f562b06b 100644 --- a/src/Neo/Cryptography/Base58.cs +++ b/src/Neo/Cryptography/Base58.cs @@ -13,7 +13,6 @@ using System.Linq; using System.Numerics; using System.Text; -using static Neo.Helper; namespace Neo.Cryptography { @@ -86,7 +85,7 @@ public static byte[] Decode(string input) var leadingZeros = new byte[leadingZeroCount]; if (bi.IsZero) return leadingZeros; var bytesWithoutLeadingZeros = bi.ToByteArray(isUnsigned: true, isBigEndian: true); - return Concat(leadingZeros, bytesWithoutLeadingZeros); + return [.. leadingZeros, .. bytesWithoutLeadingZeros]; } /// diff --git a/src/Neo/Cryptography/BloomFilter.cs b/src/Neo/Cryptography/BloomFilter.cs index 73141d9e11..30cab45392 100644 --- a/src/Neo/Cryptography/BloomFilter.cs +++ b/src/Neo/Cryptography/BloomFilter.cs @@ -41,12 +41,13 @@ public class BloomFilter /// /// Initializes a new instance of the class. /// - /// The size of the bit array used by the bloom filter. - /// The number of hash functions used by the bloom filter. + /// The size of the bit array used by the bloom filter, and must be greater than 0. + /// The number of hash functions used by the bloom filter, and must be greater than 0. /// Used to generate the seeds of the murmur hash functions. + /// Thrown when or is less than or equal to 0. public BloomFilter(int m, int k, uint nTweak) { - if (k < 0 || m < 0) throw new ArgumentOutOfRangeException(); + if (k <= 0 || m <= 0) throw new ArgumentOutOfRangeException(); seeds = Enumerable.Range(0, k).Select(p => (uint)p * 0xFBA4C795 + nTweak).ToArray(); bits = new BitArray(m) { @@ -58,13 +59,14 @@ public BloomFilter(int m, int k, uint nTweak) /// /// Initializes a new instance of the class. /// - /// The size of the bit array used by the bloom filter. - /// The number of hash functions used by the bloom filter. + /// The size of the bit array used by the bloom filter, and must be greater than 0. + /// The number of hash functions used by the bloom filter, and must be greater than 0. /// Used to generate the seeds of the murmur hash functions. /// The initial elements contained in this object. + /// Thrown when or is less than or equal to 0. public BloomFilter(int m, int k, uint nTweak, ReadOnlyMemory elements) { - if (k < 0 || m < 0) throw new ArgumentOutOfRangeException(); + if (k <= 0 || m <= 0) throw new ArgumentOutOfRangeException(); seeds = Enumerable.Range(0, k).Select(p => (uint)p * 0xFBA4C795 + nTweak).ToArray(); bits = new BitArray(elements.ToArray()) { 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/ECCurve.cs b/src/Neo/Cryptography/ECC/ECCurve.cs index def1ee0cb1..805507a08e 100644 --- a/src/Neo/Cryptography/ECC/ECCurve.cs +++ b/src/Neo/Cryptography/ECC/ECCurve.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using System.Globalization; using System.Numerics; 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..ce6b3ac588 100644 --- a/src/Neo/Cryptography/ECC/ECPoint.cs +++ b/src/Neo/Cryptography/ECC/ECPoint.cs @@ -9,12 +9,12 @@ // 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; using System.IO; using System.Numerics; -using static Neo.Helper; namespace Neo.Cryptography.ECC { @@ -224,8 +224,8 @@ public static ECPoint FromBytes(byte[] bytes, ECCurve curve) return bytes.Length switch { 33 or 65 => DecodePoint(bytes, curve), - 64 or 72 => DecodePoint(Concat(new byte[] { 0x04 }, bytes[^64..]), curve), - 96 or 104 => DecodePoint(Concat(new byte[] { 0x04 }, bytes[^96..^32]), curve), + 64 or 72 => DecodePoint([.. new byte[] { 0x04 }, .. bytes[^64..]], curve), + 96 or 104 => DecodePoint([.. new byte[] { 0x04 }, .. bytes[^96..^32]], curve), _ => throw new FormatException(), }; } diff --git a/src/Neo/Cryptography/Helper.cs b/src/Neo/Cryptography/Helper.cs index f13fa08118..41e89f5a49 100644 --- a/src/Neo/Cryptography/Helper.cs +++ b/src/Neo/Cryptography/Helper.cs @@ -22,7 +22,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Security.Cryptography; -using static Neo.Helper; using ECPoint = Neo.Cryptography.ECC.ECPoint; namespace Neo.Cryptography @@ -213,7 +212,7 @@ public static byte[] AES256Encrypt(this byte[] plainData, byte[] key, byte[] non var length = cipher.ProcessBytes(plainData, 0, plainData.Length, cipherBytes, 0); cipher.DoFinal(cipherBytes, length); } - return Concat(nonce, cipherBytes, tag); + return [.. nonce, .. cipherBytes, .. tag]; } public static byte[] AES256Decrypt(this byte[] encryptedData, byte[] key, byte[] associatedData = null) diff --git a/src/Neo/Extensions/Collections/ICollectionExtensions.cs b/src/Neo/Extensions/Collections/ICollectionExtensions.cs new file mode 100644 index 0000000000..724def6d80 --- /dev/null +++ b/src/Neo/Extensions/Collections/ICollectionExtensions.cs @@ -0,0 +1,74 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ICollectionExtensions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Neo.Extensions +{ + public static class ICollectionExtensions + { + /// + /// Gets the size of the specified array encoded in variable-length encoding. + /// + /// The type of the array element. + /// The specified array. + /// The size of the array. + public static int GetVarSize(this IReadOnlyCollection value) + { + int value_size; + var t = typeof(T); + if (typeof(ISerializable).IsAssignableFrom(t)) + { + value_size = value.OfType().Sum(p => p.Size); + } + else if (t.GetTypeInfo().IsEnum) + { + int element_size; + var u = t.GetTypeInfo().GetEnumUnderlyingType(); + if (u == typeof(sbyte) || u == typeof(byte)) + element_size = 1; + else if (u == typeof(short) || u == typeof(ushort)) + element_size = 2; + else if (u == typeof(int) || u == typeof(uint)) + element_size = 4; + else //if (u == typeof(long) || u == typeof(ulong)) + element_size = 8; + value_size = value.Count * element_size; + } + else + { + value_size = value.Count * Marshal.SizeOf(); + } + return UnsafeData.GetVarSize(value.Count) + value_size; + } + + /// + /// Converts an array to a byte array. + /// + /// The type of the array element. + /// The array to be converted. + /// The converted byte array. + public static byte[] ToByteArray(this IReadOnlyCollection value) + where T : ISerializable + { + using MemoryStream ms = new(); + using BinaryWriter writer = new(ms, Utility.StrictUTF8, true); + writer.Write(value); + writer.Flush(); + return ms.ToArray(); + } + } +} diff --git a/src/Neo/Extensions/HashSetExtensions.cs b/src/Neo/Extensions/HashSetExtensions.cs new file mode 100644 index 0000000000..fb4648b16e --- /dev/null +++ b/src/Neo/Extensions/HashSetExtensions.cs @@ -0,0 +1,36 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// HashSetExtensions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO.Caching; +using System; +using System.Collections.Generic; + +namespace Neo.Extensions +{ + /// + /// A helper class that provides common functions. + /// + public static class HashSetExtensions + { + internal static void Remove(this HashSet set, HashSetCache other) + where T : IEquatable + { + if (set.Count > other.Count) + { + set.ExceptWith(other); + } + else + { + set.RemoveWhere(u => other.Contains(u)); + } + } + } +} diff --git a/src/Neo/Extensions/MemoryExtensions.cs b/src/Neo/Extensions/MemoryExtensions.cs new file mode 100644 index 0000000000..f6576387de --- /dev/null +++ b/src/Neo/Extensions/MemoryExtensions.cs @@ -0,0 +1,60 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// MemoryExtensions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO; +using System; +using System.Reflection; + +namespace Neo.Extensions +{ + public static class MemoryExtensions + { + /// + /// Converts a byte array to an object. + /// + /// The type to convert to. + /// The byte array to be converted. + /// The converted object. + public static T AsSerializable(this ReadOnlyMemory value) + where T : ISerializable, new() + { + if (value.IsEmpty) throw new FormatException(); + MemoryReader reader = new(value); + return reader.ReadSerializable(); + } + + /// + /// Converts a byte array to an object. + /// + /// The byte array to be converted. + /// The type to convert to. + /// The converted object. + public static ISerializable AsSerializable(this ReadOnlyMemory value, Type type) + { + if (!typeof(ISerializable).GetTypeInfo().IsAssignableFrom(type)) + throw new InvalidCastException(); + var serializable = (ISerializable)Activator.CreateInstance(type); + MemoryReader reader = new(value); + serializable.Deserialize(ref reader); + return serializable; + } + + /// + /// Gets the size of the specified array encoded in variable-length encoding. + /// + /// The specified array. + /// The size of the array. + public static int GetVarSize(this ReadOnlyMemory value) + { + return UnsafeData.GetVarSize(value.Length) + value.Length; + } + } +} diff --git a/src/Neo/Helper.cs b/src/Neo/Helper.cs deleted file mode 100644 index 00099bc148..0000000000 --- a/src/Neo/Helper.cs +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright (C) 2015-2024 The Neo Project. -// -// Helper.cs file belongs to the neo project and is free -// software distributed under the MIT software license, see the -// accompanying file LICENSE in the main directory of the -// repository or http://www.opensource.org/licenses/mit-license.php -// for more details. -// -// Redistribution and use in source and binary forms with or without -// modifications are permitted. - -using Neo.IO.Caching; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net; -using System.Numerics; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Text; - -namespace Neo -{ - /// - /// A helper class that provides common functions. - /// - public static class Helper - { - private static readonly DateTime unixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int BitLen(int w) - { - return (w < 1 << 15 ? (w < 1 << 7 - ? (w < 1 << 3 ? (w < 1 << 1 - ? (w < 1 << 0 ? (w < 0 ? 32 : 0) : 1) - : (w < 1 << 2 ? 2 : 3)) : (w < 1 << 5 - ? (w < 1 << 4 ? 4 : 5) - : (w < 1 << 6 ? 6 : 7))) - : (w < 1 << 11 - ? (w < 1 << 9 ? (w < 1 << 8 ? 8 : 9) : (w < 1 << 10 ? 10 : 11)) - : (w < 1 << 13 ? (w < 1 << 12 ? 12 : 13) : (w < 1 << 14 ? 14 : 15)))) : (w < 1 << 23 ? (w < 1 << 19 - ? (w < 1 << 17 ? (w < 1 << 16 ? 16 : 17) : (w < 1 << 18 ? 18 : 19)) - : (w < 1 << 21 ? (w < 1 << 20 ? 20 : 21) : (w < 1 << 22 ? 22 : 23))) : (w < 1 << 27 - ? (w < 1 << 25 ? (w < 1 << 24 ? 24 : 25) : (w < 1 << 26 ? 26 : 27)) - : (w < 1 << 29 ? (w < 1 << 28 ? 28 : 29) : (w < 1 << 30 ? 30 : 31))))); - } - - /// - /// Concatenates the specified byte arrays. - /// - /// The byte arrays to concatenate. - /// The concatenated byte array. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte[] Concat(params byte[][] buffers) - { - int length = 0; - for (int i = 0; i < buffers.Length; i++) - length += buffers[i].Length; - byte[] dst = new byte[length]; - int p = 0; - foreach (byte[] src in buffers) - { - Buffer.BlockCopy(src, 0, dst, p, src.Length); - p += src.Length; - } - return dst; - } - - /// - /// Concatenates two byte arrays. - /// - /// The first byte array to concatenate. - /// The second byte array to concatenate. - /// The concatenated byte array. - public static byte[] Concat(ReadOnlySpan a, ReadOnlySpan b) - { - byte[] buffer = new byte[a.Length + b.Length]; - a.CopyTo(buffer); - b.CopyTo(buffer.AsSpan(a.Length)); - 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) - { - set.ExceptWith(other); - } - else - { - set.RemoveWhere(u => other.Contains(u)); - } - } - - internal static void Remove(this HashSet set, HashSetCache other) - where T : IEquatable - { - if (set.Count > other.Count) - { - set.ExceptWith(other); - } - else - { - set.RemoveWhere(u => other.Contains(u)); - } - } - - internal static void Remove(this HashSet set, IReadOnlyDictionary other) - { - if (set.Count > other.Count) - { - set.ExceptWith(other.Keys); - } - else - { - set.RemoveWhere(u => other.ContainsKey(u)); - } - } - - internal static string GetVersion(this Assembly assembly) - { - CustomAttributeData attribute = assembly.CustomAttributes.FirstOrDefault(p => p.AttributeType == typeof(AssemblyInformationalVersionAttribute)); - if (attribute == null) return assembly.GetName().Version.ToString(3); - return (string)attribute.ConstructorArguments[0].Value; - } - - /// - /// Converts a hex to byte array. - /// - /// The hex to convert. - /// The converted byte array. - public static byte[] HexToBytes(this string value) - { - if (value == null || value.Length == 0) - return Array.Empty(); - if (value.Length % 2 == 1) - throw new FormatException(); - byte[] result = new byte[value.Length / 2]; - for (int i = 0; i < result.Length; i++) - result[i] = byte.Parse(value.Substring(i * 2, 2), NumberStyles.AllowHexSpecifier); - 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) - throw new ArgumentException("sizeInBits must be non-negative"); - if (sizeInBits == 0) - return 0; - Span b = stackalloc byte[sizeInBits / 8 + 1]; - rand.NextBytes(b); - if (sizeInBits % 8 == 0) - b[^1] = 0; - else - b[^1] &= (byte)((1 << sizeInBits % 8) - 1); - 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 . - /// - /// The byte array to convert. - /// The converted hex . - public static string ToHexString(this byte[] value) - { - StringBuilder sb = new(); - foreach (byte b in value) - sb.AppendFormat("{0:x2}", b); - return sb.ToString(); - } - - /// - /// Converts a byte array to hex . - /// - /// The byte array to convert. - /// Indicates whether it should be converted in the reversed byte order. - /// The converted hex . - public static string ToHexString(this byte[] value, bool reverse = false) - { - StringBuilder sb = new(); - for (int i = 0; i < value.Length; i++) - sb.AppendFormat("{0:x2}", value[reverse ? value.Length - i - 1 : i]); - return sb.ToString(); - } - - /// - /// Converts a byte array to hex . - /// - /// The byte array to convert. - /// The converted hex . - public static string ToHexString(this ReadOnlySpan value) - { - StringBuilder sb = new(); - foreach (byte b in value) - sb.AppendFormat("{0:x2}", b); - return sb.ToString(); - } - - /// - /// Converts a to timestamp. - /// - /// The to convert. - /// The converted timestamp. - public static uint ToTimestamp(this DateTime time) - { - return (uint)(time.ToUniversalTime() - unixEpoch).TotalSeconds; - } - - /// - /// Converts a to timestamp in milliseconds. - /// - /// The to convert. - /// The converted timestamp. - public static ulong ToTimestampMS(this DateTime time) - { - return (ulong)(time.ToUniversalTime() - unixEpoch).TotalMilliseconds; - } - - /// - /// Checks if address is IPv4 Mapped to IPv6 format, if so, Map to IPv4. - /// Otherwise, return current address. - /// - internal static IPAddress Unmap(this IPAddress address) - { - if (address.IsIPv4MappedToIPv6) - address = address.MapToIPv4(); - return address; - } - - /// - /// Checks if IPEndPoint is IPv4 Mapped to IPv6 format, if so, unmap to IPv4. - /// Otherwise, return current endpoint. - /// - internal static IPEndPoint Unmap(this IPEndPoint endPoint) - { - if (!endPoint.Address.IsIPv4MappedToIPv6) - return endPoint; - return new IPEndPoint(endPoint.Address.Unmap(), endPoint.Port); - } - } -} 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..f24f4e5ab6 100644 --- a/src/Neo/IO/Caching/ReflectionCache.cs +++ b/src/Neo/IO/Caching/ReflectionCache.cs @@ -9,35 +9,37 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using System; using System.Collections.Generic; using System.Reflection; 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 +48,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/IO/Helper.cs b/src/Neo/IO/Helper.cs index 2e6d362ee1..d0e5a00ec9 100644 --- a/src/Neo/IO/Helper.cs +++ b/src/Neo/IO/Helper.cs @@ -14,9 +14,6 @@ using System.Buffers.Binary; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; namespace Neo.IO { @@ -38,35 +35,6 @@ public static class Helper return reader.ReadSerializable(); } - /// - /// Converts a byte array to an object. - /// - /// The type to convert to. - /// The byte array to be converted. - /// The converted object. - public static T AsSerializable(this ReadOnlyMemory value) where T : ISerializable, new() - { - if (value.IsEmpty) throw new FormatException(); - MemoryReader reader = new(value); - return reader.ReadSerializable(); - } - - /// - /// Converts a byte array to an object. - /// - /// The byte array to be converted. - /// The type to convert to. - /// The converted object. - public static ISerializable AsSerializable(this ReadOnlyMemory value, Type type) - { - if (!typeof(ISerializable).GetTypeInfo().IsAssignableFrom(type)) - throw new InvalidCastException(); - ISerializable serializable = (ISerializable)Activator.CreateInstance(type); - MemoryReader reader = new(value); - serializable.Deserialize(ref reader); - return serializable; - } - /// /// Converts a byte array to an array. /// @@ -124,77 +92,6 @@ public static byte[] DecompressLz4(this ReadOnlySpan data, int maxOutput) return result; } - /// - /// Gets the size of variable-length of the data. - /// - /// The length of the data. - /// The size of variable-length of the data. - public static int GetVarSize(int value) - { - if (value < 0xFD) - return sizeof(byte); - else if (value <= 0xFFFF) - return sizeof(byte) + sizeof(ushort); - else - return sizeof(byte) + sizeof(uint); - } - - /// - /// Gets the size of the specified array encoded in variable-length encoding. - /// - /// The type of the array element. - /// The specified array. - /// The size of the array. - public static int GetVarSize(this IReadOnlyCollection value) - { - int value_size; - Type t = typeof(T); - if (typeof(ISerializable).IsAssignableFrom(t)) - { - value_size = value.OfType().Sum(p => p.Size); - } - else if (t.GetTypeInfo().IsEnum) - { - int element_size; - Type u = t.GetTypeInfo().GetEnumUnderlyingType(); - if (u == typeof(sbyte) || u == typeof(byte)) - element_size = 1; - else if (u == typeof(short) || u == typeof(ushort)) - element_size = 2; - else if (u == typeof(int) || u == typeof(uint)) - element_size = 4; - else //if (u == typeof(long) || u == typeof(ulong)) - element_size = 8; - value_size = value.Count * element_size; - } - else - { - value_size = value.Count * Marshal.SizeOf(); - } - return GetVarSize(value.Count) + value_size; - } - - /// - /// Gets the size of the specified array encoded in variable-length encoding. - /// - /// The specified array. - /// The size of the array. - public static int GetVarSize(this ReadOnlyMemory value) - { - return GetVarSize(value.Length) + value.Length; - } - - /// - /// Gets the size of the specified encoded in variable-length encoding. - /// - /// The specified . - /// The size of the . - public static int GetVarSize(this string value) - { - int size = Utility.StrictUTF8.GetByteCount(value); - return GetVarSize(size) + size; - } - /// /// Reads a byte array of the specified size from a . /// @@ -315,21 +212,6 @@ public static byte[] ToArray(this ISerializable value) return ms.ToArray(); } - /// - /// Converts an array to a byte array. - /// - /// The type of the array element. - /// The array to be converted. - /// The converted byte array. - public static byte[] ToByteArray(this IReadOnlyCollection value) where T : ISerializable - { - using MemoryStream ms = new(); - using BinaryWriter writer = new(ms, Utility.StrictUTF8, true); - writer.Write(value); - writer.Flush(); - return ms.ToArray(); - } - /// /// Writes an object into a . /// diff --git a/src/Neo/Ledger/Blockchain.cs b/src/Neo/Ledger/Blockchain.cs index b35dd069e3..f675226303 100644 --- a/src/Neo/Ledger/Blockchain.cs +++ b/src/Neo/Ledger/Blockchain.cs @@ -12,7 +12,6 @@ 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; @@ -22,12 +21,11 @@ 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; +using System.Runtime.CompilerServices; namespace Neo.Ledger { @@ -421,7 +419,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 +437,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 +451,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 +470,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); + InvokeCommitting(system, block, snapshot, all_application_executed); snapshot.Commit(); } - _ = InvokeCommittedAsync(system, block); + InvokeCommitted(system, block); system.MemPool.UpdatePoolForBlockPersisted(block, system.StoreView); extensibleWitnessWhiteList = null; block_cache.Remove(block.PrevHash); @@ -484,39 +482,40 @@ private void Persist(Block block) Debug.Assert(header.Index == block.Index); } - internal static async Task InvokeCommittingAsync(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void InvokeCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) { - await InvokeHandlersAsync(Committing?.GetInvocationList(), h => ((CommittingHandler)h)(system, block, snapshot, applicationExecutedList)); + InvokeHandlers(Committing?.GetInvocationList(), h => ((CommittingHandler)h)(system, block, snapshot, applicationExecutedList)); } - internal static async Task InvokeCommittedAsync(NeoSystem system, Block block) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void InvokeCommitted(NeoSystem system, Block block) { - await InvokeHandlersAsync(Committed?.GetInvocationList(), h => ((CommittedHandler)h)(system, block)); + InvokeHandlers(Committed?.GetInvocationList(), h => ((CommittedHandler)h)(system, block)); } - private static async Task InvokeHandlersAsync(Delegate[] handlers, Action handlerAction) + private static void InvokeHandlers(Delegate[] handlers, Action handlerAction) { if (handlers == null) return; - var exceptions = new ConcurrentBag(); - var tasks = handlers.Select(handler => Task.Run(() => + foreach (var handler in handlers) { try { // skip stopped plugin. if (handler.Target is Plugin { IsStopped: true }) { - return; + continue; } handlerAction(handler); } catch (Exception ex) when (handler.Target is Plugin plugin) { + Utility.Log(nameof(plugin), LogLevel.Error, ex); switch (plugin.ExceptionPolicy) { case UnhandledExceptionPolicy.StopNode: - exceptions.Add(ex); throw; case UnhandledExceptionPolicy.StopPlugin: //Stop plugin on exception @@ -526,20 +525,11 @@ private static async Task InvokeHandlersAsync(Delegate[] handlers, Action throw e); + } } /// diff --git a/src/Neo/Ledger/HeaderCache.cs b/src/Neo/Ledger/HeaderCache.cs index ac2208011f..6cc8acf6a7 100644 --- a/src/Neo/Ledger/HeaderCache.cs +++ b/src/Neo/Ledger/HeaderCache.cs @@ -55,12 +55,26 @@ public Header this[uint index] /// /// Gets the number of elements in the cache. /// - public int Count => headers.Count; + public int Count + { + get + { + readerWriterLock.EnterReadLock(); + try + { + return headers.Count; + } + finally + { + readerWriterLock.ExitReadLock(); + } + } + } /// /// Indicates whether the cache is full. /// - public bool Full => headers.Count >= 10000; + public bool Full => Count >= 10000; /// /// Gets the last in the cache. Or if the cache is empty. diff --git a/src/Neo/Ledger/MemoryPool.cs b/src/Neo/Ledger/MemoryPool.cs index 23eb711e87..badef9d3a0 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, [NotNullWhen(true)] 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,14 +443,22 @@ 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; + _txRwLock.EnterWriteLock(); + try + { + if (!_unverifiedTransactions.TryGetValue(hash, out item)) + return false; - _unverifiedTransactions.Remove(hash); - _unverifiedSortedTransactions.Remove(item); - return true; + _unverifiedTransactions.Remove(hash); + _unverifiedSortedTransactions.Remove(item); + return true; + } + finally + { + _txRwLock.ExitWriteLock(); + } } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -653,5 +667,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/Neo.csproj b/src/Neo/Neo.csproj index 835224eddd..7c6478c33e 100644 --- a/src/Neo/Neo.csproj +++ b/src/Neo/Neo.csproj @@ -9,12 +9,12 @@ - + - + @@ -30,6 +30,7 @@ + diff --git a/src/Neo/NeoSystem.cs b/src/Neo/NeoSystem.cs index b752f16712..99be387712 100644 --- a/src/Neo/NeoSystem.cs +++ b/src/Neo/NeoSystem.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Akka.Actor; +using Neo.Extensions; using Neo.IO.Caching; using Neo.Ledger; using Neo.Network.P2P; @@ -275,12 +276,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/Message.cs b/src/Neo/Network/P2P/Message.cs index 9d3c63a85b..96c79f2b10 100644 --- a/src/Neo/Network/P2P/Message.cs +++ b/src/Neo/Network/P2P/Message.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Akka.IO; +using Neo.Extensions; using Neo.IO; using Neo.IO.Caching; using System; diff --git a/src/Neo/Network/P2P/Payloads/AddrPayload.cs b/src/Neo/Network/P2P/Payloads/AddrPayload.cs index ebdceb65ec..90958a38ef 100644 --- a/src/Neo/Network/P2P/Payloads/AddrPayload.cs +++ b/src/Neo/Network/P2P/Payloads/AddrPayload.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 System; using System.IO; diff --git a/src/Neo/Network/P2P/Payloads/Block.cs b/src/Neo/Network/P2P/Payloads/Block.cs index 3c727b9886..ab28c6c363 100644 --- a/src/Neo/Network/P2P/Payloads/Block.cs +++ b/src/Neo/Network/P2P/Payloads/Block.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography; +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.Ledger; diff --git a/src/Neo/Network/P2P/Payloads/Conditions/AndCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/AndCondition.cs index e395b10d5d..5fc1ba4381 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/AndCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/AndCondition.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.Json; using Neo.SmartContract; @@ -66,7 +67,7 @@ public override JObject ToJson() return json; } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(new VM.Types.Array(referenceCounter, Expressions.Select(p => p.ToStackItem(referenceCounter)))); diff --git a/src/Neo/Network/P2P/Payloads/Conditions/BooleanCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/BooleanCondition.cs index e7609fcbaf..011a7466fe 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/BooleanCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/BooleanCondition.cs @@ -55,7 +55,7 @@ public override JObject ToJson() return json; } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(Expression); diff --git a/src/Neo/Network/P2P/Payloads/Conditions/CalledByContractCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/CalledByContractCondition.cs index f853743b72..b27c66931d 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/CalledByContractCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/CalledByContractCondition.cs @@ -55,7 +55,7 @@ public override JObject ToJson() return json; } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(Hash.ToArray()); diff --git a/src/Neo/Network/P2P/Payloads/Conditions/CalledByGroupCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/CalledByGroupCondition.cs index 82dab60fcf..652eabd105 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/CalledByGroupCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/CalledByGroupCondition.cs @@ -39,7 +39,7 @@ protected override void DeserializeWithoutType(ref MemoryReader reader, int maxN public override bool Match(ApplicationEngine engine) { engine.ValidateCallFlags(CallFlags.ReadStates); - ContractState contract = NativeContract.ContractManagement.GetContract(engine.Snapshot, engine.CallingScriptHash); + ContractState contract = NativeContract.ContractManagement.GetContract(engine.SnapshotCache, engine.CallingScriptHash); return contract is not null && contract.Manifest.Groups.Any(p => p.PubKey.Equals(Group)); } @@ -60,7 +60,7 @@ public override JObject ToJson() return json; } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(Group.ToArray()); diff --git a/src/Neo/Network/P2P/Payloads/Conditions/GroupCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/GroupCondition.cs index ee937aff8b..be26fc0863 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/GroupCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/GroupCondition.cs @@ -39,7 +39,7 @@ protected override void DeserializeWithoutType(ref MemoryReader reader, int maxN public override bool Match(ApplicationEngine engine) { engine.ValidateCallFlags(CallFlags.ReadStates); - ContractState contract = NativeContract.ContractManagement.GetContract(engine.Snapshot, engine.CurrentScriptHash); + ContractState contract = NativeContract.ContractManagement.GetContract(engine.SnapshotCache, engine.CurrentScriptHash); return contract is not null && contract.Manifest.Groups.Any(p => p.PubKey.Equals(Group)); } @@ -60,7 +60,7 @@ public override JObject ToJson() return json; } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(Group.ToArray()); diff --git a/src/Neo/Network/P2P/Payloads/Conditions/NotCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/NotCondition.cs index 83ecd4d973..74ab01d21b 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/NotCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/NotCondition.cs @@ -61,7 +61,7 @@ public override JObject ToJson() return json; } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(Expression.ToStackItem(referenceCounter)); diff --git a/src/Neo/Network/P2P/Payloads/Conditions/OrCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/OrCondition.cs index b06fc922a8..c28d3dbb94 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/OrCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/OrCondition.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.Json; using Neo.SmartContract; @@ -66,7 +67,7 @@ public override JObject ToJson() return json; } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(new VM.Types.Array(referenceCounter, Expressions.Select(p => p.ToStackItem(referenceCounter)))); diff --git a/src/Neo/Network/P2P/Payloads/Conditions/ScriptHashCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/ScriptHashCondition.cs index 9199e1a9a2..5d648efbc0 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/ScriptHashCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/ScriptHashCondition.cs @@ -55,7 +55,7 @@ public override JObject ToJson() return json; } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { var result = (VM.Types.Array)base.ToStackItem(referenceCounter); result.Add(Hash.ToArray()); diff --git a/src/Neo/Network/P2P/Payloads/Conditions/WitnessCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/WitnessCondition.cs index 7738a6d4a8..dd955fce42 100644 --- a/src/Neo/Network/P2P/Payloads/Conditions/WitnessCondition.cs +++ b/src/Neo/Network/P2P/Payloads/Conditions/WitnessCondition.cs @@ -127,7 +127,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) throw new NotSupportedException(); } - public virtual StackItem ToStackItem(ReferenceCounter referenceCounter) + public virtual StackItem ToStackItem(IReferenceCounter referenceCounter) { return new VM.Types.Array(referenceCounter, new StackItem[] { (byte)Type }); } diff --git a/src/Neo/Network/P2P/Payloads/ExtensiblePayload.cs b/src/Neo/Network/P2P/Payloads/ExtensiblePayload.cs index 306f6327ab..6e2b94a17d 100644 --- a/src/Neo/Network/P2P/Payloads/ExtensiblePayload.cs +++ b/src/Neo/Network/P2P/Payloads/ExtensiblePayload.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.Persistence; using Neo.SmartContract; diff --git a/src/Neo/Network/P2P/Payloads/FilterAddPayload.cs b/src/Neo/Network/P2P/Payloads/FilterAddPayload.cs index ff4aa1287b..4f4e9df152 100644 --- a/src/Neo/Network/P2P/Payloads/FilterAddPayload.cs +++ b/src/Neo/Network/P2P/Payloads/FilterAddPayload.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography; +using Neo.Extensions; using Neo.IO; using System; using System.IO; diff --git a/src/Neo/Network/P2P/Payloads/FilterLoadPayload.cs b/src/Neo/Network/P2P/Payloads/FilterLoadPayload.cs index 2608e0e7b0..f0eae27278 100644 --- a/src/Neo/Network/P2P/Payloads/FilterLoadPayload.cs +++ b/src/Neo/Network/P2P/Payloads/FilterLoadPayload.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography; +using Neo.Extensions; using Neo.IO; using System; using System.IO; diff --git a/src/Neo/Network/P2P/Payloads/HeadersPayload.cs b/src/Neo/Network/P2P/Payloads/HeadersPayload.cs index 152ba32857..e5800d4544 100644 --- a/src/Neo/Network/P2P/Payloads/HeadersPayload.cs +++ b/src/Neo/Network/P2P/Payloads/HeadersPayload.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 System; using System.IO; diff --git a/src/Neo/Network/P2P/Payloads/InvPayload.cs b/src/Neo/Network/P2P/Payloads/InvPayload.cs index aa4d340d99..17625f6dc4 100644 --- a/src/Neo/Network/P2P/Payloads/InvPayload.cs +++ b/src/Neo/Network/P2P/Payloads/InvPayload.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 System; using System.Collections.Generic; diff --git a/src/Neo/Network/P2P/Payloads/MerkleBlockPayload.cs b/src/Neo/Network/P2P/Payloads/MerkleBlockPayload.cs index aebb17d5d9..97f9ed0f27 100644 --- a/src/Neo/Network/P2P/Payloads/MerkleBlockPayload.cs +++ b/src/Neo/Network/P2P/Payloads/MerkleBlockPayload.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography; +using Neo.Extensions; using Neo.IO; using System; using System.Collections; diff --git a/src/Neo/Network/P2P/Payloads/NetworkAddressWithTime.cs b/src/Neo/Network/P2P/Payloads/NetworkAddressWithTime.cs index d3cfd15f66..0b7e0acc37 100644 --- a/src/Neo/Network/P2P/Payloads/NetworkAddressWithTime.cs +++ b/src/Neo/Network/P2P/Payloads/NetworkAddressWithTime.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.Network.P2P.Capabilities; using System; @@ -68,7 +69,7 @@ void ISerializable.Deserialize(ref MemoryReader reader) // Address ReadOnlyMemory data = reader.ReadMemory(16); - Address = new IPAddress(data.Span).Unmap(); + Address = new IPAddress(data.Span).UnMap(); // Capabilities Capabilities = new NodeCapability[reader.ReadVarInt(VersionPayload.MaxCapabilities)]; diff --git a/src/Neo/Network/P2P/Payloads/OracleResponse.cs b/src/Neo/Network/P2P/Payloads/OracleResponse.cs index 2770f556a8..bf3aa70cf4 100644 --- a/src/Neo/Network/P2P/Payloads/OracleResponse.cs +++ b/src/Neo/Network/P2P/Payloads/OracleResponse.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.Json; using Neo.Persistence; diff --git a/src/Neo/Network/P2P/Payloads/PingPayload.cs b/src/Neo/Network/P2P/Payloads/PingPayload.cs index f6296a7713..6ac3d2d94e 100644 --- a/src/Neo/Network/P2P/Payloads/PingPayload.cs +++ b/src/Neo/Network/P2P/Payloads/PingPayload.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 System; using System.IO; diff --git a/src/Neo/Network/P2P/Payloads/Signer.cs b/src/Neo/Network/P2P/Payloads/Signer.cs index 79065408f4..40496dfe74 100644 --- a/src/Neo/Network/P2P/Payloads/Signer.cs +++ b/src/Neo/Network/P2P/Payloads/Signer.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.Network.P2P.Payloads.Conditions; @@ -190,7 +191,7 @@ void IInteroperable.FromStackItem(VM.Types.StackItem stackItem) throw new NotSupportedException(); } - VM.Types.StackItem IInteroperable.ToStackItem(ReferenceCounter referenceCounter) + VM.Types.StackItem IInteroperable.ToStackItem(IReferenceCounter referenceCounter) { return new VM.Types.Array(referenceCounter, [ diff --git a/src/Neo/Network/P2P/Payloads/Transaction.cs b/src/Neo/Network/P2P/Payloads/Transaction.cs index b922674d91..b5139f1c7c 100644 --- a/src/Neo/Network/P2P/Payloads/Transaction.cs +++ b/src/Neo/Network/P2P/Payloads/Transaction.cs @@ -11,6 +11,7 @@ using Neo.Cryptography; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.Ledger; @@ -458,7 +459,7 @@ public virtual VerifyResult VerifyStateIndependent(ProtocolSettings settings) return VerifyResult.Succeed; } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { if (_signers == null || _signers.Length == 0) throw new ArgumentException("Sender is not specified in the transaction."); return new Array(referenceCounter, new StackItem[] diff --git a/src/Neo/Network/P2P/Payloads/VersionPayload.cs b/src/Neo/Network/P2P/Payloads/VersionPayload.cs index 8cec6278e7..f8fe74a856 100644 --- a/src/Neo/Network/P2P/Payloads/VersionPayload.cs +++ b/src/Neo/Network/P2P/Payloads/VersionPayload.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.Network.P2P.Capabilities; using System; diff --git a/src/Neo/Network/P2P/Payloads/Witness.cs b/src/Neo/Network/P2P/Payloads/Witness.cs index 34932cc7fb..b3da47d2b7 100644 --- a/src/Neo/Network/P2P/Payloads/Witness.cs +++ b/src/Neo/Network/P2P/Payloads/Witness.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.Json; using Neo.SmartContract; diff --git a/src/Neo/Network/P2P/Payloads/WitnessRule.cs b/src/Neo/Network/P2P/Payloads/WitnessRule.cs index 3bac09e7f5..8552f0798e 100644 --- a/src/Neo/Network/P2P/Payloads/WitnessRule.cs +++ b/src/Neo/Network/P2P/Payloads/WitnessRule.cs @@ -88,7 +88,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) throw new NotSupportedException(); } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new VM.Types.Array(referenceCounter, new StackItem[] { diff --git a/src/Neo/Network/P2P/Peer.cs b/src/Neo/Network/P2P/Peer.cs index 767d747b1d..bf6b23901f 100644 --- a/src/Neo/Network/P2P/Peer.cs +++ b/src/Neo/Network/P2P/Peer.cs @@ -11,6 +11,7 @@ using Akka.Actor; using Akka.IO; +using Neo.Extensions; using Neo.IO; using System; using System.Buffers.Binary; @@ -138,14 +139,14 @@ protected virtual int ConnectingMax static Peer() { - localAddresses.UnionWith(NetworkInterface.GetAllNetworkInterfaces().SelectMany(p => p.GetIPProperties().UnicastAddresses).Select(p => p.Address.Unmap())); + localAddresses.UnionWith(NetworkInterface.GetAllNetworkInterfaces().SelectMany(p => p.GetIPProperties().UnicastAddresses).Select(p => p.Address.UnMap())); } /// /// Tries to add a set of peers to the immutable ImmutableHashSet of UnconnectedPeers. /// /// Peers that the method will try to add (union) to (with) UnconnectedPeers. - protected void AddPeers(IEnumerable peers) + protected internal void AddPeers(IEnumerable peers) { if (UnconnectedPeers.Count < UnconnectedMax) { @@ -163,7 +164,7 @@ protected void AddPeers(IEnumerable peers) /// Indicates whether the remote node is trusted. A trusted node will always be connected. protected void ConnectToPeer(IPEndPoint endPoint, bool isTrusted = false) { - endPoint = endPoint.Unmap(); + endPoint = endPoint.UnMap(); // If the address is the same, the ListenerTcpPort should be different, otherwise, return if (endPoint.Port == ListenerTcpPort && localAddresses.Contains(endPoint.Address)) return; @@ -210,7 +211,7 @@ protected override void OnReceive(object message) ConnectToPeer(connect.EndPoint, connect.IsTrusted); break; case Tcp.Connected connected: - OnTcpConnected(((IPEndPoint)connected.RemoteAddress).Unmap(), ((IPEndPoint)connected.LocalAddress).Unmap()); + OnTcpConnected(((IPEndPoint)connected.RemoteAddress).UnMap(), ((IPEndPoint)connected.LocalAddress).UnMap()); break; case Tcp.Bound _: tcp_listener = Sender; @@ -302,7 +303,7 @@ private void OnTcpCommandFailed(Tcp.Command cmd) switch (cmd) { case Tcp.Connect connect: - ImmutableInterlocked.Update(ref ConnectingPeers, p => p.Remove(((IPEndPoint)connect.RemoteAddress).Unmap())); + ImmutableInterlocked.Update(ref ConnectingPeers, p => p.Remove(((IPEndPoint)connect.RemoteAddress).UnMap())); break; } } 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/Network/P2P/TaskManager.cs b/src/Neo/Network/P2P/TaskManager.cs index e9c12e58a4..c5708df923 100644 --- a/src/Neo/Network/P2P/TaskManager.cs +++ b/src/Neo/Network/P2P/TaskManager.cs @@ -12,6 +12,7 @@ using Akka.Actor; using Akka.Configuration; using Akka.IO; +using Neo.Extensions; using Neo.IO.Actors; using Neo.IO.Caching; using Neo.Ledger; 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/Persistence/IReadOnlyStore.cs b/src/Neo/Persistence/IReadOnlyStore.cs index 4b6c3fe0ec..e52ccbb7c3 100644 --- a/src/Neo/Persistence/IReadOnlyStore.cs +++ b/src/Neo/Persistence/IReadOnlyStore.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using System; using System.Collections.Generic; namespace Neo.Persistence @@ -31,8 +32,17 @@ public interface IReadOnlyStore /// /// The key of the entry. /// The data of the entry. Or if it doesn't exist. + /// . Obsolete it later for avoiding complier warning. byte[] TryGet(byte[] key); + /// + /// Reads a specified entry from the database. + /// + /// The key of the entry. + /// The data of the entry. + /// if the entry exists; otherwise, . + bool TryGet(byte[] key, out byte[] value); + /// /// Determines whether the database contains the specified entry. /// diff --git a/src/Neo/Persistence/MemorySnapshot.cs b/src/Neo/Persistence/MemorySnapshot.cs index 096431552c..b3d06798d4 100644 --- a/src/Neo/Persistence/MemorySnapshot.cs +++ b/src/Neo/Persistence/MemorySnapshot.cs @@ -69,6 +69,11 @@ public byte[] TryGet(byte[] key) return value?[..]; } + public bool TryGet(byte[] key, out byte[] value) + { + return immutableData.TryGetValue(key, out value); + } + public bool Contains(byte[] key) { return immutableData.ContainsKey(key); diff --git a/src/Neo/Persistence/MemoryStore.cs b/src/Neo/Persistence/MemoryStore.cs index 191b89ea4f..378d5374f1 100644 --- a/src/Neo/Persistence/MemoryStore.cs +++ b/src/Neo/Persistence/MemoryStore.cs @@ -66,6 +66,12 @@ public byte[] TryGet(byte[] key) return value[..]; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryGet(byte[] key, out byte[] value) + { + return _innerData.TryGetValue(key, out value); + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Contains(byte[] key) { diff --git a/src/Neo/Plugins/Plugin.cs b/src/Neo/Plugins/Plugin.cs index 9feee25d57..7b85d85d92 100644 --- a/src/Neo/Plugins/Plugin.cs +++ b/src/Neo/Plugins/Plugin.cs @@ -70,7 +70,7 @@ public abstract class Plugin : IDisposable /// /// If the plugin should be stopped when an exception is thrown. - /// Default is . + /// Default is StopNode. /// protected internal virtual UnhandledExceptionPolicy ExceptionPolicy { get; init; } = UnhandledExceptionPolicy.StopNode; @@ -247,33 +247,46 @@ protected internal virtual void OnSystemLoaded(NeoSystem system) /// if the is handled by a plugin; otherwise, . public static bool SendMessage(object message) { + foreach (var plugin in Plugins) + { + if (plugin.IsStopped) + { + continue; + } - return Plugins.Any(plugin => + bool result; + try { - try - { - return !plugin.IsStopped && - plugin.OnMessage(message); - } - catch (Exception ex) + result = plugin.OnMessage(message); + } + catch (Exception ex) + { + Utility.Log(nameof(Plugin), LogLevel.Error, ex); + + switch (plugin.ExceptionPolicy) { - 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; + 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."); } + + continue; // Skip to the next plugin if an exception is handled + } + + if (result) + { + return true; } - ); + } + + return false; } + } } diff --git a/src/Neo/Plugins/PluginSettings.cs b/src/Neo/Plugins/PluginSettings.cs index af33e44eea..66549a390e 100644 --- a/src/Neo/Plugins/PluginSettings.cs +++ b/src/Neo/Plugins/PluginSettings.cs @@ -22,7 +22,7 @@ public UnhandledExceptionPolicy ExceptionPolicy get { var policyString = section.GetValue(nameof(UnhandledExceptionPolicy), nameof(UnhandledExceptionPolicy.StopNode)); - if (Enum.TryParse(policyString, out UnhandledExceptionPolicy policy)) + if (Enum.TryParse(policyString, true, out UnhandledExceptionPolicy policy)) { return policy; } diff --git a/src/Neo/Plugins/UnhandledExceptionPolicy.cs b/src/Neo/Plugins/UnhandledExceptionPolicy.cs index 035e173aa3..0422f2c5e7 100644 --- a/src/Neo/Plugins/UnhandledExceptionPolicy.cs +++ b/src/Neo/Plugins/UnhandledExceptionPolicy.cs @@ -11,7 +11,7 @@ namespace Neo.Plugins { - public enum UnhandledExceptionPolicy + public enum UnhandledExceptionPolicy : byte { Ignore = 0, StopPlugin = 1, diff --git a/src/Neo/SmartContract/ApplicationEngine.Contract.cs b/src/Neo/SmartContract/ApplicationEngine.Contract.cs index e5c1c762a7..889ec0e07d 100644 --- a/src/Neo/SmartContract/ApplicationEngine.Contract.cs +++ b/src/Neo/SmartContract/ApplicationEngine.Contract.cs @@ -76,7 +76,7 @@ protected internal void CallContract(UInt160 contractHash, string method, CallFl if ((callFlags & ~CallFlags.All) != 0) throw new ArgumentOutOfRangeException(nameof(callFlags)); - ContractState contract = NativeContract.ContractManagement.GetContract(Snapshot, contractHash); + ContractState contract = NativeContract.ContractManagement.GetContract(SnapshotCache, contractHash); if (contract is null) throw new InvalidOperationException($"Called Contract Does Not Exist: {contractHash}.{method}"); ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod(method, args.Count); if (md is null) throw new InvalidOperationException($"Method \"{method}\" with {args.Count} parameter(s) doesn't exist in the contract {contractHash}."); @@ -96,7 +96,7 @@ protected internal void CallNativeContract(byte version) NativeContract contract = NativeContract.GetContract(CurrentScriptHash); if (contract is null) throw new InvalidOperationException("It is not allowed to use \"System.Contract.CallNative\" directly."); - if (!contract.IsActive(ProtocolSettings, NativeContract.Ledger.CurrentIndex(Snapshot))) + if (!contract.IsActive(ProtocolSettings, NativeContract.Ledger.CurrentIndex(SnapshotCache))) throw new InvalidOperationException($"The native contract {contract.Name} is not active."); contract.Invoke(this, version); } diff --git a/src/Neo/SmartContract/ApplicationEngine.Helper.cs b/src/Neo/SmartContract/ApplicationEngine.Helper.cs new file mode 100644 index 0000000000..5d2976c288 --- /dev/null +++ b/src/Neo/SmartContract/ApplicationEngine.Helper.cs @@ -0,0 +1,56 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ApplicationEngine.Helper.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.SmartContract.Native; +using Neo.VM; +using System; +using System.Linq; +using System.Text; + +namespace Neo.SmartContract +{ + public partial class ApplicationEngine : ExecutionEngine + { + public string GetEngineStackInfoOnFault(bool exceptionStackTrace = true, bool exceptionMessage = true) + { + if (State != VMState.FAULT || FaultException == null) + return ""; + StringBuilder traceback = new(); + if (CallingScriptHash != null) + traceback.AppendLine($"CallingScriptHash={CallingScriptHash}[{NativeContract.ContractManagement.GetContract(SnapshotCache, CallingScriptHash)?.Manifest.Name}]"); + traceback.AppendLine($"CurrentScriptHash={CurrentScriptHash}[{NativeContract.ContractManagement.GetContract(SnapshotCache, CurrentScriptHash)?.Manifest.Name}]"); + traceback.AppendLine($"EntryScriptHash={EntryScriptHash}"); + + foreach (ExecutionContext context in InvocationStack.Reverse()) + { + UInt160 contextScriptHash = context.GetScriptHash(); + string contextContractName = NativeContract.ContractManagement.GetContract(SnapshotCache, contextScriptHash)?.Manifest.Name; + traceback.AppendLine($"\tInstructionPointer={context.InstructionPointer}, OpCode {context.CurrentInstruction?.OpCode}, Script Length={context.Script.Length} {contextScriptHash}[{contextContractName}]"); + } + traceback.Append(GetEngineExceptionInfo(exceptionStackTrace: exceptionStackTrace, exceptionMessage: exceptionMessage)); + + return traceback.ToString(); + } + + public string GetEngineExceptionInfo(bool exceptionStackTrace = true, bool exceptionMessage = true) + { + if (State != VMState.FAULT || FaultException == null) + return ""; + StringBuilder traceback = new(); + Exception baseException = FaultException.GetBaseException(); + if (exceptionStackTrace) + traceback.AppendLine(baseException.StackTrace); + if (exceptionMessage) + traceback.AppendLine(baseException.Message); + return traceback.ToString(); + } + } +} diff --git a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs index e54529f6d1..3771278839 100644 --- a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs +++ b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs @@ -260,8 +260,8 @@ protected internal bool CheckWitnessInternal(UInt160 hash) } else { - OracleRequest request = NativeContract.Oracle.GetRequest(Snapshot, response.Id); - signers = NativeContract.Ledger.GetTransaction(Snapshot, request.OriginalTxid).Signers; + OracleRequest request = NativeContract.Oracle.GetRequest(SnapshotCache, response.Id); + signers = NativeContract.Ledger.GetTransaction(SnapshotCache, request.OriginalTxid).Signers; } Signer signer = signers.FirstOrDefault(p => p.Account.Equals(hash)); if (signer is null) return false; @@ -280,7 +280,7 @@ protected internal bool CheckWitnessInternal(UInt160 hash) ValidateCallFlags(CallFlags.ReadStates); // only for non-Transaction types (Block, etc) - return ScriptContainer.GetScriptHashesForVerifying(Snapshot).Contains(hash); + return ScriptContainer.GetScriptHashesForVerifying(SnapshotCache).Contains(hash); } /// diff --git a/src/Neo/SmartContract/ApplicationEngine.Storage.cs b/src/Neo/SmartContract/ApplicationEngine.Storage.cs index fbd452d6cc..5550835975 100644 --- a/src/Neo/SmartContract/ApplicationEngine.Storage.cs +++ b/src/Neo/SmartContract/ApplicationEngine.Storage.cs @@ -77,7 +77,7 @@ partial class ApplicationEngine /// The storage context for the current contract. protected internal StorageContext GetStorageContext() { - ContractState contract = NativeContract.ContractManagement.GetContract(Snapshot, CurrentScriptHash); + ContractState contract = NativeContract.ContractManagement.GetContract(SnapshotCache, CurrentScriptHash); return new StorageContext { Id = contract.Id, @@ -92,7 +92,7 @@ protected internal StorageContext GetStorageContext() /// The storage context for the current contract. protected internal StorageContext GetReadOnlyContext() { - ContractState contract = NativeContract.ContractManagement.GetContract(Snapshot, CurrentScriptHash); + ContractState contract = NativeContract.ContractManagement.GetContract(SnapshotCache, CurrentScriptHash); return new StorageContext { Id = contract.Id, @@ -126,7 +126,7 @@ protected internal static StorageContext AsReadOnly(StorageContext context) /// The value of the entry. Or if the entry doesn't exist. protected internal ReadOnlyMemory? Get(StorageContext context, byte[] key) { - return Snapshot.TryGet(new StorageKey + return SnapshotCache.TryGet(new StorageKey { Id = context.Id, Key = key @@ -155,7 +155,7 @@ protected internal IIterator Find(StorageContext context, byte[] prefix, FindOpt throw new ArgumentException(null, nameof(options)); byte[] prefix_key = StorageKey.CreateSearchPrefix(context.Id, prefix); SeekDirection direction = options.HasFlag(FindOptions.Backwards) ? SeekDirection.Backward : SeekDirection.Forward; - return new StorageIterator(Snapshot.Find(prefix_key, direction).GetEnumerator(), prefix.Length, options); + return new StorageIterator(SnapshotCache.Find(prefix_key, direction).GetEnumerator(), prefix.Length, options); } /// @@ -177,11 +177,11 @@ protected internal void Put(StorageContext context, byte[] key, byte[] value) Id = context.Id, Key = key }; - StorageItem item = Snapshot.GetAndChange(skey); + StorageItem item = SnapshotCache.GetAndChange(skey); if (item is null) { newDataSize = key.Length + value.Length; - Snapshot.Add(skey, item = new StorageItem()); + SnapshotCache.Add(skey, item = new StorageItem()); } else { @@ -208,7 +208,7 @@ protected internal void Put(StorageContext context, byte[] key, byte[] value) protected internal void Delete(StorageContext context, byte[] key) { if (context.IsReadOnly) throw new ArgumentException(null, nameof(context)); - Snapshot.Delete(new StorageKey + SnapshotCache.Delete(new StorageKey { Id = context.Id, Key = key diff --git a/src/Neo/SmartContract/ApplicationEngine.cs b/src/Neo/SmartContract/ApplicationEngine.cs index fab17a2bb1..7808baae5e 100644 --- a/src/Neo/SmartContract/ApplicationEngine.cs +++ b/src/Neo/SmartContract/ApplicationEngine.cs @@ -22,6 +22,7 @@ using System.Linq; using System.Numerics; using System.Reflection; +using System.Runtime.CompilerServices; using Array = System.Array; using VMArray = Neo.VM.Types.Array; @@ -55,7 +56,7 @@ public partial class ApplicationEngine : ExecutionEngine // In the unit of datoshi, 1 datoshi = 1e-8 GAS, 1 GAS = 1e8 datoshi private readonly long _feeAmount; private Dictionary states; - private readonly DataCache originalSnapshot; + private readonly DataCache originalSnapshotCache; private List notifications; private List disposables; private readonly Dictionary invocationCounter = new(); @@ -95,7 +96,13 @@ public partial class ApplicationEngine : ExecutionEngine /// /// The snapshot used to read or write data. /// - public DataCache Snapshot => CurrentContext?.GetState().Snapshot ?? originalSnapshot; + [Obsolete("This property is deprecated. Use SnapshotCache instead.")] + public DataCache Snapshot => CurrentContext?.GetState().SnapshotCache ?? originalSnapshotCache; + + /// + /// The snapshotcache used to read or write data. + /// + public DataCache SnapshotCache => CurrentContext?.GetState().SnapshotCache ?? originalSnapshotCache; /// /// The block being persisted. This field could be if the is . @@ -164,26 +171,26 @@ public virtual UInt160 CallingScriptHash /// /// The trigger of the execution. /// The container of the script. - /// The snapshot used by the engine during execution. + /// The snapshot used by the engine during execution. /// The block being persisted. It should be if the is . /// The used by the engine. /// The maximum gas, in the unit of datoshi, used in this execution. The execution will fail when the gas is exhausted. /// The diagnostic to be used by the . /// The jump table to be used by the . protected unsafe ApplicationEngine( - TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, + TriggerType trigger, IVerifiable container, DataCache snapshotCache, Block persistingBlock, ProtocolSettings settings, long gas, IDiagnostic diagnostic, JumpTable jumpTable = null) : base(jumpTable ?? DefaultJumpTable) { Trigger = trigger; ScriptContainer = container; - originalSnapshot = snapshot; + originalSnapshotCache = snapshotCache; PersistingBlock = persistingBlock; ProtocolSettings = settings; _feeAmount = gas; Diagnostic = diagnostic; - ExecFeeFactor = snapshot is null || persistingBlock?.Index == 0 ? PolicyContract.DefaultExecFeeFactor : NativeContract.Policy.GetExecFeeFactor(snapshot); - StoragePrice = snapshot is null || persistingBlock?.Index == 0 ? PolicyContract.DefaultStoragePrice : NativeContract.Policy.GetStoragePrice(snapshot); + ExecFeeFactor = snapshotCache is null || persistingBlock?.Index == 0 ? PolicyContract.DefaultExecFeeFactor : NativeContract.Policy.GetExecFeeFactor(snapshotCache); + StoragePrice = snapshotCache is null || persistingBlock?.Index == 0 ? PolicyContract.DefaultStoragePrice : NativeContract.Policy.GetStoragePrice(snapshotCache); nonceData = container is Transaction tx ? tx.Hash.ToArray()[..16] : new byte[16]; if (persistingBlock is not null) { @@ -235,9 +242,7 @@ protected static void OnSysCall(ExecutionEngine engine, Instruction instruction) { if (engine is ApplicationEngine app) { - uint method = instruction.TokenU32; - - app.OnSysCall(services[method]); + app.OnSysCall(GetInteropDescriptor(instruction.TokenU32)); } else { @@ -274,7 +279,7 @@ internal void Throw(Exception ex) private ExecutionContext CallContractInternal(UInt160 contractHash, string method, CallFlags flags, bool hasReturnValue, StackItem[] args) { - ContractState contract = NativeContract.ContractManagement.GetContract(Snapshot, contractHash); + ContractState contract = NativeContract.ContractManagement.GetContract(SnapshotCache, contractHash); if (contract is null) throw new InvalidOperationException($"Called Contract Does Not Exist: {contractHash}"); ContractMethodDescriptor md = contract.Manifest.Abi.GetMethod(method, args.Length); if (md is null) throw new InvalidOperationException($"Method \"{method}\" with {args.Length} parameter(s) doesn't exist in the contract {contractHash}."); @@ -283,7 +288,7 @@ private ExecutionContext CallContractInternal(UInt160 contractHash, string metho private ExecutionContext CallContractInternal(ContractState contract, ContractMethodDescriptor method, CallFlags flags, bool hasReturnValue, IReadOnlyList args) { - if (NativeContract.Policy.IsBlocked(Snapshot, contract.Hash)) + if (NativeContract.Policy.IsBlocked(SnapshotCache, contract.Hash)) throw new InvalidOperationException($"The contract {contract.Hash} has been blocked."); ExecutionContext currentContext = CurrentContext; @@ -296,7 +301,7 @@ private ExecutionContext CallContractInternal(ContractState contract, ContractMe { var executingContract = IsHardforkEnabled(Hardfork.HF_Domovoi) ? state.Contract // use executing contract state to avoid possible contract update/destroy side-effects, ref. https://github.com/neo-project/neo/pull/3290. - : NativeContract.ContractManagement.GetContract(Snapshot, CurrentScriptHash); + : NativeContract.ContractManagement.GetContract(SnapshotCache, CurrentScriptHash); if (executingContract?.CanCall(contract, method.Name) == false) throw new InvalidOperationException($"Cannot Call Method {method.Name} Of Contract {contract.Hash} From Contract {CurrentScriptHash}"); } @@ -352,7 +357,7 @@ internal override void UnloadContext(ExecutionContext context) ExecutionContextState state = context.GetState(); if (UncaughtException is null) { - state.Snapshot?.Commit(); + state.SnapshotCache?.Commit(); if (CurrentContext != null) { ExecutionContextState contextState = CurrentContext.GetState(); @@ -460,7 +465,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.SnapshotCache = SnapshotCache?.CloneCache(); configureState?.Invoke(state); // Load context @@ -635,6 +640,17 @@ private static InteropDescriptor Register(string name, string handler, long fixe return descriptor; } + /// + /// Get Interop Descriptor + /// + /// Method Hash + /// InteropDescriptor + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static InteropDescriptor GetInteropDescriptor(uint methodHash) + { + return services[methodHash]; + } + /// /// Creates a new instance of the class, and use it to run the specified script. /// diff --git a/src/Neo/SmartContract/BinarySerializer.cs b/src/Neo/SmartContract/BinarySerializer.cs index 2bdeced678..b77a998d6e 100644 --- a/src/Neo/SmartContract/BinarySerializer.cs +++ b/src/Neo/SmartContract/BinarySerializer.cs @@ -51,9 +51,9 @@ public ContainerPlaceholder(StackItemType type, int count) /// /// The byte array to parse. /// The limits for the deserialization. - /// The used by the . + /// The used by the . /// The deserialized . - public static StackItem Deserialize(ReadOnlyMemory data, ExecutionEngineLimits limits, ReferenceCounter referenceCounter = null) + public static StackItem Deserialize(ReadOnlyMemory data, ExecutionEngineLimits limits, IReferenceCounter referenceCounter = null) { MemoryReader reader = new(data); return Deserialize(ref reader, (uint)Math.Min(data.Length, limits.MaxItemSize), limits.MaxStackSize, referenceCounter); @@ -64,9 +64,9 @@ public static StackItem Deserialize(ReadOnlyMemory data, ExecutionEngineLi /// /// The for reading data. /// The limits for the deserialization. - /// The used by the . + /// The used by the . /// The deserialized . - public static StackItem Deserialize(ref MemoryReader reader, ExecutionEngineLimits limits, ReferenceCounter referenceCounter = null) + public static StackItem Deserialize(ref MemoryReader reader, ExecutionEngineLimits limits, IReferenceCounter referenceCounter = null) { return Deserialize(ref reader, limits.MaxItemSize, limits.MaxStackSize, referenceCounter); } @@ -77,9 +77,9 @@ public static StackItem Deserialize(ref MemoryReader reader, ExecutionEngineLimi /// The for reading data. /// The maximum size of the result. /// The max of items to serialize - /// The used by the . + /// The used by the . /// The deserialized . - public static StackItem Deserialize(ref MemoryReader reader, uint maxSize, uint maxItems, ReferenceCounter referenceCounter = null) + public static StackItem Deserialize(ref MemoryReader reader, uint maxSize, uint maxItems, IReferenceCounter referenceCounter = null) { Stack deserialized = new(); int undeserialized = 1; diff --git a/src/Neo/SmartContract/ContractParameter.cs b/src/Neo/SmartContract/ContractParameter.cs index 4abc7aa985..f1b12c6c0a 100644 --- a/src/Neo/SmartContract/ContractParameter.cs +++ b/src/Neo/SmartContract/ContractParameter.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.Json; using System; using System.Collections.Generic; diff --git a/src/Neo/SmartContract/ContractParametersContext.cs b/src/Neo/SmartContract/ContractParametersContext.cs index e8bf3ceec9..c4129f0ad1 100644 --- a/src/Neo/SmartContract/ContractParametersContext.cs +++ b/src/Neo/SmartContract/ContractParametersContext.cs @@ -73,7 +73,18 @@ public JObject ToJson() /// /// The snapshot used to read data. /// - public readonly DataCache Snapshot; + [Obsolete("Use SnapshotCache instead")] + public DataCache Snapshot => SnapshotCache; + + /// + /// The snapshotcache used to read data. + /// + public readonly DataCache SnapshotCache; + + // /// + // /// The snapshot used to read data. + // /// + // public readonly DataCache Snapshot; /// /// The magic number of the network. @@ -99,18 +110,18 @@ public bool Completed /// /// Gets the script hashes to be verified for the . /// - public IReadOnlyList ScriptHashes => _ScriptHashes ??= Verifiable.GetScriptHashesForVerifying(Snapshot); + public IReadOnlyList ScriptHashes => _ScriptHashes ??= Verifiable.GetScriptHashesForVerifying(SnapshotCache); /// /// Initializes a new instance of the class. /// - /// The snapshot used to read data. + /// The snapshot used to read data. /// The to add witnesses. /// The magic number of the network. - public ContractParametersContext(DataCache snapshot, IVerifiable verifiable, uint network) + public ContractParametersContext(DataCache snapshotCache, IVerifiable verifiable, uint network) { Verifiable = verifiable; - Snapshot = snapshot; + SnapshotCache = snapshotCache; ContextItems = new Dictionary(); Network = network; } diff --git a/src/Neo/SmartContract/ContractState.cs b/src/Neo/SmartContract/ContractState.cs index 5b413c41a4..cd065cb8c3 100644 --- a/src/Neo/SmartContract/ContractState.cs +++ b/src/Neo/SmartContract/ContractState.cs @@ -119,7 +119,7 @@ public JObject ToJson() }; } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Array(referenceCounter, new StackItem[] { Id, (int)UpdateCounter, Hash.ToArray(), Nef.ToArray(), Manifest.ToStackItem(referenceCounter) }); } diff --git a/src/Neo/SmartContract/ExecutionContextState.cs b/src/Neo/SmartContract/ExecutionContextState.cs index 8b61ff1197..993015df13 100644 --- a/src/Neo/SmartContract/ExecutionContextState.cs +++ b/src/Neo/SmartContract/ExecutionContextState.cs @@ -11,6 +11,7 @@ using Neo.Persistence; using Neo.VM; +using System; namespace Neo.SmartContract { @@ -44,7 +45,10 @@ public class ExecutionContextState /// public CallFlags CallFlags { get; set; } = CallFlags.All; - public DataCache Snapshot { get; set; } + [Obsolete("Use SnapshotCache instead")] + public DataCache Snapshot => SnapshotCache; + + public DataCache SnapshotCache { get; set; } public int NotificationCount { get; set; } 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/IInteroperable.cs b/src/Neo/SmartContract/IInteroperable.cs index 2810d40d01..f177b3f76f 100644 --- a/src/Neo/SmartContract/IInteroperable.cs +++ b/src/Neo/SmartContract/IInteroperable.cs @@ -29,9 +29,9 @@ public interface IInteroperable /// /// Convert the current object to a . /// - /// The used by the . + /// The used by the . /// The converted . - StackItem ToStackItem(ReferenceCounter referenceCounter); + StackItem ToStackItem(IReferenceCounter referenceCounter); public IInteroperable Clone() { diff --git a/src/Neo/SmartContract/Iterators/IIterator.cs b/src/Neo/SmartContract/Iterators/IIterator.cs index 78c42f1abd..4f06182fe1 100644 --- a/src/Neo/SmartContract/Iterators/IIterator.cs +++ b/src/Neo/SmartContract/Iterators/IIterator.cs @@ -30,6 +30,6 @@ public interface IIterator : IDisposable /// Gets the element in the collection at the current position of the iterator. /// /// The element in the collection at the current position of the iterator. - StackItem Value(ReferenceCounter referenceCounter); + StackItem Value(IReferenceCounter referenceCounter); } } diff --git a/src/Neo/SmartContract/Iterators/StorageIterator.cs b/src/Neo/SmartContract/Iterators/StorageIterator.cs index 397333ad7c..b66998c9dc 100644 --- a/src/Neo/SmartContract/Iterators/StorageIterator.cs +++ b/src/Neo/SmartContract/Iterators/StorageIterator.cs @@ -39,7 +39,7 @@ public bool Next() return enumerator.MoveNext(); } - public StackItem Value(ReferenceCounter referenceCounter) + public StackItem Value(IReferenceCounter referenceCounter) { ReadOnlyMemory key = enumerator.Current.Key.Key; ReadOnlyMemory value = enumerator.Current.Value.Value; diff --git a/src/Neo/SmartContract/JsonSerializer.cs b/src/Neo/SmartContract/JsonSerializer.cs index 4d77104a6e..d2055be7d8 100644 --- a/src/Neo/SmartContract/JsonSerializer.cs +++ b/src/Neo/SmartContract/JsonSerializer.cs @@ -163,15 +163,15 @@ public static byte[] SerializeToByteArray(StackItem item, uint maxSize) /// The used. /// The to deserialize. /// The limits for the deserialization. - /// The used by the . + /// The used by the . /// The deserialized . - public static StackItem Deserialize(ApplicationEngine engine, JToken json, ExecutionEngineLimits limits, ReferenceCounter referenceCounter = null) + public static StackItem Deserialize(ApplicationEngine engine, JToken json, ExecutionEngineLimits limits, IReferenceCounter referenceCounter = null) { uint maxStackSize = limits.MaxStackSize; return Deserialize(engine, json, ref maxStackSize, referenceCounter); } - private static StackItem Deserialize(ApplicationEngine engine, JToken json, ref uint maxStackSize, ReferenceCounter referenceCounter) + private static StackItem Deserialize(ApplicationEngine engine, JToken json, ref uint maxStackSize, IReferenceCounter referenceCounter) { if (maxStackSize-- == 0) throw new FormatException(); switch (json) diff --git a/src/Neo/SmartContract/Manifest/ContractAbi.cs b/src/Neo/SmartContract/Manifest/ContractAbi.cs index 3660d1a99e..4517ffeed2 100644 --- a/src/Neo/SmartContract/Manifest/ContractAbi.cs +++ b/src/Neo/SmartContract/Manifest/ContractAbi.cs @@ -44,7 +44,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) Events = ((Array)@struct[1]).Select(p => p.ToInteroperable()).ToArray(); } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { @@ -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/ContractEventDescriptor.cs b/src/Neo/SmartContract/Manifest/ContractEventDescriptor.cs index 90227dd78d..de77597655 100644 --- a/src/Neo/SmartContract/Manifest/ContractEventDescriptor.cs +++ b/src/Neo/SmartContract/Manifest/ContractEventDescriptor.cs @@ -40,7 +40,7 @@ public virtual void FromStackItem(StackItem stackItem) Parameters = ((Array)@struct[1]).Select(p => p.ToInteroperable()).ToArray(); } - public virtual StackItem ToStackItem(ReferenceCounter referenceCounter) + public virtual StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { diff --git a/src/Neo/SmartContract/Manifest/ContractGroup.cs b/src/Neo/SmartContract/Manifest/ContractGroup.cs index 231354327c..8796c0f07a 100644 --- a/src/Neo/SmartContract/Manifest/ContractGroup.cs +++ b/src/Neo/SmartContract/Manifest/ContractGroup.cs @@ -43,7 +43,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) Signature = @struct[1].GetSpan().ToArray(); } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { PubKey.ToArray(), Signature }; } diff --git a/src/Neo/SmartContract/Manifest/ContractManifest.cs b/src/Neo/SmartContract/Manifest/ContractManifest.cs index 078e8fd35e..17722eb48a 100644 --- a/src/Neo/SmartContract/Manifest/ContractManifest.cs +++ b/src/Neo/SmartContract/Manifest/ContractManifest.cs @@ -88,7 +88,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) Extra = (JObject)JToken.Parse(@struct[7].GetSpan()); } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { @@ -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/ContractMethodDescriptor.cs b/src/Neo/SmartContract/Manifest/ContractMethodDescriptor.cs index cc8d0a16d5..56b36163de 100644 --- a/src/Neo/SmartContract/Manifest/ContractMethodDescriptor.cs +++ b/src/Neo/SmartContract/Manifest/ContractMethodDescriptor.cs @@ -47,7 +47,7 @@ public override void FromStackItem(StackItem stackItem) Safe = @struct[4].GetBoolean(); } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { Struct @struct = (Struct)base.ToStackItem(referenceCounter); @struct.Add((byte)ReturnType); diff --git a/src/Neo/SmartContract/Manifest/ContractParameterDefinition.cs b/src/Neo/SmartContract/Manifest/ContractParameterDefinition.cs index 61906558d0..b36fa353fe 100644 --- a/src/Neo/SmartContract/Manifest/ContractParameterDefinition.cs +++ b/src/Neo/SmartContract/Manifest/ContractParameterDefinition.cs @@ -38,7 +38,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) Type = (ContractParameterType)(byte)@struct[1].GetInteger(); } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { Name, (byte)Type }; } diff --git a/src/Neo/SmartContract/Manifest/ContractPermission.cs b/src/Neo/SmartContract/Manifest/ContractPermission.cs index 44c67fd9a1..cf4d5078c5 100644 --- a/src/Neo/SmartContract/Manifest/ContractPermission.cs +++ b/src/Neo/SmartContract/Manifest/ContractPermission.cs @@ -68,7 +68,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) }; } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { 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/MethodToken.cs b/src/Neo/SmartContract/MethodToken.cs index 1b391edd32..1b8fa00242 100644 --- a/src/Neo/SmartContract/MethodToken.cs +++ b/src/Neo/SmartContract/MethodToken.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.Json; using System; diff --git a/src/Neo/SmartContract/Native/AccountState.cs b/src/Neo/SmartContract/Native/AccountState.cs index 031b37f8eb..4e8944ab73 100644 --- a/src/Neo/SmartContract/Native/AccountState.cs +++ b/src/Neo/SmartContract/Native/AccountState.cs @@ -30,7 +30,7 @@ public virtual void FromStackItem(StackItem stackItem) Balance = ((Struct)stackItem)[0].GetInteger(); } - public virtual StackItem ToStackItem(ReferenceCounter referenceCounter) + public virtual StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { Balance }; } 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/ContractManagement.cs b/src/Neo/SmartContract/Native/ContractManagement.cs index c533f029a6..6fefd2d05f 100644 --- a/src/Neo/SmartContract/Native/ContractManagement.cs +++ b/src/Neo/SmartContract/Native/ContractManagement.cs @@ -52,8 +52,8 @@ internal override ContractTask InitializeAsync(ApplicationEngine engine, Hardfor { if (hardfork == ActiveIn) { - engine.Snapshot.Add(CreateStorageKey(Prefix_MinimumDeploymentFee), new StorageItem(10_00000000)); - engine.Snapshot.Add(CreateStorageKey(Prefix_NextAvailableId), new StorageItem(1)); + engine.SnapshotCache.Add(CreateStorageKey(Prefix_MinimumDeploymentFee), new StorageItem(10_00000000)); + engine.SnapshotCache.Add(CreateStorageKey(Prefix_NextAvailableId), new StorageItem(1)); } return ContractTask.CompletedTask; } @@ -73,13 +73,13 @@ internal override async ContractTask OnPersistAsync(ApplicationEngine engine) if (contract.IsInitializeBlock(engine.ProtocolSettings, engine.PersistingBlock.Index, out var hfs)) { ContractState contractState = contract.GetContractState(engine.ProtocolSettings, engine.PersistingBlock.Index); - StorageItem state = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Contract).Add(contract.Hash)); + StorageItem state = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Contract).Add(contract.Hash)); if (state is null) { // Create the contract state - engine.Snapshot.Add(CreateStorageKey(Prefix_Contract).Add(contract.Hash), new StorageItem(contractState)); - engine.Snapshot.Add(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id), new StorageItem(contract.Hash.ToArray())); + engine.SnapshotCache.Add(CreateStorageKey(Prefix_Contract).Add(contract.Hash), new StorageItem(contractState)); + engine.SnapshotCache.Add(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id), new StorageItem(contract.Hash.ToArray())); // Initialize the native smart contract if it's active starting from the genesis. // If it's not the case, then hardfork-based initialization will be performed down below. @@ -127,7 +127,7 @@ private void SetMinimumDeploymentFee(ApplicationEngine engine, BigInteger value/ { if (value < 0) throw new ArgumentOutOfRangeException(nameof(value)); if (!CheckCommittee(engine)) throw new InvalidOperationException(); - engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_MinimumDeploymentFee)).Set(value); + engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_MinimumDeploymentFee)).Set(value); } /// @@ -221,7 +221,7 @@ private async ContractTask Deploy(ApplicationEngine engine, byte[ engine.AddFee(Math.Max( engine.StoragePrice * (nefFile.Length + manifest.Length), - GetMinimumDeploymentFee(engine.Snapshot) + GetMinimumDeploymentFee(engine.SnapshotCache) )); NefFile nef = nefFile.AsSerializable(); @@ -229,15 +229,15 @@ private async ContractTask Deploy(ApplicationEngine engine, byte[ Helper.Check(new VM.Script(nef.Script, engine.IsHardforkEnabled(Hardfork.HF_Basilisk)), parsedManifest.Abi); UInt160 hash = Helper.GetContractHash(tx.Sender, nef.CheckSum, parsedManifest.Name); - if (Policy.IsBlocked(engine.Snapshot, hash)) + if (Policy.IsBlocked(engine.SnapshotCache, hash)) throw new InvalidOperationException($"The contract {hash} has been blocked."); StorageKey key = CreateStorageKey(Prefix_Contract).Add(hash); - if (engine.Snapshot.Contains(key)) + if (engine.SnapshotCache.Contains(key)) throw new InvalidOperationException($"Contract Already Exists: {hash}"); ContractState contract = new() { - Id = GetNextAvailableId(engine.Snapshot), + Id = GetNextAvailableId(engine.SnapshotCache), UpdateCounter = 0, Nef = nef, Hash = hash, @@ -246,8 +246,8 @@ private async ContractTask Deploy(ApplicationEngine engine, byte[ if (!contract.Manifest.IsValid(engine.Limits, hash)) throw new InvalidOperationException($"Invalid Manifest: {hash}"); - engine.Snapshot.Add(key, new StorageItem(contract)); - engine.Snapshot.Add(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id), new StorageItem(hash.ToArray())); + engine.SnapshotCache.Add(key, new StorageItem(contract)); + engine.SnapshotCache.Add(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id), new StorageItem(hash.ToArray())); await OnDeployAsync(engine, contract, data, false); @@ -267,7 +267,7 @@ private ContractTask Update(ApplicationEngine engine, byte[] nefFile, byte[] man engine.AddFee(engine.StoragePrice * ((nefFile?.Length ?? 0) + (manifest?.Length ?? 0))); - var contract = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Contract).Add(engine.CallingScriptHash))?.GetInteroperable(false); + var contract = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Contract).Add(engine.CallingScriptHash))?.GetInteroperable(false); if (contract is null) throw new InvalidOperationException($"Updating Contract Does Not Exist: {engine.CallingScriptHash}"); if (contract.UpdateCounter == ushort.MaxValue) throw new InvalidOperationException($"The contract reached the maximum number of updates."); @@ -300,14 +300,14 @@ private void Destroy(ApplicationEngine engine) { UInt160 hash = engine.CallingScriptHash; StorageKey ckey = CreateStorageKey(Prefix_Contract).Add(hash); - ContractState contract = engine.Snapshot.TryGet(ckey)?.GetInteroperable(false); + ContractState contract = engine.SnapshotCache.TryGet(ckey)?.GetInteroperable(false); if (contract is null) return; - engine.Snapshot.Delete(ckey); - engine.Snapshot.Delete(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id)); - foreach (var (key, _) in engine.Snapshot.Find(StorageKey.CreateSearchPrefix(contract.Id, ReadOnlySpan.Empty))) - engine.Snapshot.Delete(key); + engine.SnapshotCache.Delete(ckey); + engine.SnapshotCache.Delete(CreateStorageKey(Prefix_ContractHash).AddBigEndian(contract.Id)); + foreach (var (key, _) in engine.SnapshotCache.Find(StorageKey.CreateSearchPrefix(contract.Id, ReadOnlySpan.Empty))) + engine.SnapshotCache.Delete(key); // lock contract - Policy.BlockAccount(engine.Snapshot, hash); + Policy.BlockAccount(engine.SnapshotCache, hash); // emit event engine.SendNotification(Hash, "Destroy", new VM.Types.Array(engine.ReferenceCounter) { hash.ToArray() }); } 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/SmartContract/Native/FungibleToken.cs b/src/Neo/SmartContract/Native/FungibleToken.cs index 4ab0f01589..21de0a5644 100644 --- a/src/Neo/SmartContract/Native/FungibleToken.cs +++ b/src/Neo/SmartContract/Native/FungibleToken.cs @@ -74,11 +74,11 @@ internal async ContractTask Mint(ApplicationEngine engine, UInt160 account, BigI { if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount)); if (amount.IsZero) return; - StorageItem storage = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Account).Add(account), () => new StorageItem(new TState())); + StorageItem storage = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Account).Add(account), () => new StorageItem(new TState())); TState state = storage.GetInteroperable(); OnBalanceChanging(engine, account, state, amount); state.Balance += amount; - storage = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_TotalSupply), () => new StorageItem(BigInteger.Zero)); + storage = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_TotalSupply), () => new StorageItem(BigInteger.Zero)); storage.Add(amount); await PostTransferAsync(engine, null, account, amount, StackItem.Null, callOnPayment); } @@ -88,15 +88,15 @@ internal async ContractTask Burn(ApplicationEngine engine, UInt160 account, BigI if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount)); if (amount.IsZero) return; StorageKey key = CreateStorageKey(Prefix_Account).Add(account); - StorageItem storage = engine.Snapshot.GetAndChange(key); + StorageItem storage = engine.SnapshotCache.GetAndChange(key); TState state = storage.GetInteroperable(); if (state.Balance < amount) throw new InvalidOperationException(); OnBalanceChanging(engine, account, state, -amount); if (state.Balance == amount) - engine.Snapshot.Delete(key); + engine.SnapshotCache.Delete(key); else state.Balance -= amount; - storage = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_TotalSupply)); + storage = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_TotalSupply)); storage.Add(-amount); await PostTransferAsync(engine, account, null, amount, StackItem.Null, false); } @@ -137,7 +137,7 @@ private protected async ContractTask Transfer(ApplicationEngine engine, UI if (!from.Equals(engine.CallingScriptHash) && !engine.CheckWitnessInternal(from)) return false; StorageKey key_from = CreateStorageKey(Prefix_Account).Add(from); - StorageItem storage_from = engine.Snapshot.GetAndChange(key_from); + StorageItem storage_from = engine.SnapshotCache.GetAndChange(key_from); if (amount.IsZero) { if (storage_from != null) @@ -159,11 +159,11 @@ private protected async ContractTask Transfer(ApplicationEngine engine, UI { OnBalanceChanging(engine, from, state_from, -amount); if (state_from.Balance == amount) - engine.Snapshot.Delete(key_from); + engine.SnapshotCache.Delete(key_from); else state_from.Balance -= amount; StorageKey key_to = CreateStorageKey(Prefix_Account).Add(to); - StorageItem storage_to = engine.Snapshot.GetAndChange(key_to, () => new StorageItem(new TState())); + StorageItem storage_to = engine.SnapshotCache.GetAndChange(key_to, () => new StorageItem(new TState())); TState state_to = storage_to.GetInteroperable(); OnBalanceChanging(engine, to, state_to, amount); state_to.Balance += amount; @@ -186,7 +186,7 @@ private protected virtual async ContractTask PostTransferAsync(ApplicationEngine // Check if it's a wallet or smart contract - if (!callOnPayment || to is null || ContractManagement.GetContract(engine.Snapshot, to) is null) return; + if (!callOnPayment || to is null || ContractManagement.GetContract(engine.SnapshotCache, to) is null) return; // Call onNEP17Payment method diff --git a/src/Neo/SmartContract/Native/GasToken.cs b/src/Neo/SmartContract/Native/GasToken.cs index b8a185b6f1..42eb16c1e5 100644 --- a/src/Neo/SmartContract/Native/GasToken.cs +++ b/src/Neo/SmartContract/Native/GasToken.cs @@ -44,7 +44,7 @@ internal override async ContractTask OnPersistAsync(ApplicationEngine engine) await Burn(engine, tx.Sender, tx.SystemFee + tx.NetworkFee); totalNetworkFee += tx.NetworkFee; } - ECPoint[] validators = NEO.GetNextBlockValidators(engine.Snapshot, engine.ProtocolSettings.ValidatorsCount); + ECPoint[] validators = NEO.GetNextBlockValidators(engine.SnapshotCache, engine.ProtocolSettings.ValidatorsCount); UInt160 primary = Contract.CreateSignatureRedeemScript(validators[engine.PersistingBlock.PrimaryIndex]).ToScriptHash(); await Mint(engine, primary, totalNetworkFee, false); } diff --git a/src/Neo/SmartContract/Native/HashIndexState.cs b/src/Neo/SmartContract/Native/HashIndexState.cs index 229458ffdd..4ab7a991cf 100644 --- a/src/Neo/SmartContract/Native/HashIndexState.cs +++ b/src/Neo/SmartContract/Native/HashIndexState.cs @@ -27,7 +27,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) Index = (uint)@struct[1].GetInteger(); } - StackItem IInteroperable.ToStackItem(ReferenceCounter referenceCounter) + StackItem IInteroperable.ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { Hash.ToArray(), Index }; } diff --git a/src/Neo/SmartContract/Native/IHardforkActivable.cs b/src/Neo/SmartContract/Native/IHardforkActivable.cs new file mode 100644 index 0000000000..7794d03f25 --- /dev/null +++ b/src/Neo/SmartContract/Native/IHardforkActivable.cs @@ -0,0 +1,19 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// 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 +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.SmartContract.Native +{ + internal interface IHardforkActivable + { + public Hardfork? ActiveIn { get; } + public Hardfork? DeprecatedIn { get; } + } +} diff --git a/src/Neo/SmartContract/Native/InteroperableList.cs b/src/Neo/SmartContract/Native/InteroperableList.cs index e118db7621..09088f6156 100644 --- a/src/Neo/SmartContract/Native/InteroperableList.cs +++ b/src/Neo/SmartContract/Native/InteroperableList.cs @@ -40,7 +40,7 @@ abstract class InteroperableList : IList, IInteroperable public void Sort() => List.Sort(); protected abstract T ElementFromStackItem(StackItem item); - protected abstract StackItem ElementToStackItem(T element, ReferenceCounter referenceCounter); + protected abstract StackItem ElementToStackItem(T element, IReferenceCounter referenceCounter); public void FromStackItem(StackItem stackItem) { @@ -51,7 +51,7 @@ public void FromStackItem(StackItem stackItem) } } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Array(referenceCounter, this.Select(p => ElementToStackItem(p, referenceCounter))); } diff --git a/src/Neo/SmartContract/Native/LedgerContract.cs b/src/Neo/SmartContract/Native/LedgerContract.cs index ea757ba348..3d2d16e023 100644 --- a/src/Neo/SmartContract/Native/LedgerContract.cs +++ b/src/Neo/SmartContract/Native/LedgerContract.cs @@ -11,6 +11,7 @@ #pragma warning disable IDE0051 +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; @@ -42,22 +43,22 @@ internal override ContractTask OnPersistAsync(ApplicationEngine engine) Transaction = p, State = VMState.NONE }).ToArray(); - engine.Snapshot.Add(CreateStorageKey(Prefix_BlockHash).AddBigEndian(engine.PersistingBlock.Index), new StorageItem(engine.PersistingBlock.Hash.ToArray())); - engine.Snapshot.Add(CreateStorageKey(Prefix_Block).Add(engine.PersistingBlock.Hash), new StorageItem(Trim(engine.PersistingBlock).ToArray())); + engine.SnapshotCache.Add(CreateStorageKey(Prefix_BlockHash).AddBigEndian(engine.PersistingBlock.Index), new StorageItem(engine.PersistingBlock.Hash.ToArray())); + engine.SnapshotCache.Add(CreateStorageKey(Prefix_Block).Add(engine.PersistingBlock.Hash), new StorageItem(Trim(engine.PersistingBlock).ToArray())); foreach (TransactionState tx in transactions) { // It's possible that there are previously saved malicious conflict records for this transaction. // If so, then remove it and store the relevant transaction itself. - engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Transaction).Add(tx.Transaction.Hash), () => new StorageItem(new TransactionState())).FromReplica(new StorageItem(tx)); + engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Transaction).Add(tx.Transaction.Hash), () => new StorageItem(new TransactionState())).FromReplica(new StorageItem(tx)); // Store transaction's conflicits. var conflictingSigners = tx.Transaction.Signers.Select(s => s.Account); foreach (var attr in tx.Transaction.GetAttributes()) { - engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Transaction).Add(attr.Hash), () => new StorageItem(new TransactionState())).FromReplica(new StorageItem(new TransactionState() { BlockIndex = engine.PersistingBlock.Index })); + engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Transaction).Add(attr.Hash), () => new StorageItem(new TransactionState())).FromReplica(new StorageItem(new TransactionState() { BlockIndex = engine.PersistingBlock.Index })); foreach (var signer in conflictingSigners) { - engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Transaction).Add(attr.Hash).Add(signer), () => new StorageItem(new TransactionState())).FromReplica(new StorageItem(new TransactionState() { BlockIndex = engine.PersistingBlock.Index })); + engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Transaction).Add(attr.Hash).Add(signer), () => new StorageItem(new TransactionState())).FromReplica(new StorageItem(new TransactionState() { BlockIndex = engine.PersistingBlock.Index })); } } } @@ -67,7 +68,7 @@ internal override ContractTask OnPersistAsync(ApplicationEngine engine) internal override ContractTask PostPersistAsync(ApplicationEngine engine) { - HashIndexState state = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_CurrentBlock), () => new StorageItem(new HashIndexState())).GetInteroperable(); + HashIndexState state = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_CurrentBlock), () => new StorageItem(new HashIndexState())).GetInteroperable(); state.Hash = engine.PersistingBlock.Hash; state.Index = engine.PersistingBlock.Index; return ContractTask.CompletedTask; @@ -188,14 +189,14 @@ private TrimmedBlock GetBlock(ApplicationEngine engine, byte[] indexOrHash) { UInt256 hash; if (indexOrHash.Length < UInt256.Length) - hash = GetBlockHash(engine.Snapshot, (uint)new BigInteger(indexOrHash)); + hash = GetBlockHash(engine.SnapshotCache, (uint)new BigInteger(indexOrHash)); else if (indexOrHash.Length == UInt256.Length) hash = new UInt256(indexOrHash); else throw new ArgumentException(null, nameof(indexOrHash)); if (hash is null) return null; - TrimmedBlock block = GetTrimmedBlock(engine.Snapshot, hash); - if (block is null || !IsTraceableBlock(engine.Snapshot, block.Index, engine.ProtocolSettings.MaxTraceableBlocks)) return null; + TrimmedBlock block = GetTrimmedBlock(engine.SnapshotCache, hash); + if (block is null || !IsTraceableBlock(engine.SnapshotCache, block.Index, engine.ProtocolSettings.MaxTraceableBlocks)) return null; return block; } @@ -280,32 +281,32 @@ public Transaction GetTransaction(DataCache snapshot, UInt256 hash) [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates, Name = "getTransaction")] private Transaction GetTransactionForContract(ApplicationEngine engine, UInt256 hash) { - TransactionState state = GetTransactionState(engine.Snapshot, hash); - if (state is null || !IsTraceableBlock(engine.Snapshot, state.BlockIndex, engine.ProtocolSettings.MaxTraceableBlocks)) return null; + TransactionState state = GetTransactionState(engine.SnapshotCache, hash); + if (state is null || !IsTraceableBlock(engine.SnapshotCache, state.BlockIndex, engine.ProtocolSettings.MaxTraceableBlocks)) return null; return state.Transaction; } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] private Signer[] GetTransactionSigners(ApplicationEngine engine, UInt256 hash) { - TransactionState state = GetTransactionState(engine.Snapshot, hash); - if (state is null || !IsTraceableBlock(engine.Snapshot, state.BlockIndex, engine.ProtocolSettings.MaxTraceableBlocks)) return null; + TransactionState state = GetTransactionState(engine.SnapshotCache, hash); + if (state is null || !IsTraceableBlock(engine.SnapshotCache, state.BlockIndex, engine.ProtocolSettings.MaxTraceableBlocks)) return null; return state.Transaction.Signers; } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] private VMState GetTransactionVMState(ApplicationEngine engine, UInt256 hash) { - TransactionState state = GetTransactionState(engine.Snapshot, hash); - if (state is null || !IsTraceableBlock(engine.Snapshot, state.BlockIndex, engine.ProtocolSettings.MaxTraceableBlocks)) return VMState.NONE; + TransactionState state = GetTransactionState(engine.SnapshotCache, hash); + if (state is null || !IsTraceableBlock(engine.SnapshotCache, state.BlockIndex, engine.ProtocolSettings.MaxTraceableBlocks)) return VMState.NONE; return state.State; } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] private int GetTransactionHeight(ApplicationEngine engine, UInt256 hash) { - TransactionState state = GetTransactionState(engine.Snapshot, hash); - if (state is null || !IsTraceableBlock(engine.Snapshot, state.BlockIndex, engine.ProtocolSettings.MaxTraceableBlocks)) return -1; + TransactionState state = GetTransactionState(engine.SnapshotCache, hash); + if (state is null || !IsTraceableBlock(engine.SnapshotCache, state.BlockIndex, engine.ProtocolSettings.MaxTraceableBlocks)) return -1; return (int)state.BlockIndex; } @@ -314,17 +315,17 @@ private Transaction GetTransactionFromBlock(ApplicationEngine engine, byte[] blo { UInt256 hash; if (blockIndexOrHash.Length < UInt256.Length) - hash = GetBlockHash(engine.Snapshot, (uint)new BigInteger(blockIndexOrHash)); + hash = GetBlockHash(engine.SnapshotCache, (uint)new BigInteger(blockIndexOrHash)); else if (blockIndexOrHash.Length == UInt256.Length) hash = new UInt256(blockIndexOrHash); else throw new ArgumentException(null, nameof(blockIndexOrHash)); if (hash is null) return null; - TrimmedBlock block = GetTrimmedBlock(engine.Snapshot, hash); - if (block is null || !IsTraceableBlock(engine.Snapshot, block.Index, engine.ProtocolSettings.MaxTraceableBlocks)) return null; + TrimmedBlock block = GetTrimmedBlock(engine.SnapshotCache, hash); + if (block is null || !IsTraceableBlock(engine.SnapshotCache, block.Index, engine.ProtocolSettings.MaxTraceableBlocks)) return null; if (txIndex < 0 || txIndex >= block.Hashes.Length) throw new ArgumentOutOfRangeException(nameof(txIndex)); - return GetTransaction(engine.Snapshot, block.Hashes[txIndex]); + return GetTransaction(engine.SnapshotCache, block.Hashes[txIndex]); } private static TrimmedBlock Trim(Block block) diff --git a/src/Neo/SmartContract/Native/NativeContract.cs b/src/Neo/SmartContract/Native/NativeContract.cs index 1cc244408d..97bb0ab69a 100644 --- a/src/Neo/SmartContract/Native/NativeContract.cs +++ b/src/Neo/SmartContract/Native/NativeContract.cs @@ -41,7 +41,7 @@ public CacheEntry GetAllowedMethods(NativeContract native, ApplicationEngine eng { if (NativeContracts.TryGetValue(native.Id, out var value)) return value; - uint index = engine.PersistingBlock is null ? Ledger.CurrentIndex(engine.Snapshot) : engine.PersistingBlock.Index; + uint index = engine.PersistingBlock is null ? Ledger.CurrentIndex(engine.SnapshotCache) : engine.PersistingBlock.Index; CacheEntry methods = native.GetAllowedMethods(engine.ProtocolSettings.IsHardforkEnabled, index); NativeContracts[native.Id] = methods; return methods; @@ -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() @@ -340,7 +343,7 @@ internal bool IsActive(ProtocolSettings settings, uint blockHeight) /// if the committee has witnessed the current transaction; otherwise, . protected static bool CheckCommittee(ApplicationEngine engine) { - UInt160 committeeMultiSigAddr = NEO.GetCommitteeAddress(engine.Snapshot); + UInt160 committeeMultiSigAddr = NEO.GetCommitteeAddress(engine.SnapshotCache); return engine.CheckWitnessInternal(committeeMultiSigAddr); } @@ -383,7 +386,7 @@ internal async void Invoke(ApplicationEngine engine, byte version) engine.AddFee(method.CpuFee * engine.ExecFeeFactor + method.StorageFee * engine.StoragePrice); List parameters = new(); if (method.NeedApplicationEngine) parameters.Add(engine); - if (method.NeedSnapshot) parameters.Add(engine.Snapshot); + if (method.NeedSnapshot) parameters.Add(engine.SnapshotCache); for (int i = 0; i < method.Parameters.Length; i++) parameters.Add(engine.Convert(context.EvaluationStack.Peek(i), method.Parameters[i])); object returnValue = method.Handler.Invoke(this, parameters.ToArray()); diff --git a/src/Neo/SmartContract/Native/NeoToken.cs b/src/Neo/SmartContract/Native/NeoToken.cs index f43a0d6079..85309b4246 100644 --- a/src/Neo/SmartContract/Native/NeoToken.cs +++ b/src/Neo/SmartContract/Native/NeoToken.cs @@ -12,6 +12,7 @@ #pragma warning disable IDE0051 using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.Persistence; using Neo.SmartContract.Iterators; @@ -86,11 +87,11 @@ internal override void OnBalanceChanging(ApplicationEngine engine, UInt160 accou } if (amount.IsZero) return; if (state.VoteTo is null) return; - engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_VotersCount)).Add(amount); + engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_VotersCount)).Add(amount); StorageKey key = CreateStorageKey(Prefix_Candidate).Add(state.VoteTo); - CandidateState candidate = engine.Snapshot.GetAndChange(key).GetInteroperable(); + CandidateState candidate = engine.SnapshotCache.GetAndChange(key).GetInteroperable(); candidate.Votes += amount; - CheckCandidate(engine.Snapshot, state.VoteTo, candidate); + CheckCandidate(engine.SnapshotCache, state.VoteTo, candidate); } private protected override async ContractTask PostTransferAsync(ApplicationEngine engine, UInt160 from, UInt160 to, BigInteger amount, StackItem data, bool callOnPayment) @@ -107,12 +108,12 @@ private GasDistribution DistributeGas(ApplicationEngine engine, UInt160 account, if (engine.PersistingBlock is null) return null; // In the unit of datoshi, 1 datoshi = 1e-8 GAS - BigInteger datoshi = CalculateBonus(engine.Snapshot, state, engine.PersistingBlock.Index); + BigInteger datoshi = CalculateBonus(engine.SnapshotCache, state, engine.PersistingBlock.Index); state.BalanceHeight = engine.PersistingBlock.Index; if (state.VoteTo is not null) { var keyLastest = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(state.VoteTo); - var latestGasPerVote = engine.Snapshot.TryGet(keyLastest) ?? BigInteger.Zero; + var latestGasPerVote = engine.SnapshotCache.TryGet(keyLastest) ?? BigInteger.Zero; state.LastGasPerVote = latestGasPerVote; } if (datoshi == 0) return null; @@ -184,10 +185,10 @@ internal override ContractTask InitializeAsync(ApplicationEngine engine, Hardfor if (hardfork == ActiveIn) { var cachedCommittee = new CachedCommittee(engine.ProtocolSettings.StandbyCommittee.Select(p => (p, BigInteger.Zero))); - engine.Snapshot.Add(CreateStorageKey(Prefix_Committee), new StorageItem(cachedCommittee)); - engine.Snapshot.Add(CreateStorageKey(Prefix_VotersCount), new StorageItem(System.Array.Empty())); - engine.Snapshot.Add(CreateStorageKey(Prefix_GasPerBlock).AddBigEndian(0u), new StorageItem(5 * GAS.Factor)); - engine.Snapshot.Add(CreateStorageKey(Prefix_RegisterPrice), new StorageItem(1000 * GAS.Factor)); + engine.SnapshotCache.Add(CreateStorageKey(Prefix_Committee), new StorageItem(cachedCommittee)); + engine.SnapshotCache.Add(CreateStorageKey(Prefix_VotersCount), new StorageItem(System.Array.Empty())); + engine.SnapshotCache.Add(CreateStorageKey(Prefix_GasPerBlock).AddBigEndian(0u), new StorageItem(5 * GAS.Factor)); + engine.SnapshotCache.Add(CreateStorageKey(Prefix_RegisterPrice), new StorageItem(1000 * GAS.Factor)); return Mint(engine, Contract.GetBFTAddress(engine.ProtocolSettings.StandbyValidators), TotalAmount, false); } return ContractTask.CompletedTask; @@ -198,17 +199,17 @@ internal override ContractTask OnPersistAsync(ApplicationEngine engine) // Set next committee if (ShouldRefreshCommittee(engine.PersistingBlock.Index, engine.ProtocolSettings.CommitteeMembersCount)) { - var storageItem = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Committee)); + var storageItem = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Committee)); var cachedCommittee = storageItem.GetInteroperable(); var prevCommittee = cachedCommittee.Select(u => u.PublicKey).ToArray(); cachedCommittee.Clear(); - cachedCommittee.AddRange(ComputeCommitteeMembers(engine.Snapshot, engine.ProtocolSettings)); + cachedCommittee.AddRange(ComputeCommitteeMembers(engine.SnapshotCache, engine.ProtocolSettings)); // Hardfork check for https://github.com/neo-project/neo/pull/3158 // New notification will case 3.7.0 and 3.6.0 have different behavior - var index = engine.PersistingBlock?.Index ?? Ledger.CurrentIndex(engine.Snapshot); + var index = engine.PersistingBlock?.Index ?? Ledger.CurrentIndex(engine.SnapshotCache); if (engine.ProtocolSettings.IsHardforkEnabled(Hardfork.HF_Cockatrice, index)) { var newCommittee = cachedCommittee.Select(u => u.PublicKey).ToArray(); @@ -232,8 +233,8 @@ internal override async ContractTask PostPersistAsync(ApplicationEngine engine) int m = engine.ProtocolSettings.CommitteeMembersCount; int n = engine.ProtocolSettings.ValidatorsCount; int index = (int)(engine.PersistingBlock.Index % (uint)m); - var gasPerBlock = GetGasPerBlock(engine.Snapshot); - var committee = GetCommitteeFromCache(engine.Snapshot); + var gasPerBlock = GetGasPerBlock(engine.SnapshotCache); + var committee = GetCommitteeFromCache(engine.SnapshotCache); var pubkey = committee[index].PublicKey; var account = Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash(); await GAS.Mint(engine, account, gasPerBlock * CommitteeRewardRatio / 100, false); @@ -251,7 +252,7 @@ internal override async ContractTask PostPersistAsync(ApplicationEngine engine) { BigInteger voterSumRewardPerNEO = factor * voterRewardOfEachCommittee / Votes; StorageKey voterRewardKey = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(PublicKey); - StorageItem lastRewardPerNeo = engine.Snapshot.GetAndChange(voterRewardKey, () => new StorageItem(BigInteger.Zero)); + StorageItem lastRewardPerNeo = engine.SnapshotCache.GetAndChange(voterRewardKey, () => new StorageItem(BigInteger.Zero)); lastRewardPerNeo.Add(voterSumRewardPerNEO); } } @@ -266,7 +267,7 @@ private void SetGasPerBlock(ApplicationEngine engine, BigInteger gasPerBlock) if (!CheckCommittee(engine)) throw new InvalidOperationException(); uint index = engine.PersistingBlock.Index + 1; - StorageItem entry = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_GasPerBlock).AddBigEndian(index), () => new StorageItem(gasPerBlock)); + StorageItem entry = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_GasPerBlock).AddBigEndian(index), () => new StorageItem(gasPerBlock)); entry.Set(gasPerBlock); } @@ -287,7 +288,7 @@ private void SetRegisterPrice(ApplicationEngine engine, long registerPrice) if (registerPrice <= 0) throw new ArgumentOutOfRangeException(nameof(registerPrice)); if (!CheckCommittee(engine)) throw new InvalidOperationException(); - engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_RegisterPrice)).Set(registerPrice); + engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_RegisterPrice)).Set(registerPrice); } /// @@ -332,9 +333,9 @@ private bool RegisterCandidate(ApplicationEngine engine, ECPoint pubkey) if (!engine.CheckWitnessInternal(Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash())) return false; // In the unit of datoshi, 1 datoshi = 1e-8 GAS - engine.AddFee(GetRegisterPrice(engine.Snapshot)); + engine.AddFee(GetRegisterPrice(engine.SnapshotCache)); StorageKey key = CreateStorageKey(Prefix_Candidate).Add(pubkey); - StorageItem item = engine.Snapshot.GetAndChange(key, () => new StorageItem(new CandidateState())); + StorageItem item = engine.SnapshotCache.GetAndChange(key, () => new StorageItem(new CandidateState())); CandidateState state = item.GetInteroperable(); if (state.Registered) return true; state.Registered = true; @@ -349,12 +350,12 @@ private bool UnregisterCandidate(ApplicationEngine engine, ECPoint pubkey) if (!engine.CheckWitnessInternal(Contract.CreateSignatureRedeemScript(pubkey).ToScriptHash())) return false; StorageKey key = CreateStorageKey(Prefix_Candidate).Add(pubkey); - if (engine.Snapshot.TryGet(key) is null) return true; - StorageItem item = engine.Snapshot.GetAndChange(key); + if (engine.SnapshotCache.TryGet(key) is null) return true; + StorageItem item = engine.SnapshotCache.GetAndChange(key); CandidateState state = item.GetInteroperable(); if (!state.Registered) return true; state.Registered = false; - CheckCandidate(engine.Snapshot, pubkey, state); + CheckCandidate(engine.SnapshotCache, pubkey, state); engine.SendNotification(Hash, "CandidateStateChanged", new VM.Types.Array(engine.ReferenceCounter) { pubkey.ToArray(), false, state.Votes }); return true; @@ -364,19 +365,19 @@ private bool UnregisterCandidate(ApplicationEngine engine, ECPoint pubkey) private async ContractTask Vote(ApplicationEngine engine, UInt160 account, ECPoint voteTo) { if (!engine.CheckWitnessInternal(account)) return false; - NeoAccountState state_account = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Account).Add(account))?.GetInteroperable(); + NeoAccountState state_account = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Account).Add(account))?.GetInteroperable(); if (state_account is null) return false; if (state_account.Balance == 0) return false; CandidateState validator_new = null; if (voteTo != null) { - validator_new = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Candidate).Add(voteTo))?.GetInteroperable(); + validator_new = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Candidate).Add(voteTo))?.GetInteroperable(); if (validator_new is null) return false; if (!validator_new.Registered) return false; } if (state_account.VoteTo is null ^ voteTo is null) { - StorageItem item = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_VotersCount)); + StorageItem item = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_VotersCount)); if (state_account.VoteTo is null) item.Add(state_account.Balance); else @@ -386,15 +387,15 @@ private async ContractTask Vote(ApplicationEngine engine, UInt160 account, if (state_account.VoteTo != null) { StorageKey key = CreateStorageKey(Prefix_Candidate).Add(state_account.VoteTo); - StorageItem storage_validator = engine.Snapshot.GetAndChange(key); + StorageItem storage_validator = engine.SnapshotCache.GetAndChange(key); CandidateState state_validator = storage_validator.GetInteroperable(); state_validator.Votes -= state_account.Balance; - CheckCandidate(engine.Snapshot, state_account.VoteTo, state_validator); + CheckCandidate(engine.SnapshotCache, state_account.VoteTo, state_validator); } if (voteTo != null && voteTo != state_account.VoteTo) { StorageKey voterRewardKey = CreateStorageKey(Prefix_VoterRewardPerCommittee).Add(voteTo); - var latestGasPerVote = engine.Snapshot.TryGet(voterRewardKey) ?? BigInteger.Zero; + var latestGasPerVote = engine.SnapshotCache.TryGet(voterRewardKey) ?? BigInteger.Zero; state_account.LastGasPerVote = latestGasPerVote; } ECPoint from = state_account.VoteTo; @@ -536,7 +537,7 @@ public ECPoint[] ComputeNextBlockValidators(DataCache snapshot, ProtocolSettings [ContractMethod(CpuFee = 1 << 16, RequiredCallFlags = CallFlags.ReadStates)] private ECPoint[] GetNextBlockValidators(ApplicationEngine engine) { - return GetNextBlockValidators(engine.Snapshot, engine.ProtocolSettings.ValidatorsCount); + return GetNextBlockValidators(engine.SnapshotCache, engine.ProtocolSettings.ValidatorsCount); } /// @@ -580,7 +581,7 @@ public override void FromStackItem(StackItem stackItem) LastGasPerVote = @struct[3].GetInteger(); } - public override StackItem ToStackItem(ReferenceCounter referenceCounter) + public override StackItem ToStackItem(IReferenceCounter referenceCounter) { Struct @struct = (Struct)base.ToStackItem(referenceCounter); @struct.Add(BalanceHeight); @@ -602,7 +603,7 @@ public void FromStackItem(StackItem stackItem) Votes = @struct[1].GetInteger(); } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { Registered, Votes }; } @@ -619,7 +620,7 @@ protected override (ECPoint, BigInteger) ElementFromStackItem(StackItem item) return (ECPoint.DecodePoint(@struct[0].GetSpan(), ECCurve.Secp256r1), @struct[1].GetInteger()); } - protected override StackItem ElementToStackItem((ECPoint PublicKey, BigInteger Votes) element, ReferenceCounter referenceCounter) + protected override StackItem ElementToStackItem((ECPoint PublicKey, BigInteger Votes) element, IReferenceCounter referenceCounter) { return new Struct(referenceCounter) { element.PublicKey.ToArray(), element.Votes }; } diff --git a/src/Neo/SmartContract/Native/OracleContract.cs b/src/Neo/SmartContract/Native/OracleContract.cs index 13a7264660..54156a0029 100644 --- a/src/Neo/SmartContract/Native/OracleContract.cs +++ b/src/Neo/SmartContract/Native/OracleContract.cs @@ -56,7 +56,7 @@ private void SetPrice(ApplicationEngine engine, long price) if (price <= 0) throw new ArgumentOutOfRangeException(nameof(price)); if (!CheckCommittee(engine)) throw new InvalidOperationException(); - engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_Price)).Set(price); + engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Price)).Set(price); } /// @@ -78,7 +78,7 @@ private ContractTask Finish(ApplicationEngine engine) Transaction tx = (Transaction)engine.ScriptContainer; OracleResponse response = tx.GetAttribute(); if (response == null) throw new ArgumentException("Oracle response was not found"); - OracleRequest request = GetRequest(engine.Snapshot, response.Id); + OracleRequest request = GetRequest(engine.SnapshotCache, response.Id); if (request == null) throw new ArgumentException("Oracle request was not found"); engine.SendNotification(Hash, "OracleResponse", new VM.Types.Array(engine.ReferenceCounter) { response.Id, request.OriginalTxid.ToArray() }); StackItem userData = BinarySerializer.Deserialize(request.UserData, engine.Limits, engine.ReferenceCounter); @@ -90,7 +90,7 @@ private UInt256 GetOriginalTxid(ApplicationEngine engine) Transaction tx = (Transaction)engine.ScriptContainer; OracleResponse response = tx.GetAttribute(); if (response is null) return tx.Hash; - OracleRequest request = GetRequest(engine.Snapshot, response.Id); + OracleRequest request = GetRequest(engine.SnapshotCache, response.Id); return request.OriginalTxid; } @@ -138,8 +138,8 @@ internal override ContractTask InitializeAsync(ApplicationEngine engine, Hardfor { if (hardfork == ActiveIn) { - engine.Snapshot.Add(CreateStorageKey(Prefix_RequestId), new StorageItem(BigInteger.Zero)); - engine.Snapshot.Add(CreateStorageKey(Prefix_Price), new StorageItem(0_50000000)); + engine.SnapshotCache.Add(CreateStorageKey(Prefix_RequestId), new StorageItem(BigInteger.Zero)); + engine.SnapshotCache.Add(CreateStorageKey(Prefix_Price), new StorageItem(0_50000000)); } return ContractTask.CompletedTask; } @@ -155,22 +155,22 @@ internal override async ContractTask PostPersistAsync(ApplicationEngine engine) //Remove the request from storage StorageKey key = CreateStorageKey(Prefix_Request).AddBigEndian(response.Id); - OracleRequest request = engine.Snapshot.TryGet(key)?.GetInteroperable(); + OracleRequest request = engine.SnapshotCache.TryGet(key)?.GetInteroperable(); if (request == null) continue; - engine.Snapshot.Delete(key); + engine.SnapshotCache.Delete(key); //Remove the id from IdList key = CreateStorageKey(Prefix_IdList).Add(GetUrlHash(request.Url)); - IdList list = engine.Snapshot.GetAndChange(key).GetInteroperable(); + IdList list = engine.SnapshotCache.GetAndChange(key).GetInteroperable(); if (!list.Remove(response.Id)) throw new InvalidOperationException(); - if (list.Count == 0) engine.Snapshot.Delete(key); + if (list.Count == 0) engine.SnapshotCache.Delete(key); //Mint GAS for oracle nodes - nodes ??= RoleManagement.GetDesignatedByRole(engine.Snapshot, Role.Oracle, engine.PersistingBlock.Index).Select(p => (Contract.CreateSignatureRedeemScript(p).ToScriptHash(), BigInteger.Zero)).ToArray(); + nodes ??= RoleManagement.GetDesignatedByRole(engine.SnapshotCache, Role.Oracle, engine.PersistingBlock.Index).Select(p => (Contract.CreateSignatureRedeemScript(p).ToScriptHash(), BigInteger.Zero)).ToArray(); if (nodes.Length > 0) { int index = (int)(response.Id % (ulong)nodes.Length); - nodes[index].GAS += GetPrice(engine.Snapshot); + nodes[index].GAS += GetPrice(engine.SnapshotCache); } } if (nodes != null) @@ -193,21 +193,21 @@ private async ContractTask Request(ApplicationEngine engine, string url, string || gasForResponse < 0_10000000) throw new ArgumentException(); - engine.AddFee(GetPrice(engine.Snapshot)); + engine.AddFee(GetPrice(engine.SnapshotCache)); //Mint gas for the response engine.AddFee(gasForResponse); await GAS.Mint(engine, Hash, gasForResponse, false); //Increase the request id - StorageItem item_id = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_RequestId)); + StorageItem item_id = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_RequestId)); ulong id = (ulong)(BigInteger)item_id; item_id.Add(1); //Put the request to storage - if (ContractManagement.GetContract(engine.Snapshot, engine.CallingScriptHash) is null) + if (ContractManagement.GetContract(engine.SnapshotCache, engine.CallingScriptHash) is null) throw new InvalidOperationException(); - engine.Snapshot.Add(CreateStorageKey(Prefix_Request).AddBigEndian(id), new StorageItem(new OracleRequest + engine.SnapshotCache.Add(CreateStorageKey(Prefix_Request).AddBigEndian(id), new StorageItem(new OracleRequest { OriginalTxid = GetOriginalTxid(engine), GasForResponse = gasForResponse, @@ -219,7 +219,7 @@ private async ContractTask Request(ApplicationEngine engine, string url, string })); //Add the id to the IdList - var list = engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_IdList).Add(GetUrlHash(url)), () => new StorageItem(new IdList())).GetInteroperable(); + var list = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_IdList).Add(GetUrlHash(url)), () => new StorageItem(new IdList())).GetInteroperable(); if (list.Count >= 256) throw new InvalidOperationException("There are too many pending responses for this url"); list.Add(id); @@ -241,7 +241,7 @@ protected override ulong ElementFromStackItem(StackItem item) return (ulong)item.GetInteger(); } - protected override StackItem ElementToStackItem(ulong element, ReferenceCounter referenceCounter) + protected override StackItem ElementToStackItem(ulong element, IReferenceCounter referenceCounter) { return element; } diff --git a/src/Neo/SmartContract/Native/OracleRequest.cs b/src/Neo/SmartContract/Native/OracleRequest.cs index d18968ef00..cd4962b1cb 100644 --- a/src/Neo/SmartContract/Native/OracleRequest.cs +++ b/src/Neo/SmartContract/Native/OracleRequest.cs @@ -68,7 +68,7 @@ public void FromStackItem(StackItem stackItem) UserData = array[6].GetSpan().ToArray(); } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Array(referenceCounter) { diff --git a/src/Neo/SmartContract/Native/PolicyContract.cs b/src/Neo/SmartContract/Native/PolicyContract.cs index 2da6255094..744b7b3ab6 100644 --- a/src/Neo/SmartContract/Native/PolicyContract.cs +++ b/src/Neo/SmartContract/Native/PolicyContract.cs @@ -71,9 +71,9 @@ internal override ContractTask InitializeAsync(ApplicationEngine engine, Hardfor { if (hardfork == ActiveIn) { - engine.Snapshot.Add(CreateStorageKey(Prefix_FeePerByte), new StorageItem(DefaultFeePerByte)); - engine.Snapshot.Add(CreateStorageKey(Prefix_ExecFeeFactor), new StorageItem(DefaultExecFeeFactor)); - engine.Snapshot.Add(CreateStorageKey(Prefix_StoragePrice), new StorageItem(DefaultStoragePrice)); + engine.SnapshotCache.Add(CreateStorageKey(Prefix_FeePerByte), new StorageItem(DefaultFeePerByte)); + engine.SnapshotCache.Add(CreateStorageKey(Prefix_ExecFeeFactor), new StorageItem(DefaultExecFeeFactor)); + engine.SnapshotCache.Add(CreateStorageKey(Prefix_StoragePrice), new StorageItem(DefaultStoragePrice)); } return ContractTask.CompletedTask; } @@ -146,7 +146,7 @@ private void SetAttributeFee(ApplicationEngine engine, byte attributeType, uint if (value > MaxAttributeFee) throw new ArgumentOutOfRangeException(nameof(value)); if (!CheckCommittee(engine)) throw new InvalidOperationException(); - engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_AttributeFee).Add(attributeType), () => new StorageItem(DefaultAttributeFee)).Set(value); + engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_AttributeFee).Add(attributeType), () => new StorageItem(DefaultAttributeFee)).Set(value); } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] @@ -154,7 +154,7 @@ private void SetFeePerByte(ApplicationEngine engine, long value) { if (value < 0 || value > 1_00000000) throw new ArgumentOutOfRangeException(nameof(value)); if (!CheckCommittee(engine)) throw new InvalidOperationException(); - engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_FeePerByte)).Set(value); + engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_FeePerByte)).Set(value); } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] @@ -162,7 +162,7 @@ private void SetExecFeeFactor(ApplicationEngine engine, uint value) { if (value == 0 || value > MaxExecFeeFactor) throw new ArgumentOutOfRangeException(nameof(value)); if (!CheckCommittee(engine)) throw new InvalidOperationException(); - engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_ExecFeeFactor)).Set(value); + engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_ExecFeeFactor)).Set(value); } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] @@ -170,14 +170,14 @@ private void SetStoragePrice(ApplicationEngine engine, uint value) { if (value == 0 || value > MaxStoragePrice) throw new ArgumentOutOfRangeException(nameof(value)); if (!CheckCommittee(engine)) throw new InvalidOperationException(); - engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_StoragePrice)).Set(value); + engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_StoragePrice)).Set(value); } [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private bool BlockAccount(ApplicationEngine engine, UInt160 account) { if (!CheckCommittee(engine)) throw new InvalidOperationException(); - return BlockAccount(engine.Snapshot, account); + return BlockAccount(engine.SnapshotCache, account); } internal bool BlockAccount(DataCache snapshot, UInt160 account) @@ -197,9 +197,9 @@ private bool UnblockAccount(ApplicationEngine engine, UInt160 account) if (!CheckCommittee(engine)) throw new InvalidOperationException(); var key = CreateStorageKey(Prefix_BlockedAccount).Add(account); - if (!engine.Snapshot.Contains(key)) return false; + if (!engine.SnapshotCache.Contains(key)) return false; - engine.Snapshot.Delete(key); + engine.SnapshotCache.Delete(key); return true; } } diff --git a/src/Neo/SmartContract/Native/RoleManagement.cs b/src/Neo/SmartContract/Native/RoleManagement.cs index 6e989b78cf..e57f3f3e83 100644 --- a/src/Neo/SmartContract/Native/RoleManagement.cs +++ b/src/Neo/SmartContract/Native/RoleManagement.cs @@ -63,12 +63,12 @@ private void DesignateAsRole(ApplicationEngine engine, Role role, ECPoint[] node throw new InvalidOperationException(nameof(DesignateAsRole)); uint index = engine.PersistingBlock.Index + 1; var key = CreateStorageKey((byte)role).AddBigEndian(index); - if (engine.Snapshot.Contains(key)) + if (engine.SnapshotCache.Contains(key)) throw new InvalidOperationException(); NodeList list = new(); list.AddRange(nodes); list.Sort(); - engine.Snapshot.Add(key, new StorageItem(list)); + engine.SnapshotCache.Add(key, new StorageItem(list)); engine.SendNotification(Hash, "Designation", new VM.Types.Array(engine.ReferenceCounter, new StackItem[] { (int)role, engine.PersistingBlock.Index })); } @@ -79,7 +79,7 @@ protected override ECPoint ElementFromStackItem(StackItem item) return ECPoint.DecodePoint(item.GetSpan(), ECCurve.Secp256r1); } - protected override StackItem ElementToStackItem(ECPoint element, ReferenceCounter referenceCounter) + protected override StackItem ElementToStackItem(ECPoint element, IReferenceCounter referenceCounter) { return element.ToArray(); } diff --git a/src/Neo/SmartContract/Native/TransactionState.cs b/src/Neo/SmartContract/Native/TransactionState.cs index b17296b42d..9e3eb2527a 100644 --- a/src/Neo/SmartContract/Native/TransactionState.cs +++ b/src/Neo/SmartContract/Native/TransactionState.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.Network.P2P.Payloads; using Neo.VM; @@ -75,7 +76,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) State = (VMState)(byte)@struct[2].GetInteger(); } - StackItem IInteroperable.ToStackItem(ReferenceCounter referenceCounter) + StackItem IInteroperable.ToStackItem(IReferenceCounter referenceCounter) { if (Transaction is null) return new Struct(referenceCounter) { BlockIndex }; diff --git a/src/Neo/SmartContract/Native/TrimmedBlock.cs b/src/Neo/SmartContract/Native/TrimmedBlock.cs index 4cc4c39c0f..69dd55fdbb 100644 --- a/src/Neo/SmartContract/Native/TrimmedBlock.cs +++ b/src/Neo/SmartContract/Native/TrimmedBlock.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.Network.P2P.Payloads; using Neo.VM; @@ -79,7 +80,7 @@ void IInteroperable.FromStackItem(StackItem stackItem) throw new NotSupportedException(); } - StackItem IInteroperable.ToStackItem(ReferenceCounter referenceCounter) + StackItem IInteroperable.ToStackItem(IReferenceCounter referenceCounter) { return new VM.Types.Array(referenceCounter, new StackItem[] { diff --git a/src/Neo/SmartContract/NefFile.cs b/src/Neo/SmartContract/NefFile.cs index 3e343d3be2..5638447688 100644 --- a/src/Neo/SmartContract/NefFile.cs +++ b/src/Neo/SmartContract/NefFile.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography; +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.VM; diff --git a/src/Neo/SmartContract/NotifyEventArgs.cs b/src/Neo/SmartContract/NotifyEventArgs.cs index 257efb3a66..8d8542bca9 100644 --- a/src/Neo/SmartContract/NotifyEventArgs.cs +++ b/src/Neo/SmartContract/NotifyEventArgs.cs @@ -63,7 +63,7 @@ public void FromStackItem(StackItem stackItem) throw new NotSupportedException(); } - public StackItem ToStackItem(ReferenceCounter referenceCounter) + public StackItem ToStackItem(IReferenceCounter referenceCounter) { return new Array(referenceCounter) { @@ -73,7 +73,7 @@ public StackItem ToStackItem(ReferenceCounter referenceCounter) }; } - public StackItem ToStackItem(ReferenceCounter referenceCounter, ApplicationEngine engine) + public StackItem ToStackItem(IReferenceCounter referenceCounter, ApplicationEngine engine) { if (engine.IsHardforkEnabled(Hardfork.HF_Domovoi)) { 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/UInt160.cs b/src/Neo/UInt160.cs index 8dfd6bf70c..da4c920cbe 100644 --- a/src/Neo/UInt160.cs +++ b/src/Neo/UInt160.cs @@ -9,10 +9,13 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using System; using System.Globalization; using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Neo @@ -31,20 +34,18 @@ public class UInt160 : IComparable, IEquatable, ISerializable /// /// Represents 0. /// - public static readonly UInt160 Zero = new(); + public readonly static UInt160 Zero = new(); - [FieldOffset(0)] private ulong value1; - [FieldOffset(8)] private ulong value2; - [FieldOffset(16)] private uint value3; + [FieldOffset(0)] private ulong _value1; + [FieldOffset(8)] private ulong _value2; + [FieldOffset(16)] private uint _value3; public int Size => Length; /// /// Initializes a new instance of the class. /// - public UInt160() - { - } + public UInt160() { } /// /// Initializes a new instance of the class. @@ -52,47 +53,52 @@ public UInt160() /// The value of the . public unsafe UInt160(ReadOnlySpan value) { - if (value.Length != Length) throw new FormatException(); - fixed (ulong* p = &value1) + if (value.Length != Length) + throw new FormatException(); + + fixed (void* p = &_value1) { Span dst = new(p, Length); value[..Length].CopyTo(dst); } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public int CompareTo(UInt160 other) { - int result = value3.CompareTo(other.value3); + var result = _value3.CompareTo(other._value3); if (result != 0) return result; - result = value2.CompareTo(other.value2); + result = _value2.CompareTo(other._value2); if (result != 0) return result; - return value1.CompareTo(other.value1); + return _value1.CompareTo(other._value1); } public void Deserialize(ref MemoryReader reader) { - value1 = reader.ReadUInt64(); - value2 = reader.ReadUInt64(); - value3 = reader.ReadUInt32(); + _value1 = reader.ReadUInt64(); + _value2 = reader.ReadUInt64(); + _value3 = reader.ReadUInt32(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public override bool Equals(object obj) { if (ReferenceEquals(obj, this)) return true; return Equals(obj as UInt160); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool Equals(UInt160 other) { - if (other is null) return false; - return value1 == other.value1 - && value2 == other.value2 - && value3 == other.value3; + if (other == null) return false; + return _value1 == other._value1 && + _value2 == other._value2 && + _value3 == other._value3; } public override int GetHashCode() { - return (int)value1; + return HashCode.Combine(_value1, _value2, _value3); } /// @@ -109,9 +115,9 @@ public static UInt160 Parse(string value) public void Serialize(BinaryWriter writer) { - writer.Write(value1); - writer.Write(value2); - writer.Write(value3); + writer.Write(_value1); + writer.Write(_value2); + writer.Write(_value3); } public override string ToString() @@ -122,44 +128,61 @@ public override string ToString() /// /// Parses an from the specified . /// - /// An represented by a . + /// An represented by a . /// The parsed . /// if an is successfully parsed; otherwise, . - public static bool TryParse(string s, out UInt160 result) + public static bool TryParse(string str, out UInt160 result) { - if (s == null) + var startIndex = 0; + + result = null; + + if (string.IsNullOrWhiteSpace(str)) return false; + + if (str.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) + startIndex = 2; + + if ((str.Length - startIndex) != Length * 2) return false; + + try { - result = null; - return false; + var data = new byte[Length]; + for (var i = 0; i < Length; i++) + { + if (!byte.TryParse(str.AsSpan(i * 2 + startIndex, 2), NumberStyles.HexNumber, null, out data[Length - i - 1])) + return false; + } + result = new(data); + return true; } - if (s.StartsWith("0x", StringComparison.InvariantCultureIgnoreCase)) - s = s[2..]; - if (s.Length != Length * 2) + catch { - result = null; return false; } - byte[] data = new byte[Length]; - for (int i = 0; i < Length; i++) - if (!byte.TryParse(s.Substring(i * 2, 2), NumberStyles.AllowHexSpecifier, null, out data[Length - i - 1])) - { - result = null; - return false; - } - result = new UInt160(data); - return true; + } + + public static implicit operator UInt160(string s) + { + return Parse(s); + } + + public static implicit operator UInt160(byte[] b) + { + return new UInt160(b); } public static bool operator ==(UInt160 left, UInt160 right) { - if (ReferenceEquals(left, right)) return true; - if (left is null || right is null) return false; + if (left is null || right is null) + return Equals(left, right); return left.Equals(right); } public static bool operator !=(UInt160 left, UInt160 right) { - return !(left == right); + if (left is null || right is null) + return !Equals(left, right); + return !left.Equals(right); } public static bool operator >(UInt160 left, UInt160 right) diff --git a/src/Neo/UInt256.cs b/src/Neo/UInt256.cs index 7c4d996339..95324ef6ac 100644 --- a/src/Neo/UInt256.cs +++ b/src/Neo/UInt256.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 System; using System.Globalization; diff --git a/src/Neo/VM/Helper.cs b/src/Neo/VM/Helper.cs index 4a82041ea5..6964747473 100644 --- a/src/Neo/VM/Helper.cs +++ b/src/Neo/VM/Helper.cs @@ -54,18 +54,64 @@ public static ScriptBuilder CreateArray(this ScriptBuilder builder, IReadOnly /// The to be used. /// The key/value pairs of the map. /// The same instance as . - public static ScriptBuilder CreateMap(this ScriptBuilder builder, IEnumerable> map = null) + public static ScriptBuilder CreateMap(this ScriptBuilder builder, IEnumerable> map) + where TKey : notnull + where TValue : notnull { - builder.Emit(OpCode.NEWMAP); - if (map != null) - foreach (var p in map) - { - builder.Emit(OpCode.DUP); - builder.EmitPush(p.Key); - builder.EmitPush(p.Value); - builder.Emit(OpCode.SETITEM); - } - return builder; + var count = map.Count(); + + if (count == 0) + return builder.Emit(OpCode.NEWMAP); + + foreach (var (key, value) in map.Reverse()) + { + builder.EmitPush(value); + builder.EmitPush(key); + } + builder.EmitPush(count); + return builder.Emit(OpCode.PACKMAP); + } + + /// + /// Emits the opcodes for creating a map. + /// + /// The type of the key of the map. + /// The type of the value of the map. + /// The to be used. + /// The key/value pairs of the map. + /// The same instance as . + public static ScriptBuilder CreateMap(this ScriptBuilder builder, IReadOnlyDictionary map) + where TKey : notnull + where TValue : notnull + { + if (map.Count == 0) + return builder.Emit(OpCode.NEWMAP); + + foreach (var (key, value) in map.Reverse()) + { + builder.EmitPush(value); + builder.EmitPush(key); + } + builder.EmitPush(map.Count); + return builder.Emit(OpCode.PACKMAP); + } + + /// + /// Emits the opcodes for creating a struct. + /// + /// The type of the property. + /// The to be used. + /// The list of properties. + /// The same instance as . + public static ScriptBuilder CreateStruct(this ScriptBuilder builder, IReadOnlyList array) + where T : notnull + { + if (array.Count == 0) + return builder.Emit(OpCode.NEWSTRUCT0); + for (var i = array.Count - 1; i >= 0; i--) + builder.EmitPush(array[i]); + builder.EmitPush(array.Count); + return builder.Emit(OpCode.PACKSTRUCT); } /// @@ -217,6 +263,9 @@ public static ScriptBuilder EmitPush(this ScriptBuilder builder, object obj) case short data: builder.EmitPush(data); break; + case char data: + builder.EmitPush(data); + break; case ushort data: builder.EmitPush(data); break; @@ -316,7 +365,7 @@ private static JObject ToJson(StackItem item, HashSet context, ref in case Array array: { context ??= new HashSet(ReferenceEqualityComparer.Instance); - if (!context.Add(array)) throw new InvalidOperationException(); + if (!context.Add(array)) throw new InvalidOperationException("Circular reference."); maxSize -= 2/*[]*/+ Math.Max(0, (array.Count - 1))/*,*/; JArray a = new(); foreach (StackItem stackItem in array) @@ -349,7 +398,7 @@ private static JObject ToJson(StackItem item, HashSet context, ref in case Map map: { context ??= new HashSet(ReferenceEqualityComparer.Instance); - if (!context.Add(map)) throw new InvalidOperationException(); + if (!context.Add(map)) throw new InvalidOperationException("Circular reference."); maxSize -= 2/*[]*/+ Math.Max(0, (map.Count - 1))/*,*/; JArray a = new(); foreach (var (k, v) in map) diff --git a/src/Neo/Wallets/Helper.cs b/src/Neo/Wallets/Helper.cs index 1273bafc50..9f24e54764 100644 --- a/src/Neo/Wallets/Helper.cs +++ b/src/Neo/Wallets/Helper.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography; +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; @@ -93,7 +94,7 @@ public static long CalculateNetworkFee(this Transaction tx, DataCache snapshot, UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot); // base size for transaction: includes const_header + signers + attributes + script + hashes - int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length), index = -1; + int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + tx.Script.GetVarSize() + UnsafeData.GetVarSize(hashes.Length), index = -1; uint exec_fee_factor = NativeContract.Policy.GetExecFeeFactor(snapshot); long networkFee = 0; foreach (UInt160 hash in hashes) @@ -119,7 +120,8 @@ public static long CalculateNetworkFee(this Transaction tx, DataCache snapshot, { var contract = NativeContract.ContractManagement.GetContract(snapshot, hash); if (contract is null) - throw new ArgumentException($"The smart contract or address {hash} is not found"); + throw new ArgumentException($"The smart contract or address {hash} ({hash.ToAddress(settings.AddressVersion)}) is not found. " + + $"If this is your wallet address and you want to sign a transaction with it, make sure you have opened this wallet."); var md = contract.Manifest.Abi.GetMethod(ContractBasicMethod.Verify, ContractBasicMethod.VerifyPCount); if (md is null) throw new ArgumentException($"The smart contract {contract.Hash} haven't got verify method"); @@ -133,7 +135,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."); @@ -151,7 +153,7 @@ public static long CalculateNetworkFee(this Transaction tx, DataCache snapshot, else if (IsMultiSigContract(witnessScript, out int m, out int n)) { int size_inv = 66 * m; - size += IO.Helper.GetVarSize(size_inv) + size_inv + witnessScript.GetVarSize(); + size += UnsafeData.GetVarSize(size_inv) + size_inv + witnessScript.GetVarSize(); networkFee += exec_fee_factor * MultiSignatureContractCost(m, n); } // We can support more contract types in the future. diff --git a/src/Neo/Wallets/NEP6/NEP6Account.cs b/src/Neo/Wallets/NEP6/NEP6Account.cs index 7998b9ea29..b8e3470ab0 100644 --- a/src/Neo/Wallets/NEP6/NEP6Account.cs +++ b/src/Neo/Wallets/NEP6/NEP6Account.cs @@ -134,7 +134,7 @@ internal void ChangePasswordCommit() } } - internal void ChangePasswordRoolback() + internal void ChangePasswordRollback() { nep2KeyNew = null; } diff --git a/src/Neo/Wallets/NEP6/NEP6Wallet.cs b/src/Neo/Wallets/NEP6/NEP6Wallet.cs index 17a59fc830..c205637a97 100644 --- a/src/Neo/Wallets/NEP6/NEP6Wallet.cs +++ b/src/Neo/Wallets/NEP6/NEP6Wallet.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.Json; using Neo.SmartContract; using System; @@ -16,6 +17,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Security; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Threading.Tasks; @@ -28,7 +30,7 @@ namespace Neo.Wallets.NEP6 /// https://github.com/neo-project/proposals/blob/master/nep-6.mediawiki public class NEP6Wallet : Wallet { - private string password; + private SecureString password; private string name; private Version version; private readonly Dictionary accounts; @@ -55,10 +57,10 @@ public class NEP6Wallet : Wallet /// The name of the wallet. If the wallet is loaded from an existing file, this parameter is ignored. public NEP6Wallet(string path, string password, ProtocolSettings settings, string name = null) : base(path, settings) { - this.password = password; + this.password = password.ToSecureString(); if (File.Exists(path)) { - JObject wallet = (JObject)JToken.Parse(File.ReadAllBytes(path)); + var wallet = (JObject)JToken.Parse(File.ReadAllBytes(path)); LoadFromJson(wallet, out Scrypt, out accounts, out extra); } else @@ -80,7 +82,7 @@ public NEP6Wallet(string path, string password, ProtocolSettings settings, strin /// The JSON object representing the wallet. public NEP6Wallet(string path, string password, ProtocolSettings settings, JObject json) : base(path, settings) { - this.password = password; + this.password = password.ToSecureString(); LoadFromJson(json, out Scrypt, out accounts, out extra); } @@ -91,7 +93,7 @@ private void LoadFromJson(JObject wallet, out ScryptParameters scrypt, out Dicti scrypt = ScryptParameters.FromJson((JObject)wallet["scrypt"]); accounts = ((JArray)wallet["accounts"]).Select(p => NEP6Account.FromJson((JObject)p, this)).ToDictionary(p => p.ScriptHash); extra = wallet["extra"]; - if (!VerifyPasswordInternal(password)) + if (!VerifyPasswordInternal(password.GetClearText())) throw new InvalidOperationException("Wrong password."); } @@ -144,7 +146,7 @@ public override WalletAccount CreateAccount(byte[] privateKey) ParameterNames = new[] { "signature" }, Deployed = false }; - NEP6Account account = new(this, contract.ScriptHash, key, password) + NEP6Account account = new(this, contract.ScriptHash, key, password.GetClearText()) { Contract = contract }; @@ -168,7 +170,7 @@ public override WalletAccount CreateAccount(Contract contract, KeyPair key = nul if (key == null) account = new NEP6Account(this, nep6contract.ScriptHash); else - account = new NEP6Account(this, nep6contract.ScriptHash, key, password); + account = new NEP6Account(this, nep6contract.ScriptHash, key, password.GetClearText()); account.Contract = nep6contract; AddAccount(account); return account; @@ -188,7 +190,7 @@ public override WalletAccount CreateAccount(UInt160 scriptHash) /// The decrypted private key. internal KeyPair DecryptKey(string nep2key) { - return new KeyPair(GetPrivateKeyFromNEP2(nep2key, password, ProtocolSettings.AddressVersion, Scrypt.N, Scrypt.R, Scrypt.P)); + return new KeyPair(GetPrivateKeyFromNEP2(nep2key, password.GetClearText(), ProtocolSettings.AddressVersion, Scrypt.N, Scrypt.R, Scrypt.P)); } public override void Delete() @@ -240,7 +242,7 @@ public override WalletAccount Import(X509Certificate2 cert) ParameterNames = new[] { "signature" }, Deployed = false }; - NEP6Account account = new(this, contract.ScriptHash, key, password) + NEP6Account account = new(this, contract.ScriptHash, key, password.GetClearText()) { Contract = contract }; @@ -258,7 +260,7 @@ public override WalletAccount Import(string wif) ParameterNames = new[] { "signature" }, Deployed = false }; - NEP6Account account = new(this, contract.ScriptHash, key, password) + NEP6Account account = new(this, contract.ScriptHash, key, password.GetClearText()) { Contract = contract }; @@ -291,12 +293,18 @@ public override WalletAccount Import(string nep2, string passphrase, int N = 163 /// public JObject ToJson() { + NEP6Account[] accountValues; + lock (accounts) + { + accountValues = accounts.Values.ToArray(); + } + return new() { ["name"] = name, ["version"] = version.ToString(), ["scrypt"] = Scrypt.ToJson(), - ["accounts"] = accounts.Values.Select(p => p.ToJson()).ToArray(), + ["accounts"] = accountValues.Select(p => p.ToJson()).ToArray(), ["extra"] = extra }; } @@ -308,7 +316,7 @@ public override void Save() public override bool VerifyPassword(string password) { - return this.password == password; + return this.password.GetClearText() == password; } private bool VerifyPasswordInternal(string password) @@ -343,27 +351,29 @@ private bool VerifyPasswordInternal(string password) public override bool ChangePassword(string oldPassword, string newPassword) { bool succeed = true; + NEP6Account[] accountsValues; lock (accounts) { - Parallel.ForEach(accounts.Values, (account, state) => - { - if (!account.ChangePasswordPrepare(oldPassword, newPassword)) - { - state.Stop(); - succeed = false; - } - }); + accountsValues = accounts.Values.ToArray(); } + Parallel.ForEach(accountsValues, (account, state) => + { + if (!account.ChangePasswordPrepare(oldPassword, newPassword)) + { + state.Stop(); + succeed = false; + } + }); if (succeed) { - foreach (NEP6Account account in accounts.Values) + foreach (NEP6Account account in accountsValues) account.ChangePasswordCommit(); - password = newPassword; + password = newPassword.ToSecureString(); } else { - foreach (NEP6Account account in accounts.Values) - account.ChangePasswordRoolback(); + foreach (NEP6Account account in accountsValues) + account.ChangePasswordRollback(); } return succeed; } diff --git a/src/Neo/Wallets/Wallet.cs b/src/Neo/Wallets/Wallet.cs index aca30b008f..fc71a9135c 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) { @@ -635,7 +636,7 @@ public bool Sign(ContractParametersContext context) // Try Smart contract verification - var contract = NativeContract.ContractManagement.GetContract(context.Snapshot, scriptHash); + var contract = NativeContract.ContractManagement.GetContract(context.SnapshotCache, scriptHash); if (contract != null) { diff --git a/src/Plugins/ApplicationLogs/ApplicationLogs.csproj b/src/Plugins/ApplicationLogs/ApplicationLogs.csproj index 0d01281c6a..b0c31a7180 100644 --- a/src/Plugins/ApplicationLogs/ApplicationLogs.csproj +++ b/src/Plugins/ApplicationLogs/ApplicationLogs.csproj @@ -14,9 +14,14 @@ runtime + PreserveNewest + + + + diff --git a/src/Plugins/ApplicationLogs/LogReader.cs b/src/Plugins/ApplicationLogs/LogReader.cs index f9d1470f03..6ef4fcec32 100644 --- a/src/Plugins/ApplicationLogs/LogReader.cs +++ b/src/Plugins/ApplicationLogs/LogReader.cs @@ -30,7 +30,7 @@ public class LogReader : Plugin, ICommittingHandler, ICommittedHandler, ILogHand { #region Globals - private NeoStore _neostore; + internal NeoStore _neostore; private NeoSystem _neosystem; private readonly List _logEvents; @@ -123,7 +123,7 @@ public JToken GetApplicationLog(JArray _params) #region Console Commands [ConsoleCommand("log block", Category = "ApplicationLog Commands")] - private void OnGetBlockCommand(string blockHashOrIndex, string eventName = null) + internal void OnGetBlockCommand(string blockHashOrIndex, string eventName = null) { UInt256 blockhash; if (uint.TryParse(blockHashOrIndex, out var blockIndex)) @@ -143,19 +143,18 @@ 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); } } [ConsoleCommand("log tx", Category = "ApplicationLog Commands")] - private void OnGetTransactionCommand(UInt256 txhash, string eventName = null) + internal void OnGetTransactionCommand(UInt256 txhash, string eventName = null) { var txApplication = string.IsNullOrEmpty(eventName) ? _neostore.GetTransactionLog(txhash) : @@ -168,7 +167,7 @@ private void OnGetTransactionCommand(UInt256 txhash, string eventName = null) } [ConsoleCommand("log contract", Category = "ApplicationLog Commands")] - private void OnGetContractCommand(UInt160 scripthash, uint page = 1, uint pageSize = 1, string eventName = null) + internal void OnGetContractCommand(UInt160 scripthash, uint page = 1, uint pageSize = 1, string eventName = null) { if (page == 0) { diff --git a/src/Plugins/ApplicationLogs/Store/States/BlockLogState.cs b/src/Plugins/ApplicationLogs/Store/States/BlockLogState.cs index 54369a672a..ce957b79f6 100644 --- a/src/Plugins/ApplicationLogs/Store/States/BlockLogState.cs +++ b/src/Plugins/ApplicationLogs/Store/States/BlockLogState.cs @@ -9,7 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -using Neo; +using Neo.Extensions; using Neo.IO; namespace Neo.Plugins.ApplicationLogs.Store.States diff --git a/src/Plugins/ApplicationLogs/Store/States/EngineLogState.cs b/src/Plugins/ApplicationLogs/Store/States/EngineLogState.cs index 96f9041aaf..4a997757f5 100644 --- a/src/Plugins/ApplicationLogs/Store/States/EngineLogState.cs +++ b/src/Plugins/ApplicationLogs/Store/States/EngineLogState.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; namespace Neo.Plugins.ApplicationLogs.Store.States diff --git a/src/Plugins/ApplicationLogs/Store/States/ExecutionLogState.cs b/src/Plugins/ApplicationLogs/Store/States/ExecutionLogState.cs index 210ac36283..ace7f0e42f 100644 --- a/src/Plugins/ApplicationLogs/Store/States/ExecutionLogState.cs +++ b/src/Plugins/ApplicationLogs/Store/States/ExecutionLogState.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.Ledger; using Neo.VM; diff --git a/src/Plugins/ApplicationLogs/Store/States/NotifyLogState.cs b/src/Plugins/ApplicationLogs/Store/States/NotifyLogState.cs index 70b53268e5..87cc5e60ae 100644 --- a/src/Plugins/ApplicationLogs/Store/States/NotifyLogState.cs +++ b/src/Plugins/ApplicationLogs/Store/States/NotifyLogState.cs @@ -9,7 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -using Neo; +using Neo.Extensions; using Neo.IO; using Neo.SmartContract; diff --git a/src/Plugins/ApplicationLogs/Store/States/TransactionEngineLogState.cs b/src/Plugins/ApplicationLogs/Store/States/TransactionEngineLogState.cs index 9417f984e2..80e5be7e97 100644 --- a/src/Plugins/ApplicationLogs/Store/States/TransactionEngineLogState.cs +++ b/src/Plugins/ApplicationLogs/Store/States/TransactionEngineLogState.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; namespace Neo.Plugins.ApplicationLogs.Store.States diff --git a/src/Plugins/ApplicationLogs/Store/States/TransactionLogState.cs b/src/Plugins/ApplicationLogs/Store/States/TransactionLogState.cs index 1667478509..bca012ec50 100644 --- a/src/Plugins/ApplicationLogs/Store/States/TransactionLogState.cs +++ b/src/Plugins/ApplicationLogs/Store/States/TransactionLogState.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; namespace Neo.Plugins.ApplicationLogs.Store.States diff --git a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.Get.cs b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.Get.cs index edffc1cb09..d055ddbb4a 100644 --- a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.Get.cs +++ b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.Get.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.Network.P2P.Payloads; using Neo.Plugins.DBFTPlugin.Messages; using Neo.SmartContract; @@ -110,7 +111,7 @@ internal int GetExpectedBlockSizeWithoutTransactions(int expectedTransactions) sizeof(byte) + // PrimaryIndex UInt160.Length + // NextConsensus 1 + _witnessSize + // Witness - IO.Helper.GetVarSize(expectedTransactions); + UnsafeData.GetVarSize(expectedTransactions); } } } diff --git a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs index ee3b8a7747..a1e9b28bc4 100644 --- a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs +++ b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Plugins.DBFTPlugin.Messages; @@ -166,11 +167,20 @@ public ExtensiblePayload MakePrepareResponse() }); } + // Related to issue https://github.com/neo-project/neo/issues/3431 + // Ref. https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.randomnumbergenerator?view=net-8.0 + // + //The System.Random class relies on a seed value that can be predictable, + //especially if the seed is based on the system clock or other low-entropy sources. + //RandomNumberGenerator, however, uses sources of entropy provided by the operating + //system, which are designed to be unpredictable. private static ulong GetNonce() { - Random _random = new(); Span buffer = stackalloc byte[8]; - _random.NextBytes(buffer); + using (var rng = System.Security.Cryptography.RandomNumberGenerator.Create()) + { + rng.GetBytes(buffer); + } return BinaryPrimitives.ReadUInt64LittleEndian(buffer); } } diff --git a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.cs b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.cs index abd0309783..d6a4340544 100644 --- a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.cs +++ b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.cs @@ -116,7 +116,9 @@ public ConsensusContext(NeoSystem neoSystem, Settings settings, Wallet wallet) this.wallet = wallet; this.neoSystem = neoSystem; dbftSettings = settings; - store = neoSystem.LoadStore(settings.RecoveryLogs); + + if (dbftSettings.IgnoreRecoveryLogs == false) + store = neoSystem.LoadStore(settings.RecoveryLogs); } public Block CreateBlock() @@ -168,7 +170,7 @@ public Block EnsureHeader() public bool Load() { - byte[] data = store.TryGet(ConsensusStateKey); + byte[] data = store?.TryGet(ConsensusStateKey); if (data is null || data.Length == 0) return false; MemoryReader reader = new(data); try @@ -192,7 +194,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 { @@ -272,7 +274,7 @@ public void Reset(byte viewNumber) public void Save() { - store.PutSync(ConsensusStateKey, this.ToArray()); + store?.PutSync(ConsensusStateKey, this.ToArray()); } public void Deserialize(ref MemoryReader reader) diff --git a/src/Plugins/DBFTPlugin/Consensus/ConsensusService.OnMessage.cs b/src/Plugins/DBFTPlugin/Consensus/ConsensusService.OnMessage.cs index 3c6ab8e243..b00fdd9393 100644 --- a/src/Plugins/DBFTPlugin/Consensus/ConsensusService.OnMessage.cs +++ b/src/Plugins/DBFTPlugin/Consensus/ConsensusService.OnMessage.cs @@ -11,6 +11,7 @@ using Akka.Actor; using Neo.Cryptography; +using Neo.Extensions; using Neo.Ledger; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; diff --git a/src/Plugins/DBFTPlugin/Consensus/ConsensusService.cs b/src/Plugins/DBFTPlugin/Consensus/ConsensusService.cs index eb97e7024b..ad1b88feb1 100644 --- a/src/Plugins/DBFTPlugin/Consensus/ConsensusService.cs +++ b/src/Plugins/DBFTPlugin/Consensus/ConsensusService.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Akka.Actor; +using Neo.Extensions; using Neo.IO; using Neo.Ledger; using Neo.Network.P2P; diff --git a/src/Plugins/DBFTPlugin/Messages/ConsensusMessage.cs b/src/Plugins/DBFTPlugin/Messages/ConsensusMessage.cs index de030d166d..f8cd8cbc14 100644 --- a/src/Plugins/DBFTPlugin/Messages/ConsensusMessage.cs +++ b/src/Plugins/DBFTPlugin/Messages/ConsensusMessage.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.Plugins.DBFTPlugin.Types; using System; diff --git a/src/Plugins/DBFTPlugin/Messages/PrepareRequest.cs b/src/Plugins/DBFTPlugin/Messages/PrepareRequest.cs index 495ccbd726..c5b150b572 100644 --- a/src/Plugins/DBFTPlugin/Messages/PrepareRequest.cs +++ b/src/Plugins/DBFTPlugin/Messages/PrepareRequest.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.Plugins.DBFTPlugin.Types; using System; diff --git a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.ChangeViewPayloadCompact.cs b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.ChangeViewPayloadCompact.cs index 2d7283cd22..40528d8aaf 100644 --- a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.ChangeViewPayloadCompact.cs +++ b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.ChangeViewPayloadCompact.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 System; using System.IO; diff --git a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.CommitPayloadCompact.cs b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.CommitPayloadCompact.cs index 6d3880f3c0..dc7d532d30 100644 --- a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.CommitPayloadCompact.cs +++ b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.CommitPayloadCompact.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 System; using System.IO; diff --git a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.PreparationPayloadCompact.cs b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.PreparationPayloadCompact.cs index 8fae1596ef..cec8a45a3a 100644 --- a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.PreparationPayloadCompact.cs +++ b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.PreparationPayloadCompact.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 System; using System.IO; diff --git a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.cs b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.cs index 2de33470ea..2feb919520 100644 --- a/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.cs +++ b/src/Plugins/DBFTPlugin/Messages/RecoveryMessage/RecoveryMessage.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.Network.P2P.Payloads; using Neo.Plugins.DBFTPlugin.Consensus; 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/LevelDBStore/Plugins/Storage/Snapshot.cs b/src/Plugins/LevelDBStore/Plugins/Storage/Snapshot.cs index 0b0a63b885..f66164285b 100644 --- a/src/Plugins/LevelDBStore/Plugins/Storage/Snapshot.cs +++ b/src/Plugins/LevelDBStore/Plugins/Storage/Snapshot.cs @@ -65,5 +65,11 @@ public byte[] TryGet(byte[] key) { return db.Get(options, key); } + + public bool TryGet(byte[] key, out byte[] value) + { + value = db.Get(options, key); + return value != null; + } } } diff --git a/src/Plugins/LevelDBStore/Plugins/Storage/Store.cs b/src/Plugins/LevelDBStore/Plugins/Storage/Store.cs index 27b12a8b64..175b71a8fe 100644 --- a/src/Plugins/LevelDBStore/Plugins/Storage/Store.cs +++ b/src/Plugins/LevelDBStore/Plugins/Storage/Store.cs @@ -63,5 +63,11 @@ public byte[] TryGet(byte[] key) { return db.Get(ReadOptions.Default, key); } + + public bool TryGet(byte[] key, out byte[] value) + { + value = db.Get(ReadOptions.Default, key); + return value != null; + } } } diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Extension.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Extension.cs index 510db49250..0d68564732 100644 --- a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Extension.cs +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Extension.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.SmartContract; using System; diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Leaf.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Leaf.cs index 024a07f8c8..b1b7707729 100644 --- a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Leaf.cs +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.Leaf.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.SmartContract; using System; diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.cs index ef45548645..2e20f03a9d 100644 --- a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.cs +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Node.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 System; using System.IO; @@ -31,11 +32,11 @@ public int Size switch (type) { case NodeType.BranchNode: - return size + BranchSize + IO.Helper.GetVarSize(Reference); + return size + BranchSize + UnsafeData.GetVarSize(Reference); case NodeType.ExtensionNode: - return size + ExtensionSize + IO.Helper.GetVarSize(Reference); + return size + ExtensionSize + UnsafeData.GetVarSize(Reference); case NodeType.LeafNode: - return size + LeafSize + IO.Helper.GetVarSize(Reference); + return size + LeafSize + UnsafeData.GetVarSize(Reference); case NodeType.HashNode: return size + HashSize; case NodeType.Empty: diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Delete.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Delete.cs index 2041100a14..cb1a09bae7 100644 --- a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Delete.cs +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Delete.cs @@ -11,7 +11,6 @@ using System; using System.Collections.Generic; -using static Neo.Helper; namespace Neo.Cryptography.MPTTrie { @@ -57,7 +56,7 @@ private bool TryDelete(ref Node node, ReadOnlySpan path) if (node.Next.Type == NodeType.ExtensionNode) { if (!full) cache.DeleteNode(node.Next.Hash); - node.Key = Concat(node.Key.Span, node.Next.Key.Span); + node.Key = new([.. node.Key.Span, .. node.Next.Key.Span]); node.Next = node.Next.Next; } node.SetDirty(); @@ -107,7 +106,7 @@ private bool TryDelete(ref Node node, ReadOnlySpan path) if (lastChild.Type == NodeType.ExtensionNode) { if (!full) cache.DeleteNode(lastChild.Hash); - lastChild.Key = Concat(childrenIndexes.ToArray(), lastChild.Key.Span); + lastChild.Key = new([.. childrenIndexes.ToArray(), .. lastChild.Key.Span]); lastChild.SetDirty(); cache.PutNode(lastChild); node = lastChild; diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Find.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Find.cs index b3922e8ce8..aeb3a1ec5e 100644 --- a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Find.cs +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Find.cs @@ -12,7 +12,6 @@ using System; using System.Collections.Generic; using System.Linq; -using static Neo.Helper; namespace Neo.Cryptography.MPTTrie { @@ -47,7 +46,7 @@ private ReadOnlySpan Seek(ref Node node, ReadOnlySpan path, out Node start = node; return ReadOnlySpan.Empty; } - return Concat(path[..1], Seek(ref node.Children[path[0]], path[1..], out start)); + return new([.. path[..1], .. Seek(ref node.Children[path[0]], path[1..], out start)]); } case NodeType.ExtensionNode: { @@ -58,7 +57,7 @@ private ReadOnlySpan Seek(ref Node node, ReadOnlySpan path, out Node } if (path.StartsWith(node.Key.Span)) { - return Concat(node.Key.Span, Seek(ref node.Next, path[node.Key.Length..], out start)); + return new([.. node.Key.Span, .. Seek(ref node.Next, path[node.Key.Length..], out start)]); } if (node.Key.Span.StartsWith(path)) { @@ -135,10 +134,10 @@ private ReadOnlySpan Seek(ref Node node, ReadOnlySpan path, out Node for (int i = 0; i < Node.BranchChildCount - 1; i++) { if (from[offset] < i) - foreach (var item in Travers(node.Children[i], Concat(path, new byte[] { (byte)i }), from, from.Length)) + foreach (var item in Travers(node.Children[i], [.. path, .. new byte[] { (byte)i }], from, from.Length)) yield return item; else if (i == from[offset]) - foreach (var item in Travers(node.Children[i], Concat(path, new byte[] { (byte)i }), from, offset + 1)) + foreach (var item in Travers(node.Children[i], [.. path, .. new byte[] { (byte)i }], from, offset + 1)) yield return item; } } @@ -148,7 +147,7 @@ private ReadOnlySpan Seek(ref Node node, ReadOnlySpan path, out Node yield return item; for (int i = 0; i < Node.BranchChildCount - 1; i++) { - foreach (var item in Travers(node.Children[i], Concat(path, new byte[] { (byte)i }), from, offset)) + foreach (var item in Travers(node.Children[i], [.. path, .. new byte[] { (byte)i }], from, offset)) yield return item; } } @@ -157,10 +156,10 @@ private ReadOnlySpan Seek(ref Node node, ReadOnlySpan path, out Node case NodeType.ExtensionNode: { if (offset < from.Length && from.AsSpan()[offset..].StartsWith(node.Key.Span)) - foreach (var item in Travers(node.Next, Concat(path, node.Key.Span), from, offset + node.Key.Length)) + foreach (var item in Travers(node.Next, [.. path, .. node.Key.Span], from, offset + node.Key.Length)) yield return item; else if (from.Length <= offset || 0 < node.Key.Span.CompareTo(from.AsSpan(offset))) - foreach (var item in Travers(node.Next, Concat(path, node.Key.Span), from, from.Length)) + foreach (var item in Travers(node.Next, [.. path, .. node.Key.Span], from, from.Length)) yield return item; break; } diff --git a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Proof.cs b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Proof.cs index e0925452e3..d3c04053b9 100644 --- a/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Proof.cs +++ b/src/Plugins/MPTTrie/Cryptography/MPTTrie/Trie.Proof.cs @@ -13,7 +13,6 @@ using Neo.Persistence; using System; using System.Collections.Generic; -using static Neo.Helper; namespace Neo.Cryptography.MPTTrie { @@ -86,7 +85,7 @@ public static byte[] VerifyProof(UInt256 root, byte[] key, HashSet proof { using var memoryStore = new MemoryStore(); foreach (byte[] data in proof) - memoryStore.Put(Key(Crypto.Hash256(data)), Concat(data, new byte[] { 1 })); + memoryStore.Put(Key(Crypto.Hash256(data)), [.. data, .. new byte[] { 1 }]); using ISnapshot snapshot = memoryStore.GetSnapshot(); var trie = new Trie(snapshot, root, false); return trie[key]; diff --git a/src/Plugins/OracleService/OracleService.cs b/src/Plugins/OracleService/OracleService.cs index a1a3e92eae..f17df8167c 100644 --- a/src/Plugins/OracleService/OracleService.cs +++ b/src/Plugins/OracleService/OracleService.cs @@ -14,6 +14,7 @@ using Neo.ConsoleService; using Neo.Cryptography; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IEventHandlers; using Neo.IO; using Neo.Json; @@ -52,7 +53,7 @@ public class OracleService : Plugin, ICommittingHandler, IServiceAddedHandler, I private readonly ConcurrentDictionary pendingQueue = new ConcurrentDictionary(); private readonly ConcurrentDictionary finishedCache = new ConcurrentDictionary(); private Timer timer; - private readonly CancellationTokenSource cancelSource = new CancellationTokenSource(); + internal readonly CancellationTokenSource cancelSource = new CancellationTokenSource(); private OracleStatus status = OracleStatus.Unstarted; private IWalletProvider walletProvider; private int counter; @@ -122,25 +123,25 @@ private void OnStart() Start(walletProvider?.GetWallet()); } - public void Start(Wallet wallet) + public Task Start(Wallet wallet) { - if (status == OracleStatus.Running) return; + if (status == OracleStatus.Running) return Task.CompletedTask; if (wallet is null) { ConsoleHelper.Warning("Please open wallet first!"); - return; + return Task.CompletedTask; } - if (!CheckOracleAvaiblable(_system.StoreView, out ECPoint[] oracles)) + if (!CheckOracleAvailable(_system.StoreView, out ECPoint[] oracles)) { ConsoleHelper.Warning("The oracle service is unavailable"); - return; + return Task.CompletedTask; } if (!CheckOracleAccount(wallet, oracles)) { ConsoleHelper.Warning("There is no oracle account in wallet"); - return; + return Task.CompletedTask; } this.wallet = wallet; @@ -149,7 +150,7 @@ public void Start(Wallet wallet) status = OracleStatus.Running; timer = new Timer(OnTimer, null, RefreshIntervalMilliSeconds, Timeout.Infinite); ConsoleHelper.Info($"Oracle started"); - ProcessRequestsAsync(); + return ProcessRequestsAsync(); } [ConsoleCommand("stop oracle", Category = "Oracle", Description = "Stop oracle service")] @@ -179,7 +180,7 @@ void ICommittingHandler.Blockchain_Committing_Handler(NeoSystem system, Block bl OnStart(); } if (status != OracleStatus.Running) return; - if (!CheckOracleAvaiblable(snapshot, out ECPoint[] oracles) || !CheckOracleAccount(wallet, oracles)) + if (!CheckOracleAvailable(snapshot, out ECPoint[] oracles) || !CheckOracleAccount(wallet, oracles)) OnStop(); } @@ -236,13 +237,13 @@ public JObject SubmitOracleResponse(JArray _params) finishedCache.ContainsKey(requestId).False_Or(RpcError.OracleRequestFinished); - using (var snapshot = _system.GetSnapshot()) + using (var snapshot = _system.GetSnapshotCache()) { uint height = NativeContract.Ledger.CurrentIndex(snapshot) + 1; var oracles = NativeContract.RoleManagement.GetDesignatedByRole(snapshot, Role.Oracle, height); oracles.Any(p => p.Equals(oraclePub)).True_Or(RpcErrorFactory.OracleNotDesignatedNode(oraclePub)); NativeContract.Oracle.GetRequest(snapshot, requestId).NotNull_Or(RpcError.OracleRequestNotFound); - var data = Neo.Helper.Concat(oraclePub.ToArray(), BitConverter.GetBytes(requestId), txSign); + byte[] data = [.. oraclePub.ToArray(), .. BitConverter.GetBytes(requestId), .. txSign]; Crypto.VerifySignature(data, msgSign, oraclePub).True_Or(RpcErrorFactory.InvalidSignature($"Invalid oracle response transaction signature from '{oraclePub}'.")); AddResponseTxSign(snapshot, requestId, oraclePub, txSign); } @@ -264,7 +265,7 @@ private static async Task SendContentAsync(Uri url, string content) private async Task SendResponseSignatureAsync(ulong requestId, byte[] txSign, KeyPair keyPair) { - var message = Neo.Helper.Concat(keyPair.PublicKey.ToArray(), BitConverter.GetBytes(requestId), txSign); + byte[] message = [.. keyPair.PublicKey.ToArray(), .. BitConverter.GetBytes(requestId), .. txSign]; var sign = Crypto.Sign(message, keyPair.PrivateKey); var param = "\"" + Convert.ToBase64String(keyPair.PublicKey.ToArray()) + "\", " + requestId + ", \"" + Convert.ToBase64String(txSign) + "\",\"" + Convert.ToBase64String(sign) + "\""; var content = "{\"id\":" + Interlocked.Increment(ref counter) + ",\"jsonrpc\":\"2.0\",\"method\":\"submitoracleresponse\",\"params\":[" + param + "]}"; @@ -324,11 +325,11 @@ private async Task ProcessRequestAsync(DataCache snapshot, OracleRequest req) } } - private async void ProcessRequestsAsync() + private async Task ProcessRequestsAsync() { while (!cancelSource.IsCancellationRequested) { - using (var snapshot = _system.GetSnapshot()) + using (var snapshot = _system.GetSnapshotCache()) { SyncPendingQueue(snapshot); foreach (var (id, request) in NativeContract.Oracle.GetRequests(snapshot)) @@ -431,7 +432,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; @@ -445,8 +446,8 @@ public static Transaction CreateResponseTx(DataCache snapshot, OracleRequest req int size_inv = 66 * m; int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Script.GetVarSize() - + IO.Helper.GetVarSize(hashes.Length) + witnessDict[NativeContract.Oracle.Hash].Size - + IO.Helper.GetVarSize(size_inv) + size_inv + oracleSignContract.Script.GetVarSize(); + + UnsafeData.GetVarSize(hashes.Length) + witnessDict[NativeContract.Oracle.Hash].Size + + UnsafeData.GetVarSize(size_inv) + size_inv + oracleSignContract.Script.GetVarSize(); var feePerByte = NativeContract.Policy.GetFeePerByte(snapshot); if (response.Result.Length > OracleResponse.MaxResultSize) @@ -532,7 +533,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(); @@ -552,7 +553,7 @@ private bool CheckTxSign(DataCache snapshot, Transaction tx, ConcurrentDictionar return false; } - private static bool CheckOracleAvaiblable(DataCache snapshot, out ECPoint[] oracles) + private static bool CheckOracleAvailable(DataCache snapshot, out ECPoint[] oracles) { uint height = NativeContract.Ledger.CurrentIndex(snapshot) + 1; oracles = NativeContract.RoleManagement.GetDesignatedByRole(snapshot, Role.Oracle, height); diff --git a/src/Plugins/OracleService/OracleService.csproj b/src/Plugins/OracleService/OracleService.csproj index bb5cde6754..c77ddb75d8 100644 --- a/src/Plugins/OracleService/OracleService.csproj +++ b/src/Plugins/OracleService/OracleService.csproj @@ -24,4 +24,8 @@ + + + + diff --git a/src/Plugins/OracleService/Settings.cs b/src/Plugins/OracleService/Settings.cs index db93c1c400..08c074d363 100644 --- a/src/Plugins/OracleService/Settings.cs +++ b/src/Plugins/OracleService/Settings.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Akka.Util.Internal; using Microsoft.Extensions.Configuration; using System; using System.Linq; @@ -59,6 +60,8 @@ private Settings(IConfigurationSection section) : base(section) MaxOracleTimeout = TimeSpan.FromMilliseconds(section.GetValue("MaxOracleTimeout", 15000)); AllowPrivateHost = section.GetValue("AllowPrivateHost", false); AllowedContentTypes = section.GetSection("AllowedContentTypes").GetChildren().Select(p => p.Get()).ToArray(); + if (AllowedContentTypes.Count() == 0) + AllowedContentTypes = AllowedContentTypes.Concat("application/json").ToArray(); Https = new HttpsSettings(section.GetSection("Https")); NeoFS = new NeoFSSettings(section.GetSection("NeoFS")); AutoStart = section.GetValue("AutoStart", false); diff --git a/src/Plugins/RocksDBStore/Plugins/Storage/Snapshot.cs b/src/Plugins/RocksDBStore/Plugins/Storage/Snapshot.cs index 7423f6ae4a..4da29e41fe 100644 --- a/src/Plugins/RocksDBStore/Plugins/Storage/Snapshot.cs +++ b/src/Plugins/RocksDBStore/Plugins/Storage/Snapshot.cs @@ -73,6 +73,12 @@ public byte[] TryGet(byte[] key) return db.Get(key, readOptions: options); } + public bool TryGet(byte[] key, out byte[] value) + { + value = db.Get(key, readOptions: options); + return value != null; + } + public void Dispose() { snapshot.Dispose(); diff --git a/src/Plugins/RocksDBStore/Plugins/Storage/Store.cs b/src/Plugins/RocksDBStore/Plugins/Storage/Store.cs index ebf160dab0..3f8121ca07 100644 --- a/src/Plugins/RocksDBStore/Plugins/Storage/Store.cs +++ b/src/Plugins/RocksDBStore/Plugins/Storage/Store.cs @@ -59,6 +59,12 @@ public byte[] TryGet(byte[] key) return db.Get(key); } + public bool TryGet(byte[] key, out byte[] value) + { + value = db.Get(key); + return value != null; + } + public void Delete(byte[] key) { db.Remove(key); diff --git a/src/Plugins/RocksDBStore/RocksDBStore.csproj b/src/Plugins/RocksDBStore/RocksDBStore.csproj index d23f089160..90ed2e841a 100644 --- a/src/Plugins/RocksDBStore/RocksDBStore.csproj +++ b/src/Plugins/RocksDBStore/RocksDBStore.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/Plugins/RpcClient/Models/RpcVersion.cs b/src/Plugins/RpcClient/Models/RpcVersion.cs index 430d659f7c..369c206e93 100644 --- a/src/Plugins/RpcClient/Models/RpcVersion.cs +++ b/src/Plugins/RpcClient/Models/RpcVersion.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Cryptography.ECC; using Neo.Json; using System; using System.Collections.Generic; @@ -30,6 +31,8 @@ public class RpcProtocol public int MemoryPoolMaxTransactions { get; set; } public ulong InitialGasDistribution { get; set; } public IReadOnlyDictionary Hardforks { get; set; } + public IReadOnlyList SeedList { get; set; } + public IReadOnlyList StandbyCommittee { get; set; } public JObject ToJson() { @@ -49,6 +52,8 @@ public JObject ToJson() ["name"] = StripPrefix(s.Key.ToString(), "HF_"), ["blockheight"] = s.Value, })); + json["standbycommittee"] = new JArray(StandbyCommittee.Select(u => new JString(u.ToString()))); + json["seedlist"] = new JArray(SeedList.Select(u => new JString(u))); return json; } @@ -71,6 +76,14 @@ public static RpcProtocol FromJson(JObject json) // Add HF_ prefix to the hardfork response for proper Hardfork enum parsing. return new KeyValuePair(Enum.Parse(name.StartsWith("HF_") ? name : $"HF_{name}"), (uint)s["blockheight"].AsNumber()); })), + SeedList = new List(((JArray)json["seedlist"]).Select(s => + { + return s.AsString(); + })), + StandbyCommittee = new List(((JArray)json["standbycommittee"]).Select(s => + { + return ECPoint.Parse(s.AsString(), ECCurve.Secp256r1); + })) }; } diff --git a/src/Plugins/RpcClient/Nep17API.cs b/src/Plugins/RpcClient/Nep17API.cs index 518f470924..0870670231 100644 --- a/src/Plugins/RpcClient/Nep17API.cs +++ b/src/Plugins/RpcClient/Nep17API.cs @@ -19,7 +19,6 @@ using System.Linq; using System.Numerics; using System.Threading.Tasks; -using static Neo.Helper; namespace Neo.Network.RPC { @@ -88,10 +87,10 @@ public async Task TotalSupplyAsync(UInt160 scriptHash) public async Task GetTokenInfoAsync(UInt160 scriptHash) { var contractState = await rpcClient.GetContractStateAsync(scriptHash.ToString()).ConfigureAwait(false); - byte[] script = Concat( - scriptHash.MakeScript("symbol"), - scriptHash.MakeScript("decimals"), - scriptHash.MakeScript("totalSupply")); + byte[] script = [ + .. scriptHash.MakeScript("symbol"), + .. scriptHash.MakeScript("decimals"), + .. scriptHash.MakeScript("totalSupply")]; var name = contractState.Manifest.Name; var result = await rpcClient.InvokeScriptAsync(script).ConfigureAwait(false); var stack = result.Stack; @@ -108,10 +107,10 @@ public async Task GetTokenInfoAsync(UInt160 scriptHash) public async Task GetTokenInfoAsync(string contractHash) { var contractState = await rpcClient.GetContractStateAsync(contractHash).ConfigureAwait(false); - byte[] script = Concat( - contractState.Hash.MakeScript("symbol"), - contractState.Hash.MakeScript("decimals"), - contractState.Hash.MakeScript("totalSupply")); + byte[] script = [ + .. contractState.Hash.MakeScript("symbol"), + .. contractState.Hash.MakeScript("decimals"), + .. contractState.Hash.MakeScript("totalSupply")]; var name = contractState.Manifest.Name; var result = await rpcClient.InvokeScriptAsync(script).ConfigureAwait(false); var stack = result.Stack; diff --git a/src/Plugins/RpcClient/RpcClient.cs b/src/Plugins/RpcClient/RpcClient.cs index 27b0023ec0..7866c7e40f 100644 --- a/src/Plugins/RpcClient/RpcClient.cs +++ b/src/Plugins/RpcClient/RpcClient.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.Json; using Neo.Network.P2P.Payloads; diff --git a/src/Plugins/RpcClient/Utility.cs b/src/Plugins/RpcClient/Utility.cs index 659942f8f8..37331eec1b 100644 --- a/src/Plugins/RpcClient/Utility.cs +++ b/src/Plugins/RpcClient/Utility.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.Json; using Neo.Network.P2P.Payloads; using Neo.Network.P2P.Payloads.Conditions; diff --git a/src/Plugins/RpcServer/Model/BlockHashOrIndex.cs b/src/Plugins/RpcServer/Model/BlockHashOrIndex.cs new file mode 100644 index 0000000000..fe35d9c3e5 --- /dev/null +++ b/src/Plugins/RpcServer/Model/BlockHashOrIndex.cs @@ -0,0 +1,61 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BlockHashOrIndex.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Diagnostics.CodeAnalysis; + +namespace Neo.Plugins.RpcServer.Model; + +public class BlockHashOrIndex +{ + private readonly object _value; + + public BlockHashOrIndex(uint index) + { + _value = index; + } + + public BlockHashOrIndex(UInt256 hash) + { + _value = hash; + } + + public bool IsIndex => _value is uint; + + public static bool TryParse(string value, [NotNullWhen(true)] out BlockHashOrIndex blockHashOrIndex) + { + if (uint.TryParse(value, out var index)) + { + blockHashOrIndex = new BlockHashOrIndex(index); + return true; + } + if (UInt256.TryParse(value, out var hash)) + { + blockHashOrIndex = new BlockHashOrIndex(hash); + return true; + } + blockHashOrIndex = null; + return false; + } + + public uint AsIndex() + { + if (_value is uint intValue) + return intValue; + throw new RpcException(RpcError.InvalidParams.WithData($"Value {_value} is not a valid block index")); + } + + public UInt256 AsHash() + { + if (_value is UInt256 hash) + return hash; + throw new RpcException(RpcError.InvalidParams.WithData($"Value {_value} is not a valid block hash")); + } +} diff --git a/src/Plugins/RpcServer/Model/ContractNameOrHashOrId.cs b/src/Plugins/RpcServer/Model/ContractNameOrHashOrId.cs new file mode 100644 index 0000000000..f1b7c2c92e --- /dev/null +++ b/src/Plugins/RpcServer/Model/ContractNameOrHashOrId.cs @@ -0,0 +1,81 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ContractNameOrHashOrId.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System.Diagnostics.CodeAnalysis; + +namespace Neo.Plugins.RpcServer.Model; + +public class ContractNameOrHashOrId +{ + private readonly object _value; + + public ContractNameOrHashOrId(int id) + { + _value = id; + } + + public ContractNameOrHashOrId(UInt160 hash) + { + _value = hash; + } + + public ContractNameOrHashOrId(string name) + { + _value = name; + } + + public bool IsId => _value is int; + public bool IsHash => _value is UInt160; + public bool IsName => _value is string; + + public static bool TryParse(string value, [NotNullWhen(true)] out ContractNameOrHashOrId contractNameOrHashOrId) + { + if (int.TryParse(value, out var id)) + { + contractNameOrHashOrId = new ContractNameOrHashOrId(id); + return true; + } + if (UInt160.TryParse(value, out var hash)) + { + contractNameOrHashOrId = new ContractNameOrHashOrId(hash); + return true; + } + + if (value.Length > 0) + { + contractNameOrHashOrId = new ContractNameOrHashOrId(value); + return true; + } + contractNameOrHashOrId = null; + return false; + } + + public int AsId() + { + if (_value is int intValue) + return intValue; + throw new RpcException(RpcError.InvalidParams.WithData($"Value {_value} is not a valid contract id")); + } + + public UInt160 AsHash() + { + if (_value is UInt160 hash) + return hash; + throw new RpcException(RpcError.InvalidParams.WithData($"Value {_value} is not a valid contract hash")); + } + + public string AsName() + { + if (_value is string name) + return name; + throw new RpcException(RpcError.InvalidParams.WithData($"Value {_value} is not a valid contract name")); + } +} diff --git a/src/Plugins/RpcServer/ParameterConverter.cs b/src/Plugins/RpcServer/ParameterConverter.cs new file mode 100644 index 0000000000..7d9b754f7c --- /dev/null +++ b/src/Plugins/RpcServer/ParameterConverter.cs @@ -0,0 +1,149 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ParameterConverter.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Json; +using Neo.Plugins.RpcServer.Model; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using JToken = Neo.Json.JToken; + +namespace Neo.Plugins.RpcServer; + +public static class ParameterConverter +{ + private static readonly Dictionary> s_conversionStrategies; + + static ParameterConverter() + { + s_conversionStrategies = new Dictionary> + { + { typeof(string), token => Result.Ok_Or(token.AsString, CreateInvalidParamError(token)) }, + { typeof(byte), ConvertNumeric }, + { typeof(sbyte), ConvertNumeric }, + { typeof(short), ConvertNumeric }, + { typeof(ushort), ConvertNumeric }, + { typeof(int), ConvertNumeric }, + { typeof(uint), ConvertNumeric }, + { typeof(long), ConvertNumeric }, + { typeof(ulong), ConvertNumeric }, + { typeof(double), token => Result.Ok_Or(token.AsNumber, CreateInvalidParamError(token)) }, + { typeof(bool), token => Result.Ok_Or(token.AsBoolean, CreateInvalidParamError(token)) }, + { typeof(UInt256), ConvertUInt256 }, + { typeof(ContractNameOrHashOrId), ConvertContractNameOrHashOrId }, + { typeof(BlockHashOrIndex), ConvertBlockHashOrIndex } + }; + } + + internal static object ConvertParameter(JToken token, Type targetType) + { + if (s_conversionStrategies.TryGetValue(targetType, out var conversionStrategy)) + return conversionStrategy(token); + throw new RpcException(RpcError.InvalidParams.WithData($"Unsupported parameter type: {targetType}")); + } + + private static object ConvertNumeric(JToken token) where T : struct + { + if (TryConvertDoubleToNumericType(token, out var result)) + { + return result; + } + + throw new RpcException(CreateInvalidParamError(token)); + } + + private static bool TryConvertDoubleToNumericType(JToken token, out T result) where T : struct + { + result = default; + try + { + var value = token.AsNumber(); + var minValue = Convert.ToDouble(typeof(T).GetField("MinValue").GetValue(null)); + var maxValue = Convert.ToDouble(typeof(T).GetField("MaxValue").GetValue(null)); + + if (value < minValue || value > maxValue) + { + return false; + } + + if (!typeof(T).IsFloatingPoint() && !IsValidInteger(value)) + { + return false; + } + + result = (T)Convert.ChangeType(value, typeof(T)); + return true; + } + catch + { + return false; + } + } + + private static bool IsValidInteger(double value) + { + // Integer values are safe if they are within the range of MIN_SAFE_INTEGER and MAX_SAFE_INTEGER + if (value < JNumber.MIN_SAFE_INTEGER || value > JNumber.MAX_SAFE_INTEGER) + return false; + return Math.Abs(value % 1) <= double.Epsilon; + } + + internal static object ConvertUInt160(JToken token, byte addressVersion) + { + var value = token.AsString(); + if (UInt160.TryParse(value, out var scriptHash)) + { + return scriptHash; + } + return Result.Ok_Or(() => value.ToScriptHash(addressVersion), + RpcError.InvalidParams.WithData($"Invalid UInt160 Format: {token}")); + } + + private static object ConvertUInt256(JToken token) + { + if (UInt256.TryParse(token.AsString(), out var hash)) + { + return hash; + } + throw new RpcException(RpcError.InvalidParams.WithData($"Invalid UInt256 Format: {token}")); + } + + private static object ConvertContractNameOrHashOrId(JToken token) + { + if (ContractNameOrHashOrId.TryParse(token.AsString(), out var contractNameOrHashOrId)) + { + return contractNameOrHashOrId; + } + throw new RpcException(RpcError.InvalidParams.WithData($"Invalid contract hash or id Format: {token}")); + } + + private static object ConvertBlockHashOrIndex(JToken token) + { + if (BlockHashOrIndex.TryParse(token.AsString(), out var blockHashOrIndex)) + { + return blockHashOrIndex; + } + throw new RpcException(RpcError.InvalidParams.WithData($"Invalid block hash or index Format: {token}")); + } + + private static RpcError CreateInvalidParamError(JToken token) + { + return RpcError.InvalidParams.WithData($"Invalid {typeof(T)} value: {token}"); + } +} + +public static class TypeExtensions +{ + public static bool IsFloatingPoint(this Type type) + { + return type == typeof(float) || type == typeof(double) || type == typeof(decimal); + } +} diff --git a/src/Plugins/RpcServer/Result.cs b/src/Plugins/RpcServer/Result.cs index 9c7ace227c..c76e15153b 100644 --- a/src/Plugins/RpcServer/Result.cs +++ b/src/Plugins/RpcServer/Result.cs @@ -23,7 +23,7 @@ public static class Result /// The return type /// The execution result /// The Rpc exception - public static T Ok_Or(this Func function, RpcError err, bool withData = false) + public static T Ok_Or(Func function, RpcError err, bool withData = false) { try { diff --git a/src/Plugins/RpcServer/RpcError.cs b/src/Plugins/RpcServer/RpcError.cs index 667a3906d4..7f383025a5 100644 --- a/src/Plugins/RpcServer/RpcError.cs +++ b/src/Plugins/RpcServer/RpcError.cs @@ -55,7 +55,7 @@ public class RpcError public static readonly RpcError AlreadyInPool = new(-503, "Already in pool"); public static readonly RpcError InsufficientNetworkFee = new(-504, "Insufficient network fee"); public static readonly RpcError PolicyFailed = new(-505, "Policy check failed"); - public static readonly RpcError InvalidScript = new(-509, "Invalid transaction script"); + public static readonly RpcError InvalidScript = new(-506, "Invalid transaction script"); public static readonly RpcError InvalidAttribute = new(-507, "Invalid transaction attribute"); public static readonly RpcError InvalidSignature = new(-508, "Invalid signature"); public static readonly RpcError InvalidSize = new(-509, "Invalid inventory size"); diff --git a/src/Plugins/RpcServer/RpcErrorFactory.cs b/src/Plugins/RpcServer/RpcErrorFactory.cs index 3d2ac7c9a5..4ab6f04cbf 100644 --- a/src/Plugins/RpcServer/RpcErrorFactory.cs +++ b/src/Plugins/RpcServer/RpcErrorFactory.cs @@ -33,7 +33,7 @@ public static RpcError NewCustomError(int code, string message, string data = nu public static RpcError BadRequest(string data) => RpcError.BadRequest.WithData(data); public static RpcError InsufficientFundsWallet(string data) => RpcError.InsufficientFundsWallet.WithData(data); public static RpcError VerificationFailed(string data) => RpcError.VerificationFailed.WithData(data); - public static RpcError InvalidContractVerification(UInt160 contractHash) => RpcError.InvalidContractVerification.WithData($"The smart contract {contractHash} haven't got verify method."); + public static RpcError InvalidContractVerification(UInt160 contractHash, int pcount) => RpcError.InvalidContractVerification.WithData($"The smart contract {contractHash} haven't got verify method with {pcount} input parameters."); public static RpcError InvalidContractVerification(string data) => RpcError.InvalidContractVerification.WithData(data); public static RpcError InvalidSignature(string data) => RpcError.InvalidSignature.WithData(data); public static RpcError OracleNotDesignatedNode(ECPoint oraclePub) => RpcError.OracleNotDesignatedNode.WithData($"{oraclePub} isn't an oracle node."); diff --git a/src/Plugins/RpcServer/RpcMethodWithParamsAttribute.cs b/src/Plugins/RpcServer/RpcMethodWithParamsAttribute.cs new file mode 100644 index 0000000000..c53dd1fd85 --- /dev/null +++ b/src/Plugins/RpcServer/RpcMethodWithParamsAttribute.cs @@ -0,0 +1,20 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// RpcMethodWithParamsAttribute.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; + +namespace Neo.Plugins.RpcServer; + +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] +public class RpcMethodWithParamsAttribute : Attribute +{ + public string Name { get; set; } +} diff --git a/src/Plugins/RpcServer/RpcServer.Blockchain.cs b/src/Plugins/RpcServer/RpcServer.Blockchain.cs index 4abddbc183..446f1b4f16 100644 --- a/src/Plugins/RpcServer/RpcServer.Blockchain.cs +++ b/src/Plugins/RpcServer/RpcServer.Blockchain.cs @@ -9,9 +9,11 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.Network.P2P.Payloads; +using Neo.Plugins.RpcServer.Model; using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.VM; @@ -27,10 +29,9 @@ 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 internal virtual JToken GetBestBlockHash(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetBestBlockHash() { return NativeContract.Ledger.CurrentHash(system.StoreView).ToString(); } @@ -38,29 +39,15 @@ protected internal virtual JToken GetBestBlockHash(JArray _params) /// /// 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 hash or index. + /// Optional, the default value is false. /// 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 internal virtual JToken GetBlock(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetBlock(BlockHashOrIndex blockHashOrIndex, bool verbose = false) { - JToken key = Result.Ok_Or(() => _params[0], RpcError.InvalidParams.WithData($"Invalid Block Hash or Index: {_params[0]}")); - bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); - using var snapshot = system.GetSnapshot(); - Block block; - if (key is JNumber) - { - uint index = uint.Parse(key.AsString()); - block = NativeContract.Ledger.GetBlock(snapshot, index); - } - else - { - UInt256 hash = Result.Ok_Or(() => UInt256.Parse(key.AsString()), RpcError.InvalidParams.WithData($"Invalid block hash {_params[0]}")); - block = NativeContract.Ledger.GetBlock(snapshot, hash); - } + using var snapshot = system.GetSnapshotCache(); + var block = blockHashOrIndex.IsIndex ? NativeContract.Ledger.GetBlock(snapshot, blockHashOrIndex.AsIndex()) : NativeContract.Ledger.GetBlock(snapshot, blockHashOrIndex.AsHash()); block.NotNull_Or(RpcError.UnknownBlock); if (verbose) { @@ -77,10 +64,9 @@ protected internal virtual JToken GetBlock(JArray _params) /// /// 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) + [RpcMethodWithParams] + internal virtual JToken GetBlockHeaderCount() { return (system.HeaderCache.Last?.Index ?? NativeContract.Ledger.CurrentIndex(system.StoreView)) + 1; } @@ -88,10 +74,9 @@ internal virtual JToken GetBlockHeaderCount(JArray _params) /// /// Gets the number of blocks in the blockchain. /// - /// An empty array; no parameters are required. /// The count of blocks as a . - [RpcMethod] - protected internal virtual JToken GetBlockCount(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetBlockCount() { return NativeContract.Ledger.CurrentIndex(system.StoreView) + 1; } @@ -99,12 +84,11 @@ protected internal virtual JToken GetBlockCount(JArray _params) /// /// Gets the hash of the block at the specified height. /// - /// An array containing the block height as the first element. + /// Block index (block height) /// The hash of the block at the specified height as a . - [RpcMethod] - protected internal virtual JToken GetBlockHash(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetBlockHash(uint height) { - uint height = Result.Ok_Or(() => uint.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Height: {_params[0]}")); var snapshot = system.StoreView; if (height <= NativeContract.Ledger.CurrentIndex(snapshot)) { @@ -116,27 +100,26 @@ protected internal virtual JToken GetBlockHash(JArray _params) /// /// 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 script hash or index (i.e. block height=number of blocks - 1). + /// Optional, the default value is false. + /// + /// When verbose is false, serialized information of the block is returned in a hexadecimal string. + /// If you need the detailed information, use the SDK for deserialization. + /// When verbose is true or 1, detailed information of the block is returned in Json format. + /// /// The block header data as a . In json format if the second item of _params is true, otherwise Base64-encoded byte array. - [RpcMethod] - protected internal virtual JToken GetBlockHeader(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetBlockHeader(BlockHashOrIndex blockHashOrIndex, bool verbose = false) { - JToken key = _params[0]; - bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); var snapshot = system.StoreView; Header header; - if (key is JNumber) + if (blockHashOrIndex.IsIndex) { - uint height = uint.Parse(key.AsString()); - header = NativeContract.Ledger.GetHeader(snapshot, height).NotNull_Or(RpcError.UnknownBlock); + header = NativeContract.Ledger.GetHeader(snapshot, blockHashOrIndex.AsIndex()).NotNull_Or(RpcError.UnknownBlock); } else { - UInt256 hash = Result.Ok_Or(() => UInt256.Parse(key.AsString()), RpcError.InvalidParams.WithData($"Invalid block hash {_params[0]}")); - header = NativeContract.Ledger.GetHeader(snapshot, hash).NotNull_Or(RpcError.UnknownBlock); + header = NativeContract.Ledger.GetHeader(snapshot, blockHashOrIndex.AsHash()).NotNull_Or(RpcError.UnknownBlock); } if (verbose) { @@ -154,19 +137,19 @@ protected internal virtual JToken GetBlockHeader(JArray _params) /// /// 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. + /// Contract name or script hash or the native contract id. /// The contract state in json format as a . - [RpcMethod] - protected internal virtual JToken GetContractState(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetContractState(ContractNameOrHashOrId contractNameOrHashOrId) { - if (int.TryParse(_params[0].AsString(), out int contractId)) + if (contractNameOrHashOrId.IsId) { - var contractState = NativeContract.ContractManagement.GetContractById(system.StoreView, contractId); + var contractState = NativeContract.ContractManagement.GetContractById(system.StoreView, contractNameOrHashOrId.AsId()); return contractState.NotNull_Or(RpcError.UnknownContract).ToJson(); } - var scriptHash = Result.Ok_Or(() => ToScriptHash(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid contract hash {_params[0]}")); - var contract = NativeContract.ContractManagement.GetContract(system.StoreView, scriptHash); + var hash = contractNameOrHashOrId.IsName ? ToScriptHash(contractNameOrHashOrId.AsName()) : contractNameOrHashOrId.AsHash(); + var contract = NativeContract.ContractManagement.GetContract(system.StoreView, hash); return contract.NotNull_Or(RpcError.UnknownContract).ToJson(); } @@ -184,12 +167,11 @@ private static UInt160 ToScriptHash(string keyword) /// /// Gets the current memory pool transactions. /// - /// An array containing an optional boolean indicating whether to include unverified transactions. + /// Optional, the default value is false. /// The memory pool transactions in json format as a . - [RpcMethod] - protected internal virtual JToken GetRawMemPool(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetRawMemPool(bool shouldGetUnverified = false) { - bool shouldGetUnverified = _params.Count >= 1 && _params[0].AsBoolean(); if (!shouldGetUnverified) return new JArray(system.MemPool.GetVerifiedTransactions().Select(p => (JToken)p.Hash.ToString())); @@ -206,27 +188,23 @@ protected internal virtual JToken GetRawMemPool(JArray _params) /// /// 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 internal virtual JToken GetRawTransaction(JArray _params) + /// The transaction hash. + /// Optional, the default value is false. + /// The transaction data as a . In json format if verbose is true, otherwise base64string. + [RpcMethodWithParams] + protected internal virtual JToken GetRawTransaction(UInt256 hash, bool verbose = false) { - UInt256 hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Transaction Hash: {_params[0]}")); - bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); - if (system.MemPool.TryGetValue(hash, out Transaction tx) && !verbose) + if (system.MemPool.TryGetValue(hash, out var tx) && !verbose) return Convert.ToBase64String(tx.ToArray()); var snapshot = system.StoreView; - TransactionState state = NativeContract.Ledger.GetTransactionState(snapshot, hash); + var state = NativeContract.Ledger.GetTransactionState(snapshot, hash); tx ??= state?.Transaction; tx.NotNull_Or(RpcError.UnknownTransaction); if (!verbose) return Convert.ToBase64String(tx.ToArray()); - JObject json = Utility.TransactionToJson(tx, system.Settings); + var json = Utility.TransactionToJson(tx, system.Settings); if (state is not null) { - TrimmedBlock block = NativeContract.Ledger.GetTrimmedBlock(snapshot, NativeContract.Ledger.GetBlockHash(snapshot, state.BlockIndex)); + var block = NativeContract.Ledger.GetTrimmedBlock(snapshot, NativeContract.Ledger.GetBlockHash(snapshot, state.BlockIndex)); json["blockhash"] = block.Hash.ToString(); json["confirmations"] = NativeContract.Ledger.CurrentIndex(snapshot) - block.Index + 1; json["blocktime"] = block.Header.Timestamp; @@ -237,23 +215,26 @@ protected internal virtual JToken GetRawTransaction(JArray _params) /// /// 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 contract ID or script hash. + /// The Base64-encoded storage key. /// The storage item as a . - [RpcMethod] - protected internal virtual JToken GetStorage(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetStorage(ContractNameOrHashOrId contractNameOrHashOrId, string base64Key) { - using var snapshot = system.GetSnapshot(); - if (!int.TryParse(_params[0].AsString(), out int id)) + using var snapshot = system.GetSnapshotCache(); + int id; + if (contractNameOrHashOrId.IsHash) { - UInt160 hash = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid contract hash {_params[0]}")); - ContractState contract = NativeContract.ContractManagement.GetContract(snapshot, hash).NotNull_Or(RpcError.UnknownContract); + var hash = contractNameOrHashOrId.AsHash(); + var contract = NativeContract.ContractManagement.GetContract(snapshot, hash).NotNull_Or(RpcError.UnknownContract); id = contract.Id; } - byte[] key = Convert.FromBase64String(_params[1].AsString()); - StorageItem item = snapshot.TryGet(new StorageKey + else + { + id = contractNameOrHashOrId.AsId(); + } + var key = Convert.FromBase64String(base64Key); + var item = snapshot.TryGet(new StorageKey { Id = id, Key = key @@ -264,31 +245,28 @@ protected internal virtual JToken GetStorage(JArray _params) /// /// 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 contract ID (int) or script hash (UInt160). + /// The Base64-encoded storage key prefix. + /// The start index. /// The found storage items as a . - [RpcMethod] - protected internal virtual JToken FindStorage(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken FindStorage(ContractNameOrHashOrId contractNameOrHashOrId, string base64KeyPrefix, int start = 0) { - using var snapshot = system.GetSnapshot(); - if (!int.TryParse(_params[0].AsString(), out int id)) + using var snapshot = system.GetSnapshotCache(); + int id; + if (contractNameOrHashOrId.IsHash) { - UInt160 hash = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid contract hash {_params[0]}")); - ContractState contract = NativeContract.ContractManagement.GetContract(snapshot, hash).NotNull_Or(RpcError.UnknownContract); + ContractState contract = NativeContract.ContractManagement.GetContract(snapshot, contractNameOrHashOrId.AsHash()).NotNull_Or(RpcError.UnknownContract); id = contract.Id; } - - byte[] prefix = Result.Ok_Or(() => Convert.FromBase64String(_params[1].AsString()), RpcError.InvalidParams.WithData($"Invalid Base64 string{_params[1]}")); - byte[] prefix_key = StorageKey.CreateSearchPrefix(id, prefix); - - if (!int.TryParse(_params[2].AsString(), out int start)) + else { - start = 0; + id = contractNameOrHashOrId.AsId(); } + byte[] prefix = Result.Ok_Or(() => Convert.FromBase64String(base64KeyPrefix), RpcError.InvalidParams.WithData($"Invalid Base64 string{base64KeyPrefix}")); + byte[] prefix_key = StorageKey.CreateSearchPrefix(id, prefix); + JObject json = new(); JArray jarr = new(); int pageSize = settings.FindStoragePageSize; @@ -322,12 +300,11 @@ protected internal virtual JToken FindStorage(JArray _params) /// /// Gets the height of a transaction by its hash. /// - /// An array containing the transaction hash as the first element. + /// The transaction hash. /// The height of the transaction as a . - [RpcMethod] - protected internal virtual JToken GetTransactionHeight(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetTransactionHeight(UInt256 hash) { - UInt256 hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Transaction Hash: {_params[0]}")); uint? height = NativeContract.Ledger.GetTransactionState(system.StoreView, hash)?.BlockIndex; if (height.HasValue) return height.Value; throw new RpcException(RpcError.UnknownTransaction); @@ -336,12 +313,11 @@ protected internal virtual JToken GetTransactionHeight(JArray _params) /// /// Gets the next block validators. /// - /// An empty array; no parameters are required. /// The next block validators as a . - [RpcMethod] - protected internal virtual JToken GetNextBlockValidators(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetNextBlockValidators() { - using var snapshot = system.GetSnapshot(); + using var snapshot = system.GetSnapshotCache(); var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, system.Settings.ValidatorsCount); return validators.Select(p => { @@ -355,12 +331,11 @@ protected internal virtual JToken GetNextBlockValidators(JArray _params) /// /// 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 internal virtual JToken GetCandidates(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetCandidates() { - using var snapshot = system.GetSnapshot(); + using var snapshot = system.GetSnapshotCache(); byte[] script; using (ScriptBuilder sb = new()) { @@ -412,10 +387,9 @@ protected internal virtual JToken GetCandidates(JArray _params) /// /// Gets the list of committee members. /// - /// An empty array; no parameters are required. /// The committee members publickeys as a . - [RpcMethod] - protected internal virtual JToken GetCommittee(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetCommittee() { return new JArray(NativeContract.NEO.GetCommittee(system.StoreView).Select(p => (JToken)p.ToString())); } @@ -423,10 +397,9 @@ protected internal virtual JToken GetCommittee(JArray _params) /// /// Gets the list of native contracts. /// - /// An empty array; no parameters are required. /// The native contract states as a . - [RpcMethod] - protected internal virtual JToken GetNativeContracts(JArray _params) + [RpcMethodWithParams] + protected internal virtual JToken GetNativeContracts() { 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..23c731d01e 100644 --- a/src/Plugins/RpcServer/RpcServer.Node.cs +++ b/src/Plugins/RpcServer/RpcServer.Node.cs @@ -23,14 +23,23 @@ namespace Neo.Plugins.RpcServer { partial class RpcServer { - [RpcMethod] - protected virtual JToken GetConnectionCount(JArray _params) + + /// + /// Gets the current number of connections to the node. + /// + /// The number of connections as a JToken. + [RpcMethodWithParams] + protected internal virtual JToken GetConnectionCount() { return localNode.ConnectedCount; } - [RpcMethod] - protected virtual JToken GetPeers(JArray _params) + /// + /// Gets information about the peers connected to the node. + /// + /// A JObject containing information about unconnected, bad, and connected peers. + [RpcMethodWithParams] + protected internal virtual JToken GetPeers() { JObject json = new(); json["unconnected"] = new JArray(localNode.GetUnconnectedPeers().Select(p => @@ -51,9 +60,14 @@ protected virtual JToken GetPeers(JArray _params) return json; } + /// + /// Processes the result of a transaction or block relay and returns appropriate response or throws an exception. + /// + /// The verification result of the relay. + /// The hash of the transaction or block. + /// A JObject containing the hash if successful, otherwise throws an RpcException. private static JObject GetRelayResult(VerifyResult reason, UInt256 hash) { - switch (reason) { case VerifyResult.Succeed: @@ -109,8 +123,12 @@ private static JObject GetRelayResult(VerifyResult reason, UInt256 hash) } } - [RpcMethod] - protected virtual JToken GetVersion(JArray _params) + /// + /// Gets version information about the node, including network, protocol, and RPC settings. + /// + /// A JObject containing detailed version and configuration information. + [RpcMethodWithParams] + protected internal virtual JToken GetVersion() { JObject json = new(); json["tcpport"] = localNode.ListenerTcpPort; @@ -139,28 +157,46 @@ protected virtual JToken GetVersion(JArray _params) forkJson["blockheight"] = hf.Value; return forkJson; })); + protocol["standbycommittee"] = new JArray(system.Settings.StandbyCommittee.Select(u => new JString(u.ToString()))); + protocol["seedlist"] = new JArray(system.Settings.SeedList.Select(u => new JString(u))); json["rpc"] = rpc; json["protocol"] = protocol; return json; } + /// + /// Removes a specified prefix from a string if it exists. + /// + /// The input string. + /// The prefix to remove. + /// The string with the prefix removed if it existed, otherwise the original string. private static string StripPrefix(string s, string prefix) { return s.StartsWith(prefix) ? s.Substring(prefix.Length) : s; } - [RpcMethod] - protected virtual JToken SendRawTransaction(JArray _params) + /// + /// Sends a raw transaction to the network. + /// + /// The base64-encoded transaction. + /// A JToken containing the result of the transaction relay. + [RpcMethodWithParams] + protected internal virtual JToken SendRawTransaction(string base64Tx) { - Transaction tx = Result.Ok_Or(() => Convert.FromBase64String(_params[0].AsString()).AsSerializable(), RpcError.InvalidParams.WithData($"Invalid Transaction Format: {_params[0]}")); + Transaction tx = Result.Ok_Or(() => Convert.FromBase64String(base64Tx).AsSerializable(), RpcError.InvalidParams.WithData($"Invalid Transaction Format: {base64Tx}")); RelayResult reason = system.Blockchain.Ask(tx).Result; return GetRelayResult(reason.Result, tx.Hash); } - [RpcMethod] - protected virtual JToken SubmitBlock(JArray _params) + /// + /// Submits a new block to the network. + /// + /// The base64-encoded block. + /// A JToken containing the result of the block submission. + [RpcMethodWithParams] + protected internal virtual JToken SubmitBlock(string base64Block) { - Block block = Result.Ok_Or(() => Convert.FromBase64String(_params[0].AsString()).AsSerializable(), RpcError.InvalidParams.WithData($"Invalid Block Format: {_params[0]}")); + Block block = Result.Ok_Or(() => Convert.FromBase64String(base64Block).AsSerializable(), RpcError.InvalidParams.WithData($"Invalid Block Format: {base64Block}")); RelayResult reason = system.Blockchain.Ask(block).Result; return GetRelayResult(reason.Result, block.Hash); } diff --git a/src/Plugins/RpcServer/RpcServer.SmartContract.cs b/src/Plugins/RpcServer/RpcServer.SmartContract.cs index 2fe49d4965..11e1218e57 100644 --- a/src/Plugins/RpcServer/RpcServer.SmartContract.cs +++ b/src/Plugins/RpcServer/RpcServer.SmartContract.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.Network.P2P.Payloads; @@ -39,7 +40,7 @@ private void Initialize_SmartContract() timer = new(OnTimer, null, settings.SessionExpirationTime, settings.SessionExpirationTime); } - private void Dispose_SmartContract() + internal void Dispose_SmartContract() { timer?.Dispose(); Session[] toBeDestroyed; @@ -52,7 +53,7 @@ private void Dispose_SmartContract() session.Dispose(); } - private void OnTimer(object state) + internal void OnTimer(object state) { List<(Guid Id, Session Session)> toBeDestroyed = new(); lock (sessions) @@ -92,7 +93,7 @@ private JObject GetInvokeResult(byte[] script, Signer[] signers = null, Witness[ json["diagnostics"] = new JObject() { ["invokedcontracts"] = ToJson(diagnostic.InvocationTree.Root), - ["storagechanges"] = ToJson(session.Engine.Snapshot.GetChangeSet()) + ["storagechanges"] = ToJson(session.Engine.SnapshotCache.GetChangeSet()) }; } var stack = new JArray(); @@ -189,7 +190,7 @@ private static Signer[] SignersFromJson(JArray _params, ProtocolSettings setting // Validate format - _ = IO.Helper.ToByteArray(ret).AsSerializableArray(); + _ = ret.ToByteArray().AsSerializableArray(); return ret; } @@ -213,7 +214,7 @@ private static Witness[] WitnessesFromJson(JArray _params) } [RpcMethod] - protected virtual JToken InvokeFunction(JArray _params) + protected internal virtual JToken InvokeFunction(JArray _params) { UInt160 script_hash = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid script hash {nameof(script_hash)}")); string operation = Result.Ok_Or(() => _params[1].AsString(), RpcError.InvalidParams); @@ -231,7 +232,7 @@ protected virtual JToken InvokeFunction(JArray _params) } [RpcMethod] - protected virtual JToken InvokeScript(JArray _params) + protected internal virtual JToken InvokeScript(JArray _params) { byte[] script = Result.Ok_Or(() => Convert.FromBase64String(_params[0].AsString()), RpcError.InvalidParams); Signer[] signers = _params.Count >= 2 ? SignersFromJson((JArray)_params[1], system.Settings) : null; @@ -241,7 +242,7 @@ protected virtual JToken InvokeScript(JArray _params) } [RpcMethod] - protected virtual JToken TraverseIterator(JArray _params) + protected internal virtual JToken TraverseIterator(JArray _params) { settings.SessionEnabled.True_Or(RpcError.SessionsDisabled); Guid sid = Result.Ok_Or(() => Guid.Parse(_params[0].GetString()), RpcError.InvalidParams.WithData($"Invalid session id {nameof(sid)}")); @@ -262,7 +263,7 @@ protected virtual JToken TraverseIterator(JArray _params) } [RpcMethod] - protected virtual JToken TerminateSession(JArray _params) + protected internal virtual JToken TerminateSession(JArray _params) { settings.SessionEnabled.True_Or(RpcError.SessionsDisabled); Guid sid = Result.Ok_Or(() => Guid.Parse(_params[0].GetString()), RpcError.InvalidParams.WithData("Invalid session id")); @@ -278,7 +279,7 @@ protected virtual JToken TerminateSession(JArray _params) } [RpcMethod] - protected virtual JToken GetUnclaimedGas(JArray _params) + protected internal virtual JToken GetUnclaimedGas(JArray _params) { string address = Result.Ok_Or(() => _params[0].AsString(), RpcError.InvalidParams.WithData($"Invalid address {nameof(address)}")); JObject json = new(); diff --git a/src/Plugins/RpcServer/RpcServer.Utilities.cs b/src/Plugins/RpcServer/RpcServer.Utilities.cs index f9b874c824..e08dfd28ee 100644 --- a/src/Plugins/RpcServer/RpcServer.Utilities.cs +++ b/src/Plugins/RpcServer/RpcServer.Utilities.cs @@ -18,7 +18,7 @@ namespace Neo.Plugins.RpcServer partial class RpcServer { [RpcMethod] - protected virtual JToken ListPlugins(JArray _params) + protected internal virtual JToken ListPlugins(JArray _params) { return new JArray(Plugin.Plugins .OrderBy(u => u.Name) @@ -34,7 +34,7 @@ protected virtual JToken ListPlugins(JArray _params) } [RpcMethod] - protected virtual JToken ValidateAddress(JArray _params) + protected internal virtual JToken ValidateAddress(JArray _params) { string address = Result.Ok_Or(() => _params[0].AsString(), RpcError.InvalidParams.WithData($"Invlid address format: {_params[0]}")); JObject json = new(); diff --git a/src/Plugins/RpcServer/RpcServer.Wallet.cs b/src/Plugins/RpcServer/RpcServer.Wallet.cs index 9083f916c6..25a7333aac 100644 --- a/src/Plugins/RpcServer/RpcServer.Wallet.cs +++ b/src/Plugins/RpcServer/RpcServer.Wallet.cs @@ -48,22 +48,36 @@ public override void Delete() { } public override void Save() { } } - protected Wallet wallet; + protected internal Wallet wallet; + /// + /// Checks if a wallet is open and throws an error if not. + /// private void CheckWallet() { wallet.NotNull_Or(RpcError.NoOpenedWallet); } + /// + /// Closes the currently opened wallet. + /// + /// An empty array. + /// Returns true if the wallet was successfully closed. [RpcMethod] - protected virtual JToken CloseWallet(JArray _params) + protected internal virtual JToken CloseWallet(JArray _params) { wallet = null; return true; } + /// + /// Exports the private key of a specified address. + /// + /// An array containing the address as a string. + /// The exported private key as a string. + /// Thrown when no wallet is open or the address is invalid. [RpcMethod] - protected virtual JToken DumpPrivKey(JArray _params) + protected internal virtual JToken DumpPrivKey(JArray _params) { CheckWallet(); UInt160 scriptHash = AddressToScriptHash(_params[0].AsString(), system.Settings.AddressVersion); @@ -71,8 +85,14 @@ protected virtual JToken DumpPrivKey(JArray _params) return account.GetKey().Export(); } + /// + /// Creates a new address in the wallet. + /// + /// An empty array. + /// The newly created address as a string. + /// Thrown when no wallet is open. [RpcMethod] - protected virtual JToken GetNewAddress(JArray _params) + protected internal virtual JToken GetNewAddress(JArray _params) { CheckWallet(); WalletAccount account = wallet.CreateAccount(); @@ -81,8 +101,14 @@ protected virtual JToken GetNewAddress(JArray _params) return account.Address; } + /// + /// Gets the balance of a specified asset in the wallet. + /// + /// An array containing the asset ID as a string. + /// A JSON object containing the balance of the specified asset. + /// Thrown when no wallet is open or the asset ID is invalid. [RpcMethod] - protected virtual JToken GetWalletBalance(JArray _params) + protected internal virtual JToken GetWalletBalance(JArray _params) { CheckWallet(); UInt160 asset_id = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid asset id: {_params[0]}")); @@ -91,13 +117,19 @@ protected virtual JToken GetWalletBalance(JArray _params) return json; } + /// + /// Gets the amount of unclaimed GAS in the wallet. + /// + /// An empty array. + /// The amount of unclaimed GAS as a string. + /// Thrown when no wallet is open. [RpcMethod] - protected virtual JToken GetWalletUnclaimedGas(JArray _params) + protected internal virtual JToken GetWalletUnclaimedGas(JArray _params) { CheckWallet(); // Datoshi is the smallest unit of GAS, 1 GAS = 10^8 Datoshi BigInteger datoshi = BigInteger.Zero; - using (var snapshot = system.GetSnapshot()) + using (var snapshot = system.GetSnapshotCache()) { uint height = NativeContract.Ledger.CurrentIndex(snapshot) + 1; foreach (UInt160 account in wallet.GetAccounts().Select(p => p.ScriptHash)) @@ -106,8 +138,14 @@ protected virtual JToken GetWalletUnclaimedGas(JArray _params) return datoshi.ToString(); } + /// + /// Imports a private key into the wallet. + /// + /// An array containing the private key as a string. + /// A JSON object containing information about the imported account. + /// Thrown when no wallet is open or the private key is invalid. [RpcMethod] - protected virtual JToken ImportPrivKey(JArray _params) + protected internal virtual JToken ImportPrivKey(JArray _params) { CheckWallet(); string privkey = _params[0].AsString(); @@ -123,10 +161,20 @@ protected virtual JToken ImportPrivKey(JArray _params) }; } + /// + /// Calculates the network fee for a given transaction. + /// + /// An array containing the Base64-encoded serialized transaction. + /// A JSON object containing the calculated network fee. + /// Thrown when the input parameters are invalid or the transaction is malformed. [RpcMethod] - protected virtual JToken CalculateNetworkFee(JArray _params) + protected internal virtual JToken CalculateNetworkFee(JArray _params) { - var tx = Convert.FromBase64String(_params[0].AsString()); + if (_params.Count == 0) + { + throw new RpcException(RpcError.InvalidParams.WithData("Params array is empty, need a raw transaction.")); + } + var tx = Result.Ok_Or(() => Convert.FromBase64String(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid tx: {_params[0]}")); ; JObject account = new(); var networkfee = Wallets.Helper.CalculateNetworkFee( @@ -136,8 +184,14 @@ protected virtual JToken CalculateNetworkFee(JArray _params) return account; } + /// + /// Lists all addresses in the wallet. + /// + /// An empty array. + /// An array of JSON objects, each containing information about an address in the wallet. + /// Thrown when no wallet is open. [RpcMethod] - protected virtual JToken ListAddress(JArray _params) + protected internal virtual JToken ListAddress(JArray _params) { CheckWallet(); return wallet.GetAccounts().Select(p => @@ -151,16 +205,39 @@ protected virtual JToken ListAddress(JArray _params) }).ToArray(); } + /// + /// Opens a wallet file. + /// + /// An array containing the wallet path and password. + /// Returns true if the wallet was successfully opened. + /// Thrown when the wallet file is not found, the wallet is not supported, or the password is invalid. [RpcMethod] - protected virtual JToken OpenWallet(JArray _params) + protected internal virtual JToken OpenWallet(JArray _params) { string path = _params[0].AsString(); string password = _params[1].AsString(); File.Exists(path).True_Or(RpcError.WalletNotFound); - wallet = Wallet.Open(path, password, system.Settings).NotNull_Or(RpcError.WalletNotSupported); + try + { + wallet = Wallet.Open(path, password, system.Settings).NotNull_Or(RpcError.WalletNotSupported); + } + catch (NullReferenceException) + { + throw new RpcException(RpcError.WalletNotSupported); + } + catch (InvalidOperationException) + { + throw new RpcException(RpcError.WalletNotSupported.WithData("Invalid password.")); + } + return true; } + /// + /// Processes the result of an invocation with wallet for signing. + /// + /// The result object to process. + /// Optional signers for the transaction. private void ProcessInvokeWithWallet(JObject result, Signer[] signers = null) { if (wallet == null || signers == null || signers.Length == 0) return; @@ -189,20 +266,26 @@ private void ProcessInvokeWithWallet(JObject result, Signer[] signers = null) } } + /// + /// Transfers an asset from a specific address to another address. + /// + /// An array containing asset ID, from address, to address, amount, and optional signers. + /// The transaction details if successful, or the contract parameters if signatures are incomplete. + /// Thrown when no wallet is open, parameters are invalid, or there are insufficient funds. [RpcMethod] - protected virtual JToken SendFrom(JArray _params) + protected internal virtual JToken SendFrom(JArray _params) { CheckWallet(); 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.")); Signer[] signers = _params.Count >= 5 ? ((JArray)_params[4]).Select(p => new Signer() { Account = AddressToScriptHash(p.AsString(), system.Settings.AddressVersion), Scopes = WitnessScope.CalledByEntry }).ToArray() : null; - Transaction tx = wallet.MakeTransaction(snapshot, new[] + Transaction tx = Result.Ok_Or(() => wallet.MakeTransaction(snapshot, new[] { new TransferOutput { @@ -210,7 +293,7 @@ protected virtual JToken SendFrom(JArray _params) Value = amount, ScriptHash = to } - }, from, signers).NotNull_Or(RpcError.InsufficientFunds); + }, from, signers), RpcError.InvalidRequest.WithData("Can not process this request.")).NotNull_Or(RpcError.InsufficientFunds); ContractParametersContext transContext = new(snapshot, tx, settings.Network); wallet.Sign(transContext); @@ -227,8 +310,37 @@ protected virtual JToken SendFrom(JArray _params) return SignAndRelay(snapshot, tx); } + /// + /// Transfers assets to multiple addresses. + /// + /// + /// An array containing the following elements: + /// [0] (optional): The address to send from as a string. If omitted, the assets will be sent from any address in the wallet. + /// [1]: An array of transfer objects, each containing: + /// - "asset": The asset ID (UInt160) as a string. + /// - "value": The amount to transfer as a string. + /// - "address": The recipient address as a string. + /// [2] (optional): An array of signers, each containing: + /// - The address of the signer as a string. + /// + /// + /// If the transaction is successfully created and all signatures are present: + /// Returns a JSON object representing the transaction. + /// If not all signatures are present: + /// Returns a JSON object representing the contract parameters that need to be signed. + /// + /// + /// Thrown when: + /// - No wallet is open. + /// - The 'to' parameter is invalid or empty. + /// - Any of the asset IDs are invalid. + /// - Any of the amounts are negative or invalid. + /// - Any of the addresses are invalid. + /// - There are insufficient funds for the transfer. + /// - The network fee exceeds the maximum allowed fee. + /// [RpcMethod] - protected virtual JToken SendMany(JArray _params) + protected internal virtual JToken SendMany(JArray _params) { CheckWallet(); int to_start = 0; @@ -243,7 +355,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()); @@ -273,13 +385,19 @@ protected virtual JToken SendMany(JArray _params) return SignAndRelay(snapshot, tx); } + /// + /// Transfers an asset to a specific address. + /// + /// An array containing asset ID, to address, and amount. + /// The transaction details if successful, or the contract parameters if signatures are incomplete. + /// Thrown when no wallet is open, parameters are invalid, or there are insufficient funds. [RpcMethod] - protected virtual JToken SendToAddress(JArray _params) + protected internal 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); @@ -308,8 +426,14 @@ protected virtual JToken SendToAddress(JArray _params) return SignAndRelay(snapshot, tx); } + /// + /// Cancels an unconfirmed transaction. + /// + /// An array containing the transaction ID to cancel, signers, and optional extra fee. + /// The details of the cancellation transaction. + /// Thrown when no wallet is open, the transaction is already confirmed, or there are insufficient funds for the cancellation fee. [RpcMethod] - protected virtual JToken CancelTransaction(JArray _params) + protected internal virtual JToken CancelTransaction(JArray _params) { CheckWallet(); var txid = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid txid: {_params[0]}")); @@ -342,8 +466,14 @@ protected virtual JToken CancelTransaction(JArray _params) return SignAndRelay(system.StoreView, tx); } + /// + /// Invokes the verify method of a contract. + /// + /// An array containing the script hash, optional arguments, and optional signers and witnesses. + /// A JSON object containing the result of the verification. + /// Thrown when the script hash is invalid, the contract is not found, or the verification fails. [RpcMethod] - protected virtual JToken InvokeContractVerify(JArray _params) + protected internal virtual JToken InvokeContractVerify(JArray _params) { UInt160 script_hash = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid script hash: {_params[0]}")); ContractParameter[] args = _params.Count >= 2 ? ((JArray)_params[1]).Select(p => ContractParameter.FromJson((JObject)p)).ToArray() : Array.Empty(); @@ -352,11 +482,19 @@ protected virtual JToken InvokeContractVerify(JArray _params) return GetVerificationResult(script_hash, args, signers, witnesses); } + /// + /// Gets the result of the contract verification. + /// + /// The script hash of the contract. + /// The contract parameters. + /// Optional signers for the verification. + /// Optional witnesses for the verification. + /// A JSON object containing the verification result. 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)); + var md = contract.Manifest.Abi.GetMethod(ContractBasicMethod.Verify, args.Count()).NotNull_Or(RpcErrorFactory.InvalidContractVerification(contract.Hash, args.Count())); (md.ReturnType == ContractParameterType.Boolean).True_Or(RpcErrorFactory.InvalidContractVerification("The verify method doesn't return boolean value.")); Transaction tx = new() { @@ -365,7 +503,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(); @@ -396,6 +534,12 @@ private JObject GetVerificationResult(UInt160 scriptHash, ContractParameter[] ar return json; } + /// + /// Signs and relays a transaction. + /// + /// The data snapshot. + /// The transaction to sign and relay. + /// A JSON object containing the transaction details. private JObject SignAndRelay(DataCache snapshot, Transaction tx) { ContractParametersContext context = new(snapshot, tx, settings.Network); @@ -412,6 +556,12 @@ private JObject SignAndRelay(DataCache snapshot, Transaction tx) } } + /// + /// Converts an address to a script hash. + /// + /// The address to convert. + /// The address version. + /// The script hash corresponding to the address. internal static UInt160 AddressToScriptHash(string address, byte version) { if (UInt160.TryParse(address, out var scriptHash)) diff --git a/src/Plugins/RpcServer/RpcServer.cs b/src/Plugins/RpcServer/RpcServer.cs index c53f17b860..0dc3d467bf 100644 --- a/src/Plugins/RpcServer/RpcServer.cs +++ b/src/Plugins/RpcServer/RpcServer.cs @@ -16,6 +16,7 @@ using Microsoft.AspNetCore.ResponseCompression; using Microsoft.AspNetCore.Server.Kestrel.Https; using Microsoft.Extensions.DependencyInjection; +using Neo.Extensions; using Neo.Json; using Neo.Network.P2P; using System; @@ -23,8 +24,10 @@ using System.IO; using System.IO.Compression; using System.Linq; +using System.Linq.Expressions; using System.Net.Security; using System.Reflection; +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; @@ -36,16 +39,25 @@ public partial class RpcServer : IDisposable private const int MaxParamsDepth = 32; private readonly Dictionary> methods = new(); + private readonly Dictionary _methodsWithParams = new(); private IWebHost host; private RpcServerSettings settings; private readonly NeoSystem system; private readonly LocalNode localNode; + // avoid GetBytes every time + private readonly byte[] _rpcUser; + private readonly byte[] _rpcPass; + public RpcServer(NeoSystem system, RpcServerSettings settings) { this.system = system; this.settings = settings; + + _rpcUser = settings.RpcUser is not null ? Encoding.UTF8.GetBytes(settings.RpcUser) : []; + _rpcPass = settings.RpcPass is not null ? Encoding.UTF8.GetBytes(settings.RpcPass) : []; + localNode = system.LocalNode.Ask(new LocalNode.GetInstance()).Result; RegisterMethods(this); Initialize_SmartContract(); @@ -63,21 +75,25 @@ internal bool CheckAuth(HttpContext context) return false; } - string authstring; + byte[] auths; try { - authstring = Encoding.UTF8.GetString(Convert.FromBase64String(reqauth.Replace("Basic ", "").Trim())); + auths = Convert.FromBase64String(reqauth.Replace("Basic ", "").Trim()); } catch { return false; } - string[] authvalues = authstring.Split(new string[] { ":" }, StringSplitOptions.RemoveEmptyEntries); - if (authvalues.Length < 2) + int colonIndex = Array.IndexOf(auths, (byte)':'); + if (colonIndex == -1) return false; - return authvalues[0] == settings.RpcUser && authvalues[1] == settings.RpcPass; + byte[] user = auths[..colonIndex]; + byte[] pass = auths[(colonIndex + 1)..]; + + // Always execute both checks, but both must evaluate to true + return CryptographicOperations.FixedTimeEquals(user, _rpcUser) & CryptographicOperations.FixedTimeEquals(pass, _rpcPass); } private static JObject CreateErrorResponse(JToken id, RpcError rpcError) @@ -261,24 +277,83 @@ public async Task ProcessAsync(HttpContext context) private async Task ProcessRequestAsync(HttpContext context, JObject request) { if (!request.ContainsProperty("id")) return null; - JToken @params = request["params"] ?? new JArray(); + var @params = request["params"] ?? new JArray(); if (!request.ContainsProperty("method") || @params is not JArray) { return CreateErrorResponse(request["id"], RpcError.InvalidRequest); } - JObject response = CreateResponse(request["id"]); + + var jsonParameters = (JArray)@params; + var response = CreateResponse(request["id"]); try { - string method = request["method"].AsString(); + var method = request["method"].AsString(); (CheckAuth(context) && !settings.DisabledMethods.Contains(method)).True_Or(RpcError.AccessDenied); - methods.TryGetValue(method, out var func).True_Or(RpcErrorFactory.MethodNotFound(method)); - response["result"] = func((JArray)@params) switch + + if (methods.TryGetValue(method, out var func)) { - JToken result => result, - Task task => await task, - _ => throw new NotSupportedException() - }; - return response; + response["result"] = func(jsonParameters) switch + { + JToken result => result, + Task task => await task, + _ => throw new NotSupportedException() + }; + return response; + } + + if (_methodsWithParams.TryGetValue(method, out var func2)) + { + var paramInfos = func2.Method.GetParameters(); + var args = new object[paramInfos.Length]; + + for (var i = 0; i < paramInfos.Length; i++) + { + var param = paramInfos[i]; + if (jsonParameters.Count > i && jsonParameters[i] != null) + { + try + { + if (param.ParameterType == typeof(UInt160)) + { + args[i] = ParameterConverter.ConvertUInt160(jsonParameters[i], system.Settings.AddressVersion); + } + else + { + args[i] = ParameterConverter.ConvertParameter(jsonParameters[i], param.ParameterType); + } + } + catch (Exception e) when (e is not RpcException) + { + throw new ArgumentException($"Invalid value for parameter '{param.Name}'", e); + } + } + else + { + if (param.IsOptional) + { + args[i] = param.DefaultValue; + } + else if (param.ParameterType.IsValueType && Nullable.GetUnderlyingType(param.ParameterType) == null) + { + throw new ArgumentException($"Required parameter '{param.Name}' is missing"); + } + else + { + args[i] = null; + } + } + } + + response["result"] = func2.DynamicInvoke(args) switch + { + JToken result => result, + Task task => await task, + _ => throw new NotSupportedException() + }; + return response; + } + + throw new RpcException(RpcError.MethodNotFound.WithData(method)); } catch (FormatException ex) { @@ -288,24 +363,40 @@ private async Task ProcessRequestAsync(HttpContext context, JObject req { return CreateErrorResponse(request["id"], RpcError.InvalidParams.WithData(ex.Message)); } - catch (Exception ex) + catch (Exception ex) when (ex is not RpcException) { #if DEBUG return CreateErrorResponse(request["id"], RpcErrorFactory.NewCustomError(ex.HResult, ex.Message, ex.StackTrace)); #else - return CreateErrorResponse(request["id"], RpcErrorFactory.NewCustomError(ex.HResult, ex.Message)); + return CreateErrorResponse(request["id"], RpcErrorFactory.NewCustomError(ex.HResult, ex.Message)); #endif } } public void RegisterMethods(object handler) { - foreach (MethodInfo method in handler.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) + foreach (var method in handler.GetType().GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { - RpcMethodAttribute attribute = method.GetCustomAttribute(); - if (attribute is null) continue; - string name = string.IsNullOrEmpty(attribute.Name) ? method.Name.ToLowerInvariant() : attribute.Name; - methods[name] = method.CreateDelegate>(handler); + var attribute = method.GetCustomAttribute(); + var attributeWithParams = method.GetCustomAttribute(); + if (attribute is null && attributeWithParams is null) continue; + if (attribute is not null && attributeWithParams is not null) throw new InvalidOperationException("Method cannot have both RpcMethodAttribute and RpcMethodWithParamsAttribute"); + + if (attribute is not null) + { + var name = string.IsNullOrEmpty(attribute.Name) ? method.Name.ToLowerInvariant() : attribute.Name; + methods[name] = method.CreateDelegate>(handler); + } + + if (attributeWithParams is not null) + { + var name = string.IsNullOrEmpty(attributeWithParams.Name) ? method.Name.ToLowerInvariant() : attributeWithParams.Name; + + var parameters = method.GetParameters().Select(p => p.ParameterType).ToArray(); + var delegateType = Expression.GetDelegateType(parameters.Concat([method.ReturnType]).ToArray()); + + _methodsWithParams[name] = Delegate.CreateDelegate(delegateType, handler, method); + } } } } 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/SQLiteWallet/SQLiteWallet.csproj b/src/Plugins/SQLiteWallet/SQLiteWallet.csproj index 041233af79..b8a1646e65 100644 --- a/src/Plugins/SQLiteWallet/SQLiteWallet.csproj +++ b/src/Plugins/SQLiteWallet/SQLiteWallet.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/Plugins/SQLiteWallet/VerificationContract.cs b/src/Plugins/SQLiteWallet/VerificationContract.cs index e128045adb..2f62a7174b 100644 --- a/src/Plugins/SQLiteWallet/VerificationContract.cs +++ b/src/Plugins/SQLiteWallet/VerificationContract.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.SmartContract; diff --git a/src/Plugins/StateService/Network/Vote.cs b/src/Plugins/StateService/Network/Vote.cs index e6840c7a9f..bba40f6a54 100644 --- a/src/Plugins/StateService/Network/Vote.cs +++ b/src/Plugins/StateService/Network/Vote.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 System; using System.IO; diff --git a/src/Plugins/StateService/Storage/StateStore.cs b/src/Plugins/StateService/Storage/StateStore.cs index f2ca6d7d05..beb19818bb 100644 --- a/src/Plugins/StateService/Storage/StateStore.cs +++ b/src/Plugins/StateService/Storage/StateStore.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Akka.Actor; +using Neo.Extensions; using Neo.IO; using Neo.Ledger; using Neo.Network.P2P.Payloads; diff --git a/src/Plugins/StateService/Verification/VerificationService.cs b/src/Plugins/StateService/Verification/VerificationService.cs index a8eb9fd030..3d77bc3501 100644 --- a/src/Plugins/StateService/Verification/VerificationService.cs +++ b/src/Plugins/StateService/Verification/VerificationService.cs @@ -11,6 +11,7 @@ using Akka.Actor; using Akka.Util.Internal; +using Neo.Extensions; using Neo.IO; using Neo.Ledger; using Neo.Network.P2P.Payloads; diff --git a/src/Plugins/TokensTracker/Extensions.cs b/src/Plugins/TokensTracker/Extensions.cs index 7805056280..83b4371435 100644 --- a/src/Plugins/TokensTracker/Extensions.cs +++ b/src/Plugins/TokensTracker/Extensions.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.Persistence; using Neo.VM.Types; @@ -33,13 +34,13 @@ public static string ToBase64(this ReadOnlySpan item) public static int GetVarSize(this ByteString item) { var length = item.GetSpan().Length; - return IO.Helper.GetVarSize(length) + length; + return UnsafeData.GetVarSize(length) + length; } public static int GetVarSize(this BigInteger item) { var length = item.GetByteCount(); - return IO.Helper.GetVarSize(length) + length; + return UnsafeData.GetVarSize(length) + length; } public static IEnumerable<(TKey, TValue)> FindPrefix(this IStore db, byte[] prefix) diff --git a/src/Plugins/TokensTracker/TokensTracker.cs b/src/Plugins/TokensTracker/TokensTracker.cs index a7ab075245..34fe0a2c95 100644 --- a/src/Plugins/TokensTracker/TokensTracker.cs +++ b/src/Plugins/TokensTracker/TokensTracker.cs @@ -61,7 +61,7 @@ protected override void Configure() _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)) + if (Enum.TryParse(policyString, true, out UnhandledExceptionPolicy policy)) { _exceptionPolicy = policy; } diff --git a/src/Plugins/TokensTracker/Trackers/NEP-11/Nep11Tracker.cs b/src/Plugins/TokensTracker/Trackers/NEP-11/Nep11Tracker.cs index 12d3c208bc..e859394e91 100644 --- a/src/Plugins/TokensTracker/Trackers/NEP-11/Nep11Tracker.cs +++ b/src/Plugins/TokensTracker/Trackers/NEP-11/Nep11Tracker.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.Json; using Neo.Ledger; using Neo.Network.P2P.Payloads; @@ -283,7 +284,7 @@ public JToken GetNep11Properties(JArray _params) using ScriptBuilder sb = new(); sb.EmitDynamicCall(nep11Hash, "properties", CallFlags.ReadOnly, tokenId); - using var snapshot = _neoSystem.GetSnapshot(); + using var snapshot = _neoSystem.GetSnapshotCache(); using var engine = ApplicationEngine.Run(sb.ToArray(), snapshot, settings: _neoSystem.Settings); JObject json = new(); diff --git a/src/Plugins/TokensTracker/Trackers/NEP-17/Nep17Tracker.cs b/src/Plugins/TokensTracker/Trackers/NEP-17/Nep17Tracker.cs index d4698cba1e..71bfceb49d 100644 --- a/src/Plugins/TokensTracker/Trackers/NEP-17/Nep17Tracker.cs +++ b/src/Plugins/TokensTracker/Trackers/NEP-17/Nep17Tracker.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using Neo.Json; using Neo.Ledger; using Neo.Network.P2P.Payloads; diff --git a/tests/Neo.ConsoleService.Tests/Neo.ConsoleService.Tests.csproj b/tests/Neo.ConsoleService.Tests/Neo.ConsoleService.Tests.csproj index 6562480859..469a49e548 100644 --- a/tests/Neo.ConsoleService.Tests/Neo.ConsoleService.Tests.csproj +++ b/tests/Neo.ConsoleService.Tests/Neo.ConsoleService.Tests.csproj @@ -10,9 +10,9 @@ - - - + + + diff --git a/tests/Neo.Cryptography.BLS12_381.Tests/Neo.Cryptography.BLS12_381.Tests.csproj b/tests/Neo.Cryptography.BLS12_381.Tests/Neo.Cryptography.BLS12_381.Tests.csproj index 999bc1c5f4..827c547041 100644 --- a/tests/Neo.Cryptography.BLS12_381.Tests/Neo.Cryptography.BLS12_381.Tests.csproj +++ b/tests/Neo.Cryptography.BLS12_381.Tests/Neo.Cryptography.BLS12_381.Tests.csproj @@ -11,9 +11,9 @@ - - - + + + diff --git a/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Cache.cs b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Cache.cs index 3a34962059..0b5c0faace 100644 --- a/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Cache.cs +++ b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Cache.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.IO; using Neo.Persistence; using System.Text; diff --git a/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Node.cs b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Node.cs index 8f46fbec27..ba77dcd15e 100644 --- a/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Node.cs +++ b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Node.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.IO; using System; using System.Collections.Generic; diff --git a/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Trie.cs b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Trie.cs index 372de6a738..d7fcb92775 100644 --- a/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Trie.cs +++ b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Trie.cs @@ -10,13 +10,13 @@ // modifications are permitted. using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.IO; using Neo.Persistence; using System; using System.Collections.Generic; using System.Linq; using System.Text; -using static Neo.Helper; namespace Neo.Cryptography.MPTTrie.Tests { @@ -26,7 +26,7 @@ class TestSnapshot : ISnapshot private byte[] StoreKey(byte[] key) { - return Concat(key); + return [.. key]; } public void Put(byte[] key, byte[] value) @@ -52,6 +52,11 @@ public byte[] TryGet(byte[] key) return null; } + public bool TryGet(byte[] key, out byte[] value) + { + return store.TryGetValue(StoreKey(key), out value); + } + public void Dispose() { throw new System.NotImplementedException(); } public int Size => store.Count; @@ -65,7 +70,7 @@ public class UT_Trie private void PutToStore(IStore store, Node node) { - store.Put(Concat(new byte[] { 0xf0 }, node.Hash.ToArray()), node.ToArray()); + store.Put([.. new byte[] { 0xf0 }, .. node.Hash.ToArray()], node.ToArray()); } [TestInitialize] diff --git a/tests/Neo.Cryptography.MPTTrie.Tests/Neo.Cryptography.MPTTrie.Tests.csproj b/tests/Neo.Cryptography.MPTTrie.Tests/Neo.Cryptography.MPTTrie.Tests.csproj index 3691fba90a..ed23c8a909 100644 --- a/tests/Neo.Cryptography.MPTTrie.Tests/Neo.Cryptography.MPTTrie.Tests.csproj +++ b/tests/Neo.Cryptography.MPTTrie.Tests/Neo.Cryptography.MPTTrie.Tests.csproj @@ -6,9 +6,9 @@ - - - + + + diff --git a/tests/Neo.Extensions.Tests/Collections/UT_HashSetExtensions.cs b/tests/Neo.Extensions.Tests/Collections/UT_HashSetExtensions.cs new file mode 100644 index 0000000000..cb475a7e5c --- /dev/null +++ b/tests/Neo.Extensions.Tests/Collections/UT_HashSetExtensions.cs @@ -0,0 +1,75 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_HashSetExtensions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.IO.Caching; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.Extensions.Tests.Collections +{ + [TestClass] + public class UT_HashSetExtensions + { + [TestMethod] + public void TestRemoveHashsetDictionary() + { + var a = new HashSet + { + 1, + 2, + 3 + }; + + var b = new Dictionary + { + [2] = null + }; + + a.Remove(b); + + CollectionAssert.AreEqual(new int[] { 1, 3 }, a.ToArray()); + + b[4] = null; + b[5] = null; + b[1] = null; + a.Remove(b); + + CollectionAssert.AreEqual(new int[] { 3 }, a.ToArray()); + } + + [TestMethod] + public void TestRemoveHashsetSet() + { + var a = new HashSet + { + 1, + 2, + 3 + }; + + var b = new SortedSet() + { + 2 + }; + + a.Remove(b); + + CollectionAssert.AreEqual(new int[] { 1, 3 }, a.ToArray()); + + b.Add(4); + b.Add(5); + b.Add(1); + a.Remove(b); + + CollectionAssert.AreEqual(new int[] { 3 }, a.ToArray()); + } + } +} 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..03809a4fe3 --- /dev/null +++ b/tests/Neo.Extensions.Tests/Neo.Extensions.Tests.csproj @@ -0,0 +1,22 @@ + + + + net8.0 + enable + + + + + + + + + + + + + + + + + diff --git a/tests/Neo.Extensions.Tests/Net/UT_IpAddressExtensions.cs b/tests/Neo.Extensions.Tests/Net/UT_IpAddressExtensions.cs new file mode 100644 index 0000000000..bbf26c5cb7 --- /dev/null +++ b/tests/Neo.Extensions.Tests/Net/UT_IpAddressExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_IpAddressExtensions.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.Net; + +namespace Neo.Extensions.Tests.Net +{ + [TestClass] + public class UT_IpAddressExtensions + { + [TestMethod] + public void TestUnmapForIPAddress() + { + var addr = new IPAddress(new byte[] { 127, 0, 0, 1 }); + addr.UnMap().Should().Be(addr); + + var addr2 = addr.MapToIPv6(); + addr2.UnMap().Should().Be(addr); + } + + [TestMethod] + public void TestUnmapForIPEndPoin() + { + var addr = new IPAddress(new byte[] { 127, 0, 0, 1 }); + var endPoint = new IPEndPoint(addr, 8888); + endPoint.UnMap().Should().Be(endPoint); + + var addr2 = addr.MapToIPv6(); + var endPoint2 = new IPEndPoint(addr2, 8888); + endPoint2.UnMap().Should().Be(endPoint); + } + } +} diff --git a/tests/Neo.Extensions.Tests/UT_AssemblyExtensions.cs b/tests/Neo.Extensions.Tests/UT_AssemblyExtensions.cs new file mode 100644 index 0000000000..43f66f95da --- /dev/null +++ b/tests/Neo.Extensions.Tests/UT_AssemblyExtensions.cs @@ -0,0 +1,33 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_AssemblyExtensions.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 System; +using System.Linq; + +namespace Neo.Extensions.Tests +{ + [TestClass] + public class UT_AssemblyExtensions + { + [TestMethod] + public void TestGetVersion() + { + // assembly without version + + var asm = AppDomain.CurrentDomain.GetAssemblies() + .Where(u => u.FullName == "Anonymously Hosted DynamicMethods Assembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null") + .FirstOrDefault(); + string version = asm?.GetVersion() ?? ""; + version.Should().Be("0.0.0"); + } + } +} diff --git a/tests/Neo.Extensions.Tests/UT_BigIntegerExtensions.cs b/tests/Neo.Extensions.Tests/UT_BigIntegerExtensions.cs new file mode 100644 index 0000000000..d1d8d0aab3 --- /dev/null +++ b/tests/Neo.Extensions.Tests/UT_BigIntegerExtensions.cs @@ -0,0 +1,192 @@ +// 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 Neo.Json; +using System; +using System.Collections.Generic; +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); + + var big5 = new BigInteger(-18); + big5.GetLowestSetBit().Should().Be(1); + + var big6 = BigInteger.Pow(2, 1000); + big6.GetLowestSetBit().Should().Be(1000); + } + + [TestMethod] + public void TestGetLowestSetBit_EdgeCases() + { + BigInteger.MinusOne.GetLowestSetBit().Should().Be(0); + BigInteger.One.GetLowestSetBit().Should().Be(0); + new BigInteger(ulong.MaxValue).GetLowestSetBit().Should().Be(0); + (BigInteger.One << 1000).GetLowestSetBit().Should().Be(1000); + } + + [TestMethod] + public void TestToByteArrayStandard() + { + BigInteger number = BigInteger.Zero; + number.ToByteArrayStandard().Should().BeEmpty(); + + number = BigInteger.One; + number.ToByteArrayStandard().Should().Equal(new byte[] { 0x01 }); + + number = new BigInteger(256); // Binary: 100000000 + number.ToByteArrayStandard().Should().Equal(new byte[] { 0x00, 0x01 }); + } + + [TestMethod] + public void TestToByteArrayStandard_EdgeCases() + { + BigInteger.MinusOne.ToByteArrayStandard().Should().Equal(new byte[] { 0xFF }); + new BigInteger(byte.MaxValue).ToByteArrayStandard().Should().Equal(new byte[] { 0xFF, 0x00 }); + new BigInteger(ushort.MaxValue).ToByteArrayStandard().Should().Equal(new byte[] { 0xFF, 0xFF, 0x00 }); + new BigInteger(JNumber.MIN_SAFE_INTEGER).ToByteArrayStandard().Should().Equal(new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0 }); + } + + [TestMethod] + public void TestMod() + { + var x = new BigInteger(-13); + var y = new BigInteger(5); + var result = x.Mod(y); + result.Should().Be(2); // -13 % 5 is -3, but Mod method should return 2 + } + + [TestMethod] + public void TestMod_EdgeCases() + { + // Test case 1: Mod of zero + BigInteger.Zero.Mod(5).Should().Be(0, "Mod of zero should always be zero"); + + // Test case 2: Mod of -1 + BigInteger.MinusOne.Mod(5).Should().Be(4, "Mod of -1 should return the modulus minus 1"); + + // Test case 3: Mod with large numbers + BigInteger minValue = new BigInteger(long.MinValue); + BigInteger maxValue = new BigInteger(long.MaxValue); + minValue.Mod(maxValue).Should().Be(9223372036854775806, "Mod with large numbers should be calculated correctly"); + + // Test case 4: Comparing Mod with % operator + BigInteger result = minValue.Mod(maxValue); + result.Should().NotBe(long.MinValue % long.MaxValue, "Mod should always return non-negative values, unlike % operator"); + + // Test case 5: Verifying % operator behavior + (long.MinValue % long.MaxValue).Should().Be((long)(minValue % maxValue), "% operator should behave consistently for BigInteger and long"); + + // Test case 6: Mod with prime numbers + new BigInteger(17).Mod(19).Should().Be(17, "Mod with a larger prime should return the original number"); + + // Test case 7: Mod with powers of 2 + new BigInteger(1024).Mod(16).Should().Be(0, "Mod with powers of 2 should utilize bitwise operations efficiently"); + } + + [TestMethod] + public void TestModInverse() + { + var a = new BigInteger(3); + var n = new BigInteger(11); + var result = a.ModInverse(n); + result.Should().Be(4); // 3 * 4 % 11 == 1 + + a = new BigInteger(1); + n = new BigInteger(11); + result = a.ModInverse(n); + result.Should().Be(1); // 1 * 1 % 11 == 1 + + a = new BigInteger(13); + n = new BigInteger(11); + result = a.ModInverse(n); + result.Should().Be(6); // 13 % 11 = 2, and 2 * 6 % 11 == 1 + + a = new BigInteger(6); + n = new BigInteger(12); // 6 and 12 are not coprime + Action act = () => a.ModInverse(n); + act.Should().Throw() + .WithMessage("No modular inverse exists for the given inputs."); + } + + [TestMethod] + public void TestModInverse_EdgeCases() + { + Action act = () => BigInteger.Zero.ModInverse(11); + act.Should().Throw(); + + BigInteger.One.ModInverse(2).Should().Be(1); + + act = () => new BigInteger(2).ModInverse(4); + act.Should().Throw(); + + new BigInteger(long.MaxValue - 1).ModInverse(long.MaxValue).Should().Be(long.MaxValue - 1); + } + + [TestMethod] + public void TestBit() + { + var bigInteger = new BigInteger(5); // Binary: 101 + var result = bigInteger.TestBit(2); + result.Should().BeTrue(); // Bit at index 2 is set (1) + + bigInteger = new BigInteger(5); // Binary: 101 + result = bigInteger.TestBit(1); + result.Should().BeFalse(); // Bit at index 1 is not set (0) + } + + [TestMethod] + public void TestBit_EdgeCases() + { + BigInteger.Zero.TestBit(0).Should().BeFalse(); + BigInteger.Zero.TestBit(100).Should().BeFalse(); + BigInteger.MinusOne.TestBit(0).Should().BeTrue(); + BigInteger.MinusOne.TestBit(1000).Should().BeTrue(); + (BigInteger.One << 1000).TestBit(1000).Should().BeTrue(); + (BigInteger.One << 1000).TestBit(999).Should().BeFalse(); + } + + [TestMethod] + public void TestSum() + { + var bigIntegers = new List { 1, 2, 3, 4 }; + var result = bigIntegers.Sum(); + result.Should().Be(10); + } + + [TestMethod] + public void TestSum_EdgeCases() + { + new List().Sum().Should().Be(0); + new List { JNumber.MIN_SAFE_INTEGER, JNumber.MAX_SAFE_INTEGER }.Sum().Should().Be(0); + new List { JNumber.MAX_SAFE_INTEGER, JNumber.MAX_SAFE_INTEGER }.Sum().Should().Be(JNumber.MAX_SAFE_INTEGER * 2); + } + } +} diff --git a/tests/Neo.Extensions.Tests/UT_ByteExtensions.cs b/tests/Neo.Extensions.Tests/UT_ByteExtensions.cs new file mode 100644 index 0000000000..3c0c21b8f0 --- /dev/null +++ b/tests/Neo.Extensions.Tests/UT_ByteExtensions.cs @@ -0,0 +1,61 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ByteExtensions.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 System; + +namespace Neo.Extensions.Tests +{ + [TestClass] + public class UT_ByteExtensions + { + [TestMethod] + public void TestToHexString() + { + byte[] nullStr = null; + Assert.ThrowsException(() => nullStr.ToHexString()); + byte[] empty = Array.Empty(); + empty.ToHexString().Should().Be(""); + empty.ToHexString(false).Should().Be(""); + empty.ToHexString(true).Should().Be(""); + + byte[] str1 = new byte[] { (byte)'n', (byte)'e', (byte)'o' }; + str1.ToHexString().Should().Be("6e656f"); + str1.ToHexString(false).Should().Be("6e656f"); + str1.ToHexString(true).Should().Be("6f656e"); + } + + [TestMethod] + public void TestReadOnlySpanToHexString() + { + byte[] input = { 0x0F, 0xA4, 0x3B }; + var span = new ReadOnlySpan(input); + string result = span.ToHexString(); + result.Should().Be("0fa43b"); + + input = Array.Empty(); + span = new ReadOnlySpan(input); + result = span.ToHexString(); + result.Should().BeEmpty(); + + input = new byte[] { 0x5A }; + span = new ReadOnlySpan(input); + result = span.ToHexString(); + result.Should().Be("5a"); + + input = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF }; + span = new ReadOnlySpan(input); + result = span.ToHexString(); + result.Should().Be("0123456789abcdef"); + } + } +} diff --git a/tests/Neo.Extensions.Tests/UT_DateTimeExtensions.cs b/tests/Neo.Extensions.Tests/UT_DateTimeExtensions.cs new file mode 100644 index 0000000000..43414a8f81 --- /dev/null +++ b/tests/Neo.Extensions.Tests/UT_DateTimeExtensions.cs @@ -0,0 +1,42 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_DateTimeExtensions.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 System; + +namespace Neo.Extensions.Tests +{ + [TestClass] + public class UT_DateTimeExtensions + { + private static readonly DateTime unixEpoch = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + [TestMethod] + public void TestToTimestamp() + { + var time = DateTime.Now; + var expected = (uint)(time.ToUniversalTime() - unixEpoch).TotalSeconds; + var actual = time.ToTimestamp(); + + actual.Should().Be(expected); + } + + [TestMethod] + public void TestToTimestampMS() + { + var time = DateTime.Now; + var expected = (ulong)(time.ToUniversalTime() - unixEpoch).TotalMilliseconds; + var actual = time.ToTimestampMS(); + + actual.Should().Be(expected); + } + } +} diff --git a/tests/Neo.Extensions.Tests/UT_RandomExtensions.cs b/tests/Neo.Extensions.Tests/UT_RandomExtensions.cs new file mode 100644 index 0000000000..fb7c6a8cf7 --- /dev/null +++ b/tests/Neo.Extensions.Tests/UT_RandomExtensions.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RandomExtensions.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 System; + +namespace Neo.Extensions.Tests +{ + [TestClass] + public class UT_RandomExtensions + { + [TestMethod] + public void TestNextBigIntegerForRandom() + { + Random ran = new(); + Action action1 = () => ran.NextBigInteger(-1); + action1.Should().Throw(); + + ran.NextBigInteger(0).Should().Be(0); + ran.NextBigInteger(8).Should().NotBeNull(); + ran.NextBigInteger(9).Should().NotBeNull(); + } + } +} diff --git a/tests/Neo.Extensions.Tests/UT_SecureStringExtensions.cs b/tests/Neo.Extensions.Tests/UT_SecureStringExtensions.cs new file mode 100644 index 0000000000..cd583187b1 --- /dev/null +++ b/tests/Neo.Extensions.Tests/UT_SecureStringExtensions.cs @@ -0,0 +1,35 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_SecureStringExtensions.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.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.Extensions.Tests +{ + [TestClass] + public class UT_SecureStringExtensions + { + [TestMethod] + public void Test_String_To_SecureString() + { + var expected = "Hello World"; + var expectedSecureString = expected.ToSecureString(); + + var actual = expectedSecureString.GetClearText(); + + Assert.IsTrue(expectedSecureString.IsReadOnly()); + Assert.AreEqual(expected, actual); + } + } +} diff --git a/tests/Neo.Extensions.Tests/UT_StringExtensions.cs b/tests/Neo.Extensions.Tests/UT_StringExtensions.cs new file mode 100644 index 0000000000..6e823d2493 --- /dev/null +++ b/tests/Neo.Extensions.Tests/UT_StringExtensions.cs @@ -0,0 +1,46 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_StringExtensdions.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 System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.Extensions.Tests +{ + [TestClass] + public class UT_StringExtensions + { + [TestMethod] + public void TestHexToBytes() + { + string nullStr = null; + _ = nullStr.HexToBytes().ToHexString().Should().Be(Array.Empty().ToHexString()); + string emptyStr = ""; + emptyStr.HexToBytes().ToHexString().Should().Be(Array.Empty().ToHexString()); + string str1 = "hab"; + Action action = () => str1.HexToBytes(); + action.Should().Throw(); + string str2 = "0102"; + byte[] bytes = str2.HexToBytes(); + bytes.ToHexString().Should().Be(new byte[] { 0x01, 0x02 }.ToHexString()); + } + + [TestMethod] + public void TestGetVarSizeString() + { + int result = "AA".GetVarSize(); + Assert.AreEqual(3, result); + } + } +} diff --git a/tests/Neo.Extensions.Tests/UT_UnsafeData.cs b/tests/Neo.Extensions.Tests/UT_UnsafeData.cs new file mode 100644 index 0000000000..5c8bb310c3 --- /dev/null +++ b/tests/Neo.Extensions.Tests/UT_UnsafeData.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.Extensions.Tests +{ + [TestClass] + public class UT_UnsafeData + { + [TestMethod] + public void TestGetVarSizeInt() + { + for (int i = 0; i < 3; i++) + { + if (i == 0) + { + int result = UnsafeData.GetVarSize(1); + Assert.AreEqual(1, result); + } + else if (i == 1) + { + int result = UnsafeData.GetVarSize(0xFFFF); + Assert.AreEqual(3, result); + } + else + { + int result = UnsafeData.GetVarSize(0xFFFFFF); + Assert.AreEqual(5, result); + } + } + } + + [TestMethod] + public void TestGetVarSizeGeneric() + { + for (int i = 0; i < 9; i++) + { + if (i == 0) + { + int result = new UInt160[] { UInt160.Zero }.GetVarSize(); + Assert.AreEqual(21, result); + } + else if (i == 1)//sbyte + { + List initList = new() + { + TestEnum0.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = testList.GetVarSize(); + Assert.AreEqual(2, result); + } + else if (i == 2)//byte + { + List initList = new() + { + TestEnum1.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = testList.GetVarSize(); + Assert.AreEqual(2, result); + } + else if (i == 3)//short + { + List initList = new() + { + TestEnum2.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = testList.GetVarSize(); + Assert.AreEqual(3, result); + } + else if (i == 4)//ushort + { + List initList = new() + { + TestEnum3.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = testList.GetVarSize(); + Assert.AreEqual(3, result); + } + else if (i == 5)//int + { + List initList = new() + { + TestEnum4.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = testList.GetVarSize(); + Assert.AreEqual(5, result); + } + else if (i == 6)//uint + { + List initList = new() + { + TestEnum5.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = testList.GetVarSize(); + Assert.AreEqual(5, result); + } + else if (i == 7)//long + { + List initList = new() + { + TestEnum6.case1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = testList.GetVarSize(); + Assert.AreEqual(9, result); + } + else if (i == 8) + { + List initList = new() + { + 1 + }; + IReadOnlyCollection testList = initList.AsReadOnly(); + int result = testList.GetVarSize(); + Assert.AreEqual(5, result); + } + } + } + + enum TestEnum0 : sbyte + { + case1 = 1, case2 = 2 + } + + enum TestEnum1 : byte + { + case1 = 1, case2 = 2 + } + + enum TestEnum2 : short + { + case1 = 1, case2 = 2 + } + + enum TestEnum3 : ushort + { + case1 = 1, case2 = 2 + } + + enum TestEnum4 : int + { + case1 = 1, case2 = 2 + } + + enum TestEnum5 : uint + { + case1 = 1, case2 = 2 + } + + enum TestEnum6 : long + { + case1 = 1, case2 = 2 + } + } +} diff --git a/tests/Neo.Json.UnitTests/Neo.Json.UnitTests.csproj b/tests/Neo.Json.UnitTests/Neo.Json.UnitTests.csproj index 81e70ebbe1..c931c287a4 100644 --- a/tests/Neo.Json.UnitTests/Neo.Json.UnitTests.csproj +++ b/tests/Neo.Json.UnitTests/Neo.Json.UnitTests.csproj @@ -10,9 +10,9 @@ - - - + + + diff --git a/tests/Neo.Json.UnitTests/UT_JArray.cs b/tests/Neo.Json.UnitTests/UT_JArray.cs index 40388d11fc..768f9a8f89 100644 --- a/tests/Neo.Json.UnitTests/UT_JArray.cs +++ b/tests/Neo.Json.UnitTests/UT_JArray.cs @@ -252,7 +252,170 @@ public void TestAsString() bob, }; 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\"}}"); + 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 TestCount() + { + var jArray = new JArray { alice, bob }; + jArray.Count.Should().Be(2); + } + + [TestMethod] + public void TestInvalidIndexAccess() + { + var jArray = new JArray { alice }; + Action action = () => { var item = jArray[1]; }; + action.Should().Throw(); + } + + [TestMethod] + public void TestEmptyEnumeration() + { + var jArray = new JArray(); + foreach (var item in jArray) + { + Assert.Fail("Enumeration should not occur on an empty JArray"); + } + } + + [TestMethod] + public void TestImplicitConversionFromJTokenArray() + { + JToken[] jTokens = { alice, bob }; + JArray jArray = jTokens; + + jArray.Count.Should().Be(2); + jArray[0].Should().Be(alice); + jArray[1].Should().Be(bob); + } + + [TestMethod] + public void TestAddNullValues() + { + var jArray = new JArray(); + jArray.Add(null); + jArray.Count.Should().Be(1); + jArray[0].Should().BeNull(); + } + + [TestMethod] + public void TestClone() + { + var jArray = new JArray { alice, bob }; + var clone = (JArray)jArray.Clone(); + + clone.Should().NotBeSameAs(jArray); + clone.Count.Should().Be(jArray.Count); + + for (int i = 0; i < jArray.Count; i++) + { + clone[i]?.AsString().Should().Be(jArray[i]?.AsString()); + } + + var a = jArray.AsString(); + var b = jArray.Clone().AsString(); + a.Should().Be(b); + } + + [TestMethod] + public void TestReadOnlyBehavior() + { + var jArray = new JArray(); + jArray.IsReadOnly.Should().BeFalse(); + } + + [TestMethod] + public void TestAddNull() + { + var jArray = new JArray { null }; + + jArray.Count.Should().Be(1); + jArray[0].Should().BeNull(); + } + + [TestMethod] + public void TestSetNull() + { + var jArray = new JArray { alice }; + jArray[0] = null; + + jArray.Count.Should().Be(1); + jArray[0].Should().BeNull(); + } + + [TestMethod] + public void TestInsertNull() + { + var jArray = new JArray { alice }; + jArray.Insert(0, null); + + jArray.Count.Should().Be(2); + jArray[0].Should().BeNull(); + jArray[1].Should().Be(alice); + } + + [TestMethod] + public void TestRemoveNull() + { + var jArray = new JArray { null, alice }; + jArray.Remove(null); + + jArray.Count.Should().Be(1); + jArray[0].Should().Be(alice); + } + + [TestMethod] + public void TestContainsNull() + { + var jArray = new JArray { null, alice }; + jArray.Contains(null).Should().BeTrue(); + jArray.Contains(bob).Should().BeFalse(); + } + + [TestMethod] + public void TestIndexOfNull() + { + var jArray = new JArray { null, alice }; + jArray.IndexOf(null).Should().Be(0); + jArray.IndexOf(alice).Should().Be(1); + } + + [TestMethod] + public void TestCopyToWithNull() + { + var jArray = new JArray { null, alice }; + JObject[] jObjects = new JObject[2]; + jArray.CopyTo(jObjects, 0); + + jObjects[0].Should().BeNull(); + jObjects[1].Should().Be(alice); + } + + [TestMethod] + public void TestToStringWithNull() + { + var jArray = new JArray { null, alice, bob }; + var jsonString = jArray.ToString(); + var asString = jArray.AsString(); + // JSON string should properly represent the null value + jsonString.Should().Be("[null,{\"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\"}}]"); + asString.Should().Be("[null,{\"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 TestFromStringWithNull() + { + var jsonString = "[null,{\"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\"}}]"; + var jArray = (JArray)JArray.Parse(jsonString); + + jArray.Count.Should().Be(3); + jArray[0].Should().BeNull(); + + // Checking the second and third elements + jArray[1]["name"].AsString().Should().Be("alice"); + jArray[2]["name"].AsString().Should().Be("bob"); } } } diff --git a/tests/Neo.Json.UnitTests/UT_JBoolean.cs b/tests/Neo.Json.UnitTests/UT_JBoolean.cs index 3ab19bd1b3..4c5ed6f263 100644 --- a/tests/Neo.Json.UnitTests/UT_JBoolean.cs +++ b/tests/Neo.Json.UnitTests/UT_JBoolean.cs @@ -9,6 +9,8 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Newtonsoft.Json; + namespace Neo.Json.UnitTests { [TestClass] @@ -31,13 +33,58 @@ public void TestAsNumber() jTrue.AsNumber().Should().Be(1); } + [TestMethod] + public void TestDefaultConstructor() + { + var defaultJBoolean = new JBoolean(); + defaultJBoolean.AsNumber().Should().Be(0); + } + + [TestMethod] + public void TestExplicitFalse() + { + var explicitFalse = new JBoolean(false); + explicitFalse.AsNumber().Should().Be(0); + } + + [TestMethod] + public void TestNullJBoolean() + { + JBoolean nullJBoolean = null; + Assert.ThrowsException(() => nullJBoolean.AsNumber()); + } + + [TestMethod] + public void TestConversionToOtherTypes() + { + Assert.AreEqual("true", jTrue.ToString()); + Assert.AreEqual("false", jFalse.ToString()); + } + + [TestMethod] + public void TestComparisonsWithOtherBooleans() + { + Assert.IsTrue(jTrue.Equals(new JBoolean(true))); + Assert.IsTrue(jFalse.Equals(new JBoolean())); + } + + [TestMethod] + public void TestSerializationAndDeserialization() + { + string serialized = JsonConvert.SerializeObject(jTrue); + var deserialized = JsonConvert.DeserializeObject(serialized); + Assert.AreEqual(jTrue, deserialized); + } + [TestMethod] 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_JPath.cs b/tests/Neo.Json.UnitTests/UT_JPath.cs index 9d958b1cde..68e204a030 100644 --- a/tests/Neo.Json.UnitTests/UT_JPath.cs +++ b/tests/Neo.Json.UnitTests/UT_JPath.cs @@ -99,6 +99,75 @@ public void TestInvalidFormat() Assert.ThrowsException(() => json.JsonPath("$..*")); Assert.ThrowsException(() => json.JsonPath("..book")); Assert.ThrowsException(() => json.JsonPath("$..")); + + // Test with an empty JSON Path + // Assert.ThrowsException(() => json.JsonPath("")); + + // Test with only special characters + Assert.ThrowsException(() => json.JsonPath("@#$%^&*()")); + + // Test with unmatched brackets + Assert.ThrowsException(() => json.JsonPath("$.store.book[")); + Assert.ThrowsException(() => json.JsonPath("$.store.book)]")); + + // Test with invalid operators + Assert.ThrowsException(() => json.JsonPath("$.store.book=>2")); + + // Test with incorrect field syntax + Assert.ThrowsException(() => json.JsonPath("$.store.'book'")); + Assert.ThrowsException(() => json.JsonPath("$.store.[book]")); + + // Test with unexpected end of expression + Assert.ThrowsException(() => json.JsonPath("$.store.book[?(@.price<")); + + // Test with invalid array indexing + // Assert.ThrowsException(() => json.JsonPath("$.store.book['one']")); + // Assert.ThrowsException(() => json.JsonPath("$.store.book[999]")); + + // Test with invalid recursive descent + Assert.ThrowsException(() => json.JsonPath("$..*..author")); + + // Test with nonexistent functions + Assert.ThrowsException(() => json.JsonPath("$.store.book.length()")); + + // Test with incorrect use of wildcards + // Assert.ThrowsException(() => json.JsonPath("$.*.store")); + + // Test with improper use of filters + Assert.ThrowsException(() => json.JsonPath("$.store.book[?(@.price)]")); + + // Test with mixing of valid and invalid syntax + Assert.ThrowsException(() => json.JsonPath("$.store.book[*],$.invalid")); + + // Test with invalid escape sequences + Assert.ThrowsException(() => json.JsonPath("$.store.book[\\]")); + + // Test with incorrect property access + Assert.ThrowsException(() => json.JsonPath("$.store.'b?ook'")); + + // Test with invalid use of wildcard in array index + // Assert.ThrowsException(() => json.JsonPath("$.store.book[*]")); + + // Test with missing operators in filter expressions + Assert.ThrowsException(() => json.JsonPath("$.store.book[?(@.price)]")); + + // Test with incorrect boolean logic in filters + Assert.ThrowsException(() => json.JsonPath("$.store.book[?(@.price AND @.title)]")); + + // Test with nested filters without proper closure + Assert.ThrowsException(() => json.JsonPath("$.store.book[?(@.price[?(@ < 10)])]")); + + // Test with misplaced recursive descent operator + // Assert.ThrowsException(() => json.JsonPath("$..store..book")); + + // Test with using JSONPath reserved keywords incorrectly + Assert.ThrowsException(() => json.JsonPath("$..@.book")); + + // Test with incorrect combinations of valid operators + Assert.ThrowsException(() => json.JsonPath("$.store.book..[0]")); + + // Test with invalid script expressions (if supported) + Assert.ThrowsException(() => json.JsonPath("$.store.book[(@.length-1)]")); } } } diff --git a/tests/Neo.Json.UnitTests/UT_JString.cs b/tests/Neo.Json.UnitTests/UT_JString.cs index 7e2bec9834..a80920ee51 100644 --- a/tests/Neo.Json.UnitTests/UT_JString.cs +++ b/tests/Neo.Json.UnitTests/UT_JString.cs @@ -9,11 +9,50 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using System.Text; +using System.Text.Json; + namespace Neo.Json.UnitTests { [TestClass] public class UT_JString { + private static readonly JString AsicString = "hello world"; + private static readonly JString EscapeString = "\n\t\'\""; + private static readonly JString BadChar = ((char)0xff).ToString(); + private static readonly JString IntegerString = "123"; + private static readonly JString EmptyString = ""; + private static readonly JString SpaceString = " "; + private static readonly JString DoubleString = "123.456"; + private static readonly JString UnicodeString = "\ud83d\ude03\ud83d\ude01"; + private static readonly JString EmojString = "ã🦆"; + private static readonly JString MixedString = "abc123!@# "; + private static readonly JString LongString = new String('x', 5000); // 5000 + private static readonly JString MultiLangString = "Hello 你好 مرحبا"; + private static readonly JString JsonString = "{\"key\": \"value\"}"; + private static readonly JString HtmlEntityString = "& < >"; + private static readonly JString ControlCharString = "\t\n\r"; + private static readonly JString SingleCharString = "a"; + private static readonly JString LongWordString = "Supercalifragilisticexpialidocious"; + private static readonly JString ConcatenatedString = new JString("Hello" + "123" + "!@#"); + private static readonly JString WhiteSpaceString = new JString(" leading and trailing spaces "); + private static readonly JString FilePathString = new JString(@"C:\Users\Example\file.txt"); + private static readonly JString LargeNumberString = new JString("12345678901234567890"); + private static readonly JString HexadecimalString = new JString("0x1A3F"); + private static readonly JString PalindromeString = new JString("racecar"); + private static readonly JString SqlInjectionString = new JString("SELECT * FROM users WHERE name = 'a'; DROP TABLE users;"); + private static readonly JString RegexString = new JString(@"^\d{3}-\d{2}-\d{4}$"); + private static readonly JString DateTimeString = new JString("2023-01-01T00:00:00"); + private static readonly JString SpecialCharString = new JString("!?@#$%^&*()"); + private static readonly JString SubstringString = new JString("Hello world".Substring(0, 5)); + private static readonly JString CaseSensitiveString1 = new JString("TestString"); + private static readonly JString CaseSensitiveString2 = new JString("teststring"); + private static readonly JString BooleanString = new JString("true"); + private static readonly JString FormatSpecifierString = new JString("{0:C}"); + private static readonly JString EmojiSequenceString = new JString("👨‍👩‍👦"); + private static readonly JString NullCharString = new JString("Hello\0World"); + private static readonly JString RepeatingPatternString = new JString("abcabcabc"); + [TestMethod] public void TestConstructor() { @@ -23,50 +62,377 @@ public void TestConstructor() Assert.ThrowsException(() => new JString(null)); } + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void TestConstructorNull() + { + string s = null; + JString jstring = new JString(s); + Assert.AreEqual(s, jstring.Value); + Assert.ThrowsException(() => new JString(null!)); + } + + [TestMethod] + public void TestConstructorEmpty() + { + string s = ""; + JString jstring = new JString(s); + Assert.AreEqual(s, jstring.Value); + } + + [TestMethod] + public void TestConstructorSpace() + { + string s = " "; + JString jstring = new JString(s); + Assert.AreEqual(s, jstring.Value); + Assert.ThrowsException(() => new JString(null)); + } + [TestMethod] public void TestAsBoolean() { - string s1 = "hello world"; - string s2 = ""; - JString jstring1 = new JString(s1); - JString jstring2 = new JString(s2); - Assert.AreEqual(true, jstring1.AsBoolean()); - Assert.AreEqual(false, jstring2.AsBoolean()); + Assert.AreEqual(true, AsicString.AsBoolean()); + Assert.AreEqual(true, EscapeString.AsBoolean()); + Assert.AreEqual(true, BadChar.AsBoolean()); + Assert.AreEqual(true, IntegerString.AsBoolean()); + Assert.AreEqual(false, EmptyString.AsBoolean()); + Assert.AreEqual(true, SpaceString.AsBoolean()); + Assert.AreEqual(true, DoubleString.AsBoolean()); + Assert.AreEqual(true, UnicodeString.AsBoolean()); + Assert.AreEqual(true, EmojString.AsBoolean()); + Assert.AreEqual(true, MixedString.AsBoolean()); + Assert.AreEqual(true, LongString.AsBoolean()); + Assert.AreEqual(true, MultiLangString.AsBoolean()); + Assert.AreEqual(true, JsonString.AsBoolean()); + Assert.AreEqual(true, HtmlEntityString.AsBoolean()); + Assert.AreEqual(true, ControlCharString.AsBoolean()); + Assert.AreEqual(true, SingleCharString.AsBoolean()); + Assert.AreEqual(true, LongWordString.AsBoolean()); + Assert.AreEqual(true, ConcatenatedString.AsBoolean()); + Assert.AreEqual(true, WhiteSpaceString.AsBoolean()); + Assert.AreEqual(true, FilePathString.AsBoolean()); + Assert.AreEqual(true, LargeNumberString.AsBoolean()); + Assert.AreEqual(true, HexadecimalString.AsBoolean()); + Assert.AreEqual(true, PalindromeString.AsBoolean()); + Assert.AreEqual(true, SqlInjectionString.AsBoolean()); + Assert.AreEqual(true, RegexString.AsBoolean()); + Assert.AreEqual(true, DateTimeString.AsBoolean()); + Assert.AreEqual(true, SpecialCharString.AsBoolean()); + Assert.AreEqual(true, SubstringString.AsBoolean()); + Assert.AreEqual(true, CaseSensitiveString1.AsBoolean()); + Assert.AreEqual(true, CaseSensitiveString2.AsBoolean()); + Assert.AreEqual(true, BooleanString.AsBoolean()); + Assert.AreEqual(true, FormatSpecifierString.AsBoolean()); + Assert.AreEqual(true, EmojiSequenceString.AsBoolean()); + Assert.AreEqual(true, NullCharString.AsBoolean()); + Assert.AreEqual(true, RepeatingPatternString.AsBoolean()); } [TestMethod] public void TestAsNumber() { - string s1 = "hello world"; - string s2 = "123"; - string s3 = ""; - JString jstring1 = new JString(s1); - JString jstring2 = new JString(s2); - JString jstring3 = new JString(s3); - Assert.AreEqual(double.NaN, jstring1.AsNumber()); - Assert.AreEqual(123, jstring2.AsNumber()); - Assert.AreEqual(0, jstring3.AsNumber()); + Assert.AreEqual(double.NaN, AsicString.AsNumber()); + Assert.AreEqual(double.NaN, EscapeString.AsNumber()); + Assert.AreEqual(double.NaN, BadChar.AsNumber()); + Assert.AreEqual(123, IntegerString.AsNumber()); + Assert.AreEqual(0, EmptyString.AsNumber()); + Assert.AreEqual(double.NaN, SpaceString.AsNumber()); + Assert.AreEqual(123.456, DoubleString.AsNumber()); + Assert.AreEqual(double.NaN, UnicodeString.AsNumber()); + Assert.AreEqual(double.NaN, EmojString.AsNumber()); + Assert.AreEqual(double.NaN, MixedString.AsNumber()); + Assert.AreEqual(double.NaN, LongString.AsNumber()); + Assert.AreEqual(double.NaN, MultiLangString.AsNumber()); + Assert.AreEqual(double.NaN, JsonString.AsNumber()); + Assert.AreEqual(double.NaN, HtmlEntityString.AsNumber()); + Assert.AreEqual(double.NaN, ControlCharString.AsNumber()); + Assert.AreEqual(double.NaN, SingleCharString.AsNumber()); + Assert.AreEqual(double.NaN, LongWordString.AsNumber()); + Assert.AreEqual(double.NaN, ConcatenatedString.AsNumber()); + Assert.AreEqual(double.NaN, WhiteSpaceString.AsNumber()); + Assert.AreEqual(double.NaN, FilePathString.AsNumber()); + Assert.AreEqual(12345678901234567890d, LargeNumberString.AsNumber()); + Assert.AreEqual(double.NaN, HexadecimalString.AsNumber()); // Depending on how hexadecimal strings are handled + Assert.AreEqual(double.NaN, PalindromeString.AsNumber()); + Assert.AreEqual(double.NaN, SqlInjectionString.AsNumber()); + Assert.AreEqual(double.NaN, RegexString.AsNumber()); + Assert.AreEqual(double.NaN, DateTimeString.AsNumber()); + Assert.AreEqual(double.NaN, SpecialCharString.AsNumber()); + Assert.AreEqual(double.NaN, SubstringString.AsNumber()); + Assert.AreEqual(double.NaN, CaseSensitiveString1.AsNumber()); + Assert.AreEqual(double.NaN, CaseSensitiveString2.AsNumber()); + Assert.AreEqual(double.NaN, BooleanString.AsNumber()); + Assert.AreEqual(double.NaN, FormatSpecifierString.AsNumber()); + Assert.AreEqual(double.NaN, EmojiSequenceString.AsNumber()); + Assert.AreEqual(double.NaN, NullCharString.AsNumber()); + Assert.AreEqual(double.NaN, RepeatingPatternString.AsNumber()); } [TestMethod] - public void TestGetEnum() + public void TestValidGetEnum() { - JString s = "James"; - Woo woo = s.GetEnum(); + JString validEnum = "James"; + + Woo woo = validEnum.GetEnum(); Assert.AreEqual(Woo.James, woo); - s = ""; - woo = s.AsEnum(Woo.Jerry, false); + validEnum = ""; + woo = validEnum.AsEnum(Woo.Jerry, false); Assert.AreEqual(Woo.Jerry, woo); } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void TestInValidGetEnum() + { + JString validEnum = "_James"; + Woo woo = validEnum.GetEnum(); + } + + [TestMethod] + public void TestMixedString() + { + Assert.AreEqual("abc123!@# ", MixedString.Value); + } + + [TestMethod] + public void TestLongString() + { + Assert.AreEqual(new String('x', 5000), LongString.Value); + } + + [TestMethod] + public void TestMultiLangString() + { + Assert.AreEqual("Hello 你好 مرحبا", MultiLangString.Value); + } + + [TestMethod] + public void TestJsonString() + { + Assert.AreEqual("{\"key\": \"value\"}", JsonString.Value); + } + + [TestMethod] + public void TestHtmlEntityString() + { + Assert.AreEqual("& < >", HtmlEntityString.Value); + } + + [TestMethod] + public void TestControlCharString() + { + Assert.AreEqual("\t\n\r", ControlCharString.Value); + } + + [TestMethod] + public void TestSingleCharString() + { + Assert.AreEqual("a", SingleCharString.Value); + } + + [TestMethod] + public void TestLongWordString() + { + Assert.AreEqual("Supercalifragilisticexpialidocious", LongWordString.Value); + } + + [TestMethod] + public void TestConcatenatedString() + { + Assert.AreEqual("Hello123!@#", ConcatenatedString.Value); + } + + [TestMethod] + public void TestWhiteSpaceString() + { + Assert.AreEqual(" leading and trailing spaces ", WhiteSpaceString.Value); + } + + [TestMethod] + public void TestFilePathString() + { + Assert.AreEqual(@"C:\Users\Example\file.txt", FilePathString.Value); + } + + [TestMethod] + public void TestLargeNumberString() + { + Assert.AreEqual("12345678901234567890", LargeNumberString.Value); + } + + [TestMethod] + public void TestHexadecimalString() + { + Assert.AreEqual("0x1A3F", HexadecimalString.Value); + } + + [TestMethod] + public void TestPalindromeString() + { + Assert.AreEqual("racecar", PalindromeString.Value); + } + + [TestMethod] + public void TestSqlInjectionString() + { + Assert.AreEqual("SELECT * FROM users WHERE name = 'a'; DROP TABLE users;", SqlInjectionString.Value); + } + + [TestMethod] + public void TestRegexString() + { + Assert.AreEqual(@"^\d{3}-\d{2}-\d{4}$", RegexString.Value); + } + + [TestMethod] + public void TestDateTimeString() + { + Assert.AreEqual("2023-01-01T00:00:00", DateTimeString.Value); + } + + [TestMethod] + public void TestSpecialCharString() + { + Assert.AreEqual("!?@#$%^&*()", SpecialCharString.Value); + } + + [TestMethod] + public void TestSubstringString() + { + Assert.AreEqual("Hello", SubstringString.Value); + } + + [TestMethod] + public void TestCaseSensitiveStrings() + { + Assert.AreNotEqual(CaseSensitiveString1.Value, CaseSensitiveString2.Value); + } + + [TestMethod] + public void TestBooleanString() + { + Assert.AreEqual("true", BooleanString.Value); + } + + [TestMethod] + public void TestFormatSpecifierString() + { + Assert.AreEqual("{0:C}", FormatSpecifierString.Value); + } + + [TestMethod] + public void TestEmojiSequenceString() + { + Assert.AreEqual("👨‍👩‍👦", EmojiSequenceString.Value); + } + + [TestMethod] + public void TestNullCharString() + { + Assert.AreEqual("Hello\0World", NullCharString.Value); + } + + [TestMethod] + public void TestRepeatingPatternString() + { + Assert.AreEqual("abcabcabc", RepeatingPatternString.Value); + } + [TestMethod] 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)); + } + + [TestMethod] + public void TestWrite() + { + var jString = new JString("hello world"); + using (var stream = new MemoryStream()) + using (var writer = new Utf8JsonWriter(stream)) + { + jString.Write(writer); + writer.Flush(); + var json = Encoding.UTF8.GetString(stream.ToArray()); + Assert.AreEqual("\"hello world\"", json); + } + } + + [TestMethod] + public void TestClone() + { + var jString = new JString("hello world"); + var clone = jString.Clone(); + Assert.AreEqual(jString, clone); + Assert.AreSame(jString, clone); // Cloning should return the same instance for immutable objects + } + + [TestMethod] + public void TestEqualityWithDifferentTypes() + { + var jString = new JString("hello world"); + Assert.IsFalse(jString.Equals(123)); + Assert.IsFalse(jString.Equals(new object())); + Assert.IsFalse(jString.Equals(new JBoolean())); } + + [TestMethod] + public void TestImplicitOperators() + { + JString fromEnum = EnumExample.Value; + Assert.AreEqual("Value", fromEnum.Value); + + JString fromString = "test string"; + Assert.AreEqual("test string", fromString.Value); + + JString nullString = (string)null; + Assert.IsNull(nullString); + } + + [TestMethod] + public void TestBoundaryAndSpecialCases() + { + JString largeString = new string('a', ushort.MaxValue); + Assert.AreEqual(ushort.MaxValue, largeString.Value.Length); + + JString specialUnicode = "\uD83D\uDE00"; // 😀 emoji + Assert.AreEqual("\uD83D\uDE00", specialUnicode.Value); + + JString complexJson = "{\"nested\":{\"key\":\"value\"}}"; + Assert.AreEqual("{\"nested\":{\"key\":\"value\"}}", complexJson.Value); + } + + [TestMethod] + public void TestExceptionHandling() + { + JString invalidEnum = "invalid_value"; + + var result = invalidEnum.AsEnum(Woo.Jerry); + Assert.AreEqual(Woo.Jerry, result); + + Assert.ThrowsException(() => invalidEnum.GetEnum()); + } + } + public enum EnumExample + { + Value } } diff --git a/tests/Neo.Network.RPC.Tests/Neo.Network.RPC.Tests.csproj b/tests/Neo.Network.RPC.Tests/Neo.Network.RPC.Tests.csproj index 13a13f5c98..fc632685b4 100644 --- a/tests/Neo.Network.RPC.Tests/Neo.Network.RPC.Tests.csproj +++ b/tests/Neo.Network.RPC.Tests/Neo.Network.RPC.Tests.csproj @@ -7,9 +7,9 @@ - - - + + + diff --git a/tests/Neo.Network.RPC.Tests/RpcTestCases.json b/tests/Neo.Network.RPC.Tests/RpcTestCases.json index cfbffb3ede..623b59f0d9 100644 --- a/tests/Neo.Network.RPC.Tests/RpcTestCases.json +++ b/tests/Neo.Network.RPC.Tests/RpcTestCases.json @@ -355,7 +355,7 @@ "jsonrpc": "2.0", "id": 1, "result": { - "id": -4, + "id": -6, "updatecounter": 0, "hash": "0xd2a4cff31913016155e38e474a2c06d08be276cf", "nef": { @@ -363,8 +363,8 @@ "compiler": "neo-core-v3.0", "source": "", "tokens": [], - "script": "APxBGvd7Zw==", - "checksum": 3155977747 + "script": "EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=", + "checksum": 2663858513 }, "manifest": { "name": "GasToken", @@ -391,21 +391,21 @@ "name": "decimals", "parameters": [], "returntype": "Integer", - "offset": 0, + "offset": 7, "safe": true }, { "name": "symbol", "parameters": [], "returntype": "String", - "offset": 0, + "offset": 14, "safe": true }, { "name": "totalSupply", "parameters": [], "returntype": "Integer", - "offset": 0, + "offset": 21, "safe": true }, { @@ -429,7 +429,129 @@ } ], "returntype": "Boolean", + "offset": 28, + "safe": false + } + ], + "events": [ + { + "name": "Transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + } + ] + } + ] + }, + "permissions": [ + { + "contract": "*", + "methods": "*" + } + ], + "trusts": [], + "extra": null + } + } + } + }, + { + "Name": "getcontractstateasync", + "Request": { + "jsonrpc": "2.0", + "method": "getcontractstate", + "params": [ -6 ], + "id": 1 + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "id": -6, + "updatecounter": 0, + "hash": "0xd2a4cff31913016155e38e474a2c06d08be276cf", + "nef": { + "magic": 860243278, + "compiler": "neo-core-v3.0", + "source": "", + "tokens": [], + "script": "EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=", + "checksum": 2663858513 + }, + "manifest": { + "name": "GasToken", + "groups": [], + "features": {}, + "supportedstandards": [ + "NEP-17" + ], + "abi": { + "methods": [ + { + "name": "balanceOf", + "parameters": [ + { + "name": "account", + "type": "Hash160" + } + ], + "returntype": "Integer", "offset": 0, + "safe": true + }, + { + "name": "decimals", + "parameters": [], + "returntype": "Integer", + "offset": 7, + "safe": true + }, + { + "name": "symbol", + "parameters": [], + "returntype": "String", + "offset": 14, + "safe": true + }, + { + "name": "totalSupply", + "parameters": [], + "returntype": "Integer", + "offset": 21, + "safe": true + }, + { + "name": "transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + }, + { + "name": "data", + "type": "Any" + } + ], + "returntype": "Boolean", + "offset": 28, "safe": false } ], @@ -477,7 +599,7 @@ "jsonrpc": "2.0", "id": 1, "result": { - "id": -4, + "id": -6, "updatecounter": 0, "hash": "0xd2a4cff31913016155e38e474a2c06d08be276cf", "nef": { @@ -485,8 +607,8 @@ "compiler": "neo-core-v3.0", "source": "", "tokens": [], - "script": "APxBGvd7Zw==", - "checksum": 3155977747 + "script": "EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=", + "checksum": 2663858513 }, "manifest": { "name": "GasToken", @@ -513,45 +635,320 @@ "name": "decimals", "parameters": [], "returntype": "Integer", - "offset": 0, + "offset": 7, "safe": true }, { "name": "symbol", "parameters": [], "returntype": "String", + "offset": 14, + "safe": true + }, + { + "name": "totalSupply", + "parameters": [], + "returntype": "Integer", + "offset": 21, + "safe": true + }, + { + "name": "transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + }, + { + "name": "data", + "type": "Any" + } + ], + "returntype": "Boolean", + "offset": 28, + "safe": false + } + ], + "events": [ + { + "name": "Transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + } + ] + } + ] + }, + "permissions": [ + { + "contract": "*", + "methods": "*" + } + ], + "trusts": [], + "extra": null + } + } + } + }, + { + "Name": "getcontractstateasync", + "Request": { + "jsonrpc": "2.0", + "id": 1, + "method": "getcontractstate", + "params": [ "neotoken" ] + }, + "Response": { + "jsonrpc": "2.0", + "id": 1, + "result": { + "id": -5, + "updatecounter": 1, + "hash": "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5", + "nef": { + "magic": 860243278, + "compiler": "neo-core-v3.0", + "source": "", + "tokens": [], + "script": "EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=", + "checksum": 1325686241 + }, + "manifest": { + "name": "NeoToken", + "groups": [], + "features": {}, + "supportedstandards": [ + "NEP-17" + ], + "abi": { + "methods": [ + { + "name": "balanceOf", + "parameters": [ + { + "name": "account", + "type": "Hash160" + } + ], + "returntype": "Integer", "offset": 0, "safe": true }, + { + "name": "decimals", + "parameters": [], + "returntype": "Integer", + "offset": 7, + "safe": true + }, + { + "name": "getAccountState", + "parameters": [ + { + "name": "account", + "type": "Hash160" + } + ], + "returntype": "Array", + "offset": 14, + "safe": true + }, + { + "name": "getAllCandidates", + "parameters": [], + "returntype": "InteropInterface", + "offset": 21, + "safe": true + }, + { + "name": "getCandidateVote", + "parameters": [ + { + "name": "pubKey", + "type": "PublicKey" + } + ], + "returntype": "Integer", + "offset": 28, + "safe": true + }, + { + "name": "getCandidates", + "parameters": [], + "returntype": "Array", + "offset": 35, + "safe": true + }, + { + "name": "getCommittee", + "parameters": [], + "returntype": "Array", + "offset": 42, + "safe": true + }, + { + "name": "getCommitteeAddress", + "parameters": [], + "returntype": "Hash160", + "offset": 49, + "safe": true + }, + { + "name": "getGasPerBlock", + "parameters": [], + "returntype": "Integer", + "offset": 56, + "safe": true + }, + { + "name": "getNextBlockValidators", + "parameters": [], + "returntype": "Array", + "offset": 63, + "safe": true + }, + { + "name": "getRegisterPrice", + "parameters": [], + "returntype": "Integer", + "offset": 70, + "safe": true + }, + { + "name": "registerCandidate", + "parameters": [ + { + "name": "pubkey", + "type": "PublicKey" + } + ], + "returntype": "Boolean", + "offset": 77, + "safe": false + }, + { + "name": "setGasPerBlock", + "parameters": [ + { + "name": "gasPerBlock", + "type": "Integer" + } + ], + "returntype": "Void", + "offset": 84, + "safe": false + }, + { + "name": "setRegisterPrice", + "parameters": [ + { + "name": "registerPrice", + "type": "Integer" + } + ], + "returntype": "Void", + "offset": 91, + "safe": false + }, + { + "name": "symbol", + "parameters": [], + "returntype": "String", + "offset": 98, + "safe": true + }, { "name": "totalSupply", "parameters": [], "returntype": "Integer", - "offset": 0, + "offset": 105, + "safe": true + }, + { + "name": "transfer", + "parameters": [ + { + "name": "from", + "type": "Hash160" + }, + { + "name": "to", + "type": "Hash160" + }, + { + "name": "amount", + "type": "Integer" + }, + { + "name": "data", + "type": "Any" + } + ], + "returntype": "Boolean", + "offset": 112, + "safe": false + }, + { + "name": "unclaimedGas", + "parameters": [ + { + "name": "account", + "type": "Hash160" + }, + { + "name": "end", + "type": "Integer" + } + ], + "returntype": "Integer", + "offset": 119, "safe": true }, { - "name": "transfer", + "name": "unregisterCandidate", "parameters": [ { - "name": "from", - "type": "Hash160" - }, + "name": "pubkey", + "type": "PublicKey" + } + ], + "returntype": "Boolean", + "offset": 126, + "safe": false + }, + { + "name": "vote", + "parameters": [ { - "name": "to", + "name": "account", "type": "Hash160" }, { - "name": "amount", - "type": "Integer" - }, - { - "name": "data", - "type": "Any" + "name": "voteTo", + "type": "PublicKey" } ], "returntype": "Boolean", - "offset": 0, + "offset": 133, "safe": false } ], @@ -572,6 +969,57 @@ "type": "Integer" } ] + }, + { + "name": "CandidateStateChanged", + "parameters": [ + { + "name": "pubkey", + "type": "PublicKey" + }, + { + "name": "registered", + "type": "Boolean" + }, + { + "name": "votes", + "type": "Integer" + } + ] + }, + { + "name": "Vote", + "parameters": [ + { + "name": "account", + "type": "Hash160" + }, + { + "name": "from", + "type": "PublicKey" + }, + { + "name": "to", + "type": "PublicKey" + }, + { + "name": "amount", + "type": "Integer" + } + ] + }, + { + "name": "CommitteeChanged", + "parameters": [ + { + "name": "old", + "type": "Array" + }, + { + "name": "new", + "type": "Array" + } + ] } ] }, @@ -593,22 +1041,22 @@ "jsonrpc": "2.0", "id": 1, "method": "getcontractstate", - "params": [ "neotoken" ] + "params": [ -5 ] }, "Response": { "jsonrpc": "2.0", "id": 1, "result": { - "id": -3, - "updatecounter": 0, + "id": -5, + "updatecounter": 1, "hash": "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5", "nef": { "magic": 860243278, "compiler": "neo-core-v3.0", "source": "", "tokens": [], - "script": "AP1BGvd7Zw==", - "checksum": 3921333105 + "script": "EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=", + "checksum": 1325686241 }, "manifest": { "name": "NeoToken", @@ -635,35 +1083,80 @@ "name": "decimals", "parameters": [], "returntype": "Integer", - "offset": 0, + "offset": 7, + "safe": true + }, + { + "name": "getAccountState", + "parameters": [ + { + "name": "account", + "type": "Hash160" + } + ], + "returntype": "Array", + "offset": 14, + "safe": true + }, + { + "name": "getAllCandidates", + "parameters": [], + "returntype": "InteropInterface", + "offset": 21, + "safe": true + }, + { + "name": "getCandidateVote", + "parameters": [ + { + "name": "pubKey", + "type": "PublicKey" + } + ], + "returntype": "Integer", + "offset": 28, "safe": true }, { "name": "getCandidates", "parameters": [], "returntype": "Array", - "offset": 0, + "offset": 35, "safe": true }, { "name": "getCommittee", "parameters": [], "returntype": "Array", - "offset": 0, + "offset": 42, + "safe": true + }, + { + "name": "getCommitteeAddress", + "parameters": [], + "returntype": "Hash160", + "offset": 49, "safe": true }, { "name": "getGasPerBlock", "parameters": [], "returntype": "Integer", - "offset": 0, + "offset": 56, "safe": true }, { "name": "getNextBlockValidators", "parameters": [], "returntype": "Array", - "offset": 0, + "offset": 63, + "safe": true + }, + { + "name": "getRegisterPrice", + "parameters": [], + "returntype": "Integer", + "offset": 70, "safe": true }, { @@ -671,11 +1164,11 @@ "parameters": [ { "name": "pubkey", - "type": "ByteArray" + "type": "PublicKey" } ], "returntype": "Boolean", - "offset": 0, + "offset": 77, "safe": false }, { @@ -686,22 +1179,34 @@ "type": "Integer" } ], - "returntype": "Boolean", - "offset": 0, + "returntype": "Void", + "offset": 84, + "safe": false + }, + { + "name": "setRegisterPrice", + "parameters": [ + { + "name": "registerPrice", + "type": "Integer" + } + ], + "returntype": "Void", + "offset": 91, "safe": false }, { "name": "symbol", "parameters": [], "returntype": "String", - "offset": 0, + "offset": 98, "safe": true }, { "name": "totalSupply", "parameters": [], "returntype": "Integer", - "offset": 0, + "offset": 105, "safe": true }, { @@ -725,7 +1230,7 @@ } ], "returntype": "Boolean", - "offset": 0, + "offset": 112, "safe": false }, { @@ -741,7 +1246,7 @@ } ], "returntype": "Integer", - "offset": 0, + "offset": 119, "safe": true }, { @@ -749,11 +1254,11 @@ "parameters": [ { "name": "pubkey", - "type": "ByteArray" + "type": "PublicKey" } ], "returntype": "Boolean", - "offset": 0, + "offset": 126, "safe": false }, { @@ -765,11 +1270,11 @@ }, { "name": "voteTo", - "type": "ByteArray" + "type": "PublicKey" } ], "returntype": "Boolean", - "offset": 0, + "offset": 133, "safe": false } ], @@ -790,6 +1295,57 @@ "type": "Integer" } ] + }, + { + "name": "CandidateStateChanged", + "parameters": [ + { + "name": "pubkey", + "type": "PublicKey" + }, + { + "name": "registered", + "type": "Boolean" + }, + { + "name": "votes", + "type": "Integer" + } + ] + }, + { + "name": "Vote", + "parameters": [ + { + "name": "account", + "type": "Hash160" + }, + { + "name": "from", + "type": "PublicKey" + }, + { + "name": "to", + "type": "PublicKey" + }, + { + "name": "amount", + "type": "Integer" + } + ] + }, + { + "name": "CommitteeChanged", + "parameters": [ + { + "name": "old", + "type": "Array" + }, + { + "name": "new", + "type": "Array" + } + ] } ] }, @@ -817,16 +1373,16 @@ "jsonrpc": "2.0", "id": 1, "result": { - "id": -3, - "updatecounter": 0, + "id": -5, + "updatecounter": 1, "hash": "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5", "nef": { "magic": 860243278, "compiler": "neo-core-v3.0", "source": "", "tokens": [], - "script": "AP1BGvd7Zw==", - "checksum": 3921333105 + "script": "EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=", + "checksum": 1325686241 }, "manifest": { "name": "NeoToken", @@ -853,35 +1409,80 @@ "name": "decimals", "parameters": [], "returntype": "Integer", - "offset": 0, + "offset": 7, + "safe": true + }, + { + "name": "getAccountState", + "parameters": [ + { + "name": "account", + "type": "Hash160" + } + ], + "returntype": "Array", + "offset": 14, + "safe": true + }, + { + "name": "getAllCandidates", + "parameters": [], + "returntype": "InteropInterface", + "offset": 21, + "safe": true + }, + { + "name": "getCandidateVote", + "parameters": [ + { + "name": "pubKey", + "type": "PublicKey" + } + ], + "returntype": "Integer", + "offset": 28, "safe": true }, { "name": "getCandidates", "parameters": [], "returntype": "Array", - "offset": 0, + "offset": 35, "safe": true }, { "name": "getCommittee", "parameters": [], "returntype": "Array", - "offset": 0, + "offset": 42, + "safe": true + }, + { + "name": "getCommitteeAddress", + "parameters": [], + "returntype": "Hash160", + "offset": 49, "safe": true }, { "name": "getGasPerBlock", "parameters": [], "returntype": "Integer", - "offset": 0, + "offset": 56, "safe": true }, { "name": "getNextBlockValidators", "parameters": [], "returntype": "Array", - "offset": 0, + "offset": 63, + "safe": true + }, + { + "name": "getRegisterPrice", + "parameters": [], + "returntype": "Integer", + "offset": 70, "safe": true }, { @@ -889,11 +1490,11 @@ "parameters": [ { "name": "pubkey", - "type": "ByteArray" + "type": "PublicKey" } ], "returntype": "Boolean", - "offset": 0, + "offset": 77, "safe": false }, { @@ -904,22 +1505,34 @@ "type": "Integer" } ], - "returntype": "Boolean", - "offset": 0, + "returntype": "Void", + "offset": 84, + "safe": false + }, + { + "name": "setRegisterPrice", + "parameters": [ + { + "name": "registerPrice", + "type": "Integer" + } + ], + "returntype": "Void", + "offset": 91, "safe": false }, { "name": "symbol", "parameters": [], "returntype": "String", - "offset": 0, + "offset": 98, "safe": true }, { "name": "totalSupply", "parameters": [], "returntype": "Integer", - "offset": 0, + "offset": 105, "safe": true }, { @@ -943,7 +1556,7 @@ } ], "returntype": "Boolean", - "offset": 0, + "offset": 112, "safe": false }, { @@ -959,7 +1572,7 @@ } ], "returntype": "Integer", - "offset": 0, + "offset": 119, "safe": true }, { @@ -967,11 +1580,11 @@ "parameters": [ { "name": "pubkey", - "type": "ByteArray" + "type": "PublicKey" } ], "returntype": "Boolean", - "offset": 0, + "offset": 126, "safe": false }, { @@ -983,11 +1596,11 @@ }, { "name": "voteTo", - "type": "ByteArray" + "type": "PublicKey" } ], "returntype": "Boolean", - "offset": 0, + "offset": 133, "safe": false } ], @@ -1008,6 +1621,57 @@ "type": "Integer" } ] + }, + { + "name": "CandidateStateChanged", + "parameters": [ + { + "name": "pubkey", + "type": "PublicKey" + }, + { + "name": "registered", + "type": "Boolean" + }, + { + "name": "votes", + "type": "Integer" + } + ] + }, + { + "name": "Vote", + "parameters": [ + { + "name": "account", + "type": "Hash160" + }, + { + "name": "from", + "type": "PublicKey" + }, + { + "name": "to", + "type": "PublicKey" + }, + { + "name": "amount", + "type": "Integer" + } + ] + }, + { + "name": "CommitteeChanged", + "parameters": [ + { + "name": "old", + "type": "Array" + }, + { + "name": "new", + "type": "Array" + } + ] } ] }, @@ -2496,6 +3160,36 @@ "name": "Aspidochelone", "blockheight": 0 } + ], + "standbycommittee": [ + "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", + "02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", + "03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", + "02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", + "024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", + "02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", + "02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", + "023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe", + "03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379", + "03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050", + "03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0", + "02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62", + "03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0", + "0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654", + "020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639", + "0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30", + "03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde", + "02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad", + "0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d", + "03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc", + "02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a" + ], + "seedlist": [ + "seed1.neo.org:10333", + "seed2.neo.org:10333", + "seed3.neo.org:10333", + "seed4.neo.org:10333", + "seed5.neo.org:10333" ] } } diff --git a/tests/Neo.Network.RPC.Tests/UT_ContractClient.cs b/tests/Neo.Network.RPC.Tests/UT_ContractClient.cs index c3cf226a3e..6ac90dafc5 100644 --- a/tests/Neo.Network.RPC.Tests/UT_ContractClient.cs +++ b/tests/Neo.Network.RPC.Tests/UT_ContractClient.cs @@ -11,6 +11,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Neo.Extensions; using Neo.SmartContract; using Neo.SmartContract.Manifest; using Neo.SmartContract.Native; diff --git a/tests/Neo.Network.RPC.Tests/UT_Nep17API.cs b/tests/Neo.Network.RPC.Tests/UT_Nep17API.cs index 3bfb4e87ff..ef156a5b05 100644 --- a/tests/Neo.Network.RPC.Tests/UT_Nep17API.cs +++ b/tests/Neo.Network.RPC.Tests/UT_Nep17API.cs @@ -11,6 +11,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; +using Neo.Extensions; using Neo.Json; using Neo.SmartContract; using Neo.SmartContract.Native; @@ -19,7 +20,6 @@ using System.Linq; using System.Numerics; using System.Threading.Tasks; -using static Neo.Helper; namespace Neo.Network.RPC.Tests { @@ -84,20 +84,20 @@ public async Task TestGetTotalSupply() public async Task TestGetTokenInfo() { UInt160 scriptHash = NativeContract.GAS.Hash; - byte[] testScript = Concat( - scriptHash.MakeScript("symbol"), - scriptHash.MakeScript("decimals"), - scriptHash.MakeScript("totalSupply")); + byte[] testScript = [ + .. scriptHash.MakeScript("symbol"), + .. scriptHash.MakeScript("decimals"), + .. scriptHash.MakeScript("totalSupply")]; UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.String, Value = NativeContract.GAS.Symbol }, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(NativeContract.GAS.Decimals) }, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(1_00000000) }); scriptHash = NativeContract.NEO.Hash; - testScript = Concat( - scriptHash.MakeScript("symbol"), - scriptHash.MakeScript("decimals"), - scriptHash.MakeScript("totalSupply")); + testScript = [ + .. scriptHash.MakeScript("symbol"), + .. scriptHash.MakeScript("decimals"), + .. scriptHash.MakeScript("totalSupply")]; UT_TransactionManager.MockInvokeScript(rpcClientMock, testScript, new ContractParameter { Type = ContractParameterType.String, Value = NativeContract.NEO.Symbol }, new ContractParameter { Type = ContractParameterType.Integer, Value = new BigInteger(NativeContract.NEO.Decimals) }, diff --git a/tests/Neo.Network.RPC.Tests/UT_RpcClient.cs b/tests/Neo.Network.RPC.Tests/UT_RpcClient.cs index 4af0f557e3..b87960e76d 100644 --- a/tests/Neo.Network.RPC.Tests/UT_RpcClient.cs +++ b/tests/Neo.Network.RPC.Tests/UT_RpcClient.cs @@ -220,8 +220,17 @@ public async Task TestGetContractState() var tests = TestUtils.RpcTestCases.Where(p => p.Name == nameof(rpc.GetContractStateAsync).ToLower()); foreach (var test in tests) { - var result = await rpc.GetContractStateAsync(test.Request.Params[0].AsString()); - Assert.AreEqual(test.Response.Result.ToString(), result.ToJson().ToString()); + var type = test.Request.Params[0].GetType().Name; + if (type == "JString") + { + var result = await rpc.GetContractStateAsync(test.Request.Params[0].AsString()); + Assert.AreEqual(test.Response.Result.ToString(), result.ToJson().ToString()); + } + if (type == "JNumber") + { + var result = await rpc.GetContractStateAsync((int)test.Request.Params[0].AsNumber()); + Assert.AreEqual(test.Response.Result.ToString(), result.ToJson().ToString()); + } } } diff --git a/tests/Neo.Network.RPC.Tests/UT_Utility.cs b/tests/Neo.Network.RPC.Tests/UT_Utility.cs index fa410c5437..3c30069219 100644 --- a/tests/Neo.Network.RPC.Tests/UT_Utility.cs +++ b/tests/Neo.Network.RPC.Tests/UT_Utility.cs @@ -9,11 +9,16 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; +using Neo.Network.P2P.Payloads; using Neo.SmartContract; +using Neo.SmartContract.Native; using Neo.Wallets; using System; using System.Numerics; +using System.Security.Cryptography; namespace Neo.Network.RPC.Tests { @@ -32,6 +37,16 @@ public void TestSetup() protocolSettings = ProtocolSettings.Load("protocol.json"); } + [TestMethod] + public void TestAsScriptHash() + { + var scriptHash1 = Utility.AsScriptHash(NativeContract.NEO.Id.ToString()); + var scriptHash2 = Utility.AsScriptHash(NativeContract.NEO.Hash.ToString()); + var scriptHash3 = Utility.AsScriptHash(NativeContract.NEO.Name); + scriptHash2.Should().Be(scriptHash1); + scriptHash3.Should().Be(scriptHash1); + } + [TestMethod] public void TestGetKeyPair() { @@ -45,6 +60,13 @@ public void TestGetKeyPair() string privateKey = keyPair.PrivateKey.ToHexString(); result = Utility.GetKeyPair(privateKey); Assert.AreEqual(keyPair, result); + + string hexWith0x = $"0x{result.PrivateKey.ToHexString()}"; + result = Utility.GetKeyPair(hexWith0x); + Assert.AreEqual(keyPair, result); + + var action = () => { Utility.GetKeyPair("00"); }; + action.Should().Throw(); } [TestMethod] @@ -64,6 +86,124 @@ public void TestGetScriptHash() string publicKey = keyPair.PublicKey.ToString(); result = Utility.GetScriptHash(publicKey, protocolSettings); Assert.AreEqual(scriptHash, result); + + var action = () => { Utility.GetScriptHash("00", protocolSettings); }; + action.Should().Throw(); + } + + [TestMethod] + public void TestTransactionAttribute() + { + var attribute = new Conflicts(); + attribute.Hash = UInt256.Zero; + var json = attribute.ToJson(); + var result = Utility.TransactionAttributeFromJson(json).ToJson(); + result.ToString().Should().Be(json.ToString()); + + var attribute2 = new OracleResponse(); + attribute2.Id = 1234; + attribute2.Code = 0; + attribute2.Result = new ReadOnlyMemory { }; + json = attribute2.ToJson(); + result = Utility.TransactionAttributeFromJson(json).ToJson(); + result.ToString().Should().Be(json.ToString()); + + var attribute3 = new NotValidBefore(); + attribute3.Height = 10000; + json = attribute3.ToJson(); + result = Utility.TransactionAttributeFromJson(json).ToJson(); + result.ToString().Should().Be(json.ToString()); + + var attribute4 = new HighPriorityAttribute(); + json = attribute4.ToJson(); + result = Utility.TransactionAttributeFromJson(json).ToJson(); + result.ToString().Should().Be(json.ToString()); + } + + [TestMethod] + public void TestWitnessRule() + { + var rule = new WitnessRule(); + rule.Action = WitnessRuleAction.Allow; + rule.Condition = new Neo.Network.P2P.Payloads.Conditions.CalledByEntryCondition(); + var json = rule.ToJson(); + var result = Utility.RuleFromJson(json, ProtocolSettings.Default).ToJson(); + result.ToString().Should().Be(json.ToString()); + + rule.Condition = new Neo.Network.P2P.Payloads.Conditions.OrCondition() + { + Expressions = new P2P.Payloads.Conditions.WitnessCondition[] + { + new Neo.Network.P2P.Payloads.Conditions.BooleanCondition() + { + Expression = true + }, + new Neo.Network.P2P.Payloads.Conditions.BooleanCondition() + { + Expression = false + } + } + }; + json = rule.ToJson(); + result = Utility.RuleFromJson(json, ProtocolSettings.Default).ToJson(); + result.ToString().Should().Be(json.ToString()); + + rule.Condition = new Neo.Network.P2P.Payloads.Conditions.AndCondition() + { + Expressions = new P2P.Payloads.Conditions.WitnessCondition[] + { + new Neo.Network.P2P.Payloads.Conditions.BooleanCondition() + { + Expression = true + }, + new Neo.Network.P2P.Payloads.Conditions.BooleanCondition() + { + Expression = false + } + } + }; + json = rule.ToJson(); + result = Utility.RuleFromJson(json, ProtocolSettings.Default).ToJson(); + result.ToString().Should().Be(json.ToString()); + + rule.Condition = new Neo.Network.P2P.Payloads.Conditions.BooleanCondition() { Expression = true }; + json = rule.ToJson(); + result = Utility.RuleFromJson(json, ProtocolSettings.Default).ToJson(); + result.ToString().Should().Be(json.ToString()); + + rule.Condition = new Neo.Network.P2P.Payloads.Conditions.NotCondition() + { + Expression = new Neo.Network.P2P.Payloads.Conditions.BooleanCondition() + { + Expression = true + } + }; + json = rule.ToJson(); + result = Utility.RuleFromJson(json, ProtocolSettings.Default).ToJson(); + result.ToString().Should().Be(json.ToString()); + + var kp = Utility.GetKeyPair("KyXwTh1hB76RRMquSvnxZrJzQx7h9nQP2PCRL38v6VDb5ip3nf1p"); + rule.Condition = new Neo.Network.P2P.Payloads.Conditions.GroupCondition() { Group = kp.PublicKey }; + json = rule.ToJson(); + result = Utility.RuleFromJson(json, ProtocolSettings.Default).ToJson(); + result.ToString().Should().Be(json.ToString()); + + rule.Condition = new Neo.Network.P2P.Payloads.Conditions.CalledByContractCondition() { Hash = UInt160.Zero }; + json = rule.ToJson(); + result = Utility.RuleFromJson(json, ProtocolSettings.Default).ToJson(); + result.ToString().Should().Be(json.ToString()); + + rule.Condition = new Neo.Network.P2P.Payloads.Conditions.ScriptHashCondition() { Hash = UInt160.Zero }; + json = rule.ToJson(); + result = Utility.RuleFromJson(json, ProtocolSettings.Default).ToJson(); + result.ToString().Should().Be(json.ToString()); + result.ToString().Should().Be(json.ToString()); + + rule.Condition = new Neo.Network.P2P.Payloads.Conditions.CalledByGroupCondition() { Group = kp.PublicKey }; + json = rule.ToJson(); + result = Utility.RuleFromJson(json, ProtocolSettings.Default).ToJson(); + result.ToString().Should().Be(json.ToString()); + result.ToString().Should().Be(json.ToString()); } [TestMethod] diff --git a/tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs b/tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs index 10a35fd064..76851bbe99 100644 --- a/tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs +++ b/tests/Neo.Network.RPC.Tests/UT_WalletAPI.cs @@ -12,6 +12,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.Json; using Neo.Network.P2P.Payloads; using Neo.Network.RPC.Models; diff --git a/tests/Neo.Plugins.ApplicationLogs.Tests/Neo.Plugins.ApplicationLogs.Tests.csproj b/tests/Neo.Plugins.ApplicationLogs.Tests/Neo.Plugins.ApplicationLogs.Tests.csproj new file mode 100644 index 0000000000..92951a59dd --- /dev/null +++ b/tests/Neo.Plugins.ApplicationLogs.Tests/Neo.Plugins.ApplicationLogs.Tests.csproj @@ -0,0 +1,20 @@ + + + + net8.0 + Neo.Plugins.ApplicationsLogs.Tests + + + + + + + + + + + + + + + diff --git a/tests/Neo.Plugins.ApplicationLogs.Tests/Setup/TestStorage.cs b/tests/Neo.Plugins.ApplicationLogs.Tests/Setup/TestStorage.cs new file mode 100644 index 0000000000..17498d64e4 --- /dev/null +++ b/tests/Neo.Plugins.ApplicationLogs.Tests/Setup/TestStorage.cs @@ -0,0 +1,31 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// StorageFixture.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Persistence; +using Neo.Plugins.ApplicationLogs.Store; +using Neo.Plugins.Storage; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Neo.Plugins.ApplicationsLogs.Tests.Setup +{ + public class TestStorage + { + private static readonly string s_dirPath = Path.GetRandomFileName(); + private static readonly RocksDBStore rocksDbStore = new RocksDBStore(); + public static readonly IStore Store = rocksDbStore.GetStore(s_dirPath); + } +} diff --git a/tests/Neo.Plugins.ApplicationLogs.Tests/UT_LogReader.cs b/tests/Neo.Plugins.ApplicationLogs.Tests/UT_LogReader.cs new file mode 100644 index 0000000000..f7e2e2bdad --- /dev/null +++ b/tests/Neo.Plugins.ApplicationLogs.Tests/UT_LogReader.cs @@ -0,0 +1,213 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_ApplicationLogs.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; +using Microsoft.AspNetCore.Authorization; +using Neo.Cryptography; +using Neo.IO; +using Neo.Json; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.Plugins.ApplicationLogs; +using Neo.Plugins.ApplicationLogs.Store; +using Neo.Plugins.ApplicationLogs.Store.Models; +using Neo.Plugins.ApplicationLogs.Store.States; +using Neo.Plugins.ApplicationsLogs.Tests.Setup; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.UnitTests; +using Neo.UnitTests.Extensions; +using Neo.VM; +using Neo.Wallets; +using Neo.Wallets.NEP6; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Policy; +using System.Text; +using System.Threading.Tasks; +using Xunit; +using static Neo.Plugins.ApplicationsLogs.Tests.UT_LogReader; + +namespace Neo.Plugins.ApplicationsLogs.Tests +{ + public class UT_LogReader : IClassFixture + { + static readonly string NeoTransferScript = "CxEMFPlu76Cuc\u002BbgteStE4ozsOWTNUdrDBQtYNweHko3YcnMFOes3ceblcI/lRTAHwwIdHJhbnNmZXIMFPVj6kC8KD1NDgXEjqMFs/Kgc0DvQWJ9W1I="; + static readonly byte[] ValidatorScript = Contract.CreateSignatureRedeemScript(TestProtocolSettings.SoleNode.StandbyCommittee[0]); + static readonly UInt160 ValidatorScriptHash = ValidatorScript.ToScriptHash(); + static readonly string ValidatorAddress = ValidatorScriptHash.ToAddress(ProtocolSettings.Default.AddressVersion); + static readonly byte[] MultisigScript = Contract.CreateMultiSigRedeemScript(1, TestProtocolSettings.SoleNode.StandbyCommittee); + static readonly UInt160 MultisigScriptHash = MultisigScript.ToScriptHash(); + static readonly string MultisigAddress = MultisigScriptHash.ToAddress(ProtocolSettings.Default.AddressVersion); + + public class TestMemoryStoreProvider(MemoryStore memoryStore) : IStoreProvider + { + public MemoryStore MemoryStore { get; init; } = memoryStore; + public string Name => nameof(MemoryStore); + public IStore GetStore(string path) => MemoryStore; + } + + public class NeoSystemFixture : IDisposable + { + public NeoSystem _neoSystem; + public TestMemoryStoreProvider _memoryStoreProvider; + public MemoryStore _memoryStore; + public readonly NEP6Wallet _wallet = TestUtils.GenerateTestWallet("123"); + public WalletAccount _walletAccount; + public Transaction[] txs; + public Block block; + public LogReader logReader; + + public NeoSystemFixture() + { + _memoryStore = new MemoryStore(); + _memoryStoreProvider = new TestMemoryStoreProvider(_memoryStore); + logReader = new LogReader(); + Plugin.Plugins.Add(logReader); // initialize before NeoSystem to let NeoSystem load the plugin + _neoSystem = new NeoSystem(TestProtocolSettings.SoleNode with { Network = ApplicationLogs.Settings.Default.Network }, _memoryStoreProvider); + _walletAccount = _wallet.Import("KxuRSsHgJMb3AMSN6B9P3JHNGMFtxmuimqgR9MmXPcv3CLLfusTd"); + + NeoSystem system = _neoSystem; + txs = [ + new Transaction + { + Nonce = 233, + ValidUntilBlock = NativeContract.Ledger.CurrentIndex(system.GetSnapshotCache()) + system.Settings.MaxValidUntilBlockIncrement, + Signers = [new Signer() { Account = MultisigScriptHash, Scopes = WitnessScope.CalledByEntry }], + Attributes = Array.Empty(), + Script = Convert.FromBase64String(NeoTransferScript), + NetworkFee = 1000_0000, + SystemFee = 1000_0000, + } + ]; + byte[] signature = txs[0].Sign(_walletAccount.GetKey(), ApplicationLogs.Settings.Default.Network); + txs[0].Witnesses = [new Witness + { + InvocationScript = new byte[] { (byte)OpCode.PUSHDATA1, (byte)signature.Length }.Concat(signature).ToArray(), + VerificationScript = MultisigScript, + }]; + block = new Block + { + Header = new Header + { + Version = 0, + PrevHash = _neoSystem.GenesisBlock.Hash, + MerkleRoot = new UInt256(), + Timestamp = _neoSystem.GenesisBlock.Timestamp + 15_000, + Index = 1, + NextConsensus = _neoSystem.GenesisBlock.NextConsensus, + }, + Transactions = txs, + }; + block.Header.MerkleRoot ??= MerkleTree.ComputeRoot(block.Transactions.Select(t => t.Hash).ToArray()); + signature = block.Sign(_walletAccount.GetKey(), ApplicationLogs.Settings.Default.Network); + block.Header.Witness = new Witness + { + InvocationScript = new byte[] { (byte)OpCode.PUSHDATA1, (byte)signature.Length }.Concat(signature).ToArray(), + VerificationScript = MultisigScript, + }; + } + + public void Dispose() + { + logReader.Dispose(); + _neoSystem.Dispose(); + _memoryStore.Dispose(); + } + } + + private readonly NeoSystemFixture _neoSystemFixture; + + public UT_LogReader(NeoSystemFixture neoSystemFixture) + { + _neoSystemFixture = neoSystemFixture; + } + + [Fact] + public async Task Test_GetApplicationLog() + { + NeoSystem system = _neoSystemFixture._neoSystem; + Block block = _neoSystemFixture.block; + await system.Blockchain.Ask(block); // persist the block + + JObject blockJson = (JObject)_neoSystemFixture.logReader.GetApplicationLog([block.Hash.ToString()]); + Assert.Equal(blockJson["blockhash"], block.Hash.ToString()); + JArray executions = (JArray)blockJson["executions"]; + Assert.Equal(executions.Count, 2); + Assert.Equal(executions[0]["trigger"], "OnPersist"); + Assert.Equal(executions[1]["trigger"], "PostPersist"); + JArray notifications = (JArray)executions[1]["notifications"]; + Assert.Equal(notifications.Count, 1); + Assert.Equal(notifications[0]["contract"], GasToken.GAS.Hash.ToString()); + Assert.Equal(notifications[0]["eventname"], "Transfer"); // from null to Validator + Assert.Equal(notifications[0]["state"]["value"][0]["type"], nameof(ContractParameterType.Any)); + Assert.Equal(Convert.FromBase64String(notifications[0]["state"]["value"][1]["value"].AsString()), ValidatorScriptHash.ToArray()); + Assert.Equal(notifications[0]["state"]["value"][2]["value"], "50000000"); + + blockJson = (JObject)_neoSystemFixture.logReader.GetApplicationLog([block.Hash.ToString(), "PostPersist"]); + executions = (JArray)blockJson["executions"]; + Assert.Equal(executions.Count, 1); + Assert.Equal(executions[0]["trigger"], "PostPersist"); + + JObject transactionJson = (JObject)_neoSystemFixture.logReader.GetApplicationLog([_neoSystemFixture.txs[0].Hash.ToString(), true]); // "true" is invalid but still works + executions = (JArray)transactionJson["executions"]; + Assert.Equal(executions.Count, 1); + Assert.Equal(executions[0]["vmstate"], nameof(VMState.HALT)); + Assert.Equal(executions[0]["stack"][0]["value"], true); + notifications = (JArray)executions[0]["notifications"]; + Assert.Equal(notifications.Count, 2); + Assert.Equal(notifications[0]["eventname"].AsString(), "Transfer"); + Assert.Equal(notifications[0]["contract"].AsString(), NeoToken.NEO.Hash.ToString()); + Assert.Equal(notifications[0]["state"]["value"][2]["value"], "1"); + Assert.Equal(notifications[1]["eventname"].AsString(), "Transfer"); + Assert.Equal(notifications[1]["contract"].AsString(), GasToken.GAS.Hash.ToString()); + Assert.Equal(notifications[1]["state"]["value"][2]["value"], "50000000"); + } + + [Fact] + public async Task Test_Commands() + { + NeoSystem system = _neoSystemFixture._neoSystem; + Block block = _neoSystemFixture.block; + await system.Blockchain.Ask(block); // persist the block + + _neoSystemFixture.logReader.OnGetBlockCommand("1"); + _neoSystemFixture.logReader.OnGetBlockCommand(block.Hash.ToString()); + _neoSystemFixture.logReader.OnGetContractCommand(NeoToken.NEO.Hash); + _neoSystemFixture.logReader.OnGetTransactionCommand(_neoSystemFixture.txs[0].Hash); + + BlockchainExecutionModel blockLog = _neoSystemFixture.logReader._neostore.GetBlockLog(block.Hash, TriggerType.Application); + BlockchainExecutionModel transactionLog = _neoSystemFixture.logReader._neostore.GetTransactionLog(_neoSystemFixture.txs[0].Hash); + foreach (BlockchainExecutionModel log in new BlockchainExecutionModel[] { blockLog, transactionLog }) + { + Assert.Equal(log.VmState, VMState.HALT); + Assert.Equal(log.Stack[0].GetBoolean(), true); + Assert.Equal(log.Notifications.Count(), 2); + Assert.Equal(log.Notifications[0].EventName, "Transfer"); + Assert.Equal(log.Notifications[0].ScriptHash, NeoToken.NEO.Hash); + Assert.Equal(log.Notifications[0].State[2], 1); + Assert.Equal(log.Notifications[1].EventName, "Transfer"); + Assert.Equal(log.Notifications[1].ScriptHash, GasToken.GAS.Hash); + Assert.Equal(log.Notifications[1].State[2], 50000000); + } + + List<(BlockchainEventModel eventLog, UInt256 txHash)> neoLogs = _neoSystemFixture.logReader._neostore.GetContractLog(NeoToken.NEO.Hash, TriggerType.Application).ToList(); + Assert.Equal(neoLogs.Count, 1); + Assert.Equal(neoLogs[0].txHash, _neoSystemFixture.txs[0].Hash); + Assert.Equal(neoLogs[0].eventLog.EventName, "Transfer"); + Assert.Equal(neoLogs[0].eventLog.ScriptHash, NeoToken.NEO.Hash); + Assert.Equal(neoLogs[0].eventLog.State[2], 1); + } + } +} diff --git a/tests/Neo.Plugins.ApplicationLogs.Tests/UT_LogStorageStore.cs b/tests/Neo.Plugins.ApplicationLogs.Tests/UT_LogStorageStore.cs new file mode 100644 index 0000000000..5cb6fd2d6e --- /dev/null +++ b/tests/Neo.Plugins.ApplicationLogs.Tests/UT_LogStorageStore.cs @@ -0,0 +1,156 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_LogStorageStore.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.AspNetCore.Authorization; +using Neo.Persistence; +using Neo.Plugins.ApplicationLogs.Store; +using Neo.Plugins.ApplicationLogs.Store.States; +using Neo.Plugins.ApplicationsLogs.Tests.Setup; +using Neo.SmartContract; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Neo.Plugins.ApplicationsLogs.Tests +{ + public class UT_LogStorageStore + { + [Theory] + [InlineData(TriggerType.OnPersist, "0x0000000000000000000000000000000000000000000000000000000000000001")] + [InlineData(TriggerType.Application, "0x0000000000000000000000000000000000000000000000000000000000000002")] + [InlineData(TriggerType.PostPersist, "0x0000000000000000000000000000000000000000000000000000000000000003")] + public void Test_Put_Get_BlockState_Storage(TriggerType expectedAppTrigger, string expectedBlockHashString) + { + var expectedGuid = Guid.NewGuid(); + var expectedHash = UInt256.Parse(expectedBlockHashString); + + using (var snapshot = TestStorage.Store.GetSnapshot()) + { + using (var lss = new LogStorageStore(snapshot)) + { + // Put Block States in Storage for each Trigger + lss.PutBlockState(expectedHash, expectedAppTrigger, BlockLogState.Create([expectedGuid])); + // Commit Data to "Store" Storage for Lookup + snapshot.Commit(); + } + } + + // The Current way that ISnapshot Works we need to Create New Instance of LogStorageStore + using (var lss = new LogStorageStore(TestStorage.Store.GetSnapshot())) + { + // Get OnPersist Block State from Storage + var actualFound = lss.TryGetBlockState(expectedHash, expectedAppTrigger, out var actualState); + + Assert.True(actualFound); + Assert.NotNull(actualState); + Assert.NotNull(actualState.NotifyLogIds); + Assert.Single(actualState.NotifyLogIds); + Assert.Equal(expectedGuid, actualState.NotifyLogIds[0]); + } + } + + [Theory] + [InlineData("00000000-0000-0000-0000-000000000000", "0x0000000000000000000000000000000000000000000000000000000000000000")] + [InlineData("00000000-0000-0000-0000-000000000001", "0x0000000000000000000000000000000000000000000000000000000000000001")] + public void Test_Put_Get_TransactionEngineState_Storage(string expectedLogId, string expectedHashString) + { + var expectedGuid = Guid.Parse(expectedLogId); + var expectedTxHash = UInt256.Parse(expectedHashString); + + using (var snapshot = TestStorage.Store.GetSnapshot()) + { + using (var lss = new LogStorageStore(snapshot)) + { + // Put Block States in Storage for each Trigger + lss.PutTransactionEngineState(expectedTxHash, TransactionEngineLogState.Create([expectedGuid])); + // Commit Data to "Store" Storage for Lookup + snapshot.Commit(); + } + } + + using (var lss = new LogStorageStore(TestStorage.Store.GetSnapshot())) + { + // Get OnPersist Block State from Storage + var actualFound = lss.TryGetTransactionEngineState(expectedTxHash, out var actualState); + + Assert.True(actualFound); + Assert.NotNull(actualState); + Assert.NotNull(actualState.LogIds); + Assert.Single(actualState.LogIds); + Assert.Equal(expectedGuid, actualState.LogIds[0]); + } + } + + [Theory] + [InlineData("0x0000000000000000000000000000000000000000", "Hello World")] + [InlineData("0x0000000000000000000000000000000000000001", "Hello Again")] + public void Test_Put_Get_EngineState_Storage(string expectedScriptHashString, string expectedMessage) + { + var expectedScriptHash = UInt160.Parse(expectedScriptHashString); + var expectedGuid = Guid.Empty; + + using (var snapshot = TestStorage.Store.GetSnapshot()) + { + using (var lss = new LogStorageStore(snapshot)) + { + expectedGuid = lss.PutEngineState(EngineLogState.Create(expectedScriptHash, expectedMessage)); + snapshot.Commit(); + } + } + + using (var lss = new LogStorageStore(TestStorage.Store.GetSnapshot())) + { + var actualFound = lss.TryGetEngineState(expectedGuid, out var actualState); + + Assert.True(actualFound); + Assert.NotNull(actualState); + Assert.Equal(expectedScriptHash, actualState.ScriptHash); + Assert.Equal(expectedMessage, actualState.Message); + } + } + + [Theory] + [InlineData("0x0000000000000000000000000000000000000000", "SayHello", "00000000-0000-0000-0000-000000000000")] + [InlineData("0x0000000000000000000000000000000000000001", "SayGoodBye", "00000000-0000-0000-0000-000000000001")] + public void Test_Put_Get_NotifyState_Storage(string expectedScriptHashString, string expectedEventName, string expectedItemGuidString) + { + var expectedScriptHash = UInt160.Parse(expectedScriptHashString); + var expectedNotifyEventArgs = new NotifyEventArgs(null, expectedScriptHash, expectedEventName, []); + var expectedItemGuid = Guid.Parse(expectedItemGuidString); + var expectedGuid = Guid.Empty; + + using (var snapshot = TestStorage.Store.GetSnapshot()) + { + using (var lss = new LogStorageStore(snapshot)) + { + expectedGuid = lss.PutNotifyState(NotifyLogState.Create(expectedNotifyEventArgs, [expectedItemGuid])); + snapshot.Commit(); + } + } + + using (var lss = new LogStorageStore(TestStorage.Store.GetSnapshot())) + { + var actualFound = lss.TryGetNotifyState(expectedGuid, out var actualState); + + Assert.True(actualFound); + Assert.NotNull(actualState); + Assert.Equal(expectedScriptHash, actualState.ScriptHash); + Assert.Equal(expectedEventName, actualState.EventName); + Assert.NotNull(actualState.StackItemIds); + Assert.Single(actualState.StackItemIds); + Assert.Equal(expectedItemGuid, actualState.StackItemIds[0]); + } + } + } +} diff --git a/tests/Neo.Plugins.OracleService.Tests/E2E_Https.cs b/tests/Neo.Plugins.OracleService.Tests/E2E_Https.cs new file mode 100644 index 0000000000..c404f3f982 --- /dev/null +++ b/tests/Neo.Plugins.OracleService.Tests/E2E_Https.cs @@ -0,0 +1,104 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_OracleService.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.TestKit.Xunit2; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography; +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System; +using System.Linq; +using System.Threading.Tasks; +using static Neo.Plugins.OracleService.Tests.TestBlockchain; +using static Neo.Plugins.OracleService.Tests.TestUtils; + +namespace Neo.Plugins.OracleService.Tests +{ + [TestClass] + public class E2E_Https : TestKit + { + UInt160 customContract; + + [TestInitialize] + public void TestSetup() + { + customContract = InitializeContract(); + } + + [TestMethod] + public void TestE2EHttps() + { + byte[] script; + using (ScriptBuilder sb = new()) + { + sb.EmitDynamicCall(NativeContract.RoleManagement.Hash, "designateAsRole", + [Role.Oracle, + new ContractParameter() + { + Type = ContractParameterType.Array, + Value = settings.StandbyCommittee.Select( + p => new ContractParameter() { Type = ContractParameterType.PublicKey, Value = p }).ToList() + }]); + // Expected result: 12685221 + sb.EmitDynamicCall(customContract, "createRequest", + ["https://api.github.com/orgs/neo-project", "$.id", "callback", new byte[] { }, 1_0000_0000]); + script = sb.ToArray(); + } + Transaction[] txs = [ + new Transaction + { + Nonce = 233, + ValidUntilBlock = NativeContract.Ledger.CurrentIndex(s_theNeoSystem.GetSnapshotCache()) + s_theNeoSystem.Settings.MaxValidUntilBlockIncrement, + Signers = [new Signer() { Account = MultisigScriptHash, Scopes = WitnessScope.CalledByEntry }], + Attributes = Array.Empty(), + Script = script, + NetworkFee = 1000_0000, + SystemFee = 2_0000_0000, + } + ]; + byte[] signature = txs[0].Sign(s_walletAccount.GetKey(), settings.Network); + txs[0].Witnesses = [new Witness + { + InvocationScript = new byte[] { (byte)OpCode.PUSHDATA1, (byte)signature.Length }.Concat(signature).ToArray(), + VerificationScript = MultisigScript, + }]; + Block block = new Block + { + Header = new Header + { + Version = 0, + PrevHash = s_theNeoSystem.GenesisBlock.Hash, + Timestamp = s_theNeoSystem.GenesisBlock.Timestamp + 15_000, + Index = 1, + NextConsensus = s_theNeoSystem.GenesisBlock.NextConsensus, + }, + Transactions = txs, + }; + block.Header.MerkleRoot ??= MerkleTree.ComputeRoot(block.Transactions.Select(t => t.Hash).ToArray()); + signature = block.Sign(s_walletAccount.GetKey(), settings.Network); + block.Header.Witness = new Witness + { + InvocationScript = new byte[] { (byte)OpCode.PUSHDATA1, (byte)signature.Length }.Concat(signature).ToArray(), + VerificationScript = MultisigScript, + }; + s_theNeoSystem.Blockchain.Ask(block).Wait(); + Task t = s_oracle.Start(s_wallet); + t.Wait(TimeSpan.FromMilliseconds(900)); + s_oracle.cancelSource.Cancel(); + t.Wait(); + } + } +} diff --git a/tests/Neo.Plugins.OracleService.Tests/Neo.Plugins.OracleService.Tests.csproj b/tests/Neo.Plugins.OracleService.Tests/Neo.Plugins.OracleService.Tests.csproj index 4dd4abbfa4..d192f37ea7 100644 --- a/tests/Neo.Plugins.OracleService.Tests/Neo.Plugins.OracleService.Tests.csproj +++ b/tests/Neo.Plugins.OracleService.Tests/Neo.Plugins.OracleService.Tests.csproj @@ -7,11 +7,11 @@ - - - - - + + + + + diff --git a/tests/Neo.Plugins.OracleService.Tests/TestBlockchain.cs b/tests/Neo.Plugins.OracleService.Tests/TestBlockchain.cs index d689578e22..160ac0407e 100644 --- a/tests/Neo.Plugins.OracleService.Tests/TestBlockchain.cs +++ b/tests/Neo.Plugins.OracleService.Tests/TestBlockchain.cs @@ -9,28 +9,97 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Akka.Actor; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.VM.Types; +using Neo.Wallets; +using Neo.Wallets.NEP6; using System; namespace Neo.Plugins.OracleService.Tests { public static class TestBlockchain { - public static readonly NeoSystem TheNeoSystem; + public static readonly NeoSystem s_theNeoSystem; + public static readonly MemoryStore s_store = new(); + public static readonly NEP6Wallet s_wallet; + public static readonly WalletAccount s_walletAccount; + public static readonly OracleService s_oracle; + + private class StoreProvider : IStoreProvider + { + public string Name => "TestProvider"; + public IStore GetStore(string path) => s_store; + } static TestBlockchain() { Console.WriteLine("initialize NeoSystem"); - TheNeoSystem = new NeoSystem(ProtocolSettings.Load("config.json"), new MemoryStoreProvider()); + StoreProvider _memoryStoreProvider = new(); + s_oracle = new(); + s_theNeoSystem = new NeoSystem(TestUtils.settings, _memoryStoreProvider); + s_wallet = TestUtils.GenerateTestWallet("123"); + s_walletAccount = s_wallet.Import("KxuRSsHgJMb3AMSN6B9P3JHNGMFtxmuimqgR9MmXPcv3CLLfusTd"); + } + + public static UInt160 InitializeContract() + { + string _oracleContractSrc = """ +using System.Numerics;using Neo.SmartContract.Framework;using Neo.SmartContract.Framework.Native;using Neo.SmartContract.Framework.Services; +namespace oracle_demo{public class OracleDemo:SmartContract{ +const byte PREFIX_COUNT = 0xcc; +const byte PREFIX_DATA = 0xdd; +public static string GetRequstData() => Storage.Get(Storage.CurrentContext, new byte[] { PREFIX_DATA }); +public static BigInteger GetRequstCount() => (BigInteger)Storage.Get(Storage.CurrentContext, new byte[] { PREFIX_COUNT }); +public static void CreateRequest(string url, string filter, string callback, byte[] userData, long gasForResponse) => Oracle.Request(url, filter, callback, userData, gasForResponse); +public static void Callback(string url, byte[] userData, int code, byte[] result) +{ + ExecutionEngine.Assert(Runtime.CallingScriptHash == Oracle.Hash, "Unauthorized!"); + StorageContext currentContext = Storage.CurrentContext; + Storage.Put(currentContext, new byte[] { PREFIX_DATA }, (ByteString)result); + Storage.Put(currentContext, new byte[] { PREFIX_COUNT }, + (BigInteger)Storage.Get(currentContext, new byte[] { PREFIX_DATA }) + 1); +}}} +"""; + string base64NefFile = "TkVGM05lby5Db21waWxlci5DU2hhcnAgMy43LjQrNjAzNGExODIxY2E3MDk0NjBlYzMxMzZjNzBjMmRjYzNiZWEuLi4AAAFYhxcRfgqoEHKvq3HS3Yn+fEuS/gdyZXF1ZXN0BQAADwAAmAwB3dswQZv2Z85Bkl3oMUAMAczbMEGb9mfOQZJd6DFK2CYERRDbIUBXAAV8e3p5eDcAAEBXAQRBOVNuPAwUWIcXEX4KqBByr6tx0t2J/nxLkv6XDA1VbmF1dGhvcml6ZWQh4UGb9mfOcHvbKAwB3dswaEHmPxiEDAHd2zBoQZJd6DFK2CYERRDbIRGeDAHM2zBoQeY/GIRAnIyFhg=="; + string manifest = """{"name":"OracleDemo","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"getRequstData","parameters":[],"returntype":"String","offset":0,"safe":false},{"name":"getRequstCount","parameters":[],"returntype":"Integer","offset":16,"safe":false},{"name":"createRequest","parameters":[{"name":"url","type":"String"},{"name":"filter","type":"String"},{"name":"callback","type":"String"},{"name":"userData","type":"ByteArray"},{"name":"gasForResponse","type":"Integer"}],"returntype":"Void","offset":40,"safe":false},{"name":"callback","parameters":[{"name":"url","type":"String"},{"name":"userData","type":"ByteArray"},{"name":"code","type":"Integer"},{"name":"result","type":"ByteArray"}],"returntype":"Void","offset":52,"safe":false}],"events":[]},"permissions":[{"contract":"0xfe924b7cfe89ddd271abaf7210a80a7e11178758","methods":["request"]}],"trusts":[],"extra":{"nef":{"optimization":"All"}}}"""; + byte[] script; + using (ScriptBuilder sb = new()) + { + sb.EmitDynamicCall(NativeContract.ContractManagement.Hash, "deploy", Convert.FromBase64String(base64NefFile), manifest); + script = sb.ToArray(); + } + SnapshotCache snapshot = s_theNeoSystem.GetSnapshotCache(); + Transaction? tx = new Transaction + { + Nonce = 233, + ValidUntilBlock = NativeContract.Ledger.CurrentIndex(snapshot) + s_theNeoSystem.Settings.MaxValidUntilBlockIncrement, + Signers = [new Signer() { Account = TestUtils.ValidatorScriptHash, Scopes = WitnessScope.CalledByEntry }], + Attributes = System.Array.Empty(), + Script = script, + Witnesses = null, + }; + var engine = ApplicationEngine.Run(tx.Script, snapshot, container: tx, settings: s_theNeoSystem.Settings, gas: 1200_0000_0000); + engine.SnapshotCache.Commit(); + var result = (Neo.VM.Types.Array)engine.ResultStack.Peek(); + return new UInt160(result[2].GetSpan()); } - public static void InitializeMockNeoSystem() + internal static void ResetStore() { + s_store.Reset(); + s_theNeoSystem.Blockchain.Ask(new Blockchain.Initialize()).Wait(); } - internal static DataCache GetTestSnapshot() + internal static SnapshotCache GetTestSnapshotCache() { - return TheNeoSystem.GetSnapshot().CreateSnapshot(); + ResetStore(); + return s_theNeoSystem.GetSnapshot(); } } } diff --git a/tests/Neo.Plugins.OracleService.Tests/TestUtils.cs b/tests/Neo.Plugins.OracleService.Tests/TestUtils.cs index 7f1c3a58d5..a82be3ff9b 100644 --- a/tests/Neo.Plugins.OracleService.Tests/TestUtils.cs +++ b/tests/Neo.Plugins.OracleService.Tests/TestUtils.cs @@ -9,14 +9,27 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using FluentAssertions; using Neo.IO; +using Neo.Json; using Neo.SmartContract; using Neo.SmartContract.Native; +using Neo.Wallets; +using Neo.Wallets.NEP6; +using System; namespace Neo.Plugins.OracleService.Tests { public static class TestUtils { + public static readonly ProtocolSettings settings = ProtocolSettings.Load("config.json"); + public static readonly byte[] ValidatorScript = Contract.CreateSignatureRedeemScript(settings.StandbyCommittee[0]); + public static readonly UInt160 ValidatorScriptHash = ValidatorScript.ToScriptHash(); + public static readonly string ValidatorAddress = ValidatorScriptHash.ToAddress(ProtocolSettings.Default.AddressVersion); + public static readonly byte[] MultisigScript = Contract.CreateMultiSigRedeemScript(1, settings.StandbyCommittee); + public static readonly UInt160 MultisigScriptHash = MultisigScript.ToScriptHash(); + public static readonly string MultisigAddress = MultisigScriptHash.ToAddress(ProtocolSettings.Default.AddressVersion); + public static StorageKey CreateStorageKey(this NativeContract contract, byte prefix, ISerializable key) { var k = new KeyBuilder(contract.Id, prefix); @@ -28,5 +41,17 @@ public static StorageKey CreateStorageKey(this NativeContract contract, byte pre { return new KeyBuilder(contract.Id, prefix).AddBigEndian(value); } + + public static NEP6Wallet GenerateTestWallet(string password) + { + JObject wallet = new JObject(); + wallet["name"] = "noname"; + wallet["version"] = new Version("1.0").ToString(); + wallet["scrypt"] = new ScryptParameters(2, 1, 1).ToJson(); + wallet["accounts"] = new JArray(); + wallet["extra"] = null; + wallet.ToString().Should().Be("{\"name\":\"noname\",\"version\":\"1.0\",\"scrypt\":{\"n\":2,\"r\":1,\"p\":1},\"accounts\":[],\"extra\":null}"); + return new NEP6Wallet(null, password, settings, wallet); + } } } diff --git a/tests/Neo.Plugins.OracleService.Tests/UT_OracleService.cs b/tests/Neo.Plugins.OracleService.Tests/UT_OracleService.cs index 9622ceb9e2..3276c40c2f 100644 --- a/tests/Neo.Plugins.OracleService.Tests/UT_OracleService.cs +++ b/tests/Neo.Plugins.OracleService.Tests/UT_OracleService.cs @@ -21,12 +21,6 @@ namespace Neo.Plugins.OracleService.Tests [TestClass] public class UT_OracleService : TestKit { - [TestInitialize] - public void TestSetup() - { - TestBlockchain.InitializeMockNeoSystem(); - } - [TestMethod] public void TestFilter() { @@ -70,11 +64,11 @@ public void TestFilter() [TestMethod] public void TestCreateOracleResponseTx() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); - var executionFactor = NativeContract.Policy.GetExecFeeFactor(snapshot); + var executionFactor = NativeContract.Policy.GetExecFeeFactor(snapshotCache); Assert.AreEqual(executionFactor, (uint)30); - var feePerByte = NativeContract.Policy.GetFeePerByte(snapshot); + var feePerByte = NativeContract.Policy.GetFeePerByte(snapshotCache); Assert.AreEqual(feePerByte, 1000); OracleRequest request = new OracleRequest @@ -85,10 +79,10 @@ public void TestCreateOracleResponseTx() Filter = "", CallbackContract = UInt160.Zero, CallbackMethod = "callback", - UserData = System.Array.Empty() + UserData = [] }; byte Prefix_Transaction = 11; - snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, request.OriginalTxid), new StorageItem(new TransactionState() + snapshotCache.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, request.OriginalTxid), new StorageItem(new TransactionState() { BlockIndex = 1, Transaction = new Transaction() @@ -98,7 +92,7 @@ public void TestCreateOracleResponseTx() })); OracleResponse response = new OracleResponse() { Id = 1, Code = OracleResponseCode.Success, Result = new byte[] { 0x00 } }; ECPoint[] oracleNodes = new ECPoint[] { ECCurve.Secp256r1.G }; - var tx = OracleService.CreateResponseTx(snapshot, request, response, oracleNodes, ProtocolSettings.Default); + var tx = OracleService.CreateResponseTx(snapshotCache, request, response, oracleNodes, ProtocolSettings.Default); Assert.AreEqual(166, tx.Size); Assert.AreEqual(2198650, tx.NetworkFee); @@ -108,7 +102,7 @@ public void TestCreateOracleResponseTx() request.GasForResponse = 0_10000000; response.Result = new byte[10250]; - tx = OracleService.CreateResponseTx(snapshot, request, response, oracleNodes, ProtocolSettings.Default); + tx = OracleService.CreateResponseTx(snapshotCache, request, response, oracleNodes, ProtocolSettings.Default); Assert.AreEqual(165, tx.Size); Assert.AreEqual(OracleResponseCode.InsufficientFunds, response.Code); Assert.AreEqual(2197650, tx.NetworkFee); diff --git a/tests/Neo.Plugins.OracleService.Tests/config.json b/tests/Neo.Plugins.OracleService.Tests/config.json index 67bc3a62fa..edcd76127a 100644 --- a/tests/Neo.Plugins.OracleService.Tests/config.json +++ b/tests/Neo.Plugins.OracleService.Tests/config.json @@ -28,7 +28,7 @@ } }, "ProtocolConfiguration": { - "Network": 860833102, + "Network": 5195086, "AddressVersion": 53, "MillisecondsPerBlock": 15000, "MaxTransactionsPerBlock": 512, @@ -39,29 +39,9 @@ "HF_Basilisk": 4120000 }, "InitialGasDistribution": 5200000000000000, - "ValidatorsCount": 7, + "ValidatorsCount": 1, "StandbyCommittee": [ - "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", - "02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", - "03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", - "02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", - "024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", - "02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", - "02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", - "023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe", - "03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379", - "03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050", - "03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0", - "02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62", - "03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0", - "0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654", - "020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639", - "0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30", - "03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde", - "02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad", - "0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d", - "03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc", - "02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a" + "0278ed78c917797b637a7ed6e7a9d94e8c408444c41ee4c0a0f310a256b9271eda" ], "SeedList": [ "seed1.neo.org:10333", 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 1586b9753d..b7e2ec91ac 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/Neo.Plugins.RpcServer.Tests.csproj +++ b/tests/Neo.Plugins.RpcServer.Tests/Neo.Plugins.RpcServer.Tests.csproj @@ -8,10 +8,10 @@ - + - - + + 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/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_Parameters.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs new file mode 100644 index 0000000000..a87f1c7961 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_Parameters.cs @@ -0,0 +1,414 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_Parameters.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.Json; +using Neo.Plugins.RpcServer.Model; +using Neo.UnitTests; +using Neo.Wallets; +using System; + +namespace Neo.Plugins.RpcServer.Tests; + +[TestClass] +public class UT_Parameters +{ + [TestMethod] + public void TestTryParse_ContractNameOrHashOrId() + { + Assert.IsTrue(ContractNameOrHashOrId.TryParse("1", out var contractNameOrHashOrId)); + Assert.IsTrue(contractNameOrHashOrId.IsId); + Assert.IsTrue(ContractNameOrHashOrId.TryParse("0x1234567890abcdef1234567890abcdef12345678", out contractNameOrHashOrId)); + Assert.IsTrue(contractNameOrHashOrId.IsHash); + Assert.IsTrue(ContractNameOrHashOrId.TryParse("test", out contractNameOrHashOrId)); + Assert.IsTrue(contractNameOrHashOrId.IsName); + Assert.IsFalse(ContractNameOrHashOrId.TryParse("", out _)); + + JToken token = 1; + Assert.AreEqual(1, ((ContractNameOrHashOrId)ParameterConverter.ConvertParameter(token, typeof(ContractNameOrHashOrId))).AsId()); + + JToken token2 = "1"; + Assert.AreEqual(1, ((ContractNameOrHashOrId)ParameterConverter.ConvertParameter(token2, typeof(ContractNameOrHashOrId))).AsId()); + + JToken token3 = "0x1234567890abcdef1234567890abcdef12345678"; + Assert.AreEqual(UInt160.Parse("0x1234567890abcdef1234567890abcdef12345678"), ((ContractNameOrHashOrId)ParameterConverter.ConvertParameter(token3, typeof(ContractNameOrHashOrId))).AsHash()); + + JToken token4 = "0xabc"; + Assert.ThrowsException(() => ((ContractNameOrHashOrId)ParameterConverter.ConvertParameter(token4, typeof(ContractNameOrHashOrId))).AsHash()); + } + + [TestMethod] + public void TestTryParse_BlockHashOrIndex() + { + Assert.IsTrue(BlockHashOrIndex.TryParse("1", out var blockHashOrIndex)); + Assert.IsTrue(blockHashOrIndex.IsIndex); + Assert.AreEqual(1u, blockHashOrIndex.AsIndex()); + Assert.IsTrue(BlockHashOrIndex.TryParse("0x761a9bb72ca2a63984db0cc43f943a2a25e464f62d1a91114c2b6fbbfd24b51d", out blockHashOrIndex)); + Assert.AreEqual(UInt256.Parse("0x761a9bb72ca2a63984db0cc43f943a2a25e464f62d1a91114c2b6fbbfd24b51d"), blockHashOrIndex.AsHash()); + Assert.IsFalse(BlockHashOrIndex.TryParse("", out _)); + + JToken token = 1; + Assert.AreEqual(1u, ((BlockHashOrIndex)ParameterConverter.ConvertParameter(token, typeof(BlockHashOrIndex))).AsIndex()); + + JToken token2 = -1; + Assert.ThrowsException(() => ((BlockHashOrIndex)ParameterConverter.ConvertParameter(token2, typeof(BlockHashOrIndex))).AsIndex()); + + JToken token3 = "1"; + Assert.AreEqual(1u, ((BlockHashOrIndex)ParameterConverter.ConvertParameter(token3, typeof(BlockHashOrIndex))).AsIndex()); + + JToken token4 = "-1"; + Assert.ThrowsException(() => ((BlockHashOrIndex)ParameterConverter.ConvertParameter(token4, typeof(BlockHashOrIndex))).AsIndex()); + + JToken token5 = "0x761a9bb72ca2a63984db0cc43f943a2a25e464f62d1a91114c2b6fbbfd24b51d"; + Assert.AreEqual(UInt256.Parse("0x761a9bb72ca2a63984db0cc43f943a2a25e464f62d1a91114c2b6fbbfd24b51d"), ((BlockHashOrIndex)ParameterConverter.ConvertParameter(token5, typeof(BlockHashOrIndex))).AsHash()); + + JToken token6 = "761a9bb72ca2a63984db0cc43f943a2a25e464f62d1a91114c2b6fbbfd24b51d"; + Assert.AreEqual(UInt256.Parse("0x761a9bb72ca2a63984db0cc43f943a2a25e464f62d1a91114c2b6fbbfd24b51d"), ((BlockHashOrIndex)ParameterConverter.ConvertParameter(token6, typeof(BlockHashOrIndex))).AsHash()); + + JToken token7 = "0xabc"; + Assert.ThrowsException(() => ((BlockHashOrIndex)ParameterConverter.ConvertParameter(token7, typeof(BlockHashOrIndex))).AsHash()); + } + + [TestMethod] + public void TestUInt160() + { + JToken token = "0x1234567890abcdef1234567890abcdef12345678"; + Assert.AreEqual(UInt160.Parse("0x1234567890abcdef1234567890abcdef12345678"), ParameterConverter.ConvertUInt160(token, TestProtocolSettings.Default.AddressVersion)); + + JToken token2 = "0xabc"; + Assert.ThrowsException(() => ParameterConverter.ConvertUInt160(token2, TestProtocolSettings.Default.AddressVersion)); + + JToken token3 = "NdtB8RXRmJ7Nhw1FPTm7E6HoDZGnDw37nf"; + Assert.AreEqual("NdtB8RXRmJ7Nhw1FPTm7E6HoDZGnDw37nf".ToScriptHash(TestProtocolSettings.Default.AddressVersion), ParameterConverter.ConvertUInt160(token3, TestProtocolSettings.Default.AddressVersion)); + } + + [TestMethod] + public void TestUInt256() + { + JToken token = "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; + Assert.AreEqual(UInt256.Parse("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), ParameterConverter.ConvertParameter(token, typeof(UInt256))); + + JToken token2 = "0xabc"; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(UInt256))); + } + + [TestMethod] + public void TestInteger() + { + JToken token = 1; + Assert.AreEqual(1, ParameterConverter.ConvertParameter(token, typeof(int))); + Assert.AreEqual((long)1, ParameterConverter.ConvertParameter(token, typeof(long))); + Assert.AreEqual((uint)1, ParameterConverter.ConvertParameter(token, typeof(uint))); + Assert.AreEqual((ulong)1, ParameterConverter.ConvertParameter(token, typeof(ulong))); + Assert.AreEqual((short)1, ParameterConverter.ConvertParameter(token, typeof(short))); + Assert.AreEqual((ushort)1, ParameterConverter.ConvertParameter(token, typeof(ushort))); + Assert.AreEqual((byte)1, ParameterConverter.ConvertParameter(token, typeof(byte))); + Assert.AreEqual((sbyte)1, ParameterConverter.ConvertParameter(token, typeof(sbyte))); + + JToken token2 = 1.1; + + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(int))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(long))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(uint))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(ulong))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(short))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(ushort))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(byte))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token2, typeof(sbyte))); + + JToken token3 = "1"; + + Assert.AreEqual((int)1, ParameterConverter.ConvertParameter(token3, typeof(int))); + Assert.AreEqual((long)1, ParameterConverter.ConvertParameter(token3, typeof(long))); + Assert.AreEqual((uint)1, ParameterConverter.ConvertParameter(token3, typeof(uint))); + Assert.AreEqual((ulong)1, ParameterConverter.ConvertParameter(token3, typeof(ulong))); + Assert.AreEqual((short)1, ParameterConverter.ConvertParameter(token3, typeof(short))); + Assert.AreEqual((ushort)1, ParameterConverter.ConvertParameter(token3, typeof(ushort))); + Assert.AreEqual((byte)1, ParameterConverter.ConvertParameter(token3, typeof(byte))); + Assert.AreEqual((sbyte)1, ParameterConverter.ConvertParameter(token3, typeof(sbyte))); + + JToken token4 = "1.1"; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token4, typeof(int))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token4, typeof(long))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token4, typeof(uint))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token4, typeof(ulong))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token4, typeof(short))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token4, typeof(ushort))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token4, typeof(byte))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token4, typeof(sbyte))); + + JToken token5 = "abc"; + + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token5, typeof(int))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token5, typeof(long))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token5, typeof(uint))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token5, typeof(ulong))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token5, typeof(short))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token5, typeof(ushort))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token5, typeof(byte))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token5, typeof(sbyte))); + + JToken token6 = -1; + + Assert.AreEqual(-1, ParameterConverter.ConvertParameter(token6, typeof(int))); + Assert.AreEqual((long)-1, ParameterConverter.ConvertParameter(token6, typeof(long))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token6, typeof(uint))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token6, typeof(ulong))); + Assert.AreEqual((short)-1, ParameterConverter.ConvertParameter(token6, typeof(short))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token6, typeof(ushort))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(token6, typeof(byte))); + Assert.AreEqual((sbyte)-1, ParameterConverter.ConvertParameter(token6, typeof(sbyte))); + } + + [TestMethod] + public void TestBoolean() + { + JToken token = true; + Assert.AreEqual(true, ParameterConverter.ConvertParameter(token, typeof(bool))); + JToken token2 = false; + Assert.AreEqual(false, ParameterConverter.ConvertParameter(token2, typeof(bool))); + JToken token6 = 1; + Assert.AreEqual(true, ParameterConverter.ConvertParameter(token6, typeof(bool))); + JToken token7 = 0; + Assert.AreEqual(false, ParameterConverter.ConvertParameter(token7, typeof(bool))); + } + + [TestMethod] + public void TestNumericTypeConversions() + { + // Test integer conversions + TestIntegerConversions(); + + // Test byte conversions + TestByteConversions(); + + // Test sbyte conversions + TestSByteConversions(); + + // Test short conversions + TestShortConversions(); + + // Test ushort conversions + TestUShortConversions(); + + // Test uint conversions + TestUIntConversions(); + + // Test long conversions + TestLongConversions(); + + // Test ulong conversions + TestULongConversions(); + } + + private void TestIntegerConversions() + { + // Test max value + JToken maxToken = int.MaxValue; + Assert.AreEqual(int.MaxValue, ParameterConverter.ConvertParameter(maxToken, typeof(int))); + + // Test min value + JToken minToken = int.MinValue; + Assert.AreEqual(int.MinValue, ParameterConverter.ConvertParameter(minToken, typeof(int))); + + // Test overflow + JToken overflowToken = (long)int.MaxValue + 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(overflowToken, typeof(int))); + + // Test underflow + JToken underflowToken = (long)int.MinValue - 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(underflowToken, typeof(int))); + } + + private void TestByteConversions() + { + // Test max value + JToken maxToken = byte.MaxValue; + Assert.AreEqual(byte.MaxValue, ParameterConverter.ConvertParameter(maxToken, typeof(byte))); + + // Test min value + JToken minToken = byte.MinValue; + Assert.AreEqual(byte.MinValue, ParameterConverter.ConvertParameter(minToken, typeof(byte))); + + // Test overflow + JToken overflowToken = (int)byte.MaxValue + 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(overflowToken, typeof(byte))); + + // Test underflow + JToken underflowToken = -1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(underflowToken, typeof(byte))); + } + + private void TestSByteConversions() + { + // Test max value + JToken maxToken = sbyte.MaxValue; + Assert.AreEqual(sbyte.MaxValue, ParameterConverter.ConvertParameter(maxToken, typeof(sbyte))); + + // Test min value + JToken minToken = sbyte.MinValue; + Assert.AreEqual(sbyte.MinValue, ParameterConverter.ConvertParameter(minToken, typeof(sbyte))); + + // Test overflow + JToken overflowToken = (int)sbyte.MaxValue + 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(overflowToken, typeof(sbyte))); + + // Test underflow + JToken underflowToken = (int)sbyte.MinValue - 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(underflowToken, typeof(sbyte))); + } + + private void TestShortConversions() + { + // Test max value + JToken maxToken = short.MaxValue; + Assert.AreEqual(short.MaxValue, ParameterConverter.ConvertParameter(maxToken, typeof(short))); + + // Test min value + JToken minToken = short.MinValue; + Assert.AreEqual(short.MinValue, ParameterConverter.ConvertParameter(minToken, typeof(short))); + + // Test overflow + JToken overflowToken = (int)short.MaxValue + 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(overflowToken, typeof(short))); + + // Test underflow + JToken underflowToken = (int)short.MinValue - 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(underflowToken, typeof(short))); + } + + private void TestUShortConversions() + { + // Test max value + JToken maxToken = ushort.MaxValue; + Assert.AreEqual(ushort.MaxValue, ParameterConverter.ConvertParameter(maxToken, typeof(ushort))); + + // Test min value + JToken minToken = ushort.MinValue; + Assert.AreEqual(ushort.MinValue, ParameterConverter.ConvertParameter(minToken, typeof(ushort))); + + // Test overflow + JToken overflowToken = (int)ushort.MaxValue + 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(overflowToken, typeof(ushort))); + + // Test underflow + JToken underflowToken = -1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(underflowToken, typeof(ushort))); + } + + private void TestUIntConversions() + { + // Test max value + JToken maxToken = uint.MaxValue; + Assert.AreEqual(uint.MaxValue, ParameterConverter.ConvertParameter(maxToken, typeof(uint))); + + // Test min value + JToken minToken = uint.MinValue; + Assert.AreEqual(uint.MinValue, ParameterConverter.ConvertParameter(minToken, typeof(uint))); + + // Test overflow + JToken overflowToken = (ulong)uint.MaxValue + 1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(overflowToken, typeof(uint))); + + // Test underflow + JToken underflowToken = -1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(underflowToken, typeof(uint))); + } + + private void TestLongConversions() + { + // Test max value + JToken maxToken = JNumber.MAX_SAFE_INTEGER; + Assert.AreEqual(JNumber.MAX_SAFE_INTEGER, ParameterConverter.ConvertParameter(maxToken, typeof(long))); + + // Test min value + JToken minToken = JNumber.MIN_SAFE_INTEGER; + Assert.AreEqual(JNumber.MIN_SAFE_INTEGER, ParameterConverter.ConvertParameter(minToken, typeof(long))); + + // Test overflow + JToken overflowToken = $"{JNumber.MAX_SAFE_INTEGER}0"; // This will be parsed as a string, causing overflow + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(overflowToken, typeof(long))); + + // Test underflow + JToken underflowToken = $"-{JNumber.MIN_SAFE_INTEGER}0"; // This will be parsed as a string, causing underflow + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(underflowToken, typeof(long))); + } + + private void TestULongConversions() + { + // Test max value + JToken maxToken = JNumber.MAX_SAFE_INTEGER; + Assert.AreEqual((ulong)JNumber.MAX_SAFE_INTEGER, ParameterConverter.ConvertParameter(maxToken, typeof(ulong))); + + // Test min value + JToken minToken = ulong.MinValue; + Assert.AreEqual(ulong.MinValue, ParameterConverter.ConvertParameter(minToken, typeof(ulong))); + + // Test overflow + JToken overflowToken = $"{JNumber.MAX_SAFE_INTEGER}0"; // This will be parsed as a string, causing overflow + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(overflowToken, typeof(ulong))); + + // Test underflow + JToken underflowToken = -1; + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(underflowToken, typeof(ulong))); + } + + [TestMethod] + public void TestAdditionalEdgeCases() + { + // Test conversion of fractional values slightly less than integers + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(0.9999999999999, typeof(int))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(-0.0000000000001, typeof(int))); + + // Test conversion of very large double values to integer types + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(double.MaxValue, typeof(long))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(double.MinValue, typeof(long))); + + // Test conversion of NaN and Infinity + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(double.NaN, typeof(int))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(double.PositiveInfinity, typeof(long))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(double.NegativeInfinity, typeof(ulong))); + + // Test conversion of string representations of numbers + Assert.AreEqual(int.MaxValue, ParameterConverter.ConvertParameter(int.MaxValue.ToString(), typeof(int))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(long.MinValue.ToString(), typeof(long))); + + // Test conversion of hexadecimal string representations + Assert.ThrowsException(() => ParameterConverter.ConvertParameter("0xFF", typeof(int))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter("0x100", typeof(byte))); + + // Test conversion of whitespace-padded strings + Assert.AreEqual(42, ParameterConverter.ConvertParameter(" 42 ", typeof(int))); + Assert.AreEqual(42, ParameterConverter.ConvertParameter(" 42.0 ", typeof(int))); + + // Test conversion of empty or null values + Assert.AreEqual(0, ParameterConverter.ConvertParameter("", typeof(int))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(JToken.Null, typeof(int))); + + // Test conversion to non-numeric types + Assert.ThrowsException(() => ParameterConverter.ConvertParameter(42, typeof(DateTime))); + + // Test conversion of values just outside the safe integer range for long and ulong + Assert.ThrowsException(() => ParameterConverter.ConvertParameter((double)long.MaxValue, typeof(long))); + Assert.ThrowsException(() => ParameterConverter.ConvertParameter((double)ulong.MaxValue, typeof(ulong))); + + // Test conversion of scientific notation + Assert.AreEqual(1000000, ParameterConverter.ConvertParameter("1e6", typeof(int))); + Assert.AreEqual(150, ParameterConverter.ConvertParameter("1.5e2", typeof(int))); + + // Test conversion of boolean values to numeric types + Assert.AreEqual(1, ParameterConverter.ConvertParameter(true, typeof(int))); + Assert.AreEqual(0, ParameterConverter.ConvertParameter(false, typeof(int))); + + // Test conversion of Unicode numeric characters + Assert.ThrowsException(() => ParameterConverter.ConvertParameter("1234", typeof(int))); + } +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs index 5fd37b2bc3..b04b5e33d4 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs @@ -9,18 +9,24 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Akka.Actor; using Akka.Util.Internal; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.ECC; using Neo.IO; using Neo.Json; using Neo.Ledger; using Neo.Network.P2P.Payloads; +using Neo.Plugins.RpcServer.Model; using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.UnitTests; using Neo.UnitTests.Extensions; using System; using System.Linq; +using System.Security.Policy; +using static Neo.SmartContract.Native.NeoToken; namespace Neo.Plugins.RpcServer.Tests { @@ -33,13 +39,13 @@ public void TestGetBestBlockHash() var key = NativeContract.Ledger.CreateStorageKey(12); var expectedHash = UInt256.Zero; - var snapshot = _neoSystem.GetSnapshot(); + 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([]); + var result = _rpcServer.GetBestBlockHash(); // Assert Assert.AreEqual(expectedHash.ToString(), result.AsString()); } @@ -47,44 +53,52 @@ public void TestGetBestBlockHash() [TestMethod] public void TestGetBlockByHash() { - var snapshot = _neoSystem.GetSnapshot(); + 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 result = _rpcServer.GetBlock(new BlockHashOrIndex(block.Hash), false); var blockArr = Convert.FromBase64String(result.AsString()); var block2 = blockArr.AsSerializable(); block2.Transactions.ForEach(tx => { Assert.AreEqual(VerifyResult.Succeed, tx.VerifyStateIndependent(UnitTests.TestProtocolSettings.Default)); }); + + result = _rpcServer.GetBlock(new BlockHashOrIndex(block.Hash), true); + var block3 = block.ToJson(UnitTests.TestProtocolSettings.Default); + block3["confirmations"] = NativeContract.Ledger.CurrentIndex(snapshot) - block.Index + 1; + result.ToString().Should().Be(block3.ToString()); } [TestMethod] public void TestGetBlockByIndex() { - var snapshot = _neoSystem.GetSnapshot(); + 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 result = _rpcServer.GetBlock(new BlockHashOrIndex(block.Index), false); var blockArr = Convert.FromBase64String(result.AsString()); var block2 = blockArr.AsSerializable(); block2.Transactions.ForEach(tx => { Assert.AreEqual(VerifyResult.Succeed, tx.VerifyStateIndependent(UnitTests.TestProtocolSettings.Default)); }); + + result = _rpcServer.GetBlock(new BlockHashOrIndex(block.Index), true); + var block3 = block.ToJson(UnitTests.TestProtocolSettings.Default); + block3["confirmations"] = NativeContract.Ledger.CurrentIndex(snapshot) - block.Index + 1; + result.ToString().Should().Be(block3.ToString()); } [TestMethod] public void TestGetBlockCount() { var expectedCount = 1; - var result = _rpcServer.GetBlockCount(new JArray()); + var result = _rpcServer.GetBlockCount(); Assert.AreEqual(expectedCount, result.AsNumber()); } @@ -92,79 +106,103 @@ public void TestGetBlockCount() public void TestGetBlockHeaderCount() { var expectedCount = 1; - var result = _rpcServer.GetBlockHeaderCount(new JArray()); + var result = _rpcServer.GetBlockHeaderCount(); Assert.AreEqual(expectedCount, result.AsNumber()); } [TestMethod] public void TestGetBlockHash() { - var snapshot = _neoSystem.GetSnapshot(); + var snapshot = _neoSystem.GetSnapshotCache(); var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); - TestUtils.BlocksAdd(snapshot, block.Hash, block); - snapshot.Commit(); + // 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)); + var result = _rpcServer.GetBlockHash(block.Index); Assert.AreEqual(expectedHash, result.AsString()); } [TestMethod] public void TestGetBlockHeader() { - var snapshot = _neoSystem.GetSnapshot(); + 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 result = _rpcServer.GetBlockHeader(new BlockHashOrIndex(block.Hash), true); var header = block.Header.ToJson(_neoSystem.Settings); header["confirmations"] = NativeContract.Ledger.CurrentIndex(snapshot) - block.Index + 1; Assert.AreEqual(header.ToString(), result.ToString()); + + result = _rpcServer.GetBlockHeader(new BlockHashOrIndex(block.Hash), false); + var headerArr = Convert.FromBase64String(result.AsString()); + var header2 = headerArr.AsSerializable
(); + header2.ToJson(_neoSystem.Settings).ToString().Should().Be(block.Header.ToJson(_neoSystem.Settings).ToString()); } [TestMethod] public void TestGetContractState() { - var snapshot = _neoSystem.GetSnapshot(); + var snapshot = _neoSystem.GetSnapshotCache(); var contractState = TestUtils.GetContract(); snapshot.AddContract(contractState.Hash, contractState); snapshot.Commit(); - var result = _rpcServer.GetContractState(new JArray(contractState.Hash.ToString())); + var result = _rpcServer.GetContractState(new ContractNameOrHashOrId(contractState.Hash)); + Assert.AreEqual(contractState.ToJson().ToString(), result.ToString()); + + result = _rpcServer.GetContractState(new ContractNameOrHashOrId(contractState.Id)); Assert.AreEqual(contractState.ToJson().ToString(), result.ToString()); + + var byId = _rpcServer.GetContractState(new ContractNameOrHashOrId(-1)); + var byName = _rpcServer.GetContractState(new ContractNameOrHashOrId("ContractManagement")); + byId.ToString().Should().Be(byName.ToString()); + + snapshot.DeleteContract(contractState.Hash); + snapshot.Commit(); + Action act = () => _rpcServer.GetContractState(new ContractNameOrHashOrId(contractState.Hash)); + act.Should().Throw().WithMessage(RpcError.UnknownContract.Message); + act = () => _rpcServer.GetContractState(new ContractNameOrHashOrId(contractState.Id)); + act.Should().Throw().WithMessage(RpcError.UnknownContract.Message); } [TestMethod] public void TestGetRawMemPool() { - var snapshot = _neoSystem.GetSnapshot(); + var snapshot = _neoSystem.GetSnapshotCache(); var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount); snapshot.Commit(); _neoSystem.MemPool.TryAdd(tx, snapshot); - var result = _rpcServer.GetRawMemPool(new JArray()); - + var result = _rpcServer.GetRawMemPool(); Assert.IsTrue(((JArray)result).Any(p => p.AsString() == tx.Hash.ToString())); + + result = _rpcServer.GetRawMemPool(true); + Assert.IsTrue(((JArray)result["verified"]).Any(p => p.AsString() == tx.Hash.ToString())); } [TestMethod] public void TestGetRawTransaction() { - var snapshot = _neoSystem.GetSnapshot(); + 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 result = _rpcServer.GetRawTransaction(tx.Hash, true); var json = Utility.TransactionToJson(tx, _neoSystem.Settings); Assert.AreEqual(json.ToString(), result.ToString()); + + result = _rpcServer.GetRawTransaction(tx.Hash, false); + var tx2 = Convert.FromBase64String(result.AsString()).AsSerializable(); + tx2.ToJson(_neoSystem.Settings).ToString().Should().Be(tx.ToJson(_neoSystem.Settings).ToString()); } [TestMethod] public void TestGetStorage() { - var snapshot = _neoSystem.GetSnapshot(); + var snapshot = _neoSystem.GetSnapshotCache(); var contractState = TestUtils.GetContract(); snapshot.AddContract(contractState.Hash, contractState); var key = new byte[] { 0x01 }; @@ -172,21 +210,21 @@ public void TestGetStorage() TestUtils.StorageItemAdd(snapshot, contractState.Id, key, value); snapshot.Commit(); - var result = _rpcServer.GetStorage(new JArray(contractState.Hash.ToString(), Convert.ToBase64String(key))); + var result = _rpcServer.GetStorage(new ContractNameOrHashOrId(contractState.Hash), Convert.ToBase64String(key)); Assert.AreEqual(Convert.ToBase64String(value), result.AsString()); } [TestMethod] public void TestFindStorage() { - var snapshot = _neoSystem.GetSnapshot(); + 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 result = _rpcServer.FindStorage(new ContractNameOrHashOrId(contractState.Hash), Convert.ToBase64String(key), 0); var json = new JObject(); var jarr = new JArray(); @@ -198,25 +236,34 @@ public void TestFindStorage() json["next"] = 1; json["results"] = jarr; Assert.AreEqual(json.ToString(), result.ToString()); + + var result2 = _rpcServer.FindStorage(new ContractNameOrHashOrId(contractState.Hash), Convert.ToBase64String(key)); + result2.ToString().Should().Be(result.ToString()); + + Enumerable.Range(0, 51).ToList().ForEach(i => TestUtils.StorageItemAdd(snapshot, contractState.Id, new byte[] { 0x01, (byte)i }, new byte[] { 0x02 })); + snapshot.Commit(); + var result4 = _rpcServer.FindStorage(new ContractNameOrHashOrId(contractState.Hash), Convert.ToBase64String(new byte[] { 0x01 }), 0); + result4["next"].Should().Be(RpcServerSettings.Default.FindStoragePageSize); + (result4["truncated"]).AsBoolean().Should().Be(true); } [TestMethod] public void TestGetTransactionHeight() { - var snapshot = _neoSystem.GetSnapshot(); + 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())); + var result = _rpcServer.GetTransactionHeight(tx.Hash); Assert.AreEqual(block.Index, result.AsNumber()); } [TestMethod] public void TestGetNextBlockValidators() { - var snapshot = _neoSystem.GetSnapshot(); - var result = _rpcServer.GetNextBlockValidators(new JArray()); + var snapshot = _neoSystem.GetSnapshotCache(); + var result = _rpcServer.GetNextBlockValidators(); var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, _neoSystem.Settings.ValidatorsCount); var expected = validators.Select(p => @@ -232,13 +279,17 @@ public void TestGetNextBlockValidators() [TestMethod] public void TestGetCandidates() { - var snapshot = _neoSystem.GetSnapshot(); - var result = _rpcServer.GetCandidates(new JArray()); + var snapshot = _neoSystem.GetSnapshotCache(); + + var result = _rpcServer.GetCandidates(); var json = new JArray(); var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, _neoSystem.Settings.ValidatorsCount); - snapshot.Commit(); - var candidates = NativeContract.NEO.GetCandidates(_neoSystem.GetSnapshot()); + var key = new KeyBuilder(NativeContract.NEO.Id, 33).Add(ECPoint.Parse("02237309a0633ff930d51856db01d17c829a5b2e5cc2638e9c03b4cfa8e9c9f971", ECCurve.Secp256r1)); + snapshot.Add(key, new StorageItem(new CandidateState() { Registered = true, Votes = 10000 })); + snapshot.Commit(); + var candidates = NativeContract.NEO.GetCandidates(_neoSystem.GetSnapshotCache()); + result = _rpcServer.GetCandidates(); foreach (var candidate in candidates) { var item = new JObject(); @@ -253,8 +304,8 @@ public void TestGetCandidates() [TestMethod] public void TestGetCommittee() { - var snapshot = _neoSystem.GetSnapshot(); - var result = _rpcServer.GetCommittee(new JArray()); + var snapshot = _neoSystem.GetSnapshotCache(); + var result = _rpcServer.GetCommittee(); var committee = NativeContract.NEO.GetCommittee(snapshot); var expected = new JArray(committee.Select(p => (JToken)p.ToString())); Assert.AreEqual(expected.ToString(), result.ToString()); @@ -263,23 +314,22 @@ public void TestGetCommittee() [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())); + var result = _rpcServer.GetNativeContracts(); + var contracts = new JArray(NativeContract.Contracts.Select(p => NativeContract.ContractManagement.GetContract(_neoSystem.GetSnapshotCache(), p.Hash).ToJson())); Assert.AreEqual(contracts.ToString(), result.ToString()); } [TestMethod] public void TestGetBlockByUnknownIndex() { - var snapshot = _neoSystem.GetSnapshot(); + 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); + _rpcServer.GetBlock(new BlockHashOrIndex(int.MaxValue), false); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -291,15 +341,14 @@ public void TestGetBlockByUnknownIndex() [TestMethod] public void TestGetBlockByUnknownHash() { - var snapshot = _neoSystem.GetSnapshot(); + 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); + _rpcServer.GetBlock(new BlockHashOrIndex(TestUtils.RandomUInt256()), false); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -311,15 +360,14 @@ public void TestGetBlockByUnknownHash() [TestMethod] public void TestGetBlockByUnKnownIndex() { - var snapshot = _neoSystem.GetSnapshot(); + 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); + _rpcServer.GetBlock(new BlockHashOrIndex(int.MaxValue), false); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -331,15 +379,14 @@ public void TestGetBlockByUnKnownIndex() [TestMethod] public void TestGetBlockByUnKnownHash() { - var snapshot = _neoSystem.GetSnapshot(); + 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); + _rpcServer.GetBlock(new BlockHashOrIndex(TestUtils.RandomUInt256()), false); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -351,21 +398,22 @@ public void TestGetBlockByUnKnownHash() [TestMethod] public void TestGetBlockHashInvalidIndex() { - var snapshot = _neoSystem.GetSnapshot(); + 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))); + Action act = () => _rpcServer.GetBlockHash(block.Index + 1); + act.Should().Throw(); } [TestMethod] public void TestGetContractStateUnknownContract() { - var snapshot = _neoSystem.GetSnapshot(); + var snapshot = _neoSystem.GetSnapshotCache(); var randomHash = TestUtils.RandomUInt160(); try { - _rpcServer.GetContractState(new JArray(randomHash.ToString())); + _rpcServer.GetContractState(new ContractNameOrHashOrId(randomHash)); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -377,12 +425,12 @@ public void TestGetContractStateUnknownContract() [TestMethod] public void TestGetStorageUnknownContract() { - var snapshot = _neoSystem.GetSnapshot(); + var snapshot = _neoSystem.GetSnapshotCache(); var randomHash = TestUtils.RandomUInt160(); var key = new byte[] { 0x01 }; try { - _rpcServer.GetStorage(new JArray(randomHash.ToString(), Convert.ToBase64String(key))); + _rpcServer.GetStorage(new ContractNameOrHashOrId(randomHash), Convert.ToBase64String(key)); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -394,7 +442,7 @@ public void TestGetStorageUnknownContract() [TestMethod] public void TestGetStorageUnknownStorageItem() { - var snapshot = _neoSystem.GetSnapshot(); + var snapshot = _neoSystem.GetSnapshotCache(); var contractState = TestUtils.GetContract(); snapshot.AddContract(contractState.Hash, contractState); snapshot.Commit(); @@ -402,7 +450,7 @@ public void TestGetStorageUnknownStorageItem() var key = new byte[] { 0x01 }; try { - _rpcServer.GetStorage(new JArray(contractState.Hash.ToString(), Convert.ToBase64String(key))); + _rpcServer.GetStorage(new ContractNameOrHashOrId(contractState.Hash), Convert.ToBase64String(key)); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -417,7 +465,7 @@ public void TestGetTransactionHeightUnknownTransaction() var randomHash = TestUtils.RandomUInt256(); try { - _rpcServer.GetTransactionHeight(new JArray(randomHash.ToString())); + _rpcServer.GetTransactionHeight(randomHash); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -432,7 +480,7 @@ public void TestGetRawTransactionUnknownTransaction() var randomHash = TestUtils.RandomUInt256(); try { - _rpcServer.GetRawTransaction(new JArray(randomHash.ToString(), true)); + _rpcServer.GetRawTransaction(randomHash, true); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -441,160 +489,13 @@ public void TestGetRawTransactionUnknownTransaction() } } - [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()); + _rpcServer.GetCandidates(); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) @@ -608,7 +509,7 @@ public void TestUnknownHeight() { try { - _rpcServer.GetBlockHash(new JArray(int.MaxValue)); + _rpcServer.GetBlockHash(int.MaxValue); Assert.Fail("Expected RpcException was not thrown."); } catch (RpcException ex) 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..042bde0d30 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Node.cs @@ -0,0 +1,325 @@ +// 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 Akka.Actor; +using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.Json; +using Neo.Network.P2P; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract.Native; +using Neo.UnitTests; +using System; +using System.Collections.Generic; +using System.Net; + +namespace Neo.Plugins.RpcServer.Tests +{ + partial class UT_RpcServer + { + [TestMethod] + public void TestGetConnectionCount() + { + var result = _rpcServer.GetConnectionCount(); + result.GetType().Should().Be(typeof(JNumber)); + } + + [TestMethod] + public void TestGetPeers() + { + var settings = TestProtocolSettings.SoleNode; + var neoSystem = new NeoSystem(settings, _memoryStoreProvider); + var localNode = neoSystem.LocalNode.Ask(new LocalNode.GetInstance()).Result; + localNode.AddPeers(new List() { new IPEndPoint(new IPAddress(new byte[] { 127, 0, 0, 1 }), 11332) }); + localNode.AddPeers(new List() { new IPEndPoint(new IPAddress(new byte[] { 127, 0, 0, 1 }), 12332) }); + localNode.AddPeers(new List() { new IPEndPoint(new IPAddress(new byte[] { 127, 0, 0, 1 }), 13332) }); + var rpcServer = new RpcServer(neoSystem, RpcServerSettings.Default); + + var result = rpcServer.GetPeers(); + Assert.IsInstanceOfType(result, typeof(JObject)); + var json = (JObject)result; + json.ContainsProperty("unconnected").Should().BeTrue(); + (json["unconnected"] as JArray).Count.Should().Be(3); + json.ContainsProperty("bad").Should().BeTrue(); + json.ContainsProperty("connected").Should().BeTrue(); + } + + [TestMethod] + public void TestGetVersion() + { + var result = _rpcServer.GetVersion(); + 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")); + Assert.IsTrue(protocol.ContainsProperty("standbycommittee")); + Assert.IsTrue(protocol.ContainsProperty("seedlist")); + } + + #region SendRawTransaction Tests + + [TestMethod] + public void TestSendRawTransaction_Normal() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount); + var txString = Convert.ToBase64String(tx.ToArray()); + + var result = _rpcServer.SendRawTransaction(txString); + Assert.IsInstanceOfType(result, typeof(JObject)); + Assert.IsTrue(((JObject)result).ContainsProperty("hash")); + } + + [TestMethod] + public void TestSendRawTransaction_InvalidTransactionFormat() + { + Assert.ThrowsException(() => + _rpcServer.SendRawTransaction("invalid_transaction_string"), + "Should throw RpcException for invalid transaction format"); + } + + [TestMethod] + public void TestSendRawTransaction_InsufficientBalance() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.InsufficientBalance); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(txString), + "Should throw RpcException for insufficient balance"); + Assert.AreEqual(RpcError.InsufficientFunds.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_InvalidSignature() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.InvalidSignature); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(txString), + "Should throw RpcException for invalid signature"); + Assert.AreEqual(RpcError.InvalidSignature.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_InvalidScript() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.InvalidScript); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(txString), + "Should throw RpcException for invalid script"); + Assert.AreEqual(RpcError.InvalidScript.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_InvalidAttribute() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.InvalidAttribute); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(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.GetSnapshotCache(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.Oversized); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(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.GetSnapshotCache(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.Expired); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(txString), + "Should throw RpcException for expired transaction"); + Assert.AreEqual(RpcError.ExpiredTransaction.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_PolicyFailed() + { + var snapshot = _neoSystem.GetSnapshotCache(); + 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(txString), + "Should throw RpcException for conflicting transaction"); + Assert.AreEqual(RpcError.PolicyFailed.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_AlreadyInPool() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount); + _neoSystem.MemPool.TryAdd(tx, snapshot); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(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.GetSnapshotCache(); + 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(txString)); + Assert.AreEqual(RpcError.AlreadyExists.Code, exception.HResult); + } + + #endregion + + #region SubmitBlock Tests + + [TestMethod] + public void TestSubmitBlock_Normal() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 1); + var blockString = Convert.ToBase64String(block.ToArray()); + + var result = _rpcServer.SubmitBlock(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(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.GetSnapshotCache(); + 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(blockString), + "Should throw RpcException when block already exists"); + Assert.AreEqual(RpcError.AlreadyExists.Code, exception.HResult); + } + + [TestMethod] + public void TestSubmitBlock_InvalidBlock() + { + var snapshot = _neoSystem.GetSnapshotCache(); + 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(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((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(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((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(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.SmartContract.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs new file mode 100644 index 0000000000..9a8b87da9a --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.SmartContract.cs @@ -0,0 +1,234 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RpcServer.Wallet.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 Microsoft.AspNetCore.Http; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Cryptography.ECC; +using Neo.IO; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads.Conditions; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.UnitTests; +using Neo.UnitTests.Extensions; +using Neo.Wallets; +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; + +namespace Neo.Plugins.RpcServer.Tests; + +public partial class UT_RpcServer +{ + static readonly string NeoTotalSupplyScript = "wh8MC3RvdGFsU3VwcGx5DBT1Y\u002BpAvCg9TQ4FxI6jBbPyoHNA70FifVtS"; + static readonly string NeoTransferScript = "CxEMFPlu76Cuc\u002BbgteStE4ozsOWTNUdrDBQtYNweHko3YcnMFOes3ceblcI/lRTAHwwIdHJhbnNmZXIMFPVj6kC8KD1NDgXEjqMFs/Kgc0DvQWJ9W1I="; + static readonly UInt160 ValidatorScriptHash = Contract + .CreateSignatureRedeemScript(TestProtocolSettings.SoleNode.StandbyCommittee[0]) + .ToScriptHash(); + static readonly string ValidatorAddress = ValidatorScriptHash.ToAddress(ProtocolSettings.Default.AddressVersion); + static readonly UInt160 MultisigScriptHash = Contract + .CreateMultiSigRedeemScript(1, TestProtocolSettings.SoleNode.StandbyCommittee) + .ToScriptHash(); + static readonly string MultisigAddress = MultisigScriptHash.ToAddress(ProtocolSettings.Default.AddressVersion); + + static readonly JArray validatorSigner = [new JObject() + { + ["account"] = ValidatorScriptHash.ToString(), + ["scopes"] = nameof(WitnessScope.CalledByEntry), + ["allowedcontracts"] = new JArray([NeoToken.NEO.Hash.ToString(), GasToken.GAS.Hash.ToString()]), + ["allowedgroups"] = new JArray([TestProtocolSettings.SoleNode.StandbyCommittee[0].ToString()]), + ["rules"] = new JArray([new JObject() { ["action"] = nameof(WitnessRuleAction.Allow), ["condition"] = new JObject { ["type"] = nameof(WitnessConditionType.CalledByEntry) } }]), + }]; + static readonly JArray multisigSigner = [new JObject() + { + ["account"] = MultisigScriptHash.ToString(), + ["scopes"] = nameof(WitnessScope.CalledByEntry), + }]; + + [TestMethod] + public void TestInvokeFunction() + { + _rpcServer.wallet = _wallet; + + JObject resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "totalSupply", new JArray([]), validatorSigner, true)); + Assert.AreEqual(resp.Count, 8); + Assert.AreEqual(resp["script"], NeoTotalSupplyScript); + Assert.IsTrue(resp.ContainsProperty("gasconsumed")); + Assert.IsTrue(resp.ContainsProperty("diagnostics")); + Assert.AreEqual(resp["diagnostics"]["invokedcontracts"]["call"][0]["hash"], NeoToken.NEO.Hash.ToString()); + Assert.IsTrue(((JArray)resp["diagnostics"]["storagechanges"]).Count == 0); + Assert.AreEqual(resp["state"], nameof(VM.VMState.HALT)); + Assert.AreEqual(resp["exception"], null); + Assert.AreEqual(((JArray)resp["notifications"]).Count, 0); + Assert.AreEqual(resp["stack"][0]["type"], nameof(Neo.VM.Types.Integer)); + Assert.AreEqual(resp["stack"][0]["value"], "100000000"); + Assert.IsTrue(resp.ContainsProperty("tx")); + + resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "symbol")); + Assert.AreEqual(resp.Count, 6); + Assert.IsTrue(resp.ContainsProperty("script")); + Assert.IsTrue(resp.ContainsProperty("gasconsumed")); + Assert.AreEqual(resp["state"], nameof(VM.VMState.HALT)); + Assert.AreEqual(resp["exception"], null); + Assert.AreEqual(((JArray)resp["notifications"]).Count, 0); + Assert.AreEqual(resp["stack"][0]["type"], nameof(Neo.VM.Types.ByteString)); + Assert.AreEqual(resp["stack"][0]["value"], Convert.ToBase64String(Encoding.UTF8.GetBytes("NEO"))); + + // This call triggers not only NEO but also unclaimed GAS + resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "transfer", new JArray([ + new JObject() { ["type"] = nameof(ContractParameterType.Hash160), ["value"] = MultisigScriptHash.ToString() }, + new JObject() { ["type"] = nameof(ContractParameterType.Hash160), ["value"] = ValidatorScriptHash.ToString() }, + new JObject() { ["type"] = nameof(ContractParameterType.Integer), ["value"] = "1" }, + new JObject() { ["type"] = nameof(ContractParameterType.Any) }, + ]), multisigSigner, true)); + Assert.AreEqual(resp.Count, 7); + Assert.AreEqual(resp["script"], NeoTransferScript); + Assert.IsTrue(resp.ContainsProperty("gasconsumed")); + Assert.IsTrue(resp.ContainsProperty("diagnostics")); + Assert.AreEqual(resp["diagnostics"]["invokedcontracts"]["call"][0]["hash"], NeoToken.NEO.Hash.ToString()); + Assert.IsTrue(((JArray)resp["diagnostics"]["storagechanges"]).Count == 4); + Assert.AreEqual(resp["state"], nameof(VM.VMState.HALT)); + Assert.AreEqual(resp["exception"], $"The smart contract or address {MultisigScriptHash} ({MultisigAddress}) is not found. " + + $"If this is your wallet address and you want to sign a transaction with it, make sure you have opened this wallet."); + JArray notifications = (JArray)resp["notifications"]; + Assert.AreEqual(notifications.Count, 2); + Assert.AreEqual(notifications[0]["eventname"].AsString(), "Transfer"); + Assert.AreEqual(notifications[0]["contract"].AsString(), NeoToken.NEO.Hash.ToString()); + Assert.AreEqual(notifications[0]["state"]["value"][2]["value"], "1"); + Assert.AreEqual(notifications[1]["eventname"].AsString(), "Transfer"); + Assert.AreEqual(notifications[1]["contract"].AsString(), GasToken.GAS.Hash.ToString()); + Assert.AreEqual(notifications[1]["state"]["value"][2]["value"], "50000000"); + + _rpcServer.wallet = null; + } + + [TestMethod] + public void TestInvokeScript() + { + JObject resp = (JObject)_rpcServer.InvokeScript(new JArray(NeoTotalSupplyScript, validatorSigner, true)); + Assert.AreEqual(resp.Count, 7); + Assert.IsTrue(resp.ContainsProperty("gasconsumed")); + Assert.IsTrue(resp.ContainsProperty("diagnostics")); + Assert.AreEqual(resp["diagnostics"]["invokedcontracts"]["call"][0]["hash"], NeoToken.NEO.Hash.ToString()); + Assert.AreEqual(resp["state"], nameof(VM.VMState.HALT)); + Assert.AreEqual(resp["exception"], null); + Assert.AreEqual(((JArray)resp["notifications"]).Count, 0); + Assert.AreEqual(resp["stack"][0]["type"], nameof(Neo.VM.Types.Integer)); + Assert.AreEqual(resp["stack"][0]["value"], "100000000"); + + resp = (JObject)_rpcServer.InvokeScript(new JArray(NeoTransferScript)); + Assert.AreEqual(resp.Count, 6); + Assert.AreEqual(resp["stack"][0]["type"], nameof(Neo.VM.Types.Boolean)); + Assert.AreEqual(resp["stack"][0]["value"], false); + } + + [TestMethod] + public void TestTraverseIterator() + { + // GetAllCandidates that should return 0 candidates + JObject resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "getAllCandidates", new JArray([]), validatorSigner, true)); + string sessionId = resp["session"].AsString(); + string iteratorId = resp["stack"][0]["id"].AsString(); + JArray respArray = (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 100]); + Assert.AreEqual(respArray.Count, 0); + _rpcServer.TerminateSession([sessionId]); + Assert.ThrowsException(() => (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 100]), "Unknown session"); + + // register candidate in snapshot + resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "registerCandidate", + new JArray([new JObject() + { + ["type"] = nameof(ContractParameterType.PublicKey), + ["value"] = TestProtocolSettings.SoleNode.StandbyCommittee[0].ToString(), + }]), validatorSigner, true)); + Assert.AreEqual(resp["state"], nameof(VM.VMState.HALT)); + SnapshotCache snapshot = _neoSystem.GetSnapshotCache(); + Transaction? tx = new Transaction + { + Nonce = 233, + ValidUntilBlock = NativeContract.Ledger.CurrentIndex(snapshot) + _neoSystem.Settings.MaxValidUntilBlockIncrement, + Signers = [new Signer() { Account = ValidatorScriptHash, Scopes = WitnessScope.CalledByEntry }], + Attributes = Array.Empty(), + Script = Convert.FromBase64String(resp["script"].AsString()), + Witnesses = null, + }; + ApplicationEngine engine = ApplicationEngine.Run(tx.Script, snapshot, container: tx, settings: _neoSystem.Settings, gas: 1200_0000_0000); + engine.SnapshotCache.Commit(); + + // GetAllCandidates that should return 1 candidate + resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "getAllCandidates", new JArray([]), validatorSigner, true)); + sessionId = resp["session"].AsString(); + iteratorId = resp["stack"][0]["id"].AsString(); + respArray = (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 100]); + Assert.AreEqual(respArray.Count, 1); + Assert.AreEqual(respArray[0]["type"], nameof(Neo.VM.Types.Struct)); + JArray value = (JArray)respArray[0]["value"]; + Assert.AreEqual(value.Count, 2); + Assert.AreEqual(value[0]["type"], nameof(Neo.VM.Types.ByteString)); + Assert.AreEqual(value[0]["value"], Convert.ToBase64String(TestProtocolSettings.SoleNode.StandbyCommittee[0].ToArray())); + Assert.AreEqual(value[1]["type"], nameof(Neo.VM.Types.Integer)); + Assert.AreEqual(value[1]["value"], "0"); + + // No result when traversed again + respArray = (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 100]); + Assert.AreEqual(respArray.Count, 0); + + // GetAllCandidates again + resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "getAllCandidates", new JArray([]), validatorSigner, true)); + sessionId = resp["session"].AsString(); + iteratorId = resp["stack"][0]["id"].AsString(); + + // Insufficient result count limit + respArray = (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 0]); + Assert.AreEqual(respArray.Count, 0); + respArray = (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 1]); + Assert.AreEqual(respArray.Count, 1); + respArray = (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 1]); + Assert.AreEqual(respArray.Count, 0); + + // Mocking session timeout + Thread.Sleep((int)_rpcServerSettings.SessionExpirationTime.TotalMilliseconds + 1); + // build another session that did not expire + resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "getAllCandidates", new JArray([]), validatorSigner, true)); + string notExpiredSessionId = resp["session"].AsString(); + string notExpiredIteratorId = resp["stack"][0]["id"].AsString(); + _rpcServer.OnTimer(new object()); + Assert.ThrowsException(() => (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 100]), "Unknown session"); + // If you want to run the following line without exception, + // DO NOT BREAK IN THE DEBUGGER, because the session expires quickly + respArray = (JArray)_rpcServer.TraverseIterator([notExpiredSessionId, notExpiredIteratorId, 1]); + Assert.AreEqual(respArray.Count, 1); + + // Mocking disposal + resp = (JObject)_rpcServer.InvokeFunction(new JArray(NeoToken.NEO.Hash.ToString(), "getAllCandidates", new JArray([]), validatorSigner, true)); + sessionId = resp["session"].AsString(); + iteratorId = resp["stack"][0]["id"].AsString(); + _rpcServer.Dispose_SmartContract(); + Assert.ThrowsException(() => (JArray)_rpcServer.TraverseIterator([sessionId, iteratorId, 100]), "Unknown session"); + } + + [TestMethod] + public void TestGetUnclaimedGas() + { + JObject resp = (JObject)_rpcServer.GetUnclaimedGas([MultisigAddress]); + Assert.AreEqual(resp["unclaimed"], "50000000"); + Assert.AreEqual(resp["address"], MultisigAddress); + resp = (JObject)_rpcServer.GetUnclaimedGas([ValidatorAddress]); + Assert.AreEqual(resp["unclaimed"], "0"); + Assert.AreEqual(resp["address"], ValidatorAddress); + } +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Utilities.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Utilities.cs new file mode 100644 index 0000000000..a0f2384ce5 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Utilities.cs @@ -0,0 +1,54 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RpcServer.Wallet.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 Microsoft.AspNetCore.Http; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.UnitTests; +using Neo.UnitTests.Extensions; +using System; +using System.IO; +using System.Linq; + +namespace Neo.Plugins.RpcServer.Tests; + +public partial class UT_RpcServer +{ + [TestMethod] + public void TestListPlugins() + { + JArray resp = (JArray)_rpcServer.ListPlugins([]); + Assert.AreEqual(resp.Count, 0); + Plugins.Plugin.Plugins.Add(new RpcServerPlugin()); + resp = (JArray)_rpcServer.ListPlugins([]); + Assert.AreEqual(resp.Count, 2); + foreach (JObject p in resp) + Assert.AreEqual(p["name"], nameof(RpcServer)); + } + + [TestMethod] + public void TestValidateAddress() + { + string validAddr = "NM7Aky765FG8NhhwtxjXRx7jEL1cnw7PBP"; + JObject resp = (JObject)_rpcServer.ValidateAddress([validAddr]); + Assert.AreEqual(resp["address"], validAddr); + Assert.AreEqual(resp["isvalid"], true); + string invalidAddr = "ANeo2toNeo3MigrationAddressxwPB2Hz"; + resp = (JObject)_rpcServer.ValidateAddress([invalidAddr]); + Assert.AreEqual(resp["address"], invalidAddr); + Assert.AreEqual(resp["isvalid"], false); + } +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs new file mode 100644 index 0000000000..0897a12804 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs @@ -0,0 +1,505 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RpcServer.Wallet.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 Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads.Conditions; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.UnitTests; +using Neo.UnitTests.Extensions; +using System; +using System.IO; +using System.Linq; + +namespace Neo.Plugins.RpcServer.Tests; + +partial class UT_RpcServer +{ + [TestMethod] + public void TestOpenWallet() + { + const string Path = "wallet.json"; + const string Password = "123456"; + File.WriteAllText(Path, "{\"name\":null,\"version\":\"1.0\",\"scrypt\":{\"n\":16384,\"r\":8,\"p\":8},\"accounts\":[{\"address\":\"NVizn8DiExdmnpTQfjiVY3dox8uXg3Vrxv\",\"label\":null,\"isDefault\":false,\"lock\":false,\"key\":\"6PYPMrsCJ3D4AXJCFWYT2WMSBGF7dLoaNipW14t4UFAkZw3Z9vQRQV1bEU\",\"contract\":{\"script\":\"DCEDaR\\u002BFVb8lOdiMZ/wCHLiI\\u002Bzuf17YuGFReFyHQhB80yMpBVuezJw==\",\"parameters\":[{\"name\":\"signature\",\"type\":\"Signature\"}],\"deployed\":false},\"extra\":null}],\"extra\":null}"); + var paramsArray = new JArray(Path, Password); + var res = _rpcServer.OpenWallet(paramsArray); + Assert.IsTrue(res.AsBoolean()); + Assert.IsNotNull(_rpcServer.wallet); + Assert.AreEqual(_rpcServer.wallet.GetAccounts().FirstOrDefault()!.Address, "NVizn8DiExdmnpTQfjiVY3dox8uXg3Vrxv"); + _rpcServer.CloseWallet([]); + File.Delete(Path); + Assert.IsNull(_rpcServer.wallet); + } + + [TestMethod] + public void TestOpenInvalidWallet() + { + const string Path = "wallet.json"; + const string Password = "password"; + File.Delete(Path); + var paramsArray = new JArray(Path, Password); + var exception = Assert.ThrowsException(() => _rpcServer.OpenWallet(paramsArray), "Should throw RpcException for unsupported wallet"); + Assert.AreEqual(RpcError.WalletNotFound.Code, exception.HResult); + + File.WriteAllText(Path, "{}"); + exception = Assert.ThrowsException(() => _rpcServer.OpenWallet(paramsArray), "Should throw RpcException for unsupported wallet"); + File.Delete(Path); + Assert.AreEqual(RpcError.WalletNotSupported.Code, exception.HResult); + var result = _rpcServer.CloseWallet(new JArray()); + Assert.IsTrue(result.AsBoolean()); + Assert.IsNull(_rpcServer.wallet); + + File.WriteAllText(Path, "{\"name\":null,\"version\":\"1.0\",\"scrypt\":{\"n\":16384,\"r\":8,\"p\":8},\"accounts\":[{\"address\":\"NVizn8DiExdmnpTQfjiVY3dox8uXg3Vrxv\",\"label\":null,\"isDefault\":false,\"lock\":false,\"key\":\"6PYPMrsCJ3D4AXJCFWYT2WMSBGF7dLoaNipW14t4UFAkZw3Z9vQRQV1bEU\",\"contract\":{\"script\":\"DCEDaR\\u002BFVb8lOdiMZ/wCHLiI\\u002Bzuf17YuGFReFyHQhB80yMpBVuezJw==\",\"parameters\":[{\"name\":\"signature\",\"type\":\"Signature\"}],\"deployed\":false},\"extra\":null}],\"extra\":null}"); + exception = Assert.ThrowsException(() => _rpcServer.OpenWallet(paramsArray), "Should throw RpcException for unsupported wallet"); + Assert.AreEqual(RpcError.WalletNotSupported.Code, exception.HResult); + Assert.AreEqual(exception.Message, "Wallet not supported - Invalid password."); + File.Delete(Path); + } + + [TestMethod] + public void TestDumpPrivKey() + { + TestUtilOpenWallet(); + var account = _rpcServer.wallet.GetAccounts().FirstOrDefault(); + Assert.IsNotNull(account); + var privKey = account.GetKey().Export(); + var address = account.Address; + var result = _rpcServer.DumpPrivKey(new JArray(address)); + Assert.AreEqual(privKey, result.AsString()); + TestUtilCloseWallet(); + } + + [TestMethod] + public void TestGetNewAddress() + { + TestUtilOpenWallet(); + var result = _rpcServer.GetNewAddress([]); + Assert.IsInstanceOfType(result, typeof(JString)); + Assert.IsTrue(_rpcServer.wallet.GetAccounts().Any(a => a.Address == result.AsString())); + TestUtilCloseWallet(); + } + + [TestMethod] + public void TestGetWalletBalance() + { + TestUtilOpenWallet(); + var assetId = NativeContract.NEO.Hash; + var paramsArray = new JArray(assetId.ToString()); + var result = _rpcServer.GetWalletBalance(paramsArray); + Assert.IsInstanceOfType(result, typeof(JObject)); + var json = (JObject)result; + Assert.IsTrue(json.ContainsProperty("balance")); + TestUtilCloseWallet(); + } + + [TestMethod] + public void TestGetWalletBalanceInvalidAsset() + { + TestUtilOpenWallet(); + var assetId = UInt160.Zero; + var paramsArray = new JArray(assetId.ToString()); + var result = _rpcServer.GetWalletBalance(paramsArray); + Assert.IsInstanceOfType(result, typeof(JObject)); + var json = (JObject)result; + Assert.IsTrue(json.ContainsProperty("balance")); + TestUtilCloseWallet(); + } + + [TestMethod] + public void TestGetWalletUnclaimedGas() + { + TestUtilOpenWallet(); + var result = _rpcServer.GetWalletUnclaimedGas([]); + Assert.IsInstanceOfType(result, typeof(JString)); + TestUtilCloseWallet(); + } + + [TestMethod] + public void TestImportPrivKey() + { + TestUtilOpenWallet(); + var privKey = _walletAccount.GetKey().Export(); + var paramsArray = new JArray(privKey); + var result = _rpcServer.ImportPrivKey(paramsArray); + Assert.IsInstanceOfType(result, typeof(JObject)); + var json = (JObject)result; + Assert.IsTrue(json.ContainsProperty("address")); + Assert.IsTrue(json.ContainsProperty("haskey")); + Assert.IsTrue(json.ContainsProperty("label")); + Assert.IsTrue(json.ContainsProperty("watchonly")); + TestUtilCloseWallet(); + } + + [TestMethod] + public void TestImportPrivKeyNoWallet() + { + var privKey = _walletAccount.GetKey().Export(); + var paramsArray = new JArray(privKey); + var exception = Assert.ThrowsException(() => _rpcServer.ImportPrivKey(paramsArray)); + Assert.AreEqual(exception.HResult, RpcError.NoOpenedWallet.Code); + } + + [TestMethod] + public void TestCalculateNetworkFee() + { + var snapshot = _neoSystem.GetSnapshot(); + var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount); + var txBase64 = Convert.ToBase64String(tx.ToArray()); + var paramsArray = new JArray(txBase64); + var result = _rpcServer.CalculateNetworkFee(paramsArray); + Assert.IsInstanceOfType(result, typeof(JObject)); + var json = (JObject)result; + Assert.IsTrue(json.ContainsProperty("networkfee")); + } + + [TestMethod] + public void TestCalculateNetworkFeeNoParam() + { + var exception = Assert.ThrowsException(() => _rpcServer.CalculateNetworkFee([])); + Assert.AreEqual(exception.HResult, RpcError.InvalidParams.Code); + } + + [TestMethod] + public void TestListAddressNoWallet() + { + var exception = Assert.ThrowsException(() => _rpcServer.ListAddress([])); + Assert.AreEqual(exception.HResult, RpcError.NoOpenedWallet.Code); + } + + [TestMethod] + public void TestListAddress() + { + TestUtilOpenWallet(); + var result = _rpcServer.ListAddress([]); + Assert.IsInstanceOfType(result, typeof(JArray)); + var json = (JArray)result; + Assert.IsTrue(json.Count > 0); + TestUtilCloseWallet(); + } + + [TestMethod] + public void TestSendFromNoWallet() + { + var assetId = NativeContract.GAS.Hash; + var from = _walletAccount.Address; + var to = _walletAccount.Address; + var amount = "1"; + var paramsArray = new JArray(assetId.ToString(), from, to, amount); + var exception = Assert.ThrowsException(() => _rpcServer.SendFrom(paramsArray), "Should throw RpcException for insufficient funds"); + Assert.AreEqual(exception.HResult, RpcError.NoOpenedWallet.Code); + } + + [TestMethod] + public void TestSendFrom() + { + TestUtilOpenWallet(); + var assetId = NativeContract.GAS.Hash; + var from = _walletAccount.Address; + var to = _walletAccount.Address; + var amount = "1"; + var paramsArray = new JArray(assetId.ToString(), from, to, amount); + var exception = Assert.ThrowsException(() => _rpcServer.SendFrom(paramsArray)); + Assert.AreEqual(exception.HResult, RpcError.InvalidRequest.Code); + TestUtilCloseWallet(); + + _rpcServer.wallet = _wallet; + JObject resp = (JObject)_rpcServer.SendFrom(paramsArray); + Assert.AreEqual(resp.Count, 12); + Assert.AreEqual(resp["sender"], ValidatorAddress); + JArray signers = (JArray)resp["signers"]; + Assert.AreEqual(signers.Count, 1); + Assert.AreEqual(signers[0]["account"], ValidatorScriptHash.ToString()); + Assert.AreEqual(signers[0]["scopes"], nameof(WitnessScope.CalledByEntry)); + _rpcServer.wallet = null; + } + + [TestMethod] + public void TestSendMany() + { + var from = _walletAccount.Address; + var to = new JArray { new JObject { ["asset"] = NativeContract.GAS.Hash.ToString(), ["value"] = "1", ["address"] = _walletAccount.Address } }; + var paramsArray = new JArray(from, to); + var exception = Assert.ThrowsException(() => _rpcServer.SendMany(paramsArray), "Should throw RpcException for insufficient funds"); + Assert.AreEqual(exception.HResult, RpcError.NoOpenedWallet.Code); + + _rpcServer.wallet = _wallet; + JObject resp = (JObject)_rpcServer.SendMany(paramsArray); + Assert.AreEqual(resp.Count, 12); + Assert.AreEqual(resp["sender"], ValidatorAddress); + JArray signers = (JArray)resp["signers"]; + Assert.AreEqual(signers.Count, 1); + Assert.AreEqual(signers[0]["account"], ValidatorScriptHash.ToString()); + Assert.AreEqual(signers[0]["scopes"], nameof(WitnessScope.CalledByEntry)); + _rpcServer.wallet = null; + } + + [TestMethod] + public void TestSendToAddress() + { + var assetId = NativeContract.GAS.Hash; + var to = _walletAccount.Address; + var amount = "1"; + var paramsArray = new JArray(assetId.ToString(), to, amount); + var exception = Assert.ThrowsException(() => _rpcServer.SendToAddress(paramsArray), "Should throw RpcException for insufficient funds"); + Assert.AreEqual(exception.HResult, RpcError.NoOpenedWallet.Code); + + _rpcServer.wallet = _wallet; + JObject resp = (JObject)_rpcServer.SendToAddress(paramsArray); + Assert.AreEqual(resp.Count, 12); + Assert.AreEqual(resp["sender"], ValidatorAddress); + JArray signers = (JArray)resp["signers"]; + Assert.AreEqual(signers.Count, 1); + Assert.AreEqual(signers[0]["account"], ValidatorScriptHash.ToString()); + Assert.AreEqual(signers[0]["scopes"], nameof(WitnessScope.CalledByEntry)); + _rpcServer.wallet = null; + } + + [TestMethod] + public void TestCloseWallet_WhenWalletNotOpen() + { + _rpcServer.wallet = null; + var result = _rpcServer.CloseWallet(new JArray()); + Assert.IsTrue(result.AsBoolean()); + } + + [TestMethod] + public void TestDumpPrivKey_WhenWalletNotOpen() + { + _rpcServer.wallet = null; + var exception = Assert.ThrowsException(() => _rpcServer.DumpPrivKey(new JArray(_walletAccount.Address)), "Should throw RpcException for no opened wallet"); + Assert.AreEqual(exception.HResult, RpcError.NoOpenedWallet.Code); + } + + [TestMethod] + public void TestGetNewAddress_WhenWalletNotOpen() + { + _rpcServer.wallet = null; + var exception = Assert.ThrowsException(() => _rpcServer.GetNewAddress(new JArray()), "Should throw RpcException for no opened wallet"); + Assert.AreEqual(exception.HResult, RpcError.NoOpenedWallet.Code); + } + + [TestMethod] + public void TestGetWalletBalance_WhenWalletNotOpen() + { + _rpcServer.wallet = null; + var exception = Assert.ThrowsException(() => _rpcServer.GetWalletBalance(new JArray(NativeContract.NEO.Hash.ToString())), "Should throw RpcException for no opened wallet"); + Assert.AreEqual(exception.HResult, RpcError.NoOpenedWallet.Code); + } + + [TestMethod] + public void TestGetWalletUnclaimedGas_WhenWalletNotOpen() + { + _rpcServer.wallet = null; + var exception = Assert.ThrowsException(() => _rpcServer.GetWalletUnclaimedGas(new JArray()), "Should throw RpcException for no opened wallet"); + Assert.AreEqual(exception.HResult, RpcError.NoOpenedWallet.Code); + } + + [TestMethod] + public void TestImportPrivKey_WhenWalletNotOpen() + { + _rpcServer.wallet = null; + var privKey = _walletAccount.GetKey().Export(); + var exception = Assert.ThrowsException(() => _rpcServer.ImportPrivKey(new JArray(privKey)), "Should throw RpcException for no opened wallet"); + Assert.AreEqual(exception.HResult, RpcError.NoOpenedWallet.Code); + } + + [TestMethod] + public void TestCalculateNetworkFee_InvalidTransactionFormat() + { + var invalidTxBase64 = "invalid_base64"; + var paramsArray = new JArray(invalidTxBase64); + var exception = Assert.ThrowsException(() => _rpcServer.CalculateNetworkFee(paramsArray), "Should throw RpcException for invalid transaction format"); + Assert.AreEqual(exception.HResult, RpcError.InvalidParams.Code); + } + + [TestMethod] + public void TestListAddress_WhenWalletNotOpen() + { + // Ensure the wallet is not open + _rpcServer.wallet = null; + + // Attempt to call ListAddress and expect an RpcException + var exception = Assert.ThrowsException(() => _rpcServer.ListAddress(new JArray())); + + // Verify the exception has the expected error code + Assert.AreEqual(RpcError.NoOpenedWallet.Code, exception.HResult); + } + + [TestMethod] + public void TestCancelTransaction() + { + TestUtilOpenWallet(); + var snapshot = _neoSystem.GetSnapshot(); + var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount); + snapshot.Commit(); + var paramsArray = new JArray(tx.Hash.ToString(), new JArray(_walletAccount.Address)); + var exception = Assert.ThrowsException(() => _rpcServer.CancelTransaction(paramsArray), "Should throw RpcException for non-existing transaction"); + + Assert.AreEqual(RpcError.InsufficientFunds.Code, exception.HResult); + + // Test with invalid transaction id + var invalidParamsArray = new JArray("invalid_txid", new JArray(_walletAccount.Address)); + exception = Assert.ThrowsException(() => _rpcServer.CancelTransaction(invalidParamsArray), "Should throw RpcException for invalid txid"); + Assert.AreEqual(exception.HResult, RpcError.InvalidParams.Code); + + // Test with no signer + invalidParamsArray = new JArray(tx.Hash.ToString()); + exception = Assert.ThrowsException(() => _rpcServer.CancelTransaction(invalidParamsArray), "Should throw RpcException for invalid txid"); + Assert.AreEqual(exception.HResult, RpcError.BadRequest.Code); + + // Test with null wallet + _rpcServer.wallet = null; + exception = Assert.ThrowsException(() => _rpcServer.CancelTransaction(paramsArray), "Should throw RpcException for no opened wallet"); + Assert.AreEqual(exception.HResult, RpcError.NoOpenedWallet.Code); + TestUtilCloseWallet(); + + // Test valid cancel + _rpcServer.wallet = _wallet; + JObject resp = (JObject)_rpcServer.SendFrom(new JArray(NativeContract.GAS.Hash.ToString(), _walletAccount.Address, _walletAccount.Address, "1")); + string txHash = resp["hash"].AsString(); + resp = (JObject)_rpcServer.CancelTransaction(new JArray(txHash, new JArray(ValidatorAddress), "1")); + Assert.AreEqual(resp.Count, 12); + Assert.AreEqual(resp["sender"], ValidatorAddress); + JArray signers = (JArray)resp["signers"]; + Assert.AreEqual(signers.Count, 1); + Assert.AreEqual(signers[0]["account"], ValidatorScriptHash.ToString()); + Assert.AreEqual(signers[0]["scopes"], nameof(WitnessScope.None)); + Assert.AreEqual(resp["attributes"][0]["type"], nameof(TransactionAttributeType.Conflicts)); + _rpcServer.wallet = null; + } + + [TestMethod] + public void TestInvokeContractVerify() + { + var scriptHash = UInt160.Parse("0x70cde1619e405cdef363ab66a1e8dce430d798d5"); + var paramsArray = new JArray(scriptHash.ToString()); + var exception = Assert.ThrowsException(() => _rpcServer.InvokeContractVerify(paramsArray), "Should throw RpcException for unknown contract"); + Assert.AreEqual(exception.HResult, RpcError.UnknownContract.Code); + // Test with invalid script hash + var invalidParamsArray = new JArray("invalid_script_hash"); + exception = Assert.ThrowsException(() => _rpcServer.InvokeContractVerify(invalidParamsArray), "Should throw RpcException for invalid script hash"); + Assert.AreEqual(exception.HResult, RpcError.InvalidParams.Code); + + // deploy a contract with `Verify` method; + string _contractSourceCode = """ +using Neo;using Neo.SmartContract.Framework;using Neo.SmartContract.Framework.Services; +namespace ContractWithVerify{public class ContractWithVerify:SmartContract { + const byte PREFIX_OWNER = 0x20; + public static void _deploy(object data, bool update) { + if (update) return; + Storage.Put(Storage.CurrentContext, new byte[] { PREFIX_OWNER }, + ((Transaction)Runtime.ScriptContainer).Sender);} + public static bool Verify() => Runtime.CheckWitness((UInt160)Storage.Get(Storage.CurrentContext, new byte[] { PREFIX_OWNER })); + public static bool Verify(byte prefix) => Runtime.CheckWitness((UInt160)Storage.Get(Storage.CurrentContext, new byte[] { prefix }));}} +"""; + string base64NefFile = "TkVGM05lby5Db21waWxlci5DU2hhcnAgMy43LjQrNjAzNGExODIxY2E3MDk0NjBlYzMxMzZjNzBjMmRjYzNiZWEuLi4AAAAAAGNXAAJ5JgQiGEEtUQgwE84MASDbMEGb9mfOQeY/GIRADAEg2zBBm/ZnzkGSXegxStgkCUrKABQoAzpB\u002BCfsjEBXAAERiEoQeNBBm/ZnzkGSXegxStgkCUrKABQoAzpB\u002BCfsjEDo2WhC"; + string manifest = """{"name":"ContractWithVerify","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"_deploy","parameters":[{"name":"data","type":"Any"},{"name":"update","type":"Boolean"}],"returntype":"Void","offset":0,"safe":false},{"name":"verify","parameters":[],"returntype":"Boolean","offset":31,"safe":false},{"name":"verify","parameters":[{"name":"prefix","type":"Integer"}],"returntype":"Boolean","offset":63,"safe":false}],"events":[]},"permissions":[],"trusts":[],"extra":{"nef":{"optimization":"All"}}}"""; + JObject deployResp = (JObject)_rpcServer.InvokeFunction(new JArray([ContractManagement.ContractManagement.Hash.ToString(), + "deploy", + new JArray([ + new JObject() { ["type"] = nameof(ContractParameterType.ByteArray), ["value"] = base64NefFile }, + new JObject() { ["type"] = nameof(ContractParameterType.String), ["value"] = manifest }, + ]), + validatorSigner])); + Assert.AreEqual(deployResp["state"], nameof(VM.VMState.HALT)); + UInt160 deployedScriptHash = new UInt160(Convert.FromBase64String(deployResp["notifications"][0]["state"]["value"][0]["value"].AsString())); + SnapshotCache snapshot = _neoSystem.GetSnapshotCache(); + Transaction? tx = new Transaction + { + Nonce = 233, + ValidUntilBlock = NativeContract.Ledger.CurrentIndex(snapshot) + _neoSystem.Settings.MaxValidUntilBlockIncrement, + Signers = [new Signer() { Account = ValidatorScriptHash, Scopes = WitnessScope.CalledByEntry }], + Attributes = Array.Empty(), + Script = Convert.FromBase64String(deployResp["script"].AsString()), + Witnesses = null, + }; + ApplicationEngine engine = ApplicationEngine.Run(tx.Script, snapshot, container: tx, settings: _neoSystem.Settings, gas: 1200_0000_0000); + engine.SnapshotCache.Commit(); + + // invoke verify without signer; should return false + JObject resp = (JObject)_rpcServer.InvokeContractVerify([deployedScriptHash.ToString()]); + Assert.AreEqual(resp["state"], nameof(VM.VMState.HALT)); + Assert.AreEqual(resp["stack"][0]["value"].AsBoolean(), false); + // invoke verify with signer; should return true + resp = (JObject)_rpcServer.InvokeContractVerify([deployedScriptHash.ToString(), new JArray([]), validatorSigner]); + Assert.AreEqual(resp["state"], nameof(VM.VMState.HALT)); + Assert.AreEqual(resp["stack"][0]["value"].AsBoolean(), true); + // invoke verify with wrong input value; should FAULT + resp = (JObject)_rpcServer.InvokeContractVerify([deployedScriptHash.ToString(), new JArray([new JObject() { ["type"] = nameof(ContractParameterType.Integer), ["value"] = "0" }]), validatorSigner]); + Assert.AreEqual(resp["state"], nameof(VM.VMState.FAULT)); + Assert.AreEqual(resp["exception"], "Object reference not set to an instance of an object."); + // invoke verify with 1 param and signer; should return true + resp = (JObject)_rpcServer.InvokeContractVerify([deployedScriptHash.ToString(), new JArray([new JObject() { ["type"] = nameof(ContractParameterType.Integer), ["value"] = "32" }]), validatorSigner]); + Assert.AreEqual(resp["state"], nameof(VM.VMState.HALT)); + Assert.AreEqual(resp["stack"][0]["value"].AsBoolean(), true); + // invoke verify with 2 param (which does not exist); should throw Exception + Assert.ThrowsException(() => _rpcServer.InvokeContractVerify([deployedScriptHash.ToString(), new JArray([new JObject() { ["type"] = nameof(ContractParameterType.Integer), ["value"] = "32" }, new JObject() { ["type"] = nameof(ContractParameterType.Integer), ["value"] = "32" }]), validatorSigner]), + $"Invalid contract verification function - The smart contract {deployedScriptHash} haven't got verify method with 2 input parameters."); + } + + + private void TestUtilOpenWallet() + { + try + { + const string Path = "wallet.json"; + const string Password = "123456"; + File.WriteAllText(Path, "{\"name\":null,\"version\":\"1.0\",\"scrypt\":{\"n\":16384,\"r\":8,\"p\":8},\"accounts\":[{\"address\":\"NVizn8DiExdmnpTQfjiVY3dox8uXg3Vrxv\",\"label\":null,\"isDefault\":false,\"lock\":false,\"key\":\"6PYPMrsCJ3D4AXJCFWYT2WMSBGF7dLoaNipW14t4UFAkZw3Z9vQRQV1bEU\",\"contract\":{\"script\":\"DCEDaR\\u002BFVb8lOdiMZ/wCHLiI\\u002Bzuf17YuGFReFyHQhB80yMpBVuezJw==\",\"parameters\":[{\"name\":\"signature\",\"type\":\"Signature\"}],\"deployed\":false},\"extra\":null}],\"extra\":null}"); + var paramsArray = new JArray(Path, Password); + _rpcServer.OpenWallet(paramsArray); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + private void TestUtilCloseWallet() + { + try + { + const string Path = "wallet.json"; + _rpcServer.CloseWallet([]); + File.Delete(Path); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + private UInt160 TestUtilAddTestContract() + { + var state = TestUtils.GetContract(); + + var storageKey = new StorageKey + { + Id = state.Id, + Key = new byte[] { 0x01 } + }; + + var storageItem = new StorageItem + { + Value = new byte[] { 0x01, 0x02, 0x03, 0x04 } + }; + + var snapshot = _neoSystem.GetSnapshotCache(); + snapshot.AddContract(state.Hash, state); + snapshot.Add(storageKey, storageItem); + snapshot.Commit(); + return state.Hash; + } +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs index 4ba95b7798..b8425ee0a9 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs @@ -11,6 +11,8 @@ using Microsoft.AspNetCore.Http; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.Ledger; using Neo.Persistence; using Neo.SmartContract; using Neo.SmartContract.Native; @@ -18,6 +20,9 @@ using Neo.Wallets; using Neo.Wallets.NEP6; using System; +using System.Linq; +using System.Net; +using System.Numerics; using System.Text; namespace Neo.Plugins.RpcServer.Tests @@ -26,23 +31,33 @@ namespace Neo.Plugins.RpcServer.Tests public partial class UT_RpcServer { private NeoSystem _neoSystem; + private RpcServerSettings _rpcServerSettings; private RpcServer _rpcServer; private TestMemoryStoreProvider _memoryStoreProvider; private MemoryStore _memoryStore; private readonly NEP6Wallet _wallet = TestUtils.GenerateTestWallet("123"); private WalletAccount _walletAccount; + const byte NativePrefixAccount = 20; + const byte NativePrefixTotalSupply = 11; + [TestInitialize] public void TestSetup() { _memoryStore = new MemoryStore(); _memoryStoreProvider = new TestMemoryStoreProvider(_memoryStore); - var protocolSettings = TestProtocolSettings.Default; - _neoSystem = new NeoSystem(protocolSettings, _memoryStoreProvider); - _rpcServer = new RpcServer(_neoSystem, RpcServerSettings.Default); - _walletAccount = _wallet.CreateAccount(); + _neoSystem = new NeoSystem(TestProtocolSettings.SoleNode, _memoryStoreProvider); + _rpcServerSettings = RpcServerSettings.Default with + { + SessionEnabled = true, + SessionExpirationTime = TimeSpan.FromSeconds(0.3), + MaxGasInvoke = 1500_0000_0000, + Network = TestProtocolSettings.SoleNode.Network, + }; + _rpcServer = new RpcServer(_neoSystem, _rpcServerSettings); + _walletAccount = _wallet.Import("KxuRSsHgJMb3AMSN6B9P3JHNGMFtxmuimqgR9MmXPcv3CLLfusTd"); var key = new KeyBuilder(NativeContract.GAS.Id, 20).Add(_walletAccount.ScriptHash); - var snapshot = _neoSystem.GetSnapshot(); + var snapshot = _neoSystem.GetSnapshotCache(); var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 100_000_000 * NativeContract.GAS.Factor; snapshot.Commit(); @@ -51,8 +66,10 @@ public void TestSetup() [TestCleanup] public void TestCleanup() { + // Please build and test in debug mode + _neoSystem.MemPool.Clear(); _memoryStore.Reset(); - var snapshot = _neoSystem.GetSnapshot(); + 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; @@ -70,5 +87,47 @@ public void TestCheckAuth_ValidCredentials_ReturnsTrue() // Assert Assert.IsTrue(result); } + + [TestMethod] + public void TestCheckAuth() + { + var memoryStoreProvider = new TestMemoryStoreProvider(new MemoryStore()); + var neoSystem = new NeoSystem(TestProtocolSettings.SoleNode, memoryStoreProvider); + var rpcServerSettings = RpcServerSettings.Default with + { + SessionEnabled = true, + SessionExpirationTime = TimeSpan.FromSeconds(0.3), + MaxGasInvoke = 1500_0000_0000, + Network = TestProtocolSettings.SoleNode.Network, + RpcUser = "testuser", + RpcPass = "testpass", + }; + var rpcServer = new RpcServer(neoSystem, rpcServerSettings); + + var context = new DefaultHttpContext(); + context.Request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("testuser:testpass")); + var result = rpcServer.CheckAuth(context); + Assert.IsTrue(result); + + context.Request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("testuser:wrongpass")); + result = rpcServer.CheckAuth(context); + Assert.IsFalse(result); + + context.Request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("wronguser:testpass")); + result = rpcServer.CheckAuth(context); + Assert.IsFalse(result); + + context.Request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("testuser:")); + result = rpcServer.CheckAuth(context); + Assert.IsFalse(result); + + context.Request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(":testpass")); + result = rpcServer.CheckAuth(context); + Assert.IsFalse(result); + + context.Request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("")); + result = rpcServer.CheckAuth(context); + Assert.IsFalse(result); + } } } diff --git a/tests/Neo.Plugins.Storage.Tests/Neo.Plugins.Storage.Tests.csproj b/tests/Neo.Plugins.Storage.Tests/Neo.Plugins.Storage.Tests.csproj index 3e06cf32ca..3d2031e4d5 100644 --- a/tests/Neo.Plugins.Storage.Tests/Neo.Plugins.Storage.Tests.csproj +++ b/tests/Neo.Plugins.Storage.Tests/Neo.Plugins.Storage.Tests.csproj @@ -6,9 +6,9 @@ - - - + + + diff --git a/tests/Neo.Plugins.Storage.Tests/StoreTest.cs b/tests/Neo.Plugins.Storage.Tests/StoreTest.cs index 2773f92e80..ed44faab53 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,157 @@ 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)); + Assert.IsFalse(snapshot.TryGet(testKey, out var got)); + Assert.IsNull(got); + + // 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)); + Assert.IsFalse(snapshot.TryGet(testKey, out got)); + Assert.IsNull(got); + + CollectionAssert.AreEqual(testValue, store.TryGet(testKey)); + Assert.IsTrue(store.TryGet(testKey, out got)); + CollectionAssert.AreEqual(testValue, got); + + 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.IsFalse(snapshot.TryGet(testKey, out var got)); + Assert.IsNull(got); + + Assert.IsNull(store.TryGet(testKey)); + Assert.IsFalse(store.TryGet(testKey, out got)); + Assert.IsNull(got); + + // 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)); + Assert.IsFalse(snapshot.TryGet(testKey, out got)); + Assert.IsNull(got); + + CollectionAssert.AreEqual(testValue, store.TryGet(testKey)); + Assert.IsTrue(store.TryGet(testKey, out got)); + CollectionAssert.AreEqual(testValue, got); + + 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/Builders/UT_SignerBuilder.cs b/tests/Neo.UnitTests/Builders/UT_SignerBuilder.cs new file mode 100644 index 0000000000..7d79f7428c --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_SignerBuilder.cs @@ -0,0 +1,101 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_SignerBuilder.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.Builders; +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads.Conditions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_SignerBuilder + { + [TestMethod] + public void TestCreateEmpty() + { + var sb = SignerBuilder.CreateEmpty(); + Assert.IsNotNull(sb); + } + + [TestMethod] + public void TestAccount() + { + var signer = SignerBuilder.CreateEmpty() + .Account(UInt160.Zero) + .Build(); + + Assert.IsNotNull(signer); + Assert.AreEqual(UInt160.Zero, signer.Account); + } + + [TestMethod] + public void TestAllowContract() + { + var signer = SignerBuilder.CreateEmpty() + .AllowContract(UInt160.Zero) + .Build(); + + Assert.IsNotNull(signer); + Assert.AreEqual(1, signer.AllowedContracts.Length); + Assert.AreEqual(UInt160.Zero, signer.AllowedContracts[0]); + } + + [TestMethod] + public void TestAllowGroup() + { + var myPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var signer = SignerBuilder.CreateEmpty() + .AllowGroup(myPublicKey) + .Build(); + + Assert.IsNotNull(signer); + Assert.AreEqual(1, signer.AllowedGroups.Length); + Assert.AreEqual(myPublicKey, signer.AllowedGroups[0]); + } + + [TestMethod] + public void TestAddWitnessScope() + { + var signer = SignerBuilder.CreateEmpty() + .AddWitnessScope(WitnessScope.Global) + .Build(); + + Assert.IsNotNull(signer); + Assert.AreEqual(WitnessScope.Global, signer.Scopes); + } + + [TestMethod] + public void TestAddWitnessRule() + { + var signer = SignerBuilder.CreateEmpty() + .AddWitnessRule(WitnessRuleAction.Allow, rb => + { + rb.AddCondition(cb => + { + cb.ScriptHash(UInt160.Zero); + }); + }) + .Build(); + + Assert.IsNotNull(signer); + Assert.AreEqual(1, signer.Rules.Length); + Assert.AreEqual(WitnessRuleAction.Allow, signer.Rules[0].Action); + Assert.IsInstanceOfType(signer.Rules[0].Condition); + } + } +} diff --git a/tests/Neo.UnitTests/Builders/UT_TransactionAttributesBuilder.cs b/tests/Neo.UnitTests/Builders/UT_TransactionAttributesBuilder.cs new file mode 100644 index 0000000000..d9ade210ea --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_TransactionAttributesBuilder.cs @@ -0,0 +1,92 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_TransactionAttributesBuilder.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.Builders; +using Neo.Network.P2P.Payloads; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_TransactionAttributesBuilder + { + [TestMethod] + public void TestCreateEmpty() + { + var builder = TransactionAttributesBuilder.CreateEmpty(); + + Assert.IsNotNull(builder); + } + + [TestMethod] + public void TestConflict() + { + var attr = TransactionAttributesBuilder.CreateEmpty() + .AddConflict(c => c.Hash = UInt256.Zero) + .Build(); + + Assert.IsNotNull(attr); + Assert.AreEqual(1, attr.Length); + Assert.IsInstanceOfType(attr[0]); + Assert.AreEqual(UInt256.Zero, ((Conflicts)attr[0]).Hash); + } + + [TestMethod] + public void TestOracleResponse() + { + var attr = TransactionAttributesBuilder.CreateEmpty() + .AddOracleResponse(c => + { + c.Id = 1ul; + c.Code = OracleResponseCode.Success; + c.Result = new byte[] { 0x01, 0x02, 0x03 }; + }) + .Build(); + + Assert.IsNotNull(attr); + Assert.AreEqual(1, attr.Length); + Assert.IsInstanceOfType(attr[0]); + Assert.AreEqual(1ul, ((OracleResponse)attr[0]).Id); + Assert.AreEqual(OracleResponseCode.Success, ((OracleResponse)attr[0]).Code); + CollectionAssert.AreEqual(new byte[] { 0x01, 0x02, 0x03 }, ((OracleResponse)attr[0]).Result.ToArray()); + } + + [TestMethod] + public void TestHighPriority() + { + var attr = TransactionAttributesBuilder.CreateEmpty() + .AddHighPriority() + .Build(); + + Assert.IsNotNull(attr); + Assert.AreEqual(1, attr.Length); + Assert.IsInstanceOfType(attr[0]); + } + + [TestMethod] + public void TestNotValidBefore() + { + var attr = TransactionAttributesBuilder.CreateEmpty() + .AddNotValidBefore(10u) + .Build(); + + Assert.IsNotNull(attr); + Assert.AreEqual(1, attr.Length); + Assert.IsInstanceOfType(attr[0]); + Assert.AreEqual(10u, ((NotValidBefore)attr[0]).Height); + } + } +} diff --git a/tests/Neo.UnitTests/Builders/UT_TransactionBuilder.cs b/tests/Neo.UnitTests/Builders/UT_TransactionBuilder.cs new file mode 100644 index 0000000000..17eb5796ad --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_TransactionBuilder.cs @@ -0,0 +1,201 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_TransactionBuilder.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.Builders; +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads.Conditions; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_TransactionBuilder + { + [TestMethod] + public void TestCreateEmpty() + { + var builder = TransactionBuilder.CreateEmpty(); + + Assert.IsNotNull(builder); + } + + [TestMethod] + public void TestEmptyTransaction() + { + var tx = TransactionBuilder.CreateEmpty() + .Build(); + + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestVersion() + { + byte expectedVersion = 1; + var tx = TransactionBuilder.CreateEmpty() + .Version(expectedVersion) + .Build(); + + Assert.AreEqual(expectedVersion, tx.Version); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestNonce() + { + var expectedNonce = (uint)Random.Shared.Next(); + var tx = TransactionBuilder.CreateEmpty() + .Nonce(expectedNonce) + .Build(); + + Assert.AreEqual(expectedNonce, tx.Nonce); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestSystemFee() + { + var expectedSystemFee = (uint)Random.Shared.Next(); + var tx = TransactionBuilder.CreateEmpty() + .SystemFee(expectedSystemFee) + .Build(); + + Assert.AreEqual(expectedSystemFee, tx.SystemFee); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestNetworkFee() + { + var expectedNetworkFee = (uint)Random.Shared.Next(); + var tx = TransactionBuilder.CreateEmpty() + .NetworkFee(expectedNetworkFee) + .Build(); + + Assert.AreEqual(expectedNetworkFee, tx.NetworkFee); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestValidUntilBlock() + { + var expectedValidUntilBlock = (uint)Random.Shared.Next(); + var tx = TransactionBuilder.CreateEmpty() + .ValidUntil(expectedValidUntilBlock) + .Build(); + + Assert.AreEqual(expectedValidUntilBlock, tx.ValidUntilBlock); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestAttachScript() + { + byte[] expectedScript = [(byte)OpCode.NOP]; + var tx = TransactionBuilder.CreateEmpty() + .AttachSystem(sb => sb.Emit(OpCode.NOP)) + .Build(); + + CollectionAssert.AreEqual(expectedScript, tx.Script.ToArray()); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestTransactionAttributes() + { + var tx = TransactionBuilder.CreateEmpty() + .AddAttributes(ab => ab.AddHighPriority()) + .Build(); + + Assert.AreEqual(1, tx.Attributes.Length); + Assert.IsInstanceOfType(tx.Attributes[0]); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestWitness() + { + var tx = TransactionBuilder.CreateEmpty() + .AddWitness(wb => + { + // Contract signature + wb.AddInvocation([]); + wb.AddVerification([]); + }) + .Build(); + + Assert.AreEqual(1, tx.Witnesses.Length); + Assert.AreEqual(0, tx.Witnesses[0].InvocationScript.Length); + Assert.AreEqual(0, tx.Witnesses[0].VerificationScript.Length); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestWitnessWithTransactionParameter() + { + var tx = TransactionBuilder.CreateEmpty() + .AddWitness((wb, tx) => + { + // Checks to make sure the transaction is hash able + // NOTE: transaction can be used for signing here + Assert.IsNotNull(tx.Hash); + }) + .Build(); + } + + [TestMethod] + public void TestSigner() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var expectedContractHash = UInt160.Zero; + + var tx = TransactionBuilder.CreateEmpty() + .AddSigner((sb, tx) => + { + sb.Account(expectedContractHash); + sb.AllowContract(expectedContractHash); + sb.AllowGroup(expectedPublicKey); + sb.AddWitnessScope(WitnessScope.WitnessRules); + sb.AddWitnessRule(WitnessRuleAction.Deny, wrb => + { + wrb.AddCondition(cb => + { + cb.ScriptHash(expectedContractHash); + }); + }); + }) + .Build(); + + Assert.IsNotNull(tx.Hash); + Assert.AreEqual(1, tx.Signers.Length); + Assert.AreEqual(expectedContractHash, tx.Signers[0].Account); + Assert.AreEqual(1, tx.Signers[0].AllowedContracts.Length); + Assert.AreEqual(expectedContractHash, tx.Signers[0].AllowedContracts[0]); + Assert.AreEqual(1, tx.Signers[0].AllowedGroups.Length); + Assert.AreEqual(expectedPublicKey, tx.Signers[0].AllowedGroups[0]); + Assert.AreEqual(WitnessScope.WitnessRules, tx.Signers[0].Scopes); + Assert.AreEqual(1, tx.Signers[0].Rules.Length); + Assert.AreEqual(WitnessRuleAction.Deny, tx.Signers[0].Rules[0].Action); + Assert.IsNotNull(tx.Signers[0].Rules[0].Condition); + Assert.IsInstanceOfType(tx.Signers[0].Rules[0].Condition); + } + } +} diff --git a/tests/Neo.UnitTests/Builders/UT_WitnessBuilder.cs b/tests/Neo.UnitTests/Builders/UT_WitnessBuilder.cs new file mode 100644 index 0000000000..1b73a69124 --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_WitnessBuilder.cs @@ -0,0 +1,91 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_WitnessBuilder.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.Builders; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_WitnessBuilder + { + [TestMethod] + public void TestCreateEmpty() + { + var wb = WitnessBuilder.CreateEmpty(); + Assert.IsNotNull(wb); + } + + [TestMethod] + public void TestAddInvocationWithScriptBuilder() + { + var witness = WitnessBuilder.CreateEmpty() + .AddInvocation(sb => + { + sb.Emit(VM.OpCode.NOP); + sb.Emit(VM.OpCode.NOP); + sb.Emit(VM.OpCode.NOP); + }) + .Build(); + + Assert.IsNotNull(witness); + Assert.AreEqual(3, witness.InvocationScript.Length); + CollectionAssert.AreEqual(new byte[] { 0x21, 0x21, 0x21 }, witness.InvocationScript.ToArray()); + } + + [TestMethod] + public void TestAddInvocation() + { + var witness = WitnessBuilder.CreateEmpty() + .AddInvocation(new byte[] { 0x01, 0x02, 0x03 }) + .Build(); + + Assert.IsNotNull(witness); + Assert.AreEqual(3, witness.InvocationScript.Length); + CollectionAssert.AreEqual(new byte[] { 0x01, 0x02, 0x03 }, witness.InvocationScript.ToArray()); + } + + [TestMethod] + public void TestAddVerificationWithScriptBuilder() + { + var witness = WitnessBuilder.CreateEmpty() + .AddVerification(sb => + { + sb.Emit(VM.OpCode.NOP); + sb.Emit(VM.OpCode.NOP); + sb.Emit(VM.OpCode.NOP); + }) + .Build(); + + Assert.IsNotNull(witness); + Assert.AreEqual(3, witness.VerificationScript.Length); + CollectionAssert.AreEqual(new byte[] { 0x21, 0x21, 0x21 }, witness.VerificationScript.ToArray()); + } + + [TestMethod] + public void TestAddVerification() + { + var witness = WitnessBuilder.CreateEmpty() + .AddVerification(new byte[] { 0x01, 0x02, 0x03 }) + .Build(); + + Assert.IsNotNull(witness); + Assert.AreEqual(3, witness.VerificationScript.Length); + CollectionAssert.AreEqual(new byte[] { 0x01, 0x02, 0x03 }, witness.VerificationScript.ToArray()); + } + } +} diff --git a/tests/Neo.UnitTests/Builders/UT_WitnessConditionBuilder.cs b/tests/Neo.UnitTests/Builders/UT_WitnessConditionBuilder.cs new file mode 100644 index 0000000000..4378aa2bfa --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_WitnessConditionBuilder.cs @@ -0,0 +1,220 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_WitnessConditionBuilder.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.Builders; +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads.Conditions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_WitnessConditionBuilder + { + [TestMethod] + public void TestAndCondition() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .And(and => + { + and.CalledByContract(expectedContractHash); + and.CalledByGroup(expectedPublicKey); + }) + .Build(); + + var actual = condition as AndCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(2, actual.Expressions.Length); + Assert.IsInstanceOfType(actual.Expressions[0]); + Assert.IsInstanceOfType(actual.Expressions[1]); + Assert.AreEqual(expectedContractHash, (actual.Expressions[0] as CalledByContractCondition).Hash); + Assert.AreEqual(expectedPublicKey, (actual.Expressions[1] as CalledByGroupCondition).Group); + } + + [TestMethod] + public void TestOrCondition() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .Or(or => + { + or.CalledByContract(expectedContractHash); + or.CalledByGroup(expectedPublicKey); + }) + .Build(); + + var actual = condition as OrCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(2, actual.Expressions.Length); + Assert.IsInstanceOfType(actual.Expressions[0]); + Assert.IsInstanceOfType(actual.Expressions[1]); + Assert.AreEqual(expectedContractHash, (actual.Expressions[0] as CalledByContractCondition).Hash); + Assert.AreEqual(expectedPublicKey, (actual.Expressions[1] as CalledByGroupCondition).Group); + } + + [TestMethod] + public void TestBoolean() + { + var condition = WitnessConditionBuilder.Create() + .Boolean(true) + .Build(); + + var actual = condition as BooleanCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.IsTrue(actual.Expression); + } + + [TestMethod] + public void TestCalledByContract() + { + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .CalledByContract(expectedContractHash) + .Build(); + + var actual = condition as CalledByContractCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(expectedContractHash, actual.Hash); + } + + [TestMethod] + public void TestCalledByEntry() + { + var condition = WitnessConditionBuilder.Create() + .CalledByEntry() + .Build(); + + var actual = condition as CalledByEntryCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + } + + [TestMethod] + public void TestCalledByGroup() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var condition = WitnessConditionBuilder.Create() + .CalledByGroup(expectedPublicKey) + .Build(); + + var actual = condition as CalledByGroupCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(expectedPublicKey, actual.Group); + } + + [TestMethod] + public void TestGroup() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var condition = WitnessConditionBuilder.Create() + .Group(expectedPublicKey) + .Build(); + + var actual = condition as GroupCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(expectedPublicKey, actual.Group); + } + + [TestMethod] + public void TestScriptHash() + { + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .ScriptHash(expectedContractHash) + .Build(); + + var actual = condition as ScriptHashCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(expectedContractHash, actual.Hash); + } + + [TestMethod] + public void TestNotConditionWithAndCondition() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .Not(not => + { + not.And(and => + { + and.CalledByContract(expectedContractHash); + and.CalledByGroup(expectedPublicKey); + }); + }) + .Build(); + + var actual = condition as NotCondition; + var actualAndCondition = actual.Expression as AndCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.IsInstanceOfType(actual.Expression); + Assert.AreEqual(2, actualAndCondition.Expressions.Length); + Assert.IsInstanceOfType(actualAndCondition.Expressions[0]); + Assert.IsInstanceOfType(actualAndCondition.Expressions[1]); + Assert.AreEqual(expectedContractHash, (actualAndCondition.Expressions[0] as CalledByContractCondition).Hash); + Assert.AreEqual(expectedPublicKey, (actualAndCondition.Expressions[1] as CalledByGroupCondition).Group); + } + + [TestMethod] + public void TestNotConditionWithOrCondition() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .Not(not => + { + not.Or(or => + { + or.CalledByContract(expectedContractHash); + or.CalledByGroup(expectedPublicKey); + }); + }) + .Build(); + + var actual = condition as NotCondition; + var actualOrCondition = actual.Expression as OrCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.IsInstanceOfType(actual.Expression); + Assert.AreEqual(2, actualOrCondition.Expressions.Length); + Assert.IsInstanceOfType(actualOrCondition.Expressions[0]); + Assert.IsInstanceOfType(actualOrCondition.Expressions[1]); + Assert.AreEqual(expectedContractHash, (actualOrCondition.Expressions[0] as CalledByContractCondition).Hash); + Assert.AreEqual(expectedPublicKey, (actualOrCondition.Expressions[1] as CalledByGroupCondition).Group); + } + } +} diff --git a/tests/Neo.UnitTests/Builders/UT_WitnessRuleBuilder.cs b/tests/Neo.UnitTests/Builders/UT_WitnessRuleBuilder.cs new file mode 100644 index 0000000000..1b87e2c52f --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_WitnessRuleBuilder.cs @@ -0,0 +1,69 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_WitnessRuleBuilder.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.Builders; +using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads.Conditions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_WitnessRuleBuilder + { + [TestMethod] + public void TestCreate() + { + var builder = WitnessRuleBuilder.Create(WitnessRuleAction.Allow); + + Assert.IsNotNull(builder); + } + + [TestMethod] + public void TestCondition() + { + var rule = WitnessRuleBuilder.Create(WitnessRuleAction.Allow) + .AddCondition(wcb => + { + wcb.ScriptHash(UInt160.Zero); + }).Build(); + + Assert.IsNotNull(rule.Condition); + Assert.AreEqual(WitnessRuleAction.Allow, rule.Action); + Assert.IsInstanceOfType(rule.Condition); + Assert.AreEqual(UInt160.Zero, ((ScriptHashCondition)rule.Condition).Hash); + } + + [TestMethod] + public void TestCondition2() + { + var rule = WitnessRuleBuilder.Create(WitnessRuleAction.Allow) + .AddCondition(wcb => + { + wcb.And(and => + { + and.ScriptHash(UInt160.Zero); + }); + }).Build(); + + Assert.IsNotNull(rule.Condition); + Assert.AreEqual(WitnessRuleAction.Allow, rule.Action); + Assert.IsInstanceOfType(rule.Condition); + Assert.IsInstanceOfType((rule.Condition as AndCondition).Expressions[0]); + Assert.AreEqual(UInt160.Zero, ((rule.Condition as AndCondition).Expressions[0] as ScriptHashCondition).Hash); + } + } +} diff --git a/tests/Neo.UnitTests/Cryptography/ECC/UT_ECFieldElement.cs b/tests/Neo.UnitTests/Cryptography/ECC/UT_ECFieldElement.cs index 3d4e4c56f2..0d1e20c37a 100644 --- a/tests/Neo.UnitTests/Cryptography/ECC/UT_ECFieldElement.cs +++ b/tests/Neo.UnitTests/Cryptography/ECC/UT_ECFieldElement.cs @@ -12,6 +12,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; +using Neo.Extensions; using System; using System.Globalization; using System.Numerics; diff --git a/tests/Neo.UnitTests/Cryptography/ECC/UT_ECPoint.cs b/tests/Neo.UnitTests/Cryptography/ECC/UT_ECPoint.cs index 4b0c7d484f..e5767acd37 100644 --- a/tests/Neo.UnitTests/Cryptography/ECC/UT_ECPoint.cs +++ b/tests/Neo.UnitTests/Cryptography/ECC/UT_ECPoint.cs @@ -12,6 +12,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using System; using System.IO; diff --git a/tests/Neo.UnitTests/Cryptography/UT_Base58.cs b/tests/Neo.UnitTests/Cryptography/UT_Base58.cs index e7cb467dfa..091513862c 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_Base58.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_Base58.cs @@ -12,6 +12,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; +using Neo.Extensions; using System; using System.Collections.Generic; diff --git a/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs b/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs index 907a5df2b9..8693157724 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; @@ -76,5 +78,16 @@ public void TestGetBits() foreach (byte value in result) value.Should().Be(0); } + + [TestMethod] + public void TestInvalidArguments() + { + uint nTweak = 123456; + Action action = () => new BloomFilter(0, 3, nTweak); + action.Should().Throw(); + + action = () => new BloomFilter(3, 0, nTweak); + action.Should().Throw(); + } } } diff --git a/tests/Neo.UnitTests/Cryptography/UT_Crypto.cs b/tests/Neo.UnitTests/Cryptography/UT_Crypto.cs index e311d6cafe..2f7de65b77 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_Crypto.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_Crypto.cs @@ -12,6 +12,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; +using Neo.Extensions; using Neo.Wallets; using System; using System.Security.Cryptography; diff --git a/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs b/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs index 01022baab9..8cddc26d16 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs @@ -12,6 +12,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.SmartContract; @@ -41,6 +42,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 +62,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/Cryptography/UT_Murmur128.cs b/tests/Neo.UnitTests/Cryptography/UT_Murmur128.cs index f33dd7110a..167caf6d3e 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_Murmur128.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_Murmur128.cs @@ -12,6 +12,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; +using Neo.Extensions; using System.Text; namespace Neo.UnitTests.Cryptography diff --git a/tests/Neo.UnitTests/Cryptography/UT_SCrypt.cs b/tests/Neo.UnitTests/Cryptography/UT_SCrypt.cs index c3a3127ff0..ee41f8149a 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_SCrypt.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_SCrypt.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Org.BouncyCastle.Crypto.Generators; namespace Neo.UnitTests.Cryptography diff --git a/tests/Neo.UnitTests/Extensions/NativeContractExtensions.cs b/tests/Neo.UnitTests/Extensions/NativeContractExtensions.cs index 9a282759a9..c205a7e4bd 100644 --- a/tests/Neo.UnitTests/Extensions/NativeContractExtensions.cs +++ b/tests/Neo.UnitTests/Extensions/NativeContractExtensions.cs @@ -9,6 +9,8 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using FluentAssertions; +using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.SmartContract; @@ -100,14 +102,26 @@ public static void DestroyContract(this DataCache snapshot, UInt160 callingScrip public static void AddContract(this DataCache snapshot, UInt160 hash, ContractState state) { + //key: hash, value: ContractState var key = new KeyBuilder(NativeContract.ContractManagement.Id, 8).Add(hash); snapshot.Add(key, new StorageItem(state)); + //key: id, value: hash + var key2 = new KeyBuilder(NativeContract.ContractManagement.Id, 12).AddBigEndian(state.Id); + if (!snapshot.Contains(key2)) snapshot.Add(key2, new StorageItem(hash.ToArray())); } public static void DeleteContract(this DataCache snapshot, UInt160 hash) { + //key: hash, value: ContractState var key = new KeyBuilder(NativeContract.ContractManagement.Id, 8).Add(hash); + var value = snapshot.TryGet(key)?.GetInteroperable(); snapshot.Delete(key); + if (value != null) + { + //key: id, value: hash + var key2 = new KeyBuilder(NativeContract.ContractManagement.Id, 12).AddBigEndian(value.Id); + snapshot.Delete(key2); + } } public static StackItem Call(this NativeContract contract, DataCache snapshot, string method, params ContractParameter[] args) diff --git a/tests/Neo.UnitTests/IO/Caching/UT_CloneCache.cs b/tests/Neo.UnitTests/IO/Caching/UT_CloneCache.cs index 4d57da62b9..fd0694f413 100644 --- a/tests/Neo.UnitTests/IO/Caching/UT_CloneCache.cs +++ b/tests/Neo.UnitTests/IO/Caching/UT_CloneCache.cs @@ -11,6 +11,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.IO; using Neo.Persistence; using Neo.SmartContract; @@ -155,8 +156,8 @@ public void TestUpdateInternal() [TestMethod] public void TestCacheOverrideIssue2572() { - var snapshot = TestBlockchain.GetTestSnapshot(); - var storages = snapshot.CreateSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + var storages = snapshotCache.CreateSnapshot(); storages.Add ( @@ -174,10 +175,10 @@ public void TestCacheOverrideIssue2572() var item = storages.GetAndChange(new StorageKey() { Key = new byte[] { 0x01, 0x01 }, Id = 0 }); item.Value = new byte[] { 0x06 }; - var res = snapshot.TryGet(new StorageKey() { Key = new byte[] { 0x01, 0x01 }, Id = 0 }); + var res = snapshotCache.TryGet(new StorageKey() { Key = new byte[] { 0x01, 0x01 }, Id = 0 }); Assert.AreEqual("05", res.Value.Span.ToHexString()); storages.Commit(); - res = snapshot.TryGet(new StorageKey() { Key = new byte[] { 0x01, 0x01 }, Id = 0 }); + res = snapshotCache.TryGet(new StorageKey() { Key = new byte[] { 0x01, 0x01 }, Id = 0 }); Assert.AreEqual("06", res.Value.Span.ToHexString()); } } diff --git a/tests/Neo.UnitTests/IO/Caching/UT_DataCache.cs b/tests/Neo.UnitTests/IO/Caching/UT_DataCache.cs index 5235b99c70..224b1cc3f6 100644 --- a/tests/Neo.UnitTests/IO/Caching/UT_DataCache.cs +++ b/tests/Neo.UnitTests/IO/Caching/UT_DataCache.cs @@ -122,18 +122,24 @@ public void TestCommit() myDataCache.Add(key3, value4); Assert.AreEqual(TrackState.Changed, myDataCache.GetChangeSet().Where(u => u.Key.Equals(key3)).Select(u => u.State).FirstOrDefault()); + // If we use myDataCache after it is committed, it will return wrong result. myDataCache.Commit(); Assert.AreEqual(0, myDataCache.GetChangeSet().Count()); store.TryGet(key1.ToArray()).SequenceEqual(value1.ToArray()).Should().BeTrue(); store.TryGet(key2.ToArray()).Should().BeNull(); store.TryGet(key3.ToArray()).SequenceEqual(value4.ToArray()).Should().BeTrue(); + + myDataCache.TryGet(key1).Value.ToArray().SequenceEqual(value1.ToArray()).Should().BeTrue(); + // Though value is deleted from the store, the value can still be gotten from the snapshot cache. + myDataCache.TryGet(key2).Value.ToArray().SequenceEqual(value2.ToArray()).Should().BeTrue(); + myDataCache.TryGet(key3).Value.ToArray().SequenceEqual(value4.ToArray()).Should().BeTrue(); } [TestMethod] public void TestCreateSnapshot() { - myDataCache.CreateSnapshot().Should().NotBeNull(); + myDataCache.CloneCache().Should().NotBeNull(); } [TestMethod] diff --git a/tests/Neo.UnitTests/IO/Caching/UT_KeyedCollectionSlim.cs b/tests/Neo.UnitTests/IO/Caching/UT_KeyedCollectionSlim.cs new file mode 100644 index 0000000000..3c382cc7a8 --- /dev/null +++ b/tests/Neo.UnitTests/IO/Caching/UT_KeyedCollectionSlim.cs @@ -0,0 +1,135 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_KeyedCollectionSlim.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 Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO.Caching; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.IO.Caching +{ + [TestClass] + public class UT_KeyedCollectionSlim + { + [TestMethod] + public void Add_ShouldAddItem() + { + // Arrange + var collection = new TestKeyedCollectionSlim(); + var item = new TestItem { Id = 1, Name = "Item1" }; + + // Act + collection.Add(item); + + // Assert + collection.Count.Should().Be(1); + collection.Contains(1).Should().BeTrue(); + collection.First.Should().Be(item); + } + + [TestMethod] + public void Add_ShouldThrowException_WhenKeyAlreadyExists() + { + // Arrange + var collection = new TestKeyedCollectionSlim(); + var item1 = new TestItem { Id = 1, Name = "Item1" }; + var item2 = new TestItem { Id = 1, Name = "Item2" }; // Same ID as item1 + + // Act + collection.Add(item1); + + // Assert + var act = (() => collection.Add(item2)); + act.Should().Throw(); + } + + [TestMethod] + public void Remove_ShouldRemoveItem() + { + // Arrange + var collection = new TestKeyedCollectionSlim(); + var item = new TestItem { Id = 1, Name = "Item1" }; + collection.Add(item); + + // Act + collection.Remove(1); + + // Assert + collection.Count.Should().Be(0); + collection.Contains(1).Should().BeFalse(); + } + + [TestMethod] + public void RemoveFirst_ShouldRemoveFirstItem() + { + // Arrange + var collection = new TestKeyedCollectionSlim(); + var item1 = new TestItem { Id = 1, Name = "Item1" }; + var item2 = new TestItem { Id = 2, Name = "Item2" }; + collection.Add(item1); + collection.Add(item2); + + // Act + collection.RemoveFirst(); + + // Assert + collection.Count.Should().Be(1); + collection.Contains(1).Should().BeFalse(); + collection.Contains(2).Should().BeTrue(); + } + + public class TestItem : IStructuralEquatable, IStructuralComparable, IComparable + { + public int Id { get; set; } + + public string Name { get; set; } + + public int CompareTo(object? obj) + { + if (obj is not TestItem other) throw new ArgumentException("Object is not a TestItem"); + return Id.CompareTo(other.Id); + } + + public bool Equals(object? other, IEqualityComparer comparer) + { + return other is TestItem item && Id == item.Id && Name == item.Name; + } + + public int GetHashCode(IEqualityComparer comparer) + { + return HashCode.Combine(Id, Name); + } + + public int CompareTo(TestItem other) + { + return Id.CompareTo(other.Id); + } + + public int CompareTo(object other, IComparer comparer) + { + throw new NotImplementedException(); + } + } + + internal class TestKeyedCollectionSlim : KeyedCollectionSlim + { + protected override int GetKeyForItem(TestItem? item) + { + return item?.Id ?? throw new ArgumentNullException(nameof(item), "Item cannot be null"); + } + } + } +} diff --git a/tests/Neo.UnitTests/IO/UT_IOHelper.cs b/tests/Neo.UnitTests/IO/UT_IOHelper.cs index 4b74a495b5..d9d753e292 100644 --- a/tests/Neo.UnitTests/IO/UT_IOHelper.cs +++ b/tests/Neo.UnitTests/IO/UT_IOHelper.cs @@ -11,6 +11,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.IO; using System; using System.Collections.Generic; @@ -109,23 +110,9 @@ public void TestNullableArray() [TestMethod] public void TestAsSerializable() { - for (int i = 0; i < 2; i++) - { - if (i == 0) - { - byte[] caseArray = new byte[] { 0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00, - 0x00,0x00,0x00,0x00,0x00 }; - ISerializable result = Neo.IO.Helper.AsSerializable(caseArray, typeof(UInt160)); - Assert.AreEqual(UInt160.Zero, result); - } - else - { - Action action = () => Neo.IO.Helper.AsSerializable(Array.Empty(), typeof(double)); - action.Should().Throw(); - } - } + byte[] caseArray = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]; + ISerializable result = caseArray.AsSerializable(); + Assert.AreEqual(UInt160.Zero, result); } [TestMethod] @@ -163,169 +150,12 @@ public void TestCompression() [TestMethod] public void TestAsSerializableArray() { - byte[] byteArray = Neo.IO.Helper.ToByteArray(new UInt160[] { UInt160.Zero }); + byte[] byteArray = new UInt160[] { UInt160.Zero }.ToByteArray(); UInt160[] result = Neo.IO.Helper.AsSerializableArray(byteArray); Assert.AreEqual(1, result.Length); Assert.AreEqual(UInt160.Zero, result[0]); } - [TestMethod] - public void TestGetVarSizeInt() - { - for (int i = 0; i < 3; i++) - { - if (i == 0) - { - int result = Neo.IO.Helper.GetVarSize(1); - Assert.AreEqual(1, result); - } - else if (i == 1) - { - int result = Neo.IO.Helper.GetVarSize(0xFFFF); - Assert.AreEqual(3, result); - } - else - { - int result = Neo.IO.Helper.GetVarSize(0xFFFFFF); - Assert.AreEqual(5, result); - } - } - } - enum TestEnum0 : sbyte - { - case1 = 1, case2 = 2 - } - - enum TestEnum1 : byte - { - case1 = 1, case2 = 2 - } - - enum TestEnum2 : short - { - case1 = 1, case2 = 2 - } - - enum TestEnum3 : ushort - { - case1 = 1, case2 = 2 - } - - enum TestEnum4 : int - { - case1 = 1, case2 = 2 - } - - enum TestEnum5 : uint - { - case1 = 1, case2 = 2 - } - - enum TestEnum6 : long - { - case1 = 1, case2 = 2 - } - - [TestMethod] - public void TestGetVarSizeGeneric() - { - for (int i = 0; i < 9; i++) - { - if (i == 0) - { - int result = Neo.IO.Helper.GetVarSize(new UInt160[] { UInt160.Zero }); - Assert.AreEqual(21, result); - } - else if (i == 1)//sbyte - { - List initList = new() - { - TestEnum0.case1 - }; - IReadOnlyCollection testList = initList.AsReadOnly(); - int result = Neo.IO.Helper.GetVarSize(testList); - Assert.AreEqual(2, result); - } - else if (i == 2)//byte - { - List initList = new() - { - TestEnum1.case1 - }; - IReadOnlyCollection testList = initList.AsReadOnly(); - int result = Neo.IO.Helper.GetVarSize(testList); - Assert.AreEqual(2, result); - } - else if (i == 3)//short - { - List initList = new() - { - TestEnum2.case1 - }; - IReadOnlyCollection testList = initList.AsReadOnly(); - int result = Neo.IO.Helper.GetVarSize(testList); - Assert.AreEqual(3, result); - } - else if (i == 4)//ushort - { - List initList = new() - { - TestEnum3.case1 - }; - IReadOnlyCollection testList = initList.AsReadOnly(); - int result = Neo.IO.Helper.GetVarSize(testList); - Assert.AreEqual(3, result); - } - else if (i == 5)//int - { - List initList = new() - { - TestEnum4.case1 - }; - IReadOnlyCollection testList = initList.AsReadOnly(); - int result = Neo.IO.Helper.GetVarSize(testList); - Assert.AreEqual(5, result); - } - else if (i == 6)//uint - { - List initList = new() - { - TestEnum5.case1 - }; - IReadOnlyCollection testList = initList.AsReadOnly(); - int result = Neo.IO.Helper.GetVarSize(testList); - Assert.AreEqual(5, result); - } - else if (i == 7)//long - { - List initList = new() - { - TestEnum6.case1 - }; - IReadOnlyCollection testList = initList.AsReadOnly(); - int result = Neo.IO.Helper.GetVarSize(testList); - Assert.AreEqual(9, result); - } - else if (i == 8) - { - List initList = new() - { - 1 - }; - IReadOnlyCollection testList = initList.AsReadOnly(); - int result = Neo.IO.Helper.GetVarSize(testList); - Assert.AreEqual(5, result); - } - } - } - - [TestMethod] - public void TestGetVarSizeString() - { - int result = Neo.IO.Helper.GetVarSize("AA"); - Assert.AreEqual(3, result); - } - [TestMethod] public void TestReadSerializable() { @@ -412,7 +242,7 @@ public void TestToArray() [TestMethod] public void TestToByteArrayGeneric() { - byte[] byteArray = Neo.IO.Helper.ToByteArray(new UInt160[] { UInt160.Zero }); + byte[] byteArray = new UInt160[] { UInt160.Zero }.ToByteArray(); Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x01,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00, diff --git a/tests/Neo.UnitTests/IO/UT_MemoryReader.cs b/tests/Neo.UnitTests/IO/UT_MemoryReader.cs index a045a0b688..3f514e09a8 100644 --- a/tests/Neo.UnitTests/IO/UT_MemoryReader.cs +++ b/tests/Neo.UnitTests/IO/UT_MemoryReader.cs @@ -9,8 +9,12 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.IO; +using Newtonsoft.Json.Linq; +using System; using System.IO; using System.Text; @@ -49,5 +53,175 @@ public void TestReadNullableArray() var n = reader.ReadNullableArray(); Assert.AreEqual(5, reader.Position); } + + [TestMethod] + public void TestReadSByte() + { + var values = new sbyte[] { 0, 1, -1, 5, -5, sbyte.MaxValue, sbyte.MinValue }; + foreach (var v in values) + { + byte[] byteArray = new byte[1]; + byteArray[0] = (byte)v; + MemoryReader reader = new(byteArray); + var n = reader.ReadSByte(); + n.Should().Be(v); + } + + var values2 = new long[] { (long)int.MaxValue + 1, (long)int.MinValue - 1 }; + foreach (var v in values2) + { + byte[] byteArray = new byte[1]; + byteArray[0] = (byte)v; + MemoryReader reader = new(byteArray); + var n = reader.ReadSByte(); + n.Should().Be((sbyte)v); + } + } + + [TestMethod] + public void TestReadInt32() + { + var values = new int[] { 0, 1, -1, 5, -5, int.MaxValue, int.MinValue }; + foreach (var v in values) + { + byte[] bytes = BitConverter.GetBytes(v); + MemoryReader reader = new(bytes); + var n = reader.ReadInt32(); + n.Should().Be(v); + } + + var values2 = new long[] { (long)int.MaxValue + 1, (long)int.MinValue - 1 }; + foreach (var v in values2) + { + byte[] bytes = BitConverter.GetBytes(v); + MemoryReader reader = new(bytes); + var n = reader.ReadInt32(); + n.Should().Be((int)v); + } + } + + [TestMethod] + public void TestReadUInt64() + { + var values = new ulong[] { 0, 1, 5, ulong.MaxValue, ulong.MinValue }; + foreach (var v in values) + { + byte[] bytes = BitConverter.GetBytes(v); + MemoryReader reader = new(bytes); + var n = reader.ReadUInt64(); + n.Should().Be(v); + } + + var values2 = new long[] { long.MinValue, -1, long.MaxValue }; + foreach (var v in values2) + { + byte[] bytes = BitConverter.GetBytes(v); + MemoryReader reader = new(bytes); + var n = reader.ReadUInt64(); + n.Should().Be((ulong)v); + } + } + + [TestMethod] + public void TestReadInt16BigEndian() + { + var values = new short[] { short.MinValue, -1, 0, 1, 12345, short.MaxValue }; + foreach (var v in values) + { + byte[] bytes = BitConverter.GetBytes(v); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); + } + MemoryReader reader = new(bytes); + var n = reader.ReadInt16BigEndian(); + n.Should().Be(v); + } + } + + [TestMethod] + public void TestReadUInt16BigEndian() + { + var values = new ushort[] { ushort.MinValue, 0, 1, 12345, ushort.MaxValue }; + foreach (var v in values) + { + byte[] bytes = BitConverter.GetBytes(v); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); + } + MemoryReader reader = new(bytes); + var n = reader.ReadUInt16BigEndian(); + n.Should().Be(v); + } + } + + [TestMethod] + public void TestReadInt32BigEndian() + { + var values = new int[] { int.MinValue, -1, 0, 1, 12345, int.MaxValue }; + foreach (var v in values) + { + byte[] bytes = BitConverter.GetBytes(v); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); + } + MemoryReader reader = new(bytes); + var n = reader.ReadInt32BigEndian(); + n.Should().Be(v); + } + } + + [TestMethod] + public void TestReadUInt32BigEndian() + { + var values = new uint[] { uint.MinValue, 0, 1, 12345, uint.MaxValue }; + foreach (var v in values) + { + byte[] bytes = BitConverter.GetBytes(v); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); + } + MemoryReader reader = new(bytes); + var n = reader.ReadUInt32BigEndian(); + n.Should().Be(v); + } + } + + [TestMethod] + public void TestReadInt64BigEndian() + { + var values = new long[] { long.MinValue, int.MinValue, -1, 0, 1, 12345, int.MaxValue, long.MaxValue }; + foreach (var v in values) + { + byte[] bytes = BitConverter.GetBytes(v); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); + } + MemoryReader reader = new(bytes); + var n = reader.ReadInt64BigEndian(); + n.Should().Be(v); + } + } + + [TestMethod] + public void TestReadUInt64BigEndian() + { + var values = new ulong[] { ulong.MinValue, 0, 1, 12345, ulong.MaxValue }; + foreach (var v in values) + { + byte[] bytes = BitConverter.GetBytes(v); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); + } + MemoryReader reader = new(bytes); + var n = reader.ReadUInt64BigEndian(); + n.Should().Be(v); + } + } } } diff --git a/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs b/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs index cedc68bb4d..9ee5dd6929 100644 --- a/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs +++ b/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs @@ -38,14 +38,14 @@ public void Initialize() { system = TestBlockchain.TheNeoSystem; senderProbe = CreateTestProbe(); - txSample = new Transaction() + txSample = new Transaction { - Attributes = Array.Empty(), + Attributes = [], Script = Array.Empty(), - Signers = new Signer[] { new Signer() { Account = UInt160.Zero } }, - Witnesses = Array.Empty() + Signers = [new Signer { Account = UInt160.Zero }], + Witnesses = [] }; - system.MemPool.TryAdd(txSample, TestBlockchain.GetTestSnapshot()); + system.MemPool.TryAdd(txSample, TestBlockchain.GetTestSnapshotCache()); } [TestCleanup] @@ -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(); @@ -95,7 +95,7 @@ internal static StorageKey CreateStorageKey(byte prefix, byte[] key = null) [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"); @@ -146,7 +146,7 @@ public void TestMaliciousOnChainConflict() { engine2.LoadScript(onPersistScript); if (engine2.Execute() != VMState.HALT) throw engine2.FaultException; - engine2.Snapshot.Commit(); + engine2.SnapshotCache.Commit(); } snapshot.Commit(); @@ -162,7 +162,7 @@ public void TestMaliciousOnChainConflict() { engine2.LoadScript(postPersistScript); if (engine2.Execute() != VMState.HALT) throw engine2.FaultException; - engine2.Snapshot.Commit(); + engine2.SnapshotCache.Commit(); } snapshot.Commit(); diff --git a/tests/Neo.UnitTests/Ledger/UT_HeaderCache.cs b/tests/Neo.UnitTests/Ledger/UT_HeaderCache.cs new file mode 100644 index 0000000000..71706fda32 --- /dev/null +++ b/tests/Neo.UnitTests/Ledger/UT_HeaderCache.cs @@ -0,0 +1,70 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_HeaderCache.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 Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using System; + +namespace Neo.UnitTests.Ledger +{ + [TestClass] + public class UT_HeaderCache + { + [TestMethod] + public void TestHeaderCache() + { + var cache = new HeaderCache(); + var header = new Header(); + header.Index = 1; + cache.Add(header); + + var got = cache[1]; + got.Should().NotBeNull(); + got.Index.Should().Be(1); + + var count = cache.Count; + count.Should().Be(1); + + var full = cache.Full; + full.Should().BeFalse(); + + var last = cache.Last; + last.Should().NotBeNull(); + last.Index.Should().Be(1); + + got = cache[2]; + got.Should().BeNull(); + + // enumerate + var enumerator = cache.GetEnumerator(); + enumerator.MoveNext().Should().BeTrue(); + enumerator.Current.Index.Should().Be(1); + enumerator.MoveNext().Should().BeFalse(); + + var removed = cache.TryRemoveFirst(out header); + removed.Should().BeTrue(); + + count = cache.Count; + count.Should().Be(0); + + full = cache.Full; + full.Should().BeFalse(); + + last = cache.Last; + last.Should().BeNull(); + + got = cache[1]; + got.Should().BeNull(); + } + } +} diff --git a/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs b/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs index 1063413073..3226015f18 100644 --- a/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs +++ b/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs @@ -14,6 +14,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Neo.Cryptography; +using Neo.Extensions; using Neo.IO; using Neo.Ledger; using Neo.Network.P2P.Payloads; @@ -47,7 +48,7 @@ public static void TestSetup(TestContext ctx) private static DataCache GetSnapshot() { - return testBlockchain.StoreView.CreateSnapshot(); + return testBlockchain.StoreView.CloneCache(); } [TestInitialize] @@ -225,7 +226,7 @@ public async Task BlockPersistAndReverificationWillAbandonTxAsBalanceTransfered( _ = NativeContract.GAS.Mint(engine, UInt160.Zero, 70, true); long txFee = 1; - AddTransactionsWithBalanceVerify(70, txFee, engine.Snapshot); + AddTransactionsWithBalanceVerify(70, txFee, engine.SnapshotCache); _unit.SortedTxCount.Should().Be(70); @@ -244,7 +245,7 @@ public async Task BlockPersistAndReverificationWillAbandonTxAsBalanceTransfered( _ = NativeContract.GAS.Mint(applicationEngine, sender, txFee * 30, true); // Set the balance to meet 30 txs only // Persist block and reverify all the txs in mempool, but half of the txs will be discarded - _unit.UpdatePoolForBlockPersisted(block, applicationEngine.Snapshot); + _unit.UpdatePoolForBlockPersisted(block, applicationEngine.SnapshotCache); _unit.SortedTxCount.Should().Be(30); _unit.UnverifiedSortedTxCount.Should().Be(0); @@ -266,30 +267,30 @@ public async Task UpdatePoolForBlockPersisted_RemoveBlockConflicts() _ = NativeContract.GAS.Mint(engine, UInt160.Zero, 7, true); // balance enough for 7 mempooled txs var mp1 = CreateTransactionWithFeeAndBalanceVerify(txFee); // mp1 doesn't conflict with anyone - _unit.TryAdd(mp1, engine.Snapshot).Should().Be(VerifyResult.Succeed); + _unit.TryAdd(mp1, engine.SnapshotCache).Should().Be(VerifyResult.Succeed); var tx1 = CreateTransactionWithFeeAndBalanceVerify(txFee); // but in-block tx1 conflicts with mempooled mp1 => mp1 should be removed from pool after persist tx1.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp1.Hash } }; var mp2 = CreateTransactionWithFeeAndBalanceVerify(txFee); // mp1 and mp2 don't conflict with anyone - _unit.TryAdd(mp2, engine.Snapshot); + _unit.TryAdd(mp2, engine.SnapshotCache); var mp3 = CreateTransactionWithFeeAndBalanceVerify(txFee); - _unit.TryAdd(mp3, engine.Snapshot); + _unit.TryAdd(mp3, engine.SnapshotCache); var tx2 = CreateTransactionWithFeeAndBalanceVerify(txFee); // in-block tx2 conflicts with mempooled mp2 and mp3 => mp2 and mp3 should be removed from pool after persist tx2.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp2.Hash }, new Conflicts() { Hash = mp3.Hash } }; var tx3 = CreateTransactionWithFeeAndBalanceVerify(txFee); // in-block tx3 doesn't conflict with anyone var mp4 = CreateTransactionWithFeeAndBalanceVerify(txFee); // mp4 conflicts with in-block tx3 => mp4 should be removed from pool after persist mp4.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = tx3.Hash } }; - _unit.TryAdd(mp4, engine.Snapshot); + _unit.TryAdd(mp4, engine.SnapshotCache); var tx4 = CreateTransactionWithFeeAndBalanceVerify(txFee); // in-block tx4 and tx5 don't conflict with anyone var tx5 = CreateTransactionWithFeeAndBalanceVerify(txFee); var mp5 = CreateTransactionWithFeeAndBalanceVerify(txFee); // mp5 conflicts with in-block tx4 and tx5 => mp5 should be removed from pool after persist mp5.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = tx4.Hash }, new Conflicts() { Hash = tx5.Hash } }; - _unit.TryAdd(mp5, engine.Snapshot); + _unit.TryAdd(mp5, engine.SnapshotCache); var mp6 = CreateTransactionWithFeeAndBalanceVerify(txFee); // mp6 doesn't conflict with anyone and noone conflicts with mp6 => mp6 should be left in the pool after persist - _unit.TryAdd(mp6, engine.Snapshot); + _unit.TryAdd(mp6, engine.SnapshotCache); _unit.SortedTxCount.Should().Be(6); _unit.UnverifiedSortedTxCount.Should().Be(0); @@ -298,7 +299,7 @@ public async Task UpdatePoolForBlockPersisted_RemoveBlockConflicts() var tx6 = CreateTransactionWithFeeAndBalanceVerify(txFee); // in-block tx6 conflicts with mp7, but doesn't include sender of mp7 into signers list => even if tx6 is included into block, mp7 shouldn't be removed from the pool tx6.Signers = new Signer[] { new Signer() { Account = new UInt160(Crypto.Hash160(new byte[] { 1, 2, 3 })) }, new Signer() { Account = new UInt160(Crypto.Hash160(new byte[] { 4, 5, 6 })) } }; tx6.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp7.Hash } }; - _unit.TryAdd(mp7, engine.Snapshot); + _unit.TryAdd(mp7, engine.SnapshotCache); // Act: persist block and reverify all mempooled txs. var block = new Block @@ -306,7 +307,7 @@ public async Task UpdatePoolForBlockPersisted_RemoveBlockConflicts() Header = new Header(), Transactions = new Transaction[] { tx1, tx2, tx3, tx4, tx5, tx6 }, }; - _unit.UpdatePoolForBlockPersisted(block, engine.Snapshot); + _unit.UpdatePoolForBlockPersisted(block, engine.SnapshotCache); // Assert: conflicting txs should be removed from the pool; the only mp6 that doesn't conflict with anyone should be left. _unit.SortedTxCount.Should().Be(2); @@ -337,14 +338,14 @@ public async Task TryAdd_AddRangeOfConflictingTransactions() var mp2_1 = CreateTransactionWithFeeAndBalanceVerify(txFee); // mp2_1 conflicts with mp1 and has the same network fee mp2_1.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp1.Hash } }; - _unit.TryAdd(mp2_1, engine.Snapshot); + _unit.TryAdd(mp2_1, engine.SnapshotCache); var mp2_2 = CreateTransactionWithFeeAndBalanceVerify(txFee); // mp2_2 also conflicts with mp1 and has the same network fee as mp1 and mp2_1 mp2_2.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp1.Hash } }; - _unit.TryAdd(mp2_2, engine.Snapshot); + _unit.TryAdd(mp2_2, engine.SnapshotCache); var mp3 = CreateTransactionWithFeeAndBalanceVerify(2 * txFee); // mp3 conflicts with mp1 and has larger network fee mp3.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp1.Hash } }; - _unit.TryAdd(mp3, engine.Snapshot); + _unit.TryAdd(mp3, engine.SnapshotCache); var mp4 = CreateTransactionWithFeeAndBalanceVerify(3 * txFee); // mp4 conflicts with mp3 and has larger network fee mp4.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp3.Hash } }; @@ -372,44 +373,44 @@ public async Task TryAdd_AddRangeOfConflictingTransactions() _unit.UnverifiedSortedTxCount.Should().Be(0); // Act & Assert: try to add conlflicting transactions to the pool. - _unit.TryAdd(mp1, engine.Snapshot).Should().Be(VerifyResult.HasConflicts); // mp1 conflicts with mp2_1, mp2_2 and mp3 but has lower network fee than mp3 => mp1 fails to be added + _unit.TryAdd(mp1, engine.SnapshotCache).Should().Be(VerifyResult.HasConflicts); // mp1 conflicts with mp2_1, mp2_2 and mp3 but has lower network fee than mp3 => mp1 fails to be added _unit.SortedTxCount.Should().Be(3); _unit.GetVerifiedTransactions().Should().Contain(new List() { mp2_1, mp2_2, mp3 }); - _unit.TryAdd(malicious, engine.Snapshot).Should().Be(VerifyResult.HasConflicts); // malicious conflicts with mp3, has larger network fee but malicious (different) sender => mp3 shoould be left in pool + _unit.TryAdd(malicious, engine.SnapshotCache).Should().Be(VerifyResult.HasConflicts); // malicious conflicts with mp3, has larger network fee but malicious (different) sender => mp3 shoould be left in pool _unit.SortedTxCount.Should().Be(3); _unit.GetVerifiedTransactions().Should().Contain(new List() { mp2_1, mp2_2, mp3 }); - _unit.TryAdd(mp4, engine.Snapshot).Should().Be(VerifyResult.Succeed); // mp4 conflicts with mp3 and has larger network fee => mp3 shoould be removed from pool + _unit.TryAdd(mp4, engine.SnapshotCache).Should().Be(VerifyResult.Succeed); // mp4 conflicts with mp3 and has larger network fee => mp3 shoould be removed from pool _unit.SortedTxCount.Should().Be(3); _unit.GetVerifiedTransactions().Should().Contain(new List() { mp2_1, mp2_2, mp4 }); - _unit.TryAdd(mp1, engine.Snapshot).Should().Be(VerifyResult.HasConflicts); // mp1 conflicts with mp2_1 and mp2_2 and has same network fee => mp2_1 and mp2_2 should be left in pool. + _unit.TryAdd(mp1, engine.SnapshotCache).Should().Be(VerifyResult.HasConflicts); // mp1 conflicts with mp2_1 and mp2_2 and has same network fee => mp2_1 and mp2_2 should be left in pool. _unit.SortedTxCount.Should().Be(3); _unit.GetVerifiedTransactions().Should().Contain(new List() { mp2_1, mp2_2, mp4 }); - _unit.TryAdd(mp6, engine.Snapshot).Should().Be(VerifyResult.Succeed); // mp6 conflicts with mp2_1 and mp2_2 and has larger network fee than the sum of mp2_1 and mp2_2 fees => mp6 should be added. + _unit.TryAdd(mp6, engine.SnapshotCache).Should().Be(VerifyResult.Succeed); // mp6 conflicts with mp2_1 and mp2_2 and has larger network fee than the sum of mp2_1 and mp2_2 fees => mp6 should be added. _unit.SortedTxCount.Should().Be(2); _unit.GetVerifiedTransactions().Should().Contain(new List() { mp6, mp4 }); - _unit.TryAdd(mp1, engine.Snapshot).Should().Be(VerifyResult.Succeed); // mp1 conflicts with mp2_1 and mp2_2, but they are not in the pool now => mp1 should be added. + _unit.TryAdd(mp1, engine.SnapshotCache).Should().Be(VerifyResult.Succeed); // mp1 conflicts with mp2_1 and mp2_2, but they are not in the pool now => mp1 should be added. _unit.SortedTxCount.Should().Be(3); _unit.GetVerifiedTransactions().Should().Contain(new List() { mp1, mp6, mp4 }); - _unit.TryAdd(mp2_1, engine.Snapshot).Should().Be(VerifyResult.HasConflicts); // mp2_1 conflicts with mp1 and has same network fee => mp2_1 shouldn't be added to the pool. + _unit.TryAdd(mp2_1, engine.SnapshotCache).Should().Be(VerifyResult.HasConflicts); // mp2_1 conflicts with mp1 and has same network fee => mp2_1 shouldn't be added to the pool. _unit.SortedTxCount.Should().Be(3); _unit.GetVerifiedTransactions().Should().Contain(new List() { mp1, mp6, mp4 }); - _unit.TryAdd(mp5, engine.Snapshot).Should().Be(VerifyResult.HasConflicts); // mp5 conflicts with mp4 and has smaller network fee => mp5 fails to be added. + _unit.TryAdd(mp5, engine.SnapshotCache).Should().Be(VerifyResult.HasConflicts); // mp5 conflicts with mp4 and has smaller network fee => mp5 fails to be added. _unit.SortedTxCount.Should().Be(3); _unit.GetVerifiedTransactions().Should().Contain(new List() { mp1, mp6, mp4 }); - _unit.TryAdd(mp8, engine.Snapshot).Should().Be(VerifyResult.Succeed); // mp8, mp9 and mp10malicious conflict with mp7, but mo7 is not in the pool yet. - _unit.TryAdd(mp9, engine.Snapshot).Should().Be(VerifyResult.Succeed); - _unit.TryAdd(mp10malicious, engine.Snapshot).Should().Be(VerifyResult.Succeed); + _unit.TryAdd(mp8, engine.SnapshotCache).Should().Be(VerifyResult.Succeed); // mp8, mp9 and mp10malicious conflict with mp7, but mo7 is not in the pool yet. + _unit.TryAdd(mp9, engine.SnapshotCache).Should().Be(VerifyResult.Succeed); + _unit.TryAdd(mp10malicious, engine.SnapshotCache).Should().Be(VerifyResult.Succeed); _unit.SortedTxCount.Should().Be(6); _unit.GetVerifiedTransactions().Should().Contain(new List() { mp1, mp6, mp4, mp8, mp9, mp10malicious }); - _unit.TryAdd(mp7, engine.Snapshot).Should().Be(VerifyResult.Succeed); // mp7 has larger network fee than the sum of mp8 and mp9 fees => should be added to the pool. + _unit.TryAdd(mp7, engine.SnapshotCache).Should().Be(VerifyResult.Succeed); // mp7 has larger network fee than the sum of mp8 and mp9 fees => should be added to the pool. _unit.SortedTxCount.Should().Be(4); _unit.GetVerifiedTransactions().Should().Contain(new List() { mp1, mp6, mp4, mp7 }); @@ -436,18 +437,18 @@ public async Task TryRemoveVerified_RemoveVerifiedTxWithConflicts() var mp2 = CreateTransactionWithFeeAndBalanceVerify(2 * txFee); // mp2 conflicts with mp1 and has larger same network fee mp2.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = mp1.Hash } }; - _unit.TryAdd(mp2, engine.Snapshot); + _unit.TryAdd(mp2, engine.SnapshotCache); _unit.SortedTxCount.Should().Be(1); _unit.UnverifiedSortedTxCount.Should().Be(0); - _unit.TryAdd(mp1, engine.Snapshot).Should().Be(VerifyResult.HasConflicts); // mp1 conflicts with mp2 but has lower network fee + _unit.TryAdd(mp1, engine.SnapshotCache).Should().Be(VerifyResult.HasConflicts); // mp1 conflicts with mp2 but has lower network fee _unit.SortedTxCount.Should().Be(1); _unit.GetVerifiedTransactions().Should().Contain(new List() { mp2 }); // Act & Assert: try to invalidate verified transactions and push conflicting one. _unit.InvalidateVerifiedTransactions(); - _unit.TryAdd(mp1, engine.Snapshot).Should().Be(VerifyResult.Succeed); // mp1 conflicts with mp2 but mp2 is not verified anymore + _unit.TryAdd(mp1, engine.SnapshotCache).Should().Be(VerifyResult.Succeed); // mp1 conflicts with mp2 but mp2 is not verified anymore _unit.SortedTxCount.Should().Be(1); _unit.GetVerifiedTransactions().Should().Contain(new List() { mp1 }); @@ -457,7 +458,7 @@ public async Task TryRemoveVerified_RemoveVerifiedTxWithConflicts() Header = new Header(), Transactions = new Transaction[] { tx1 }, }; - _unit.UpdatePoolForBlockPersisted(block, engine.Snapshot); + _unit.UpdatePoolForBlockPersisted(block, engine.SnapshotCache); _unit.SortedTxCount.Should().Be(1); _unit.GetVerifiedTransactions().Should().Contain(new List() { mp2 }); // after reverificaion mp2 should be back at verified list; mp1 should be completely kicked off } @@ -746,6 +747,25 @@ public void TestUpdatePoolForBlockPersisted() _unit.VerifiedCount.Should().Be(0); } + [TestMethod] + public void TestTryRemoveUnVerified() + { + AddTransactions(32); + _unit.SortedTxCount.Should().Be(32); + + var txs = _unit.GetSortedVerifiedTransactions().ToArray(); + _unit.InvalidateVerifiedTransactions(); + + _unit.SortedTxCount.Should().Be(0); + + foreach (var tx in txs) + { + _unit.TryRemoveUnVerified(tx.Hash, out _); + } + + _unit.UnVerifiedCount.Should().Be(0); + } + public static StorageKey CreateStorageKey(int id, byte prefix, byte[] key = null) { byte[] buffer = GC.AllocateUninitializedArray(sizeof(byte) + (key?.Length ?? 0)); diff --git a/tests/Neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs b/tests/Neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs index 32a0bd6237..57cf996acd 100644 --- a/tests/Neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs +++ b/tests/Neo.UnitTests/Ledger/UT_TransactionVerificationContext.cs @@ -61,10 +61,10 @@ private Transaction CreateTransactionWithFee(long networkFee, long systemFee) public async Task TestDuplicateOracle() { // Fake balance - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); - BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, UInt160.Zero); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); + BigInteger balance = NativeContract.GAS.BalanceOf(snapshotCache, UInt160.Zero); await NativeContract.GAS.Burn(engine, UInt160.Zero, balance); _ = NativeContract.GAS.Mint(engine, UInt160.Zero, 8, false); @@ -73,43 +73,43 @@ public async Task TestDuplicateOracle() var tx = CreateTransactionWithFee(1, 2); tx.Attributes = new TransactionAttribute[] { new OracleResponse() { Code = OracleResponseCode.ConsensusUnreachable, Id = 1, Result = Array.Empty() } }; var conflicts = new List(); - verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeTrue(); + verificationContext.CheckTransaction(tx, conflicts, snapshotCache).Should().BeTrue(); verificationContext.AddTransaction(tx); tx = CreateTransactionWithFee(2, 1); tx.Attributes = new TransactionAttribute[] { new OracleResponse() { Code = OracleResponseCode.ConsensusUnreachable, Id = 1, Result = Array.Empty() } }; - verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeFalse(); + verificationContext.CheckTransaction(tx, conflicts, snapshotCache).Should().BeFalse(); } [TestMethod] public async Task TestTransactionSenderFee() { - var snapshot = TestBlockchain.GetTestSnapshot(); - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); - BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, UInt160.Zero); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); + BigInteger balance = NativeContract.GAS.BalanceOf(snapshotCache, UInt160.Zero); await NativeContract.GAS.Burn(engine, UInt160.Zero, balance); _ = NativeContract.GAS.Mint(engine, UInt160.Zero, 8, true); TransactionVerificationContext verificationContext = new(); var tx = CreateTransactionWithFee(1, 2); var conflicts = new List(); - verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeTrue(); + verificationContext.CheckTransaction(tx, conflicts, snapshotCache).Should().BeTrue(); verificationContext.AddTransaction(tx); - verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeTrue(); + verificationContext.CheckTransaction(tx, conflicts, snapshotCache).Should().BeTrue(); verificationContext.AddTransaction(tx); - verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeFalse(); + verificationContext.CheckTransaction(tx, conflicts, snapshotCache).Should().BeFalse(); verificationContext.RemoveTransaction(tx); - verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeTrue(); + verificationContext.CheckTransaction(tx, conflicts, snapshotCache).Should().BeTrue(); verificationContext.AddTransaction(tx); - verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeFalse(); + verificationContext.CheckTransaction(tx, conflicts, snapshotCache).Should().BeFalse(); } [TestMethod] public async Task TestTransactionSenderFeeWithConflicts() { - var snapshot = TestBlockchain.GetTestSnapshot(); - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); - BigInteger balance = NativeContract.GAS.BalanceOf(snapshot, UInt160.Zero); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); + BigInteger balance = NativeContract.GAS.BalanceOf(snapshotCache, UInt160.Zero); await NativeContract.GAS.Burn(engine, UInt160.Zero, balance); _ = NativeContract.GAS.Mint(engine, UInt160.Zero, 3 + 3 + 1, true); // balance is enough for 2 transactions and 1 GAS is left. @@ -118,14 +118,14 @@ public async Task TestTransactionSenderFeeWithConflicts() var conflictingTx = CreateTransactionWithFee(1, 1); // costs 2 GAS var conflicts = new List(); - verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeTrue(); + verificationContext.CheckTransaction(tx, conflicts, snapshotCache).Should().BeTrue(); verificationContext.AddTransaction(tx); - verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeTrue(); + verificationContext.CheckTransaction(tx, conflicts, snapshotCache).Should().BeTrue(); verificationContext.AddTransaction(tx); - verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeFalse(); + verificationContext.CheckTransaction(tx, conflicts, snapshotCache).Should().BeFalse(); conflicts.Add(conflictingTx); - verificationContext.CheckTransaction(tx, conflicts, snapshot).Should().BeTrue(); // 1 GAS is left on the balance + 2 GAS is free after conflicts removal => enough for one more trasnaction. + verificationContext.CheckTransaction(tx, conflicts, snapshotCache).Should().BeTrue(); // 1 GAS is left on the balance + 2 GAS is free after conflicts removal => enough for one more trasnaction. } } } diff --git a/tests/Neo.UnitTests/Ledger/UT_TrimmedBlock.cs b/tests/Neo.UnitTests/Ledger/UT_TrimmedBlock.cs index ea797f8be3..ae3bc2570b 100644 --- a/tests/Neo.UnitTests/Ledger/UT_TrimmedBlock.cs +++ b/tests/Neo.UnitTests/Ledger/UT_TrimmedBlock.cs @@ -11,6 +11,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.SmartContract.Native; @@ -48,7 +49,7 @@ public static TrimmedBlock GetTrimmedBlockWithNoTransaction() [TestMethod] public void TestGetBlock() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var tx1 = TestUtils.GetTransaction(UInt160.Zero); tx1.Script = new byte[] { 0x01,0x01,0x01,0x01, 0x01,0x01,0x01,0x01, @@ -69,13 +70,13 @@ public void TestGetBlock() Transaction = tx2, BlockIndex = 1 }; - TestUtils.TransactionAdd(snapshot, state1, state2); + TestUtils.TransactionAdd(snapshotCache, state1, state2); TrimmedBlock tblock = GetTrimmedBlockWithNoTransaction(); tblock.Hashes = new UInt256[] { tx1.Hash, tx2.Hash }; - TestUtils.BlocksAdd(snapshot, tblock.Hash, tblock); + TestUtils.BlocksAdd(snapshotCache, tblock.Hash, tblock); - Block block = NativeContract.Ledger.GetBlock(snapshot, tblock.Hash); + Block block = NativeContract.Ledger.GetBlock(snapshotCache, tblock.Hash); block.Index.Should().Be(1); block.MerkleRoot.Should().Be(UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff02")); diff --git a/tests/Neo.UnitTests/Neo.UnitTests.csproj b/tests/Neo.UnitTests/Neo.UnitTests.csproj index fb246cbdc3..54320cd223 100644 --- a/tests/Neo.UnitTests/Neo.UnitTests.csproj +++ b/tests/Neo.UnitTests/Neo.UnitTests.csproj @@ -6,12 +6,15 @@ - - + + + + PreserveNewest + PreserveNewest PreserveNewest @@ -19,13 +22,14 @@ + - - - + + + diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Block.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Block.cs index 2021e5a187..c91811c3e2 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Block.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Block.cs @@ -11,9 +11,13 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; 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 +25,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 snapshotCache = hasSnapshot ? TestBlockchain.GetTestSnapshotCache() : null; + var block = hasBlock ? new Block { Header = new Header() } : null; + var engine = ApplicationEngine.Create(TriggerType.Application, tx, snapshotCache, block, TestBlockchain.TheNeoSystem.Settings, gas: gas); + if (addScript) engine.LoadScript(new byte[] { 0x01 }); + return engine; + } [TestInitialize] public void TestSetup() @@ -38,7 +51,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 +66,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 +76,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 +90,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 +106,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 +116,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 +149,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).SnapshotCache; + NativeContract.Ledger.GetBlock(snapshot, 0).GetHashCode().Should().Be(-626492395); } [TestMethod] @@ -144,8 +166,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 +183,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 +193,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_Conflicts.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Conflicts.cs index ce46c5106d..a0dac53fbe 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Conflicts.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Conflicts.cs @@ -73,28 +73,28 @@ public void DeserializeAndSerialize() public void Verify() { var test = new Conflicts() { Hash = _u }; - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var key = Ledger.UT_MemoryPool.CreateStorageKey(NativeContract.Ledger.Id, Prefix_Transaction, _u.ToArray()); // Conflicting transaction is in the Conflicts attribute of some other on-chain transaction. var conflict = new TransactionState(); - snapshot.Add(key, new StorageItem(conflict)); - Assert.IsTrue(test.Verify(snapshot, new Transaction())); + snapshotCache.Add(key, new StorageItem(conflict)); + Assert.IsTrue(test.Verify(snapshotCache, new Transaction())); // Conflicting transaction is on-chain. - snapshot.Delete(key); + snapshotCache.Delete(key); conflict = new TransactionState { BlockIndex = 123, Transaction = new Transaction(), State = VMState.NONE }; - snapshot.Add(key, new StorageItem(conflict)); - Assert.IsFalse(test.Verify(snapshot, new Transaction())); + snapshotCache.Add(key, new StorageItem(conflict)); + Assert.IsFalse(test.Verify(snapshotCache, new Transaction())); // There's no conflicting transaction at all. - snapshot.Delete(key); - Assert.IsTrue(test.Verify(snapshot, new Transaction())); + snapshotCache.Delete(key); + Assert.IsTrue(test.Verify(snapshotCache, new Transaction())); } } } diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Header.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Header.cs index a597c88cb6..7604db6db0 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Header.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Header.cs @@ -11,6 +11,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.SmartContract.Native; @@ -34,7 +35,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 +45,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()); } @@ -52,11 +53,11 @@ public void GetHashCodeTest() public void TrimTest() { UInt256 val256 = UInt256.Zero; - var snapshot = TestBlockchain.GetTestSnapshot().CreateSnapshot(); - TestUtils.SetupHeaderWithValues(uut, val256, out _, out _, out _, out _, out _, out _); + var snapshotCache = TestBlockchain.GetTestSnapshotCache().CreateSnapshot(); + TestUtils.SetupHeaderWithValues(null, uut, val256, out _, out _, out _, out _, out _, out _); uut.Witness = new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() }; - TestUtils.BlocksAdd(snapshot, uut.Hash, new TrimmedBlock() + TestUtils.BlocksAdd(snapshotCache, uut.Hash, new TrimmedBlock() { Header = new Header { @@ -69,7 +70,7 @@ public void TrimTest() Hashes = Array.Empty() }); - var trim = NativeContract.Ledger.GetTrimmedBlock(snapshot, uut.Hash); + var trim = NativeContract.Ledger.GetTrimmedBlock(snapshotCache, uut.Hash); var header = trim.Header; header.Version.Should().Be(uut.Version); @@ -87,11 +88,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 +131,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 +147,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/Network/P2P/Payloads/UT_HighPriorityAttribute.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_HighPriorityAttribute.cs index 76808072d5..09454d298f 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_HighPriorityAttribute.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_HighPriorityAttribute.cs @@ -74,11 +74,11 @@ public void DeserializeAndSerialize() public void Verify() { var test = new HighPriorityAttribute(); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); - Assert.IsFalse(test.Verify(snapshot, new Transaction() { Signers = Array.Empty() })); - Assert.IsFalse(test.Verify(snapshot, new Transaction() { Signers = new Signer[] { new Signer() { Account = UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01") } } })); - Assert.IsTrue(test.Verify(snapshot, new Transaction() { Signers = new Signer[] { new Signer() { Account = NativeContract.NEO.GetCommitteeAddress(snapshot) } } })); + Assert.IsFalse(test.Verify(snapshotCache, new Transaction() { Signers = Array.Empty() })); + Assert.IsFalse(test.Verify(snapshotCache, new Transaction() { Signers = new Signer[] { new Signer() { Account = UInt160.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff01") } } })); + Assert.IsTrue(test.Verify(snapshotCache, new Transaction() { Signers = new Signer[] { new Signer() { Account = NativeContract.NEO.GetCommitteeAddress(snapshotCache) } } })); } } } diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NetworkAddressWithTime.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NetworkAddressWithTime.cs index 2ed86e4da7..3e2c6c336c 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NetworkAddressWithTime.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NetworkAddressWithTime.cs @@ -11,6 +11,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Capabilities; using Neo.Network.P2P.Payloads; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotValidBefore.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotValidBefore.cs index 8e7ad9087b..bff21a4e5e 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotValidBefore.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_NotValidBefore.cs @@ -77,12 +77,12 @@ public void DeserializeAndSerialize() public void Verify() { var test = new NotValidBefore(); - var snapshot = TestBlockchain.GetTestSnapshot(); - test.Height = NativeContract.Ledger.CurrentIndex(snapshot) + 1; + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + test.Height = NativeContract.Ledger.CurrentIndex(snapshotCache) + 1; - Assert.IsFalse(test.Verify(snapshot, new Transaction())); + Assert.IsFalse(test.Verify(snapshotCache, new Transaction())); test.Height--; - Assert.IsTrue(test.Verify(snapshot, new Transaction())); + Assert.IsTrue(test.Verify(snapshotCache, new Transaction())); } } } diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Signers.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Signers.cs index 44d337fd1c..d2358e947c 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Signers.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Signers.cs @@ -12,6 +12,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Network.P2P.Payloads.Conditions; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs index 56d9a66bec..dc910c5142 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Transaction.cs @@ -12,6 +12,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.Ledger; @@ -100,16 +101,16 @@ public void Gas_Set() public void Size_Get() { uut.Script = TestUtils.GetByteArray(32, 0x42); - uut.Signers = Array.Empty(); - uut.Attributes = Array.Empty(); - uut.Witnesses = new[] - { + uut.Signers = []; + uut.Attributes = []; + uut.Witnesses = + [ new Witness { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } - }; + ]; uut.Version.Should().Be(0); uut.Script.Length.Should().Be(32); @@ -122,17 +123,16 @@ public void FeeIsMultiSigContract() { var walletA = TestUtils.GenerateTestWallet("123"); var walletB = TestUtils.GenerateTestWallet("123"); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var a = walletA.CreateAccount(); var b = walletB.CreateAccount(); var multiSignContract = Contract.CreateMultiSigContract(2, - new ECPoint[] - { - a.GetKey().PublicKey, - b.GetKey().PublicKey - }); + [ + a.GetKey().PublicKey, + b.GetKey().PublicKey + ]); walletA.CreateAccount(multiSignContract, a.GetKey()); var acc = walletB.CreateAccount(multiSignContract, b.GetKey()); @@ -140,32 +140,31 @@ public void FeeIsMultiSigContract() // Fake balance var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); - var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + var entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; - snapshot.Commit(); + snapshotCache.Commit(); // Make transaction - var tx = walletA.MakeTransaction(snapshot, new TransferOutput[] - { - new TransferOutput() - { - AssetId = NativeContract.GAS.Hash, - ScriptHash = acc.ScriptHash, - Value = new BigDecimal(BigInteger.One,8) - } - }, acc.ScriptHash); + var tx = walletA.MakeTransaction(snapshotCache, [ + new TransferOutput + { + AssetId = NativeContract.GAS.Hash, + ScriptHash = acc.ScriptHash, + Value = new BigDecimal(BigInteger.One, 8) + } + ], acc.ScriptHash); Assert.IsNotNull(tx); // Sign - var wrongData = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network + 1); + var wrongData = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network + 1); Assert.IsFalse(walletA.Sign(wrongData)); - var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); Assert.IsTrue(walletA.Sign(data)); Assert.IsTrue(walletB.Sign(data)); Assert.IsTrue(data.Completed); @@ -174,14 +173,14 @@ public void FeeIsMultiSigContract() // Fast check - Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshotCache, tx.NetworkFee)); // Check long verificationGas = 0; foreach (var witness in tx.Witnesses) { - using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee); + using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee); engine.LoadScript(witness.VerificationScript); engine.LoadScript(witness.InvocationScript); Assert.AreEqual(VMState.HALT, engine.Execute()); @@ -190,7 +189,7 @@ public void FeeIsMultiSigContract() verificationGas += engine.FeeConsumed; } - var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); + var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshotCache); Assert.AreEqual(1967100, verificationGas); Assert.AreEqual(348000, sizeGas); Assert.AreEqual(2315100, tx.NetworkFee); @@ -200,31 +199,30 @@ public void FeeIsMultiSigContract() public void FeeIsSignatureContractDetailed() { var wallet = TestUtils.GenerateTestWallet("123"); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var acc = wallet.CreateAccount(); // Fake balance var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); - var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + var entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; - snapshot.Commit(); + snapshotCache.Commit(); // Make transaction // self-transfer of 1e-8 GAS - var tx = wallet.MakeTransaction(snapshot, new TransferOutput[] - { - new TransferOutput() - { - AssetId = NativeContract.GAS.Hash, - ScriptHash = acc.ScriptHash, - Value = new BigDecimal(BigInteger.One,8) - } - }, acc.ScriptHash); + var tx = wallet.MakeTransaction(snapshotCache, [ + new TransferOutput + { + AssetId = NativeContract.GAS.Hash, + ScriptHash = acc.ScriptHash, + Value = new BigDecimal(BigInteger.One, 8) + } + ], acc.ScriptHash); Assert.IsNotNull(tx); Assert.IsNull(tx.Witnesses); @@ -236,7 +234,7 @@ public void FeeIsSignatureContractDetailed() // Sign // ---- - var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); // 'from' is always required as witness // if not included on cosigner with a scope, its scope should be considered 'CalledByEntry' data.ScriptHashes.Count.Should().Be(1); @@ -250,14 +248,14 @@ public void FeeIsSignatureContractDetailed() // Fast check - Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshotCache, tx.NetworkFee)); // Check long verificationGas = 0; foreach (var witness in tx.Witnesses) { - using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee); + using var engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee); engine.LoadScript(witness.VerificationScript); engine.LoadScript(witness.InvocationScript); Assert.AreEqual(VMState.HALT, engine.Execute()); @@ -288,8 +286,8 @@ public void FeeIsSignatureContractDetailed() // I + II + III + IV Assert.AreEqual(25 + 22 + 1 + 88 + 109, tx.Size); - Assert.AreEqual(1000, NativeContract.Policy.GetFeePerByte(snapshot)); - var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); + Assert.AreEqual(1000, NativeContract.Policy.GetFeePerByte(snapshotCache)); + var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshotCache); // final check: verification_cost and tx_size Assert.AreEqual(245000, sizeGas); @@ -303,18 +301,18 @@ public void FeeIsSignatureContractDetailed() public void FeeIsSignatureContract_TestScope_Global() { var wallet = TestUtils.GenerateTestWallet(""); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var acc = wallet.CreateAccount(); // Fake balance var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); - var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + var entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; - snapshot.Commit(); + snapshotCache.Commit(); // Make transaction // Manually creating script @@ -323,14 +321,14 @@ public void FeeIsSignatureContract_TestScope_Global() using (ScriptBuilder sb = new()) { // self-transfer of 1e-8 GAS - BigInteger value = new BigDecimal(BigInteger.One, 8).Value; + var value = new BigDecimal(BigInteger.One, 8).Value; sb.EmitDynamicCall(NativeContract.GAS.Hash, "transfer", acc.ScriptHash, acc.ScriptHash, value, null); sb.Emit(OpCode.ASSERT); script = sb.ToArray(); } // trying global scope - var signers = new Signer[]{ new Signer + var signers = new[]{ new Signer { Account = acc.ScriptHash, Scopes = WitnessScope.Global @@ -338,7 +336,7 @@ public void FeeIsSignatureContract_TestScope_Global() // using this... - var tx = wallet.MakeTransaction(snapshot, script, acc.ScriptHash, signers); + var tx = wallet.MakeTransaction(snapshotCache, script, acc.ScriptHash, signers); Assert.IsNotNull(tx); Assert.IsNull(tx.Witnesses); @@ -347,7 +345,7 @@ public void FeeIsSignatureContract_TestScope_Global() // Sign // ---- - var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); bool signed = wallet.Sign(data); Assert.IsTrue(signed); @@ -356,13 +354,13 @@ public void FeeIsSignatureContract_TestScope_Global() tx.Witnesses.Length.Should().Be(1); // Fast check - Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshotCache, tx.NetworkFee)); // Check long verificationGas = 0; foreach (var witness in tx.Witnesses) { - using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee); + using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee); engine.LoadScript(witness.VerificationScript); engine.LoadScript(witness.InvocationScript); Assert.AreEqual(VMState.HALT, engine.Execute()); @@ -371,7 +369,7 @@ public void FeeIsSignatureContract_TestScope_Global() verificationGas += engine.FeeConsumed; } // get sizeGas - var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); + var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshotCache); // final check on sum: verification_cost + tx_size Assert.AreEqual(1228520, verificationGas + sizeGas); // final assert @@ -382,18 +380,18 @@ public void FeeIsSignatureContract_TestScope_Global() public void FeeIsSignatureContract_TestScope_CurrentHash_GAS() { var wallet = TestUtils.GenerateTestWallet(""); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var acc = wallet.CreateAccount(); // Fake balance var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); - var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + var entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; - snapshot.Commit(); + snapshotCache.Commit(); // Make transaction // Manually creating script @@ -409,16 +407,16 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_GAS() } // trying global scope - var signers = new Signer[]{ new Signer + var signers = new[]{ new Signer { Account = acc.ScriptHash, Scopes = WitnessScope.CustomContracts, - AllowedContracts = new[] { NativeContract.GAS.Hash } + AllowedContracts = [NativeContract.GAS.Hash] } }; // using this... - var tx = wallet.MakeTransaction(snapshot, script, acc.ScriptHash, signers); + var tx = wallet.MakeTransaction(snapshotCache, script, acc.ScriptHash, signers); Assert.IsNotNull(tx); Assert.IsNull(tx.Witnesses); @@ -427,7 +425,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_GAS() // Sign // ---- - var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); bool signed = wallet.Sign(data); Assert.IsTrue(signed); @@ -436,13 +434,13 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_GAS() tx.Witnesses.Length.Should().Be(1); // Fast check - Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshotCache, tx.NetworkFee)); // Check long verificationGas = 0; foreach (var witness in tx.Witnesses) { - using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee); + using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee); engine.LoadScript(witness.VerificationScript); engine.LoadScript(witness.InvocationScript); Assert.AreEqual(VMState.HALT, engine.Execute()); @@ -451,7 +449,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_GAS() verificationGas += engine.FeeConsumed; } // get sizeGas - var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); + var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshotCache); // final check on sum: verification_cost + tx_size Assert.AreEqual(1249520, verificationGas + sizeGas); // final assert @@ -462,18 +460,18 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_GAS() public void FeeIsSignatureContract_TestScope_CalledByEntry_Plus_GAS() { var wallet = TestUtils.GenerateTestWallet(""); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var acc = wallet.CreateAccount(); // Fake balance var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); - var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + var entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; - snapshot.Commit(); + snapshotCache.Commit(); // Make transaction // Manually creating script @@ -482,26 +480,26 @@ public void FeeIsSignatureContract_TestScope_CalledByEntry_Plus_GAS() using (ScriptBuilder sb = new()) { // self-transfer of 1e-8 GAS - BigInteger value = new BigDecimal(BigInteger.One, 8).Value; + var value = new BigDecimal(BigInteger.One, 8).Value; sb.EmitDynamicCall(NativeContract.GAS.Hash, "transfer", acc.ScriptHash, acc.ScriptHash, value, null); sb.Emit(OpCode.ASSERT); script = sb.ToArray(); } // trying CalledByEntry together with GAS - var signers = new Signer[]{ new Signer + var signers = new[]{ new Signer { Account = acc.ScriptHash, // This combination is supposed to actually be an OR, // where it's valid in both Entry and also for Custom hash provided (in any execution level) // it would be better to test this in the future including situations where a deeper call level uses this custom witness successfully Scopes = WitnessScope.CustomContracts | WitnessScope.CalledByEntry, - AllowedContracts = new[] { NativeContract.GAS.Hash } + AllowedContracts = [NativeContract.GAS.Hash] } }; // using this... - var tx = wallet.MakeTransaction(snapshot, script, acc.ScriptHash, signers); + var tx = wallet.MakeTransaction(snapshotCache, script, acc.ScriptHash, signers); Assert.IsNotNull(tx); Assert.IsNull(tx.Witnesses); @@ -510,7 +508,7 @@ public void FeeIsSignatureContract_TestScope_CalledByEntry_Plus_GAS() // Sign // ---- - var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); bool signed = wallet.Sign(data); Assert.IsTrue(signed); @@ -519,13 +517,13 @@ public void FeeIsSignatureContract_TestScope_CalledByEntry_Plus_GAS() tx.Witnesses.Length.Should().Be(1); // Fast check - Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshotCache, tx.NetworkFee)); // Check long verificationGas = 0; foreach (var witness in tx.Witnesses) { - using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee); + using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee); engine.LoadScript(witness.VerificationScript); engine.LoadScript(witness.InvocationScript); Assert.AreEqual(VMState.HALT, engine.Execute()); @@ -534,7 +532,7 @@ public void FeeIsSignatureContract_TestScope_CalledByEntry_Plus_GAS() verificationGas += engine.FeeConsumed; } // get sizeGas - var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); + var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshotCache); // final check on sum: verification_cost + tx_size Assert.AreEqual(1249520, verificationGas + sizeGas); // final assert @@ -545,14 +543,14 @@ public void FeeIsSignatureContract_TestScope_CalledByEntry_Plus_GAS() public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_FAULT() { var wallet = TestUtils.GenerateTestWallet(""); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var acc = wallet.CreateAccount(); // Fake balance var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); - var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + var entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; @@ -570,11 +568,11 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_FAULT() } // trying global scope - var signers = new Signer[]{ new Signer + var signers = new[]{ new Signer { Account = acc.ScriptHash, Scopes = WitnessScope.CustomContracts, - AllowedContracts = new[] { NativeContract.NEO.Hash } + AllowedContracts = [NativeContract.NEO.Hash] } }; // using this... @@ -582,7 +580,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_FAULT() // expects FAULT on execution of 'transfer' Application script // due to lack of a valid witness validation Transaction tx = null; - Assert.ThrowsException(() => tx = wallet.MakeTransaction(snapshot, script, acc.ScriptHash, signers)); + Assert.ThrowsException(() => tx = wallet.MakeTransaction(snapshotCache, script, acc.ScriptHash, signers)); Assert.IsNull(tx); } @@ -590,18 +588,18 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_FAULT() public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS() { var wallet = TestUtils.GenerateTestWallet(""); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var acc = wallet.CreateAccount(); // Fake balance var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); - var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + var entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; - snapshot.Commit(); + snapshotCache.Commit(); // Make transaction // Manually creating script @@ -617,16 +615,16 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS() } // trying two custom hashes, for same target account - var signers = new Signer[]{ new Signer + var signers = new[]{ new Signer { Account = acc.ScriptHash, Scopes = WitnessScope.CustomContracts, - AllowedContracts = new[] { NativeContract.NEO.Hash, NativeContract.GAS.Hash } + AllowedContracts = [NativeContract.NEO.Hash, NativeContract.GAS.Hash] } }; // using this... - var tx = wallet.MakeTransaction(snapshot, script, acc.ScriptHash, signers); + var tx = wallet.MakeTransaction(snapshotCache, script, acc.ScriptHash, signers); Assert.IsNotNull(tx); Assert.IsNull(tx.Witnesses); @@ -635,7 +633,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS() // Sign // ---- - var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); bool signed = wallet.Sign(data); Assert.IsTrue(signed); @@ -649,13 +647,13 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS() tx.Signers.Length.Should().Be(1); // Fast check - Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshotCache, tx.NetworkFee)); // Check long verificationGas = 0; foreach (var witness in tx.Witnesses) { - using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee); + using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee); engine.LoadScript(witness.VerificationScript); engine.LoadScript(witness.InvocationScript); Assert.AreEqual(VMState.HALT, engine.Execute()); @@ -664,7 +662,7 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS() verificationGas += engine.FeeConsumed; } // get sizeGas - var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); + var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshotCache); // final check on sum: verification_cost + tx_size Assert.AreEqual(1269520, verificationGas + sizeGas); // final assert @@ -675,14 +673,14 @@ public void FeeIsSignatureContract_TestScope_CurrentHash_NEO_GAS() public void FeeIsSignatureContract_TestScope_NoScopeFAULT() { var wallet = TestUtils.GenerateTestWallet(""); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var acc = wallet.CreateAccount(); // Fake balance var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); - var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + var entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; @@ -701,11 +699,11 @@ public void FeeIsSignatureContract_TestScope_NoScopeFAULT() // trying with no scope var attributes = Array.Empty(); - var signers = new Signer[]{ new Signer + var signers = new[]{ new Signer { Account = acc.ScriptHash, Scopes = WitnessScope.CustomContracts, - AllowedContracts = new[] { NativeContract.NEO.Hash, NativeContract.GAS.Hash } + AllowedContracts = [NativeContract.NEO.Hash, NativeContract.GAS.Hash] } }; // using this... @@ -713,7 +711,7 @@ public void FeeIsSignatureContract_TestScope_NoScopeFAULT() // expects FAULT on execution of 'transfer' Application script // due to lack of a valid witness validation Transaction tx = null; - Assert.ThrowsException(() => tx = wallet.MakeTransaction(snapshot, script, acc.ScriptHash, signers, attributes)); + Assert.ThrowsException(() => tx = wallet.MakeTransaction(snapshotCache, script, acc.ScriptHash, signers, attributes)); Assert.IsNull(tx); } @@ -721,18 +719,18 @@ public void FeeIsSignatureContract_TestScope_NoScopeFAULT() public void FeeIsSignatureContract_UnexistingVerificationContractFAULT() { var wallet = TestUtils.GenerateTestWallet(""); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var acc = wallet.CreateAccount(); // Fake balance var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); - var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + var entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; - snapshot.Commit(); + snapshotCache.Commit(); // Make transaction // Manually creating script @@ -763,14 +761,14 @@ public void FeeIsSignatureContract_UnexistingVerificationContractFAULT() // expects ArgumentException on execution of 'CalculateNetworkFee' due to // null witness_script (no account in the wallet, no corresponding witness // and no verification contract for the signer) - Assert.ThrowsException(() => walletWithoutAcc.MakeTransaction(snapshot, script, acc.ScriptHash, signers)); + Assert.ThrowsException(() => walletWithoutAcc.MakeTransaction(snapshotCache, script, acc.ScriptHash, signers)); Assert.IsNull(tx); } [TestMethod] public void Transaction_Reverify_Hashes_Length_Unequal_To_Witnesses_Length() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); Transaction txSimple = new() { Version = 0x00, @@ -789,9 +787,9 @@ public void Transaction_Reverify_Hashes_Length_Unequal_To_Witnesses_Length() Script = new byte[] { (byte)OpCode.PUSH1 }, Witnesses = Array.Empty() }; - UInt160[] hashes = txSimple.GetScriptHashesForVerifying(snapshot); + UInt160[] hashes = txSimple.GetScriptHashesForVerifying(snapshotCache); Assert.AreEqual(1, hashes.Length); - Assert.AreNotEqual(VerifyResult.Succeed, txSimple.VerifyStateDependent(TestProtocolSettings.Default, snapshot, new TransactionVerificationContext(), new List())); + Assert.AreNotEqual(VerifyResult.Succeed, txSimple.VerifyStateDependent(TestProtocolSettings.Default, snapshotCache, new TransactionVerificationContext(), new List())); } [TestMethod] @@ -805,10 +803,11 @@ public void Transaction_Serialize_Deserialize_Simple() SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS NetworkFee = 0x0000000000000001, ValidUntilBlock = 0x01020304, - Signers = new Signer[] { new Signer() { Account = UInt160.Zero } }, - Attributes = Array.Empty(), - Script = new byte[] { (byte)OpCode.PUSH1 }, - Witnesses = new Witness[] { new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } } + Signers = [new Signer() { Account = UInt160.Zero }], + Attributes = [], + Script = new[] { (byte)OpCode.PUSH1 }, + Witnesses = [new Witness { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } + ] }; byte[] sTx = txSimple.ToArray(); @@ -826,7 +825,7 @@ public void Transaction_Serialize_Deserialize_Simple() "010000"); // empty witnesses // try to deserialize - Transaction tx2 = Neo.IO.Helper.AsSerializable(sTx); + var tx2 = sTx.AsSerializable(); tx2.Version.Should().Be(0x00); tx2.Nonce.Should().Be(0x01020304); @@ -835,17 +834,16 @@ public void Transaction_Serialize_Deserialize_Simple() tx2.NetworkFee.Should().Be(0x0000000000000001); tx2.ValidUntilBlock.Should().Be(0x01020304); tx2.Attributes.Should().BeEquivalentTo(Array.Empty()); - tx2.Signers.Should().BeEquivalentTo(new[] - { + tx2.Signers.Should().BeEquivalentTo([ new Signer { Account = UInt160.Zero, - AllowedContracts = Array.Empty(), - AllowedGroups = Array.Empty(), - Rules = Array.Empty() + AllowedContracts = [], + AllowedGroups = [], + Rules = [] } - }); - tx2.Script.Span.SequenceEqual(new byte[] { (byte)OpCode.PUSH1 }).Should().BeTrue(); + ]); + tx2.Script.Span.SequenceEqual([(byte)OpCode.PUSH1]).Should().BeTrue(); tx2.Witnesses[0].InvocationScript.Span.IsEmpty.Should().BeTrue(); tx2.Witnesses[0].VerificationScript.Span.IsEmpty.Should().BeTrue(); } @@ -862,25 +860,26 @@ public void Transaction_Serialize_Deserialize_DistinctCosigners() SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS NetworkFee = 0x0000000000000001, ValidUntilBlock = 0x01020304, - Attributes = Array.Empty(), - Signers = new Signer[] - { - new Signer() + Attributes = [], + Signers = + [ + new Signer { Account = UInt160.Parse("0x0001020304050607080900010203040506070809"), Scopes = WitnessScope.Global }, - new Signer() + new Signer { Account = UInt160.Parse("0x0001020304050607080900010203040506070809"), // same account as above Scopes = WitnessScope.CalledByEntry // different scope, but still, same account (cannot do that) } - }, - Script = new byte[] { (byte)OpCode.PUSH1 }, - Witnesses = new Witness[] { new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } } + ], + Script = new[] { (byte)OpCode.PUSH1 }, + Witnesses = [new Witness { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } + ] }; - byte[] sTx = txDoubleCosigners.ToArray(); + var sTx = txDoubleCosigners.ToArray(); // no need for detailed hexstring here (see basic tests for it) sTx.ToHexString().Should().Be("000403020100e1f5050000000001000000000000000403020102090807060504030201000908070605040302010080090807060504030201000908070605040302010001000111010000"); @@ -924,16 +923,17 @@ public void Transaction_Serialize_Deserialize_MaxSizeCosigners() SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS NetworkFee = 0x0000000000000001, ValidUntilBlock = 0x01020304, - Attributes = Array.Empty(), + Attributes = [], Signers = cosigners1, // max + 1 (should fail) - Script = new byte[] { (byte)OpCode.PUSH1 }, - Witnesses = new Witness[] { new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } } + Script = new[] { (byte)OpCode.PUSH1 }, + Witnesses = [new Witness { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } + ] }; byte[] sTx1 = txCosigners1.ToArray(); // back to transaction (should fail, due to non-distinct cosigners) - Assert.ThrowsException(() => Neo.IO.Helper.AsSerializable(sTx1)); + Assert.ThrowsException(() => sTx1.AsSerializable()); // ---------------------------- // this should fail (max + 1) @@ -941,7 +941,7 @@ public void Transaction_Serialize_Deserialize_MaxSizeCosigners() var cosigners = new Signer[maxCosigners + 1]; for (var i = 0; i < maxCosigners + 1; i++) { - string hex = i.ToString("X4"); + var hex = i.ToString("X4"); while (hex.Length < 40) hex = hex.Insert(0, "0"); cosigners[i] = new Signer @@ -957,10 +957,11 @@ public void Transaction_Serialize_Deserialize_MaxSizeCosigners() SystemFee = (long)BigInteger.Pow(10, 8), // 1 GAS NetworkFee = 0x0000000000000001, ValidUntilBlock = 0x01020304, - Attributes = Array.Empty(), + Attributes = [], Signers = cosigners, // max + 1 (should fail) - Script = new byte[] { (byte)OpCode.PUSH1 }, - Witnesses = new Witness[] { new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } } + Script = new[] { (byte)OpCode.PUSH1 }, + Witnesses = [new Witness { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } + ] }; byte[] sTx2 = txCosigners.ToArray(); @@ -968,7 +969,7 @@ public void Transaction_Serialize_Deserialize_MaxSizeCosigners() // back to transaction (should fail, due to non-distinct cosigners) Transaction tx2 = null; Assert.ThrowsException(() => - tx2 = Neo.IO.Helper.AsSerializable(sTx2) + tx2 = sTx2.AsSerializable() ); Assert.IsNull(tx2); } @@ -982,18 +983,18 @@ public void FeeIsSignatureContract_TestScope_FeeOnly_Default() cosigner.Scopes.Should().Be(WitnessScope.None); var wallet = TestUtils.GenerateTestWallet(""); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var acc = wallet.CreateAccount(); // Fake balance var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); - var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + var entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; - snapshot.Commit(); + snapshotCache.Commit(); // Make transaction // Manually creating script @@ -1009,18 +1010,18 @@ public void FeeIsSignatureContract_TestScope_FeeOnly_Default() } // try to use fee only inside the smart contract - var signers = new Signer[]{ new Signer + var signers = new[]{ new Signer { Account = acc.ScriptHash, Scopes = WitnessScope.None } }; - Assert.ThrowsException(() => wallet.MakeTransaction(snapshot, script, acc.ScriptHash, signers)); + Assert.ThrowsException(() => wallet.MakeTransaction(snapshotCache, script, acc.ScriptHash, signers)); // change to global scope signers[0].Scopes = WitnessScope.Global; - var tx = wallet.MakeTransaction(snapshot, script, acc.ScriptHash, signers); + var tx = wallet.MakeTransaction(snapshotCache, script, acc.ScriptHash, signers); Assert.IsNotNull(tx); Assert.IsNull(tx.Witnesses); @@ -1029,7 +1030,7 @@ public void FeeIsSignatureContract_TestScope_FeeOnly_Default() // Sign // ---- - var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); bool signed = wallet.Sign(data); Assert.IsTrue(signed); @@ -1038,13 +1039,13 @@ public void FeeIsSignatureContract_TestScope_FeeOnly_Default() tx.Witnesses.Length.Should().Be(1); // Fast check - Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshot, tx.NetworkFee)); + Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, snapshotCache, tx.NetworkFee)); // Check long verificationGas = 0; foreach (var witness in tx.Witnesses) { - using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee); + using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings, gas: tx.NetworkFee); engine.LoadScript(witness.VerificationScript); engine.LoadScript(witness.InvocationScript); Assert.AreEqual(VMState.HALT, engine.Execute()); @@ -1053,7 +1054,7 @@ public void FeeIsSignatureContract_TestScope_FeeOnly_Default() verificationGas += engine.FeeConsumed; } // get sizeGas - var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshot); + var sizeGas = tx.Size * NativeContract.Policy.GetFeePerByte(snapshotCache); // final check on sum: verification_cost + tx_size Assert.AreEqual(1228520, verificationGas + sizeGas); // final assert @@ -1065,16 +1066,16 @@ public void ToJson() { uut.Script = TestUtils.GetByteArray(32, 0x42); uut.SystemFee = 4200000000; - uut.Signers = new Signer[] { new Signer() { Account = UInt160.Zero } }; - uut.Attributes = Array.Empty(); - uut.Witnesses = new[] - { + uut.Signers = [new Signer { Account = UInt160.Zero }]; + uut.Attributes = []; + uut.Witnesses = + [ new Witness { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } - }; + ]; JObject jObj = uut.ToJson(ProtocolSettings.Default); jObj.Should().NotBeNull(); @@ -1090,23 +1091,23 @@ public void ToJson() [TestMethod] public void Test_GetAttribute() { - var tx = new Transaction() + var tx = new Transaction { - Attributes = Array.Empty(), + Attributes = [], NetworkFee = 0, Nonce = (uint)Environment.TickCount, Script = new byte[Transaction.MaxTransactionSize], - Signers = new Signer[] { new Signer() { Account = UInt160.Zero } }, + Signers = [new Signer { Account = UInt160.Zero }], SystemFee = 0, ValidUntilBlock = 0, Version = 0, - Witnesses = Array.Empty(), + Witnesses = [], }; Assert.IsNull(tx.GetAttribute()); Assert.IsNull(tx.GetAttribute()); - tx.Attributes = new TransactionAttribute[] { new HighPriorityAttribute() }; + tx.Attributes = [new HighPriorityAttribute()]; Assert.IsNull(tx.GetAttribute()); Assert.IsNotNull(tx.GetAttribute()); @@ -1115,24 +1116,24 @@ public void Test_GetAttribute() [TestMethod] public void Test_VerifyStateIndependent() { - var tx = new Transaction() + var tx = new Transaction { - Attributes = Array.Empty(), + Attributes = [], NetworkFee = 0, Nonce = (uint)Environment.TickCount, Script = new byte[Transaction.MaxTransactionSize], - Signers = new Signer[] { new Signer() { Account = UInt160.Zero } }, + Signers = [new Signer { Account = UInt160.Zero }], SystemFee = 0, ValidUntilBlock = 0, Version = 0, - Witnesses = new[] - { + Witnesses = + [ new Witness { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } - } + ] }; tx.VerifyStateIndependent(TestProtocolSettings.Default).Should().Be(VerifyResult.OverSize); tx.Script = Array.Empty(); @@ -1140,17 +1141,16 @@ public void Test_VerifyStateIndependent() var walletA = TestUtils.GenerateTestWallet("123"); var walletB = TestUtils.GenerateTestWallet("123"); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var a = walletA.CreateAccount(); var b = walletB.CreateAccount(); var multiSignContract = Contract.CreateMultiSigContract(2, - new ECPoint[] - { - a.GetKey().PublicKey, - b.GetKey().PublicKey - }); + [ + a.GetKey().PublicKey, + b.GetKey().PublicKey + ]); walletA.CreateAccount(multiSignContract, a.GetKey()); var acc = walletB.CreateAccount(multiSignContract, b.GetKey()); @@ -1158,27 +1158,26 @@ public void Test_VerifyStateIndependent() // Fake balance var key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); - var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + var entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; - snapshot.Commit(); + snapshotCache.Commit(); // Make transaction - tx = walletA.MakeTransaction(snapshot, new TransferOutput[] - { - new TransferOutput() - { - AssetId = NativeContract.GAS.Hash, - ScriptHash = acc.ScriptHash, - Value = new BigDecimal(BigInteger.One,8) - } - }, acc.ScriptHash); + tx = walletA.MakeTransaction(snapshotCache, [ + new TransferOutput + { + AssetId = NativeContract.GAS.Hash, + ScriptHash = acc.ScriptHash, + Value = new BigDecimal(BigInteger.One, 8) + } + ], acc.ScriptHash); // Sign - var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); Assert.IsTrue(walletA.Sign(data)); Assert.IsTrue(walletB.Sign(data)); Assert.IsTrue(data.Completed); @@ -1199,35 +1198,36 @@ public void Test_VerifyStateIndependent() [TestMethod] public void Test_VerifyStateDependent() { - var snapshot = TestBlockchain.GetTestSnapshot(); - var height = NativeContract.Ledger.CurrentIndex(snapshot); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + var height = NativeContract.Ledger.CurrentIndex(snapshotCache); var tx = new Transaction() { - Attributes = Array.Empty(), + Attributes = [], NetworkFee = 55000, Nonce = (uint)Environment.TickCount, Script = Array.Empty(), - Signers = new Signer[] { new Signer() { Account = UInt160.Zero } }, + Signers = [new Signer() { Account = UInt160.Zero }], SystemFee = 0, ValidUntilBlock = height + 1, Version = 0, - Witnesses = new Witness[] { - new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() }, - new Witness() { InvocationScript = Array.Empty(), VerificationScript = new byte[1] } - } + Witnesses = + [ + new Witness { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() }, + new Witness { InvocationScript = Array.Empty(), VerificationScript = new byte[1] } + ] }; // Fake balance var key = NativeContract.GAS.CreateStorageKey(20, tx.Sender); - var balance = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + var balance = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); balance.GetInteroperable().Balance = tx.NetworkFee; var conflicts = new List(); - tx.VerifyStateDependent(ProtocolSettings.Default, snapshot, new TransactionVerificationContext(), conflicts).Should().Be(VerifyResult.Invalid); + tx.VerifyStateDependent(ProtocolSettings.Default, snapshotCache, new TransactionVerificationContext(), conflicts).Should().Be(VerifyResult.Invalid); balance.GetInteroperable().Balance = 0; tx.SystemFee = 10; - tx.VerifyStateDependent(ProtocolSettings.Default, snapshot, new TransactionVerificationContext(), conflicts).Should().Be(VerifyResult.InsufficientFunds); + tx.VerifyStateDependent(ProtocolSettings.Default, snapshotCache, new TransactionVerificationContext(), conflicts).Should().Be(VerifyResult.InsufficientFunds); var walletA = TestUtils.GenerateTestWallet("123"); var walletB = TestUtils.GenerateTestWallet("123"); @@ -1236,26 +1236,25 @@ public void Test_VerifyStateDependent() var b = walletB.CreateAccount(); var multiSignContract = Contract.CreateMultiSigContract(2, - new ECPoint[] - { - a.GetKey().PublicKey, - b.GetKey().PublicKey - }); + [ + a.GetKey().PublicKey, + b.GetKey().PublicKey + ]); walletA.CreateAccount(multiSignContract, a.GetKey()); var acc = walletB.CreateAccount(multiSignContract, b.GetKey()); // Fake balance - snapshot = TestBlockchain.GetTestSnapshot(); + snapshotCache = TestBlockchain.GetTestSnapshotCache(); key = NativeContract.GAS.CreateStorageKey(20, acc.ScriptHash); - balance = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + balance = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); balance.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; // Make transaction - snapshot.Commit(); - tx = walletA.MakeTransaction(snapshot, new TransferOutput[] + snapshotCache.Commit(); + tx = walletA.MakeTransaction(snapshotCache, new[] { new TransferOutput() { @@ -1267,13 +1266,13 @@ public void Test_VerifyStateDependent() // Sign - var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + var data = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); Assert.IsTrue(walletA.Sign(data)); Assert.IsTrue(walletB.Sign(data)); Assert.IsTrue(data.Completed); tx.Witnesses = data.GetWitnesses(); - tx.VerifyStateDependent(TestProtocolSettings.Default, snapshot, new TransactionVerificationContext(), new List()).Should().Be(VerifyResult.Succeed); + tx.VerifyStateDependent(TestProtocolSettings.Default, snapshotCache, new TransactionVerificationContext(), new List()).Should().Be(VerifyResult.Succeed); } [TestMethod] diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_VersionPayload.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_VersionPayload.cs index a77740a1e5..66bebf68d8 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_VersionPayload.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_VersionPayload.cs @@ -11,6 +11,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Capabilities; using Neo.Network.P2P.Payloads; diff --git a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Witness.cs b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Witness.cs index 74898eb4fb..107eb5858c 100644 --- a/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Witness.cs +++ b/tests/Neo.UnitTests/Network/P2P/Payloads/UT_Witness.cs @@ -11,6 +11,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.Network.P2P.Payloads; @@ -43,7 +44,7 @@ private static Witness PrepareDummyWitness(int pubKeys, int m) { var address = new WalletAccount[pubKeys]; var wallets = new NEP6Wallet[pubKeys]; - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); for (int x = 0; x < pubKeys; x++) { @@ -62,7 +63,7 @@ private static Witness PrepareDummyWitness(int pubKeys, int m) // Sign - var data = new ContractParametersContext(snapshot, new Transaction() + var data = new ContractParametersContext(snapshotCache, new Transaction() { Attributes = Array.Empty(), Signers = new[] {new Signer() 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..8eea33c9a7 --- /dev/null +++ b/tests/Neo.UnitTests/Persistence/UT_MemorySnapshot.cs @@ -0,0 +1,129 @@ +// 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)); + + Assert.IsFalse(_snapshot.TryGet(key1, out var value2)); + + Assert.IsTrue(snapshot2.TryGet(key1, out value2)); + CollectionAssert.AreEqual(value1, value2); + + Assert.IsTrue(_memoryStore.TryGet(key1, out value2)); + CollectionAssert.AreEqual(value1, value2); + + _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..4473ae63a8 100644 --- a/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs +++ b/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs @@ -51,6 +51,9 @@ public void StoreTest() store.Delete([1]); Assert.AreEqual(null, store.TryGet([1])); + Assert.IsFalse(store.TryGet([1], out var got)); + Assert.AreEqual(null, got); + store.Put([1], [1, 2, 3]); CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, store.TryGet([1])); @@ -79,19 +82,24 @@ public void NeoSystemStoreViewTest() var store = _neoSystem.StoreView; var key = new StorageKey(Encoding.UTF8.GetBytes("testKey")); var value = new StorageItem(Encoding.UTF8.GetBytes("testValue")); + store.Add(key, value); store.Commit(); + var result = store.TryGet(key); // The StoreView is a readonly view of the store, here it will have value in the cache Assert.AreEqual("testValue", Encoding.UTF8.GetString(result.Value.ToArray())); - // But the value will not be written to the underlying store even its committed. + + // But the value will not be written to the underlying store even its committed. Assert.IsNull(_memoryStore.TryGet(key.ToArray())); + Assert.IsFalse(_memoryStore.TryGet(key.ToArray(), out var got)); + Assert.AreEqual(null, got); } [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 +110,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..d25d1ca3f3 100644 --- a/tests/Neo.UnitTests/Plugins/TestPlugin.cs +++ b/tests/Neo.UnitTests/Plugins/TestPlugin.cs @@ -36,12 +36,12 @@ public TestNonPlugin() Blockchain.Committed += OnCommitted; } - private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + private static void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) { throw new NotImplementedException("Test exception from OnCommitting"); } - private void OnCommitted(NeoSystem system, Block block) + private static void OnCommitted(NeoSystem system, Block block) { throw new NotImplementedException("Test exception from OnCommitted"); } @@ -53,7 +53,7 @@ internal class TestPlugin : Plugin private readonly UnhandledExceptionPolicy _exceptionPolicy; protected internal override UnhandledExceptionPolicy ExceptionPolicy => _exceptionPolicy; - public TestPlugin(UnhandledExceptionPolicy exceptionPolicy = UnhandledExceptionPolicy.StopPlugin) : base() + public TestPlugin(UnhandledExceptionPolicy exceptionPolicy = UnhandledExceptionPolicy.StopPlugin) { Blockchain.Committing += OnCommitting; Blockchain.Committed += OnCommitted; diff --git a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs index c48d32563f..a76119f11b 100644 --- a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs +++ b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs @@ -15,14 +15,13 @@ using Neo.Plugins; using System; using System.Reflection; -using System.Threading.Tasks; namespace Neo.UnitTests.Plugins { [TestClass] public class UT_Plugin { - private static readonly object locker = new(); + private static readonly object s_locker = new(); [TestInitialize] public void TestInitialize() @@ -94,7 +93,7 @@ public void TestGetVersion() [TestMethod] public void TestSendMessage() { - lock (locker) + lock (s_locker) { Plugin.Plugins.Clear(); Plugin.SendMessage("hey1").Should().BeFalse(); @@ -112,14 +111,14 @@ public void TestGetConfiguration() } [TestMethod] - public async Task TestOnException() + public void TestOnException() { _ = new TestPlugin(); // Ensure no exception is thrown try { - await Blockchain.InvokeCommittingAsync(null, null, null, null); - await Blockchain.InvokeCommittedAsync(null, null); + Blockchain.InvokeCommitting(null, null, null, null); + Blockchain.InvokeCommitted(null, null); } catch (Exception ex) { @@ -130,27 +129,27 @@ public async Task TestOnException() _ = 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); - }); + Assert.ThrowsException(() => + { + Blockchain.InvokeCommitting(null, null, null, null); + }); + + Assert.ThrowsException(() => + { + Blockchain.InvokeCommitted(null, null); + }); } [TestMethod] - public async Task TestOnPluginStopped() + public void 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); + Blockchain.InvokeCommitting(null, null, null, null); + Blockchain.InvokeCommitted(null, null); } catch (Exception ex) { @@ -161,7 +160,7 @@ public async Task TestOnPluginStopped() } [TestMethod] - public async Task TestOnPluginStopOnException() + public void TestOnPluginStopOnException() { // pp will stop on exception. var pp = new TestPlugin(); @@ -169,8 +168,8 @@ public async Task TestOnPluginStopOnException() // Ensure no exception is thrown try { - await Blockchain.InvokeCommittingAsync(null, null, null, null); - await Blockchain.InvokeCommittedAsync(null, null); + Blockchain.InvokeCommitting(null, null, null, null); + Blockchain.InvokeCommitted(null, null); } catch (Exception ex) { @@ -185,8 +184,8 @@ public async Task TestOnPluginStopOnException() // Ensure no exception is thrown try { - await Blockchain.InvokeCommittingAsync(null, null, null, null); - await Blockchain.InvokeCommittedAsync(null, null); + Blockchain.InvokeCommitting(null, null, null, null); + Blockchain.InvokeCommitted(null, null); } catch (Exception ex) { @@ -197,19 +196,19 @@ public async Task TestOnPluginStopOnException() } [TestMethod] - public async Task TestOnNodeStopOnPluginException() + public void TestOnNodeStopOnPluginException() { // node will stop on pp exception. var pp = new TestPlugin(UnhandledExceptionPolicy.StopNode); Assert.AreEqual(false, pp.IsStopped); - await Assert.ThrowsExceptionAsync(async () => + Assert.ThrowsException(() => { - await Blockchain.InvokeCommittingAsync(null, null, null, null); + Blockchain.InvokeCommitting(null, null, null, null); }); - await Assert.ThrowsExceptionAsync(async () => + Assert.ThrowsException(() => { - await Blockchain.InvokeCommittedAsync(null, null); + Blockchain.InvokeCommitted(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_ContractManifest.cs b/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs index 4e2e866281..fdbcf671b2 100644 --- a/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs +++ b/tests/Neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs @@ -11,6 +11,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.Json; using Neo.SmartContract; 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_CryptoLib.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs index f65e061093..daf1922644 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_CryptoLib.cs @@ -14,6 +14,7 @@ using Neo.Cryptography; using Neo.Cryptography.BLS12_381; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.Ledger; using Neo.Network.P2P; @@ -45,11 +46,11 @@ public class UT_CryptoLib [TestMethod] public void TestG1() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using ScriptBuilder script = new(); script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", g1); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(VMState.HALT, engine.Execute()); var result = engine.ResultStack.Pop(); @@ -59,11 +60,11 @@ public void TestG1() [TestMethod] public void TestG2() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using ScriptBuilder script = new(); script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", g2); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(VMState.HALT, engine.Execute()); var result = engine.ResultStack.Pop(); @@ -73,11 +74,11 @@ public void TestG2() [TestMethod] public void TestNotG1() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using ScriptBuilder script = new(); script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", not_g1); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(VMState.FAULT, engine.Execute()); } @@ -85,18 +86,18 @@ public void TestNotG1() [TestMethod] public void TestNotG2() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using ScriptBuilder script = new(); script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", not_g2); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(VMState.FAULT, engine.Execute()); } [TestMethod] public void TestBls12381Add() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using ScriptBuilder script = new(); script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", gt); script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", gt); @@ -107,7 +108,7 @@ public void TestBls12381Add() script.EmitPush(NativeContract.CryptoLib.Hash); script.EmitSysCall(ApplicationEngine.System_Contract_Call); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(VMState.HALT, engine.Execute()); var result = engine.ResultStack.Pop(); @@ -119,7 +120,7 @@ public void TestBls12381Mul() { var data = new byte[32]; data[0] = 0x03; - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using (ScriptBuilder script = new()) { script.EmitPush(false); @@ -132,7 +133,7 @@ public void TestBls12381Mul() script.EmitPush(NativeContract.CryptoLib.Hash); script.EmitSysCall(ApplicationEngine.System_Contract_Call); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(VMState.HALT, engine.Execute()); var result = engine.ResultStack.Pop(); @@ -150,7 +151,7 @@ public void TestBls12381Mul() script.EmitPush(NativeContract.CryptoLib.Hash); script.EmitSysCall(ApplicationEngine.System_Contract_Call); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(VMState.HALT, engine.Execute()); var result = engine.ResultStack.Pop(); @@ -161,7 +162,7 @@ public void TestBls12381Mul() [TestMethod] public void TestBls12381Pairing() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using ScriptBuilder script = new(); script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", g2); script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", g1); @@ -172,7 +173,7 @@ public void TestBls12381Pairing() script.EmitPush(NativeContract.CryptoLib.Hash); script.EmitSysCall(ApplicationEngine.System_Contract_Call); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(VMState.HALT, engine.Execute()); var result = engine.ResultStack.Pop(); @@ -182,7 +183,7 @@ public void TestBls12381Pairing() [TestMethod] public void Bls12381Equal() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using ScriptBuilder script = new(); script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", g1); script.EmitDynamicCall(NativeContract.CryptoLib.Hash, "bls12381Deserialize", g1); @@ -193,7 +194,7 @@ public void Bls12381Equal() script.EmitPush(NativeContract.CryptoLib.Hash); script.EmitSysCall(ApplicationEngine.System_Contract_Call); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(VMState.HALT, engine.Execute()); var result = engine.ResultStack.Pop(); @@ -211,7 +212,7 @@ private void CheckBls12381ScalarMul_Compat(string point, string mul, bool negati { var data = new byte[32]; data[0] = 0x03; - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using (ScriptBuilder script = new()) { script.EmitPush(negative); @@ -224,7 +225,7 @@ private void CheckBls12381ScalarMul_Compat(string point, string mul, bool negati script.EmitPush(NativeContract.CryptoLib.Hash); script.EmitSysCall(ApplicationEngine.System_Contract_Call); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(VMState.HALT, engine.Execute()); var result = engine.ResultStack.Pop(); @@ -506,16 +507,16 @@ public void TestVerifyWithECDsa_CustomTxWitness_SingleSig() tx.VerifyStateIndependent(TestProtocolSettings.Default).Should().Be(VerifyResult.Succeed); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); // Create fake balance to pay the fees. - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); _ = NativeContract.GAS.Mint(engine, acc, 5_0000_0000, false); - snapshot.Commit(); + snapshotCache.Commit(); var txVrfContext = new TransactionVerificationContext(); var conflicts = new List(); - tx.VerifyStateDependent(TestProtocolSettings.Default, snapshot, txVrfContext, conflicts).Should().Be(VerifyResult.Succeed); + tx.VerifyStateDependent(TestProtocolSettings.Default, snapshotCache, txVrfContext, conflicts).Should().Be(VerifyResult.Succeed); // The resulting witness verification cost is 2154270 * 10e-8GAS. // The resulting witness Invocation script (66 bytes length): @@ -534,19 +535,19 @@ public void TestVerifyWithECDsa_CustomTxWitness_SingleSig() // NEO-VM 0 > ops // INDEX OPCODE PARAMETER // 0 PUSHINT8 122 (7a) << - // 2 SWAP + // 2 SWAP // 3 PUSHDATA1 02fd0a8c1ce5ae5570fdd46e7599c16b175bf0ebdfe9c178f1ab848fb16dac74a5 // 38 SYSCALL System.Runtime.GetNetwork (c5fba0e0) // 43 PUSHINT64 4294967296 (0000000001000000) - // 52 ADD - // 53 PUSH4 - // 54 LEFT + // 52 ADD + // 53 PUSH4 + // 54 LEFT // 55 SYSCALL System.Runtime.GetScriptContainer (2d510830) - // 60 PUSH0 - // 61 PICKITEM - // 62 CAT - // 63 PUSH4 - // 64 PACK + // 60 PUSH0 + // 61 PICKITEM + // 62 CAT + // 63 PUSH4 + // 64 PACK // 65 PUSH0 // 66 PUSHDATA1 766572696679576974684543447361 ("verifyWithECDsa") // 83 PUSHDATA1 1bf575ab1189688413610a35a12886cde0b66c72 ("NNToUmdQBe5n8o53BTzjTFAnSEcpouyy3B", "0x726cb6e0cd8628a1350a611384688911ab75f51b") @@ -563,17 +564,17 @@ public void TestVerifyWithECDsa_CustomTxWitness_SingleSig() [TestMethod] public void TestVerifyWithECDsa_CustomTxWitness_MultiSig() { - byte[] privkey1 = "b2dde592bfce654ef03f1ceea452d2b0112e90f9f52099bcd86697a2bd0a2b60".HexToBytes(); - ECPoint pubKey1 = ECPoint.Parse("040486468683c112125978ffe876245b2006bfe739aca8539b67335079262cb27ad0dedc9e5583f99b61c6f46bf80b97eaec3654b87add0e5bd7106c69922a229d", ECCurve.Secp256k1); - byte[] privkey2 = "b9879e26941872ee6c9e6f01045681496d8170ed2cc4a54ce617b39ae1891b3a".HexToBytes(); - ECPoint pubKey2 = ECPoint.Parse("040d26fc2ad3b1aae20f040b5f83380670f8ef5c2b2ac921ba3bdd79fd0af0525177715fd4370b1012ddd10579698d186ab342c223da3e884ece9cab9b6638c7bb", ECCurve.Secp256k1); - byte[] privkey3 = "4e1fe2561a6da01ee030589d504d62b23c26bfd56c5e07dfc9b8b74e4602832a".HexToBytes(); - ECPoint pubKey3 = ECPoint.Parse("047b4e72ae854b6a0955b3e02d92651ab7fa641a936066776ad438f95bb674a269a63ff98544691663d91a6cfcd215831f01bfb7a226363a6c5c67ef14541dba07", ECCurve.Secp256k1); - byte[] privkey4 = "6dfd066bb989d3786043aa5c1f0476215d6f5c44f5fc3392dd15e2599b67a728".HexToBytes(); - ECPoint pubKey4 = ECPoint.Parse("04b62ac4c8a352a892feceb18d7e2e3a62c8c1ecbaae5523d89d747b0219276e225be2556a137e0e806e4915762d816cdb43f572730d23bb1b1cba750011c4edc6", ECCurve.Secp256k1); - - // Public keys must be sorted, exactly like for standard CreateMultiSigRedeemScript. - var keys = new List<(byte[], ECPoint)>() + var privkey1 = "b2dde592bfce654ef03f1ceea452d2b0112e90f9f52099bcd86697a2bd0a2b60".HexToBytes(); + var pubKey1 = ECPoint.Parse("040486468683c112125978ffe876245b2006bfe739aca8539b67335079262cb27ad0dedc9e5583f99b61c6f46bf80b97eaec3654b87add0e5bd7106c69922a229d", ECCurve.Secp256k1); + var privkey2 = "b9879e26941872ee6c9e6f01045681496d8170ed2cc4a54ce617b39ae1891b3a".HexToBytes(); + var pubKey2 = ECPoint.Parse("040d26fc2ad3b1aae20f040b5f83380670f8ef5c2b2ac921ba3bdd79fd0af0525177715fd4370b1012ddd10579698d186ab342c223da3e884ece9cab9b6638c7bb", ECCurve.Secp256k1); + var privkey3 = "4e1fe2561a6da01ee030589d504d62b23c26bfd56c5e07dfc9b8b74e4602832a".HexToBytes(); + var pubKey3 = ECPoint.Parse("047b4e72ae854b6a0955b3e02d92651ab7fa641a936066776ad438f95bb674a269a63ff98544691663d91a6cfcd215831f01bfb7a226363a6c5c67ef14541dba07", ECCurve.Secp256k1); + var privkey4 = "6dfd066bb989d3786043aa5c1f0476215d6f5c44f5fc3392dd15e2599b67a728".HexToBytes(); + var pubKey4 = ECPoint.Parse("04b62ac4c8a352a892feceb18d7e2e3a62c8c1ecbaae5523d89d747b0219276e225be2556a137e0e806e4915762d816cdb43f572730d23bb1b1cba750011c4edc6", ECCurve.Secp256k1); + + // Public keys must be sorted, exactly like for standard CreateMultiSigRedeemScript. + var keys = new List<(byte[], ECPoint)> { (privkey1, pubKey1), (privkey2, pubKey2), @@ -583,7 +584,7 @@ public void TestVerifyWithECDsa_CustomTxWitness_MultiSig() // Consider 4 users willing to sign 3/4 multisignature transaction with their Secp256k1 private keys. var m = 3; - var n = keys.Count(); + var n = keys.Count; // Must ensure the following conditions are met before verification script construction: n.Should().BeGreaterThan(0); @@ -607,7 +608,7 @@ public void TestVerifyWithECDsa_CustomTxWitness_MultiSig() // return sigCnt == m // } - // vrf is a builder of M out of N multisig witness verification script corresponding to the public keys. + // vrf is a builder of M out of N multisig witness verification script corresponding to the public keys. using ScriptBuilder vrf = new(); // Start the same way as regular multisig script. @@ -747,17 +748,21 @@ public void TestVerifyWithECDsa_CustomTxWitness_MultiSig() tx.VerifyStateIndependent(TestProtocolSettings.Default).Should().Be(VerifyResult.Succeed); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); // Create fake balance to pay the fees. - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); + var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings, gas: long.MaxValue); _ = NativeContract.GAS.Mint(engine, acc, 5_0000_0000, false); - snapshot.Commit(); + + // We should not use commit here cause once its committed, the value we get from the snapshot can be different + // from the underline storage. Thought there isn't any issue triggered here, its wrong to use it this way. + // We should either ignore the commit, or get a new snapshot of the store after the commit. + // snapshot.Commit(); // Check that witness verification passes. var txVrfContext = new TransactionVerificationContext(); var conflicts = new List(); - tx.VerifyStateDependent(TestProtocolSettings.Default, snapshot, txVrfContext, conflicts).Should().Be(VerifyResult.Succeed); + tx.VerifyStateDependent(TestProtocolSettings.Default, snapshotCache, txVrfContext, conflicts).Should().Be(VerifyResult.Succeed); // The resulting witness verification cost for 3/4 multisig is 8389470 * 10e-8GAS. Cost depends on M/N. // The resulting witness Invocation script (198 bytes for 3 signatures): @@ -780,65 +785,65 @@ public void TestVerifyWithECDsa_CustomTxWitness_MultiSig() // 36 PUSHDATA1 030d26fc2ad3b1aae20f040b5f83380670f8ef5c2b2ac921ba3bdd79fd0af05251 // 71 PUSHDATA1 037b4e72ae854b6a0955b3e02d92651ab7fa641a936066776ad438f95bb674a269 // 106 PUSHDATA1 02b62ac4c8a352a892feceb18d7e2e3a62c8c1ecbaae5523d89d747b0219276e22 - // 141 PUSH4 + // 141 PUSH4 // 142 INITSLOT 7 local, 0 arg - // 145 STLOC5 - // 146 LDLOC5 - // 147 PACK - // 148 STLOC1 - // 149 STLOC6 - // 150 DEPTH - // 151 LDLOC6 + // 145 STLOC5 + // 146 LDLOC5 + // 147 PACK + // 148 STLOC1 + // 149 STLOC6 + // 150 DEPTH + // 151 LDLOC6 // 152 JMPEQ 155 (3/03) - // 154 ABORT - // 155 LDLOC6 - // 156 PACK - // 157 STLOC0 + // 154 ABORT + // 155 LDLOC6 + // 156 PACK + // 157 STLOC0 // 158 SYSCALL System.Runtime.GetNetwork (c5fba0e0) // 163 PUSHINT64 4294967296 (0000000001000000) - // 172 ADD - // 173 PUSH4 - // 174 LEFT + // 172 ADD + // 173 PUSH4 + // 174 LEFT // 175 SYSCALL System.Runtime.GetScriptContainer (2d510830) - // 180 PUSH0 - // 181 PICKITEM - // 182 CAT - // 183 STLOC2 - // 184 PUSH0 - // 185 STLOC3 - // 186 PUSH0 - // 187 STLOC4 - // 188 LDLOC3 - // 189 LDLOC6 - // 190 GE - // 191 LDLOC4 - // 192 LDLOC5 - // 193 GE - // 194 OR + // 180 PUSH0 + // 181 PICKITEM + // 182 CAT + // 183 STLOC2 + // 184 PUSH0 + // 185 STLOC3 + // 186 PUSH0 + // 187 STLOC4 + // 188 LDLOC3 + // 189 LDLOC6 + // 190 GE + // 191 LDLOC4 + // 192 LDLOC5 + // 193 GE + // 194 OR // 195 JMPIF 261 (66/42) // 197 PUSHINT8 122 (7a) - // 199 LDLOC0 - // 200 LDLOC3 - // 201 PICKITEM - // 202 LDLOC1 - // 203 LDLOC4 - // 204 PICKITEM - // 205 LDLOC2 - // 206 PUSH4 - // 207 PACK + // 199 LDLOC0 + // 200 LDLOC3 + // 201 PICKITEM + // 202 LDLOC1 + // 203 LDLOC4 + // 204 PICKITEM + // 205 LDLOC2 + // 206 PUSH4 + // 207 PACK // 208 PUSH0 // 209 PUSHDATA1 766572696679576974684543447361 ("verifyWithECDsa") // 226 PUSHDATA1 1bf575ab1189688413610a35a12886cde0b66c72 ("NNToUmdQBe5n8o53BTzjTFAnSEcpouyy3B", "0x726cb6e0cd8628a1350a611384688911ab75f51b") // 248 SYSCALL System.Contract.Call (627d5b52) - // 253 LDLOC3 - // 254 ADD - // 255 STLOC3 - // 256 LDLOC4 - // 257 INC - // 258 STLOC4 + // 253 LDLOC3 + // 254 ADD + // 255 STLOC3 + // 256 LDLOC4 + // 257 INC + // 258 STLOC4 // 259 JMP 188 (-71/b9) - // 261 LDLOC3 - // 262 LDLOC6 + // 261 LDLOC3 + // 262 LDLOC6 // 263 NUMEQUAL } @@ -885,7 +890,7 @@ public void TestVerifyWithECDsa() private bool CallVerifyWithECDsa(byte[] message, ECPoint pub, byte[] signature, NamedCurveHash curveHash) { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshot = TestBlockchain.GetTestSnapshotCache(); using (ScriptBuilder script = new()) { script.EmitPush((int)curveHash); diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_FungibleToken.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_FungibleToken.cs index 5d6d00986e..7e7e27084c 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_FungibleToken.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_FungibleToken.cs @@ -22,8 +22,8 @@ public class UT_FungibleToken : TestKit [TestMethod] public void TestTotalSupply() { - var snapshot = TestBlockchain.GetTestSnapshot(); - NativeContract.GAS.TotalSupply(snapshot).Should().Be(5200000050000000); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + NativeContract.GAS.TotalSupply(snapshotCache).Should().Be(5200000050000000); } } } diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs index 51617b6744..a7d087fa86 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_GasToken.cs @@ -27,13 +27,13 @@ namespace Neo.UnitTests.SmartContract.Native [TestClass] public class UT_GasToken { - private DataCache _snapshot; + private DataCache _snapshotCache; private Block _persistingBlock; [TestInitialize] public void TestSetup() { - _snapshot = TestBlockchain.GetTestSnapshot(); + _snapshotCache = TestBlockchain.GetTestSnapshotCache(); _persistingBlock = new Block { Header = new Header() }; } @@ -41,15 +41,15 @@ public void TestSetup() public void Check_Name() => NativeContract.GAS.Name.Should().Be(nameof(GasToken)); [TestMethod] - public void Check_Symbol() => NativeContract.GAS.Symbol(_snapshot).Should().Be("GAS"); + public void Check_Symbol() => NativeContract.GAS.Symbol(_snapshotCache).Should().Be("GAS"); [TestMethod] - public void Check_Decimals() => NativeContract.GAS.Decimals(_snapshot).Should().Be(8); + public void Check_Decimals() => NativeContract.GAS.Decimals(_snapshotCache).Should().Be(8); [TestMethod] public async Task Check_BalanceOfTransferAndBurn() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshotCache.CloneCache(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; byte[] from = Contract.GetBFTAddress(TestProtocolSettings.Default.StandbyValidators).ToArray(); byte[] to = new byte[20]; @@ -119,20 +119,20 @@ await Assert.ThrowsExceptionAsync(async () => await NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(1)); - NativeContract.GAS.BalanceOf(engine.Snapshot, to).Should().Be(5200049999999999); + NativeContract.GAS.BalanceOf(engine.SnapshotCache, to).Should().Be(5200049999999999); - engine.Snapshot.GetChangeSet().Count().Should().Be(2); + engine.SnapshotCache.GetChangeSet().Count().Should().Be(2); // Burn all await NativeContract.GAS.Burn(engine, new UInt160(to), new BigInteger(5200049999999999)); - (keyCount - 2).Should().Be(engine.Snapshot.GetChangeSet().Count()); + (keyCount - 2).Should().Be(engine.SnapshotCache.GetChangeSet().Count()); // Bad inputs - Assert.ThrowsException(() => NativeContract.GAS.Transfer(engine.Snapshot, from, to, BigInteger.MinusOne, true, persistingBlock)); - Assert.ThrowsException(() => NativeContract.GAS.Transfer(engine.Snapshot, new byte[19], to, BigInteger.One, false, persistingBlock)); - Assert.ThrowsException(() => NativeContract.GAS.Transfer(engine.Snapshot, from, new byte[19], BigInteger.One, false, persistingBlock)); + Assert.ThrowsException(() => NativeContract.GAS.Transfer(engine.SnapshotCache, from, to, BigInteger.MinusOne, true, persistingBlock)); + Assert.ThrowsException(() => NativeContract.GAS.Transfer(engine.SnapshotCache, new byte[19], to, BigInteger.One, false, persistingBlock)); + Assert.ThrowsException(() => NativeContract.GAS.Transfer(engine.SnapshotCache, from, new byte[19], BigInteger.One, false, persistingBlock)); } internal static StorageKey CreateStorageKey(byte prefix, uint key) diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs index 1989b4323b..f9a3089f9d 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_NativeContract.cs @@ -27,7 +27,7 @@ namespace Neo.UnitTests.SmartContract.Native [TestClass] public class UT_NativeContract { - private DataCache _snapshot; + private DataCache _snapshotCache; /// /// _nativeStates contains a mapping from native contract name to expected native contract state /// constructed with all hardforks enabled and marshalled in JSON. @@ -37,7 +37,7 @@ public class UT_NativeContract [TestInitialize] public void TestSetup() { - _snapshot = TestBlockchain.GetTestSnapshot(); + _snapshotCache = TestBlockchain.GetTestSnapshotCache(); _nativeStates = new Dictionary { {"ContractManagement", """{"id":-1,"updatecounter":0,"hash":"0xfffdc93764dbaddd97c48f252a53ea4643faa3fd","nef":{"magic":860243278,"compiler":"neo-core-v3.0","source":"","tokens":[],"script":"EEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0AQQRr3e2dAEEEa93tnQBBBGvd7Z0A=","checksum":1094259016},"manifest":{"name":"ContractManagement","groups":[],"features":{},"supportedstandards":[],"abi":{"methods":[{"name":"deploy","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Array","offset":0,"safe":false},{"name":"deploy","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Array","offset":7,"safe":false},{"name":"destroy","parameters":[],"returntype":"Void","offset":14,"safe":false},{"name":"getContract","parameters":[{"name":"hash","type":"Hash160"}],"returntype":"Array","offset":21,"safe":true},{"name":"getContractById","parameters":[{"name":"id","type":"Integer"}],"returntype":"Array","offset":28,"safe":true},{"name":"getContractHashes","parameters":[],"returntype":"InteropInterface","offset":35,"safe":true},{"name":"getMinimumDeploymentFee","parameters":[],"returntype":"Integer","offset":42,"safe":true},{"name":"hasMethod","parameters":[{"name":"hash","type":"Hash160"},{"name":"method","type":"String"},{"name":"pcount","type":"Integer"}],"returntype":"Boolean","offset":49,"safe":true},{"name":"setMinimumDeploymentFee","parameters":[{"name":"value","type":"Integer"}],"returntype":"Void","offset":56,"safe":false},{"name":"update","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"}],"returntype":"Void","offset":63,"safe":false},{"name":"update","parameters":[{"name":"nefFile","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","offset":70,"safe":false}],"events":[{"name":"Deploy","parameters":[{"name":"Hash","type":"Hash160"}]},{"name":"Update","parameters":[{"name":"Hash","type":"Hash160"}]},{"name":"Destroy","parameters":[{"name":"Hash","type":"Hash160"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}}""" }, @@ -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 = _snapshotCache.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 = _snapshotCache.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..2ff07d5a69 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_NeoToken.cs @@ -12,6 +12,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; @@ -30,13 +31,13 @@ namespace Neo.UnitTests.SmartContract.Native [TestClass] public class UT_NeoToken { - private DataCache _snapshot; + private DataCache _snapshotCache; private Block _persistingBlock; [TestInitialize] public void TestSetup() { - _snapshot = TestBlockchain.GetTestSnapshot(); + _snapshotCache = TestBlockchain.GetTestSnapshotCache(); _persistingBlock = new Block { Header = new Header(), @@ -48,37 +49,37 @@ public void TestSetup() public void Check_Name() => NativeContract.NEO.Name.Should().Be(nameof(NeoToken)); [TestMethod] - public void Check_Symbol() => NativeContract.NEO.Symbol(_snapshot).Should().Be("NEO"); + public void Check_Symbol() => NativeContract.NEO.Symbol(_snapshotCache).Should().Be("NEO"); [TestMethod] - public void Check_Decimals() => NativeContract.NEO.Decimals(_snapshot).Should().Be(0); + public void Check_Decimals() => NativeContract.NEO.Decimals(_snapshotCache).Should().Be(0); [TestMethod] public void Check_Vote() { - var snapshot = _snapshot.CreateSnapshot(); + var clonedCache = _snapshotCache.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 })); + clonedCache.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); byte[] from = Contract.GetBFTAddress(TestProtocolSettings.Default.StandbyValidators).ToArray(); // No signature - var ret = Check_Vote(snapshot, from, null, false, persistingBlock); + var ret = Check_Vote(clonedCache, from, null, false, persistingBlock); ret.Result.Should().BeFalse(); ret.State.Should().BeTrue(); // Wrong address - ret = Check_Vote(snapshot, new byte[19], null, false, persistingBlock); + ret = Check_Vote(clonedCache, new byte[19], null, false, persistingBlock); ret.Result.Should().BeFalse(); ret.State.Should().BeFalse(); // Wrong ec - ret = Check_Vote(snapshot, from, new byte[19], true, persistingBlock); + ret = Check_Vote(clonedCache, from, new byte[19], true, persistingBlock); ret.Result.Should().BeFalse(); ret.State.Should().BeFalse(); @@ -88,130 +89,130 @@ public void Check_Vote() fakeAddr[0] = 0x5F; fakeAddr[5] = 0xFF; - ret = Check_Vote(snapshot, fakeAddr, null, true, persistingBlock); + ret = Check_Vote(clonedCache, fakeAddr, null, true, persistingBlock); ret.Result.Should().BeFalse(); ret.State.Should().BeTrue(); // no registered - var accountState = snapshot.TryGet(CreateStorageKey(20, from)).GetInteroperable(); + var accountState = clonedCache.TryGet(CreateStorageKey(20, from)).GetInteroperable(); accountState.VoteTo = null; - ret = Check_Vote(snapshot, from, ECCurve.Secp256r1.G.ToArray(), true, persistingBlock); + ret = Check_Vote(clonedCache, from, ECCurve.Secp256r1.G.ToArray(), true, persistingBlock); ret.Result.Should().BeFalse(); ret.State.Should().BeTrue(); accountState.VoteTo.Should().BeNull(); // normal case - snapshot.Add(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray()), new StorageItem(new CandidateState() { Registered = true })); - ret = Check_Vote(snapshot, from, ECCurve.Secp256r1.G.ToArray(), true, persistingBlock); + clonedCache.Add(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray()), new StorageItem(new CandidateState() { Registered = true })); + ret = Check_Vote(clonedCache, from, ECCurve.Secp256r1.G.ToArray(), true, persistingBlock); ret.Result.Should().BeTrue(); ret.State.Should().BeTrue(); - accountState = snapshot.TryGet(CreateStorageKey(20, from)).GetInteroperable(); + accountState = clonedCache.TryGet(CreateStorageKey(20, from)).GetInteroperable(); accountState.VoteTo.Should().Be(ECCurve.Secp256r1.G); } [TestMethod] public void Check_Vote_Sameaccounts() { - var snapshot = _snapshot.CreateSnapshot(); + var clonedCache = _snapshotCache.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 })); + clonedCache.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); byte[] from = Contract.GetBFTAddress(TestProtocolSettings.Default.StandbyValidators).ToArray(); - var accountState = snapshot.TryGet(CreateStorageKey(20, from)).GetInteroperable(); + var accountState = clonedCache.TryGet(CreateStorageKey(20, from)).GetInteroperable(); accountState.Balance = 100; - snapshot.Add(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray()), new StorageItem(new CandidateState() { Registered = true })); - var ret = Check_Vote(snapshot, from, ECCurve.Secp256r1.G.ToArray(), true, persistingBlock); + clonedCache.Add(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray()), new StorageItem(new CandidateState() { Registered = true })); + var ret = Check_Vote(clonedCache, from, ECCurve.Secp256r1.G.ToArray(), true, persistingBlock); ret.Result.Should().BeTrue(); ret.State.Should().BeTrue(); - accountState = snapshot.TryGet(CreateStorageKey(20, from)).GetInteroperable(); + accountState = clonedCache.TryGet(CreateStorageKey(20, from)).GetInteroperable(); accountState.VoteTo.Should().Be(ECCurve.Secp256r1.G); //two account vote for the same account - var stateValidator = snapshot.GetAndChange(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray())).GetInteroperable(); + var stateValidator = clonedCache.GetAndChange(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray())).GetInteroperable(); stateValidator.Votes.Should().Be(100); var G_Account = Contract.CreateSignatureContract(ECCurve.Secp256r1.G).ScriptHash.ToArray(); - snapshot.Add(CreateStorageKey(20, G_Account), new StorageItem(new NeoAccountState { Balance = 200 })); - var secondAccount = snapshot.TryGet(CreateStorageKey(20, G_Account)).GetInteroperable(); + clonedCache.Add(CreateStorageKey(20, G_Account), new StorageItem(new NeoAccountState { Balance = 200 })); + var secondAccount = clonedCache.TryGet(CreateStorageKey(20, G_Account)).GetInteroperable(); secondAccount.Balance.Should().Be(200); - ret = Check_Vote(snapshot, G_Account, ECCurve.Secp256r1.G.ToArray(), true, persistingBlock); + ret = Check_Vote(clonedCache, G_Account, ECCurve.Secp256r1.G.ToArray(), true, persistingBlock); ret.Result.Should().BeTrue(); ret.State.Should().BeTrue(); - stateValidator = snapshot.GetAndChange(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray())).GetInteroperable(); + stateValidator = clonedCache.GetAndChange(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray())).GetInteroperable(); stateValidator.Votes.Should().Be(300); } [TestMethod] public void Check_Vote_ChangeVote() { - var snapshot = _snapshot.CreateSnapshot(); + var clonedCache = _snapshotCache.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 })); + clonedCache.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); //from vote to G byte[] from = TestProtocolSettings.Default.StandbyValidators[0].ToArray(); var from_Account = Contract.CreateSignatureContract(TestProtocolSettings.Default.StandbyValidators[0]).ScriptHash.ToArray(); - snapshot.Add(CreateStorageKey(20, from_Account), new StorageItem(new NeoAccountState())); - var accountState = snapshot.TryGet(CreateStorageKey(20, from_Account)).GetInteroperable(); + clonedCache.Add(CreateStorageKey(20, from_Account), new StorageItem(new NeoAccountState())); + var accountState = clonedCache.TryGet(CreateStorageKey(20, from_Account)).GetInteroperable(); accountState.Balance = 100; - snapshot.Add(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray()), new StorageItem(new CandidateState() { Registered = true })); - var ret = Check_Vote(snapshot, from_Account, ECCurve.Secp256r1.G.ToArray(), true, persistingBlock); + clonedCache.Add(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray()), new StorageItem(new CandidateState() { Registered = true })); + var ret = Check_Vote(clonedCache, from_Account, ECCurve.Secp256r1.G.ToArray(), true, persistingBlock); ret.Result.Should().BeTrue(); ret.State.Should().BeTrue(); - accountState = snapshot.TryGet(CreateStorageKey(20, from_Account)).GetInteroperable(); + accountState = clonedCache.TryGet(CreateStorageKey(20, from_Account)).GetInteroperable(); accountState.VoteTo.Should().Be(ECCurve.Secp256r1.G); //from change vote to itself - var G_stateValidator = snapshot.GetAndChange(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray())).GetInteroperable(); + var G_stateValidator = clonedCache.GetAndChange(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray())).GetInteroperable(); G_stateValidator.Votes.Should().Be(100); var G_Account = Contract.CreateSignatureContract(ECCurve.Secp256r1.G).ScriptHash.ToArray(); - snapshot.Add(CreateStorageKey(20, G_Account), new StorageItem(new NeoAccountState { Balance = 200 })); - snapshot.Add(CreateStorageKey(33, from), new StorageItem(new CandidateState() { Registered = true })); - ret = Check_Vote(snapshot, from_Account, from, true, persistingBlock); + clonedCache.Add(CreateStorageKey(20, G_Account), new StorageItem(new NeoAccountState { Balance = 200 })); + clonedCache.Add(CreateStorageKey(33, from), new StorageItem(new CandidateState() { Registered = true })); + ret = Check_Vote(clonedCache, from_Account, from, true, persistingBlock); ret.Result.Should().BeTrue(); ret.State.Should().BeTrue(); - G_stateValidator = snapshot.GetAndChange(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray())).GetInteroperable(); + G_stateValidator = clonedCache.GetAndChange(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray())).GetInteroperable(); G_stateValidator.Votes.Should().Be(0); - var from_stateValidator = snapshot.GetAndChange(CreateStorageKey(33, from)).GetInteroperable(); + var from_stateValidator = clonedCache.GetAndChange(CreateStorageKey(33, from)).GetInteroperable(); from_stateValidator.Votes.Should().Be(100); } [TestMethod] public void Check_Vote_VoteToNull() { - var snapshot = _snapshot.CreateSnapshot(); + var clonedCache = _snapshotCache.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 })); + clonedCache.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); byte[] from = TestProtocolSettings.Default.StandbyValidators[0].ToArray(); var from_Account = Contract.CreateSignatureContract(TestProtocolSettings.Default.StandbyValidators[0]).ScriptHash.ToArray(); - snapshot.Add(CreateStorageKey(20, from_Account), new StorageItem(new NeoAccountState())); - var accountState = snapshot.TryGet(CreateStorageKey(20, from_Account)).GetInteroperable(); + clonedCache.Add(CreateStorageKey(20, from_Account), new StorageItem(new NeoAccountState())); + var accountState = clonedCache.TryGet(CreateStorageKey(20, from_Account)).GetInteroperable(); accountState.Balance = 100; - snapshot.Add(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray()), new StorageItem(new CandidateState() { Registered = true })); - snapshot.Add(CreateStorageKey(23, ECCurve.Secp256r1.G.ToArray()), new StorageItem(new BigInteger(100500))); - var ret = Check_Vote(snapshot, from_Account, ECCurve.Secp256r1.G.ToArray(), true, persistingBlock); + clonedCache.Add(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray()), new StorageItem(new CandidateState() { Registered = true })); + clonedCache.Add(CreateStorageKey(23, ECCurve.Secp256r1.G.ToArray()), new StorageItem(new BigInteger(100500))); + var ret = Check_Vote(clonedCache, from_Account, ECCurve.Secp256r1.G.ToArray(), true, persistingBlock); ret.Result.Should().BeTrue(); ret.State.Should().BeTrue(); - accountState = snapshot.TryGet(CreateStorageKey(20, from_Account)).GetInteroperable(); + accountState = clonedCache.TryGet(CreateStorageKey(20, from_Account)).GetInteroperable(); accountState.VoteTo.Should().Be(ECCurve.Secp256r1.G); accountState.LastGasPerVote.Should().Be(100500); //from vote to null account G votes becomes 0 - var G_stateValidator = snapshot.GetAndChange(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray())).GetInteroperable(); + var G_stateValidator = clonedCache.GetAndChange(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray())).GetInteroperable(); G_stateValidator.Votes.Should().Be(100); var G_Account = Contract.CreateSignatureContract(ECCurve.Secp256r1.G).ScriptHash.ToArray(); - snapshot.Add(CreateStorageKey(20, G_Account), new StorageItem(new NeoAccountState { Balance = 200 })); - snapshot.Add(CreateStorageKey(33, from), new StorageItem(new CandidateState() { Registered = true })); - ret = Check_Vote(snapshot, from_Account, null, true, persistingBlock); + clonedCache.Add(CreateStorageKey(20, G_Account), new StorageItem(new NeoAccountState { Balance = 200 })); + clonedCache.Add(CreateStorageKey(33, from), new StorageItem(new CandidateState() { Registered = true })); + ret = Check_Vote(clonedCache, from_Account, null, true, persistingBlock); ret.Result.Should().BeTrue(); ret.State.Should().BeTrue(); - G_stateValidator = snapshot.GetAndChange(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray())).GetInteroperable(); + G_stateValidator = clonedCache.GetAndChange(CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray())).GetInteroperable(); G_stateValidator.Votes.Should().Be(0); - accountState = snapshot.TryGet(CreateStorageKey(20, from_Account)).GetInteroperable(); + accountState = clonedCache.TryGet(CreateStorageKey(20, from_Account)).GetInteroperable(); accountState.VoteTo.Should().Be(null); accountState.LastGasPerVote.Should().Be(0); } @@ -219,19 +220,19 @@ public void Check_Vote_VoteToNull() [TestMethod] public void Check_UnclaimedGas() { - var snapshot = _snapshot.CreateSnapshot(); + var clonedCache = _snapshotCache.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 })); + clonedCache.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); byte[] from = Contract.GetBFTAddress(TestProtocolSettings.Default.StandbyValidators).ToArray(); - var unclaim = Check_UnclaimedGas(snapshot, from, persistingBlock); + var unclaim = Check_UnclaimedGas(clonedCache, from, persistingBlock); unclaim.Value.Should().Be(new BigInteger(0.5 * 1000 * 100000000L)); unclaim.State.Should().BeTrue(); - unclaim = Check_UnclaimedGas(snapshot, new byte[19], persistingBlock); + unclaim = Check_UnclaimedGas(clonedCache, new byte[19], persistingBlock); unclaim.Value.Should().Be(BigInteger.Zero); unclaim.State.Should().BeFalse(); } @@ -239,113 +240,113 @@ public void Check_UnclaimedGas() [TestMethod] public void Check_RegisterValidator() { - var snapshot = _snapshot.CreateSnapshot(); + var clonedCache = _snapshotCache.CloneCache(); - var keyCount = snapshot.GetChangeSet().Count(); + var keyCount = clonedCache.GetChangeSet().Count(); var point = TestProtocolSettings.Default.StandbyValidators[0].EncodePoint(true).Clone() as byte[]; - var ret = Check_RegisterValidator(snapshot, point, _persistingBlock); // Exists + var ret = Check_RegisterValidator(clonedCache, point, _persistingBlock); // Exists ret.State.Should().BeTrue(); ret.Result.Should().BeTrue(); - snapshot.GetChangeSet().Count().Should().Be(++keyCount); // No changes + clonedCache.GetChangeSet().Count().Should().Be(++keyCount); // No changes point[20]++; // fake point - ret = Check_RegisterValidator(snapshot, point, _persistingBlock); // New + ret = Check_RegisterValidator(clonedCache, point, _persistingBlock); // New ret.State.Should().BeTrue(); ret.Result.Should().BeTrue(); - snapshot.GetChangeSet().Count().Should().Be(keyCount + 1); // New validator + clonedCache.GetChangeSet().Count().Should().Be(keyCount + 1); // New validator // Check GetRegisteredValidators - var members = NativeContract.NEO.GetCandidatesInternal(snapshot); + var members = NativeContract.NEO.GetCandidatesInternal(clonedCache); Assert.AreEqual(2, members.Count()); } [TestMethod] public void Check_UnregisterCandidate() { - var snapshot = _snapshot.CreateSnapshot(); + var clonedCache = _snapshotCache.CloneCache(); _persistingBlock.Header.Index = 1; - var keyCount = snapshot.GetChangeSet().Count(); + var keyCount = clonedCache.GetChangeSet().Count(); var point = TestProtocolSettings.Default.StandbyValidators[0].EncodePoint(true); //without register - var ret = Check_UnregisterCandidate(snapshot, point, _persistingBlock); + var ret = Check_UnregisterCandidate(clonedCache, point, _persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeTrue(); - snapshot.GetChangeSet().Count().Should().Be(keyCount); + clonedCache.GetChangeSet().Count().Should().Be(keyCount); //register and then unregister - ret = Check_RegisterValidator(snapshot, point, _persistingBlock); - StorageItem item = snapshot.GetAndChange(CreateStorageKey(33, point)); + ret = Check_RegisterValidator(clonedCache, point, _persistingBlock); + StorageItem item = clonedCache.GetAndChange(CreateStorageKey(33, point)); item.Size.Should().Be(7); ret.State.Should().BeTrue(); ret.Result.Should().BeTrue(); - var members = NativeContract.NEO.GetCandidatesInternal(snapshot); + var members = NativeContract.NEO.GetCandidatesInternal(clonedCache); Assert.AreEqual(1, members.Count()); - snapshot.GetChangeSet().Count().Should().Be(keyCount + 1); + clonedCache.GetChangeSet().Count().Should().Be(keyCount + 1); StorageKey key = CreateStorageKey(33, point); - snapshot.TryGet(key).Should().NotBeNull(); + clonedCache.TryGet(key).Should().NotBeNull(); - ret = Check_UnregisterCandidate(snapshot, point, _persistingBlock); + ret = Check_UnregisterCandidate(clonedCache, point, _persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeTrue(); - snapshot.GetChangeSet().Count().Should().Be(keyCount); + clonedCache.GetChangeSet().Count().Should().Be(keyCount); - members = NativeContract.NEO.GetCandidatesInternal(snapshot); + members = NativeContract.NEO.GetCandidatesInternal(clonedCache); Assert.AreEqual(0, members.Count()); - snapshot.TryGet(key).Should().BeNull(); + clonedCache.TryGet(key).Should().BeNull(); //register with votes, then unregister - ret = Check_RegisterValidator(snapshot, point, _persistingBlock); + ret = Check_RegisterValidator(clonedCache, point, _persistingBlock); ret.State.Should().BeTrue(); var G_Account = Contract.CreateSignatureContract(ECCurve.Secp256r1.G).ScriptHash.ToArray(); - snapshot.Add(CreateStorageKey(20, G_Account), new StorageItem(new NeoAccountState())); - var accountState = snapshot.TryGet(CreateStorageKey(20, G_Account)).GetInteroperable(); + clonedCache.Add(CreateStorageKey(20, G_Account), new StorageItem(new NeoAccountState())); + var accountState = clonedCache.TryGet(CreateStorageKey(20, G_Account)).GetInteroperable(); accountState.Balance = 100; - Check_Vote(snapshot, G_Account, TestProtocolSettings.Default.StandbyValidators[0].ToArray(), true, _persistingBlock); - ret = Check_UnregisterCandidate(snapshot, point, _persistingBlock); + Check_Vote(clonedCache, G_Account, TestProtocolSettings.Default.StandbyValidators[0].ToArray(), true, _persistingBlock); + ret = Check_UnregisterCandidate(clonedCache, point, _persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeTrue(); - snapshot.TryGet(key).Should().NotBeNull(); - StorageItem pointItem = snapshot.TryGet(key); + clonedCache.TryGet(key).Should().NotBeNull(); + StorageItem pointItem = clonedCache.TryGet(key); CandidateState pointState = pointItem.GetInteroperable(); pointState.Registered.Should().BeFalse(); pointState.Votes.Should().Be(100); //vote fail - ret = Check_Vote(snapshot, G_Account, TestProtocolSettings.Default.StandbyValidators[0].ToArray(), true, _persistingBlock); + ret = Check_Vote(clonedCache, G_Account, TestProtocolSettings.Default.StandbyValidators[0].ToArray(), true, _persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeFalse(); - accountState = snapshot.TryGet(CreateStorageKey(20, G_Account)).GetInteroperable(); + accountState = clonedCache.TryGet(CreateStorageKey(20, G_Account)).GetInteroperable(); accountState.VoteTo.Should().Be(TestProtocolSettings.Default.StandbyValidators[0]); } [TestMethod] public void Check_GetCommittee() { - var snapshot = _snapshot.CreateSnapshot(); - var keyCount = snapshot.GetChangeSet().Count(); + var clonedCache = _snapshotCache.CloneCache(); + var keyCount = clonedCache.GetChangeSet().Count(); var point = TestProtocolSettings.Default.StandbyValidators[0].EncodePoint(true); var persistingBlock = _persistingBlock; persistingBlock.Header.Index = 1; //register with votes with 20000000 var G_Account = Contract.CreateSignatureContract(ECCurve.Secp256r1.G).ScriptHash.ToArray(); - snapshot.Add(CreateStorageKey(20, G_Account), new StorageItem(new NeoAccountState())); - var accountState = snapshot.TryGet(CreateStorageKey(20, G_Account)).GetInteroperable(); + clonedCache.Add(CreateStorageKey(20, G_Account), new StorageItem(new NeoAccountState())); + var accountState = clonedCache.TryGet(CreateStorageKey(20, G_Account)).GetInteroperable(); accountState.Balance = 20000000; - var ret = Check_RegisterValidator(snapshot, ECCurve.Secp256r1.G.ToArray(), persistingBlock); + var ret = Check_RegisterValidator(clonedCache, ECCurve.Secp256r1.G.ToArray(), persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeTrue(); - ret = Check_Vote(snapshot, G_Account, ECCurve.Secp256r1.G.ToArray(), true, persistingBlock); + ret = Check_Vote(clonedCache, G_Account, ECCurve.Secp256r1.G.ToArray(), true, persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeTrue(); - var committeemembers = NativeContract.NEO.GetCommittee(snapshot); + var committeemembers = NativeContract.NEO.GetCommittee(clonedCache); var defaultCommittee = TestProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray(); committeemembers.GetType().Should().Be(typeof(ECPoint[])); for (int i = 0; i < TestProtocolSettings.Default.CommitteeMembersCount; i++) @@ -368,14 +369,14 @@ public void Check_GetCommittee() }; for (int i = 0; i < TestProtocolSettings.Default.CommitteeMembersCount - 1; i++) { - ret = Check_RegisterValidator(snapshot, TestProtocolSettings.Default.StandbyCommittee[i].ToArray(), persistingBlock); + ret = Check_RegisterValidator(clonedCache, TestProtocolSettings.Default.StandbyCommittee[i].ToArray(), persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeTrue(); } - Check_OnPersist(snapshot, persistingBlock).Should().BeTrue(); + Check_OnPersist(clonedCache, persistingBlock).Should().BeTrue(); - committeemembers = NativeContract.NEO.GetCommittee(snapshot); + committeemembers = NativeContract.NEO.GetCommittee(clonedCache); committeemembers.Length.Should().Be(TestProtocolSettings.Default.CommitteeMembersCount); committeemembers.Contains(ECCurve.Secp256r1.G).Should().BeTrue(); for (int i = 0; i < TestProtocolSettings.Default.CommitteeMembersCount - 1; i++) @@ -388,79 +389,79 @@ public void Check_GetCommittee() [TestMethod] public void Check_Transfer() { - var snapshot = _snapshot.CreateSnapshot(); + var clonedCache = _snapshotCache.CloneCache(); var persistingBlock = new Block { Header = new Header { Index = 1000 } }; byte[] from = Contract.GetBFTAddress(TestProtocolSettings.Default.StandbyValidators).ToArray(); byte[] to = new byte[20]; var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); - snapshot.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); - var keyCount = snapshot.GetChangeSet().Count(); + clonedCache.Add(storageKey, new StorageItem(new HashIndexState { Hash = UInt256.Zero, Index = persistingBlock.Index - 1 })); + var keyCount = clonedCache.GetChangeSet().Count(); // Check unclaim - var unclaim = Check_UnclaimedGas(snapshot, from, persistingBlock); + var unclaim = Check_UnclaimedGas(clonedCache, from, persistingBlock); unclaim.Value.Should().Be(new BigInteger(0.5 * 1000 * 100000000L)); unclaim.State.Should().BeTrue(); // Transfer - NativeContract.NEO.Transfer(snapshot, from, to, BigInteger.One, false, persistingBlock).Should().BeFalse(); // Not signed - NativeContract.NEO.Transfer(snapshot, from, to, BigInteger.One, true, persistingBlock).Should().BeTrue(); - NativeContract.NEO.BalanceOf(snapshot, from).Should().Be(99999999); - NativeContract.NEO.BalanceOf(snapshot, to).Should().Be(1); + NativeContract.NEO.Transfer(clonedCache, from, to, BigInteger.One, false, persistingBlock).Should().BeFalse(); // Not signed + NativeContract.NEO.Transfer(clonedCache, from, to, BigInteger.One, true, persistingBlock).Should().BeTrue(); + NativeContract.NEO.BalanceOf(clonedCache, from).Should().Be(99999999); + NativeContract.NEO.BalanceOf(clonedCache, to).Should().Be(1); - var (from_balance, _, _) = GetAccountState(snapshot, new UInt160(from)); - var (to_balance, _, _) = GetAccountState(snapshot, new UInt160(to)); + var (from_balance, _, _) = GetAccountState(clonedCache, new UInt160(from)); + var (to_balance, _, _) = GetAccountState(clonedCache, new UInt160(to)); from_balance.Should().Be(99999999); to_balance.Should().Be(1); // Check unclaim - unclaim = Check_UnclaimedGas(snapshot, from, persistingBlock); + unclaim = Check_UnclaimedGas(clonedCache, from, persistingBlock); unclaim.Value.Should().Be(new BigInteger(0)); unclaim.State.Should().BeTrue(); - snapshot.GetChangeSet().Count().Should().Be(keyCount + 4); // Gas + new balance + clonedCache.GetChangeSet().Count().Should().Be(keyCount + 4); // Gas + new balance // Return balance - keyCount = snapshot.GetChangeSet().Count(); + keyCount = clonedCache.GetChangeSet().Count(); - NativeContract.NEO.Transfer(snapshot, to, from, BigInteger.One, true, persistingBlock).Should().BeTrue(); - NativeContract.NEO.BalanceOf(snapshot, to).Should().Be(0); - snapshot.GetChangeSet().Count().Should().Be(keyCount - 1); // Remove neo balance from address two + NativeContract.NEO.Transfer(clonedCache, to, from, BigInteger.One, true, persistingBlock).Should().BeTrue(); + NativeContract.NEO.BalanceOf(clonedCache, to).Should().Be(0); + clonedCache.GetChangeSet().Count().Should().Be(keyCount - 1); // Remove neo balance from address two // Bad inputs - Assert.ThrowsException(() => NativeContract.NEO.Transfer(snapshot, from, to, BigInteger.MinusOne, true, persistingBlock)); - Assert.ThrowsException(() => NativeContract.NEO.Transfer(snapshot, new byte[19], to, BigInteger.One, false, persistingBlock)); - Assert.ThrowsException(() => NativeContract.NEO.Transfer(snapshot, from, new byte[19], BigInteger.One, false, persistingBlock)); + Assert.ThrowsException(() => NativeContract.NEO.Transfer(clonedCache, from, to, BigInteger.MinusOne, true, persistingBlock)); + Assert.ThrowsException(() => NativeContract.NEO.Transfer(clonedCache, new byte[19], to, BigInteger.One, false, persistingBlock)); + Assert.ThrowsException(() => NativeContract.NEO.Transfer(clonedCache, from, new byte[19], BigInteger.One, false, persistingBlock)); // More than balance - NativeContract.NEO.Transfer(snapshot, to, from, new BigInteger(2), true, persistingBlock).Should().BeFalse(); + NativeContract.NEO.Transfer(clonedCache, to, from, new BigInteger(2), true, persistingBlock).Should().BeFalse(); } [TestMethod] public void Check_BalanceOf() { - var snapshot = _snapshot.CreateSnapshot(); + var clonedCache = _snapshotCache.CloneCache(); byte[] account = Contract.GetBFTAddress(TestProtocolSettings.Default.StandbyValidators).ToArray(); - NativeContract.NEO.BalanceOf(snapshot, account).Should().Be(100_000_000); + NativeContract.NEO.BalanceOf(clonedCache, account).Should().Be(100_000_000); account[5]++; // Without existing balance - NativeContract.NEO.BalanceOf(snapshot, account).Should().Be(0); + NativeContract.NEO.BalanceOf(clonedCache, account).Should().Be(0); } [TestMethod] public void Check_CommitteeBonus() { - var snapshot = _snapshot.CreateSnapshot(); + var clonedCache = _snapshotCache.CloneCache(); var persistingBlock = new Block { Header = new Header @@ -474,103 +475,103 @@ public void Check_CommitteeBonus() Transactions = Array.Empty() }; - Check_PostPersist(snapshot, persistingBlock).Should().BeTrue(); + Check_PostPersist(clonedCache, persistingBlock).Should().BeTrue(); var committee = TestProtocolSettings.Default.StandbyCommittee; - NativeContract.GAS.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[0]).ScriptHash.ToArray()).Should().Be(50000000); - NativeContract.GAS.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[1]).ScriptHash.ToArray()).Should().Be(50000000); - NativeContract.GAS.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[2]).ScriptHash.ToArray()).Should().Be(0); + NativeContract.GAS.BalanceOf(clonedCache, Contract.CreateSignatureContract(committee[0]).ScriptHash.ToArray()).Should().Be(50000000); + NativeContract.GAS.BalanceOf(clonedCache, Contract.CreateSignatureContract(committee[1]).ScriptHash.ToArray()).Should().Be(50000000); + NativeContract.GAS.BalanceOf(clonedCache, Contract.CreateSignatureContract(committee[2]).ScriptHash.ToArray()).Should().Be(0); } [TestMethod] public void Check_Initialize() { - var snapshot = _snapshot.CreateSnapshot(); + var clonedCache = _snapshotCache.CloneCache(); // StandbyValidators - Check_GetCommittee(snapshot, null); + Check_GetCommittee(clonedCache, null); } [TestMethod] public void TestCalculateBonus() { - var snapshot = _snapshot.CreateSnapshot(); + var clonedCache = _snapshotCache.CloneCache(); var persistingBlock = new Block(); StorageKey key = CreateStorageKey(20, UInt160.Zero.ToArray()); // Fault: balance < 0 - snapshot.Add(key, new StorageItem(new NeoAccountState + clonedCache.Add(key, new StorageItem(new NeoAccountState { Balance = -100 })); - Action action = () => NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 10).Should().Be(new BigInteger(0)); + Action action = () => NativeContract.NEO.UnclaimedGas(clonedCache, UInt160.Zero, 10).Should().Be(new BigInteger(0)); action.Should().Throw(); - snapshot.Delete(key); + clonedCache.Delete(key); // Fault range: start >= end - snapshot.GetAndChange(key, () => new StorageItem(new NeoAccountState + clonedCache.GetAndChange(key, () => new StorageItem(new NeoAccountState { Balance = 100, BalanceHeight = 100 })); - action = () => NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 10).Should().Be(new BigInteger(0)); - snapshot.Delete(key); + action = () => NativeContract.NEO.UnclaimedGas(clonedCache, UInt160.Zero, 10).Should().Be(new BigInteger(0)); + clonedCache.Delete(key); // Fault range: start >= end - snapshot.GetAndChange(key, () => new StorageItem(new NeoAccountState + clonedCache.GetAndChange(key, () => new StorageItem(new NeoAccountState { Balance = 100, BalanceHeight = 100 })); - action = () => NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 10).Should().Be(new BigInteger(0)); - snapshot.Delete(key); + action = () => NativeContract.NEO.UnclaimedGas(clonedCache, UInt160.Zero, 10).Should().Be(new BigInteger(0)); + clonedCache.Delete(key); // Normal 1) votee is non exist - snapshot.GetAndChange(key, () => new StorageItem(new NeoAccountState + clonedCache.GetAndChange(key, () => new StorageItem(new NeoAccountState { Balance = 100 })); var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); - var item = snapshot.GetAndChange(storageKey).GetInteroperable(); + var item = clonedCache.GetAndChange(storageKey).GetInteroperable(); item.Index = 99; - NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 100).Should().Be(new BigInteger(0.5 * 100 * 100)); - snapshot.Delete(key); + NativeContract.NEO.UnclaimedGas(clonedCache, UInt160.Zero, 100).Should().Be(new BigInteger(0.5 * 100 * 100)); + clonedCache.Delete(key); // Normal 2) votee is not committee - snapshot.GetAndChange(key, () => new StorageItem(new NeoAccountState + clonedCache.GetAndChange(key, () => new StorageItem(new NeoAccountState { Balance = 100, VoteTo = ECCurve.Secp256r1.G })); - NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 100).Should().Be(new BigInteger(0.5 * 100 * 100)); - snapshot.Delete(key); + NativeContract.NEO.UnclaimedGas(clonedCache, UInt160.Zero, 100).Should().Be(new BigInteger(0.5 * 100 * 100)); + clonedCache.Delete(key); // Normal 3) votee is committee - snapshot.GetAndChange(key, () => new StorageItem(new NeoAccountState + clonedCache.GetAndChange(key, () => new StorageItem(new NeoAccountState { Balance = 100, VoteTo = TestProtocolSettings.Default.StandbyCommittee[0] })); - snapshot.Add(new KeyBuilder(NativeContract.NEO.Id, 23).Add(TestProtocolSettings.Default.StandbyCommittee[0]).AddBigEndian(uint.MaxValue - 50), new StorageItem() { Value = new BigInteger(50 * 10000L).ToByteArray() }); - NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, 100).Should().Be(new BigInteger(50 * 100)); - snapshot.Delete(key); + clonedCache.Add(new KeyBuilder(NativeContract.NEO.Id, 23).Add(TestProtocolSettings.Default.StandbyCommittee[0]).AddBigEndian(uint.MaxValue - 50), new StorageItem() { Value = new BigInteger(50 * 10000L).ToByteArray() }); + NativeContract.NEO.UnclaimedGas(clonedCache, UInt160.Zero, 100).Should().Be(new BigInteger(50 * 100)); + clonedCache.Delete(key); } [TestMethod] public void TestGetNextBlockValidators1() { - var snapshot = TestBlockchain.GetTestSnapshot(); - var result = (VM.Types.Array)NativeContract.NEO.Call(snapshot, "getNextBlockValidators"); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + var result = (VM.Types.Array)NativeContract.NEO.Call(snapshotCache, "getNextBlockValidators"); result.Count.Should().Be(7); result[0].GetSpan().ToHexString().Should().Be("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70"); result[1].GetSpan().ToHexString().Should().Be("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d"); @@ -584,8 +585,8 @@ public void TestGetNextBlockValidators1() [TestMethod] public void TestGetNextBlockValidators2() { - var snapshot = _snapshot.CreateSnapshot(); - var result = NativeContract.NEO.GetNextBlockValidators(snapshot, 7); + var clonedCache = _snapshotCache.CloneCache(); + var result = NativeContract.NEO.GetNextBlockValidators(clonedCache, 7); result.Length.Should().Be(7); result[0].ToArray().ToHexString().Should().Be("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70"); result[1].ToArray().ToHexString().Should().Be("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d"); @@ -599,40 +600,40 @@ public void TestGetNextBlockValidators2() [TestMethod] public void TestGetCandidates1() { - var snapshot = TestBlockchain.GetTestSnapshot(); - var array = (VM.Types.Array)NativeContract.NEO.Call(snapshot, "getCandidates"); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + var array = (VM.Types.Array)NativeContract.NEO.Call(snapshotCache, "getCandidates"); array.Count.Should().Be(0); } [TestMethod] public void TestGetCandidates2() { - var snapshot = _snapshot.CreateSnapshot(); - var result = NativeContract.NEO.GetCandidatesInternal(snapshot); + var clonedCache = _snapshotCache.CloneCache(); + var result = NativeContract.NEO.GetCandidatesInternal(clonedCache); result.Count().Should().Be(0); StorageKey key = NativeContract.NEO.CreateStorageKey(33, ECCurve.Secp256r1.G); - snapshot.Add(key, new StorageItem(new CandidateState() { Registered = true })); - NativeContract.NEO.GetCandidatesInternal(snapshot).Count().Should().Be(1); + clonedCache.Add(key, new StorageItem(new CandidateState() { Registered = true })); + NativeContract.NEO.GetCandidatesInternal(clonedCache).Count().Should().Be(1); } [TestMethod] public void TestCheckCandidate() { - var snapshot = _snapshot.CreateSnapshot(); - var committee = NativeContract.NEO.GetCommittee(snapshot); + var cloneCache = _snapshotCache.CloneCache(); + var committee = NativeContract.NEO.GetCommittee(cloneCache); var point = committee[0].EncodePoint(true); // Prepare Prefix_VoterRewardPerCommittee var storageKey = new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[0]); - snapshot.Add(storageKey, new StorageItem(new BigInteger(1000))); + cloneCache.Add(storageKey, new StorageItem(new BigInteger(1000))); // Prepare Candidate storageKey = new KeyBuilder(NativeContract.NEO.Id, 33).Add(committee[0]); - snapshot.Add(storageKey, new StorageItem(new CandidateState { Registered = true, Votes = BigInteger.One })); + cloneCache.Add(storageKey, new StorageItem(new CandidateState { Registered = true, Votes = BigInteger.One })); storageKey = new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[0]); - snapshot.Find(storageKey.ToArray()).ToArray().Length.Should().Be(1); + cloneCache.Find(storageKey.ToArray()).ToArray().Length.Should().Be(1); // Pre-persist var persistingBlock = new Block @@ -647,32 +648,32 @@ public void TestCheckCandidate() }, Transactions = Array.Empty() }; - Check_OnPersist(snapshot, persistingBlock).Should().BeTrue(); + Check_OnPersist(cloneCache, persistingBlock).Should().BeTrue(); // Clear votes storageKey = new KeyBuilder(NativeContract.NEO.Id, 33).Add(committee[0]); - snapshot.GetAndChange(storageKey).GetInteroperable().Votes = BigInteger.Zero; + cloneCache.GetAndChange(storageKey).GetInteroperable().Votes = BigInteger.Zero; // Unregister candidate, remove - var ret = Check_UnregisterCandidate(snapshot, point, persistingBlock); + var ret = Check_UnregisterCandidate(cloneCache, point, persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeTrue(); storageKey = new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[0]); - snapshot.Find(storageKey.ToArray()).ToArray().Length.Should().Be(0); + cloneCache.Find(storageKey.ToArray()).ToArray().Length.Should().Be(0); // Post-persist - Check_PostPersist(snapshot, persistingBlock).Should().BeTrue(); + Check_PostPersist(cloneCache, persistingBlock).Should().BeTrue(); storageKey = new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[0]); - snapshot.Find(storageKey.ToArray()).ToArray().Length.Should().Be(1); + cloneCache.Find(storageKey.ToArray()).ToArray().Length.Should().Be(1); } [TestMethod] public void TestGetCommittee() { - var snapshot = TestBlockchain.GetTestSnapshot(); - var result = (VM.Types.Array)NativeContract.NEO.Call(snapshot, "getCommittee"); + var clonedCache = TestBlockchain.GetTestSnapshotCache(); + var result = (VM.Types.Array)NativeContract.NEO.Call(clonedCache, "getCommittee"); result.Count.Should().Be(21); result[0].GetSpan().ToHexString().Should().Be("020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639"); result[1].GetSpan().ToHexString().Should().Be("03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0"); @@ -700,8 +701,8 @@ public void TestGetCommittee() [TestMethod] public void TestGetValidators() { - var snapshot = _snapshot.CreateSnapshot(); - var result = NativeContract.NEO.ComputeNextBlockValidators(snapshot, TestProtocolSettings.Default); + var clonedCache = _snapshotCache.CloneCache(); + var result = NativeContract.NEO.ComputeNextBlockValidators(clonedCache, TestProtocolSettings.Default); result[0].ToArray().ToHexString().Should().Be("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70"); result[1].ToArray().ToHexString().Should().Be("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d"); result[2].ToArray().ToHexString().Should().Be("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e"); @@ -730,64 +731,64 @@ public void TestOnBalanceChanging() [TestMethod] public void TestTotalSupply() { - var snapshot = _snapshot.CreateSnapshot(); - NativeContract.NEO.TotalSupply(snapshot).Should().Be(new BigInteger(100000000)); + var clonedCache = _snapshotCache.CloneCache(); + NativeContract.NEO.TotalSupply(clonedCache).Should().Be(new BigInteger(100000000)); } [TestMethod] public void TestEconomicParameter() { const byte Prefix_CurrentBlock = 12; - var snapshot = _snapshot.CreateSnapshot(); + var clonedCache = _snapshotCache.CloneCache(); var persistingBlock = new Block { Header = new Header() }; - (BigInteger, bool) result = Check_GetGasPerBlock(snapshot, persistingBlock); + (BigInteger, bool) result = Check_GetGasPerBlock(clonedCache, persistingBlock); result.Item2.Should().BeTrue(); result.Item1.Should().Be(5 * NativeContract.GAS.Factor); persistingBlock = new Block { Header = new Header { Index = 10 } }; - (VM.Types.Boolean, bool) result1 = Check_SetGasPerBlock(snapshot, 10 * NativeContract.GAS.Factor, persistingBlock); + (VM.Types.Boolean, bool) result1 = Check_SetGasPerBlock(clonedCache, 10 * NativeContract.GAS.Factor, persistingBlock); result1.Item2.Should().BeTrue(); result1.Item1.GetBoolean().Should().BeTrue(); - var height = snapshot[NativeContract.Ledger.CreateStorageKey(Prefix_CurrentBlock)].GetInteroperable(); + var height = clonedCache[NativeContract.Ledger.CreateStorageKey(Prefix_CurrentBlock)].GetInteroperable(); height.Index = persistingBlock.Index + 1; - result = Check_GetGasPerBlock(snapshot, persistingBlock); + result = Check_GetGasPerBlock(clonedCache, persistingBlock); result.Item2.Should().BeTrue(); result.Item1.Should().Be(10 * NativeContract.GAS.Factor); // Check calculate bonus - StorageItem storage = snapshot.GetOrAdd(CreateStorageKey(20, UInt160.Zero.ToArray()), () => new StorageItem(new NeoAccountState())); + StorageItem storage = clonedCache.GetOrAdd(CreateStorageKey(20, UInt160.Zero.ToArray()), () => new StorageItem(new NeoAccountState())); NeoAccountState state = storage.GetInteroperable(); state.Balance = 1000; state.BalanceHeight = 0; height.Index = persistingBlock.Index + 1; - NativeContract.NEO.UnclaimedGas(snapshot, UInt160.Zero, persistingBlock.Index + 2).Should().Be(6500); + NativeContract.NEO.UnclaimedGas(clonedCache, UInt160.Zero, persistingBlock.Index + 2).Should().Be(6500); } [TestMethod] public void TestClaimGas() { - var snapshot = _snapshot.CreateSnapshot(); + var clonedCache = _snapshotCache.CloneCache(); // Initialize block - snapshot.Add(CreateStorageKey(1), new StorageItem(new BigInteger(30000000))); + clonedCache.Add(CreateStorageKey(1), new StorageItem(new BigInteger(30000000))); ECPoint[] standbyCommittee = TestProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray(); CachedCommittee cachedCommittee = new(); for (var i = 0; i < TestProtocolSettings.Default.CommitteeMembersCount; i++) { ECPoint member = standbyCommittee[i]; - snapshot.Add(new KeyBuilder(NativeContract.NEO.Id, 33).Add(member), new StorageItem(new CandidateState() + clonedCache.Add(new KeyBuilder(NativeContract.NEO.Id, 33).Add(member), new StorageItem(new CandidateState() { Registered = true, Votes = 200 * 10000 })); cachedCommittee.Add((member, 200 * 10000)); } - snapshot.GetOrAdd(new KeyBuilder(NativeContract.NEO.Id, 14), () => new StorageItem()).Value = BinarySerializer.Serialize(cachedCommittee.ToStackItem(null), ExecutionEngineLimits.Default); + clonedCache.GetOrAdd(new KeyBuilder(NativeContract.NEO.Id, 14), () => new StorageItem()).Value = BinarySerializer.Serialize(cachedCommittee.ToStackItem(null), ExecutionEngineLimits.Default); - var item = snapshot.GetAndChange(new KeyBuilder(NativeContract.NEO.Id, 1), () => new StorageItem()); + var item = clonedCache.GetAndChange(new KeyBuilder(NativeContract.NEO.Id, 1), () => new StorageItem()); item.Value = ((BigInteger)2100 * 10000L).ToByteArray(); var persistingBlock = new Block @@ -802,17 +803,17 @@ public void TestClaimGas() }, Transactions = Array.Empty() }; - Check_PostPersist(snapshot, persistingBlock).Should().BeTrue(); + Check_PostPersist(clonedCache, persistingBlock).Should().BeTrue(); var committee = TestProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray(); var accountA = committee[0]; var accountB = committee[TestProtocolSettings.Default.CommitteeMembersCount - 1]; - NativeContract.NEO.BalanceOf(snapshot, Contract.CreateSignatureContract(accountA).ScriptHash).Should().Be(0); + NativeContract.NEO.BalanceOf(clonedCache, Contract.CreateSignatureContract(accountA).ScriptHash).Should().Be(0); - StorageItem storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(accountA)); + StorageItem storageItem = clonedCache.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(accountA)); ((BigInteger)storageItem).Should().Be(30000000000); - snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(accountB).AddBigEndian(uint.MaxValue - 1)).Should().BeNull(); + clonedCache.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(accountB).AddBigEndian(uint.MaxValue - 1)).Should().BeNull(); // Next block @@ -828,11 +829,11 @@ public void TestClaimGas() }, Transactions = Array.Empty() }; - Check_PostPersist(snapshot, persistingBlock).Should().BeTrue(); + Check_PostPersist(clonedCache, persistingBlock).Should().BeTrue(); - NativeContract.NEO.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[1]).ScriptHash).Should().Be(0); + NativeContract.NEO.BalanceOf(clonedCache, Contract.CreateSignatureContract(committee[1]).ScriptHash).Should().Be(0); - storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[1])); + storageItem = clonedCache.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[1])); ((BigInteger)storageItem).Should().Be(30000000000); // Next block @@ -849,99 +850,99 @@ public void TestClaimGas() }, Transactions = Array.Empty() }; - Check_PostPersist(snapshot, persistingBlock).Should().BeTrue(); + Check_PostPersist(clonedCache, persistingBlock).Should().BeTrue(); accountA = TestProtocolSettings.Default.StandbyCommittee.OrderBy(p => p).ToArray()[2]; - NativeContract.NEO.BalanceOf(snapshot, Contract.CreateSignatureContract(committee[2]).ScriptHash).Should().Be(0); + NativeContract.NEO.BalanceOf(clonedCache, Contract.CreateSignatureContract(committee[2]).ScriptHash).Should().Be(0); - storageItem = snapshot.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[2])); + storageItem = clonedCache.TryGet(new KeyBuilder(NativeContract.NEO.Id, 23).Add(committee[2])); ((BigInteger)storageItem).Should().Be(30000000000 * 2); // Claim GAS var account = Contract.CreateSignatureContract(committee[2]).ScriptHash; - snapshot.Add(new KeyBuilder(NativeContract.NEO.Id, 20).Add(account), new StorageItem(new NeoAccountState + clonedCache.Add(new KeyBuilder(NativeContract.NEO.Id, 20).Add(account), new StorageItem(new NeoAccountState { BalanceHeight = 3, Balance = 200 * 10000 - 2 * 100, VoteTo = committee[2], LastGasPerVote = 30000000000, })); - NativeContract.NEO.BalanceOf(snapshot, account).Should().Be(1999800); + NativeContract.NEO.BalanceOf(clonedCache, account).Should().Be(1999800); var storageKey = new KeyBuilder(NativeContract.Ledger.Id, 12); - snapshot.GetAndChange(storageKey).GetInteroperable().Index = 29 + 2; - BigInteger value = NativeContract.NEO.UnclaimedGas(snapshot, account, 29 + 3); + clonedCache.GetAndChange(storageKey).GetInteroperable().Index = 29 + 2; + BigInteger value = NativeContract.NEO.UnclaimedGas(clonedCache, account, 29 + 3); value.Should().Be(1999800 * 30000000000 / 100000000L + (1999800L * 10 * 5 * 29 / 100)); } [TestMethod] public void TestUnclaimedGas() { - var snapshot = _snapshot.CreateSnapshot(); - 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)); + var clonedCache = _snapshotCache.CloneCache(); + NativeContract.NEO.UnclaimedGas(clonedCache, UInt160.Zero, 10).Should().Be(new BigInteger(0)); + clonedCache.Add(CreateStorageKey(20, UInt160.Zero.ToArray()), new StorageItem(new NeoAccountState())); + NativeContract.NEO.UnclaimedGas(clonedCache, UInt160.Zero, 10).Should().Be(new BigInteger(0)); } [TestMethod] public void TestVote() { - var snapshot = _snapshot.CreateSnapshot(); + var clonedCache = _snapshotCache.CloneCache(); UInt160 account = UInt160.Parse("01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4"); StorageKey keyAccount = CreateStorageKey(20, account.ToArray()); StorageKey keyValidator = CreateStorageKey(33, ECCurve.Secp256r1.G.ToArray()); _persistingBlock.Header.Index = 1; - var ret = Check_Vote(snapshot, account.ToArray(), ECCurve.Secp256r1.G.ToArray(), false, _persistingBlock); + var ret = Check_Vote(clonedCache, account.ToArray(), ECCurve.Secp256r1.G.ToArray(), false, _persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeFalse(); - ret = Check_Vote(snapshot, account.ToArray(), ECCurve.Secp256r1.G.ToArray(), true, _persistingBlock); + ret = Check_Vote(clonedCache, account.ToArray(), ECCurve.Secp256r1.G.ToArray(), true, _persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeFalse(); - snapshot.Add(keyAccount, new StorageItem(new NeoAccountState())); - ret = Check_Vote(snapshot, account.ToArray(), ECCurve.Secp256r1.G.ToArray(), true, _persistingBlock); + clonedCache.Add(keyAccount, new StorageItem(new NeoAccountState())); + ret = Check_Vote(clonedCache, account.ToArray(), ECCurve.Secp256r1.G.ToArray(), true, _persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeFalse(); - var (_, _, vote_to_null) = GetAccountState(snapshot, account); + var (_, _, vote_to_null) = GetAccountState(clonedCache, account); vote_to_null.Should().BeNull(); - snapshot.Delete(keyAccount); - snapshot.GetAndChange(keyAccount, () => new StorageItem(new NeoAccountState + clonedCache.Delete(keyAccount); + clonedCache.GetAndChange(keyAccount, () => new StorageItem(new NeoAccountState { Balance = 1, VoteTo = ECCurve.Secp256r1.G })); - snapshot.Add(keyValidator, new StorageItem(new CandidateState() { Registered = true })); - ret = Check_Vote(snapshot, account.ToArray(), ECCurve.Secp256r1.G.ToArray(), true, _persistingBlock); + clonedCache.Add(keyValidator, new StorageItem(new CandidateState() { Registered = true })); + ret = Check_Vote(clonedCache, account.ToArray(), ECCurve.Secp256r1.G.ToArray(), true, _persistingBlock); ret.State.Should().BeTrue(); ret.Result.Should().BeTrue(); - var (_, _, voteto) = GetAccountState(snapshot, account); + var (_, _, voteto) = GetAccountState(clonedCache, account); voteto.ToHexString().Should().Be(ECCurve.Secp256r1.G.ToArray().ToHexString()); } internal (bool State, bool Result) Transfer4TesingOnBalanceChanging(BigInteger amount, bool addVotes) { - var snapshot = _snapshot.CreateSnapshot(); + var clonedCache = _snapshotCache.CloneCache(); _persistingBlock.Header.Index = 1; - var engine = ApplicationEngine.Create(TriggerType.Application, TestBlockchain.TheNeoSystem.GenesisBlock, snapshot, _persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); + var engine = ApplicationEngine.Create(TriggerType.Application, TestBlockchain.TheNeoSystem.GenesisBlock, clonedCache, _persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); ScriptBuilder sb = new(); - var tmp = engine.ScriptContainer.GetScriptHashesForVerifying(engine.Snapshot); - UInt160 from = engine.ScriptContainer.GetScriptHashesForVerifying(engine.Snapshot)[0]; + var tmp = engine.ScriptContainer.GetScriptHashesForVerifying(engine.SnapshotCache); + UInt160 from = engine.ScriptContainer.GetScriptHashesForVerifying(engine.SnapshotCache)[0]; if (addVotes) { - snapshot.Add(CreateStorageKey(20, from.ToArray()), new StorageItem(new NeoAccountState + clonedCache.Add(CreateStorageKey(20, from.ToArray()), new StorageItem(new NeoAccountState { VoteTo = ECCurve.Secp256r1.G, Balance = new BigInteger(1000) })); - snapshot.Add(NativeContract.NEO.CreateStorageKey(33, ECCurve.Secp256r1.G), new StorageItem(new CandidateState())); + clonedCache.Add(NativeContract.NEO.CreateStorageKey(33, ECCurve.Secp256r1.G), new StorageItem(new CandidateState())); } else { - snapshot.Add(CreateStorageKey(20, from.ToArray()), new StorageItem(new NeoAccountState + clonedCache.Add(CreateStorageKey(20, from.ToArray()), new StorageItem(new NeoAccountState { Balance = new BigInteger(1000) })); @@ -956,29 +957,29 @@ public void TestVote() return (true, result.GetBoolean()); } - internal static bool Check_OnPersist(DataCache snapshot, Block persistingBlock) + internal static bool Check_OnPersist(DataCache clonedCache, Block persistingBlock) { var script = new ScriptBuilder(); script.EmitSysCall(ApplicationEngine.System_Contract_NativeOnPersist); - var engine = ApplicationEngine.Create(TriggerType.OnPersist, null, snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); + var engine = ApplicationEngine.Create(TriggerType.OnPersist, null, clonedCache, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); return engine.Execute() == VMState.HALT; } - internal static bool Check_PostPersist(DataCache snapshot, Block persistingBlock) + internal static bool Check_PostPersist(DataCache clonedCache, Block persistingBlock) { using var script = new ScriptBuilder(); script.EmitSysCall(ApplicationEngine.System_Contract_NativePostPersist); - using var engine = ApplicationEngine.Create(TriggerType.PostPersist, null, snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.PostPersist, null, clonedCache, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); return engine.Execute() == VMState.HALT; } - internal static (BigInteger Value, bool State) Check_GetGasPerBlock(DataCache snapshot, Block persistingBlock) + internal static (BigInteger Value, bool State) Check_GetGasPerBlock(DataCache clonedCache, Block persistingBlock) { - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, clonedCache, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NEO.Hash, "getGasPerBlock"); @@ -995,10 +996,10 @@ internal static (BigInteger Value, bool State) Check_GetGasPerBlock(DataCache sn return (((VM.Types.Integer)result).GetInteger(), true); } - internal static (VM.Types.Boolean Value, bool State) Check_SetGasPerBlock(DataCache snapshot, BigInteger gasPerBlock, Block persistingBlock) + internal static (VM.Types.Boolean Value, bool State) Check_SetGasPerBlock(DataCache clonedCache, BigInteger gasPerBlock, Block persistingBlock) { - UInt160 committeeMultiSigAddr = NativeContract.NEO.GetCommitteeAddress(snapshot); - using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); + UInt160 committeeMultiSigAddr = NativeContract.NEO.GetCommitteeAddress(clonedCache); + using var engine = ApplicationEngine.Create(TriggerType.Application, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), clonedCache, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NEO.Hash, "setGasPerBlock", gasPerBlock); @@ -1012,10 +1013,10 @@ internal static (VM.Types.Boolean Value, bool State) Check_SetGasPerBlock(DataCa return (true, true); } - internal static (bool State, bool Result) Check_Vote(DataCache snapshot, byte[] account, byte[] pubkey, bool signAccount, Block persistingBlock) + internal static (bool State, bool Result) Check_Vote(DataCache clonedCache, byte[] account, byte[] pubkey, bool signAccount, Block persistingBlock) { using var engine = ApplicationEngine.Create(TriggerType.Application, - new Nep17NativeContractExtensions.ManualWitness(signAccount ? new UInt160(account) : UInt160.Zero), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); + new Nep17NativeContractExtensions.ManualWitness(signAccount ? new UInt160(account) : UInt160.Zero), clonedCache, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NEO.Hash, "vote", account, pubkey); @@ -1033,10 +1034,10 @@ internal static (bool State, bool Result) Check_Vote(DataCache snapshot, byte[] return (true, result.GetBoolean()); } - internal static (bool State, bool Result) Check_RegisterValidator(DataCache snapshot, byte[] pubkey, Block persistingBlock) + internal static (bool State, bool Result) Check_RegisterValidator(DataCache clonedCache, byte[] pubkey, Block persistingBlock) { using var engine = ApplicationEngine.Create(TriggerType.Application, - new Nep17NativeContractExtensions.ManualWitness(Contract.CreateSignatureRedeemScript(ECPoint.DecodePoint(pubkey, ECCurve.Secp256r1)).ToScriptHash()), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings, gas: 1100_00000000); + new Nep17NativeContractExtensions.ManualWitness(Contract.CreateSignatureRedeemScript(ECPoint.DecodePoint(pubkey, ECCurve.Secp256r1)).ToScriptHash()), clonedCache, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings, gas: 1100_00000000); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NEO.Hash, "registerCandidate", pubkey); @@ -1053,9 +1054,9 @@ internal static (bool State, bool Result) Check_RegisterValidator(DataCache snap return (true, result.GetBoolean()); } - internal static ECPoint[] Check_GetCommittee(DataCache snapshot, Block persistingBlock) + internal static ECPoint[] Check_GetCommittee(DataCache clonedCache, Block persistingBlock) { - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, clonedCache, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NEO.Hash, "getCommittee"); @@ -1069,9 +1070,9 @@ internal static ECPoint[] Check_GetCommittee(DataCache snapshot, Block persistin return (result as VM.Types.Array).Select(u => ECPoint.DecodePoint(u.GetSpan(), ECCurve.Secp256r1)).ToArray(); } - internal static (BigInteger Value, bool State) Check_UnclaimedGas(DataCache snapshot, byte[] address, Block persistingBlock) + internal static (BigInteger Value, bool State) Check_UnclaimedGas(DataCache clonedCache, byte[] address, Block persistingBlock) { - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, clonedCache, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NEO.Hash, "unclaimedGas", address, persistingBlock.Index); @@ -1123,10 +1124,10 @@ internal static StorageKey CreateStorageKey(byte prefix, byte[] key = null) }; } - internal static (bool State, bool Result) Check_UnregisterCandidate(DataCache snapshot, byte[] pubkey, Block persistingBlock) + internal static (bool State, bool Result) Check_UnregisterCandidate(DataCache clonedCache, byte[] pubkey, Block persistingBlock) { using var engine = ApplicationEngine.Create(TriggerType.Application, - new Nep17NativeContractExtensions.ManualWitness(Contract.CreateSignatureRedeemScript(ECPoint.DecodePoint(pubkey, ECCurve.Secp256r1)).ToScriptHash()), snapshot, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); + new Nep17NativeContractExtensions.ManualWitness(Contract.CreateSignatureRedeemScript(ECPoint.DecodePoint(pubkey, ECCurve.Secp256r1)).ToScriptHash()), clonedCache, persistingBlock, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NEO.Hash, "unregisterCandidate", pubkey); @@ -1143,9 +1144,9 @@ internal static (bool State, bool Result) Check_UnregisterCandidate(DataCache sn return (true, result.GetBoolean()); } - internal static (BigInteger balance, BigInteger height, byte[] voteto) GetAccountState(DataCache snapshot, UInt160 account) + internal static (BigInteger balance, BigInteger height, byte[] voteto) GetAccountState(DataCache clonedCache, UInt160 account) { - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, clonedCache, settings: TestBlockchain.TheNeoSystem.Settings); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.NEO.Hash, "getAccountState", account); diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs index 703253364c..745670977e 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs @@ -26,21 +26,21 @@ namespace Neo.UnitTests.SmartContract.Native [TestClass] public class UT_PolicyContract { - private DataCache _snapshot; + private DataCache _snapshotCache; [TestInitialize] public void TestSetup() { - _snapshot = TestBlockchain.GetTestSnapshot(); + _snapshotCache = TestBlockchain.GetTestSnapshotCache(); - ApplicationEngine engine = ApplicationEngine.Create(TriggerType.OnPersist, null, _snapshot, new Block { Header = new Header() }, settings: TestBlockchain.TheNeoSystem.Settings, gas: 0); + ApplicationEngine engine = ApplicationEngine.Create(TriggerType.OnPersist, null, _snapshotCache, new Block { Header = new Header() }, settings: TestBlockchain.TheNeoSystem.Settings, gas: 0); NativeContract.ContractManagement.OnPersistAsync(engine); } [TestMethod] public void Check_Default() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshotCache.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 = _snapshotCache.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 = _snapshotCache.CloneCache(); // Fake blockchain @@ -154,7 +154,7 @@ public void Check_SetFeePerByte() [TestMethod] public void Check_SetBaseExecFee() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshotCache.CloneCache(); // Fake blockchain @@ -204,7 +204,7 @@ public void Check_SetBaseExecFee() [TestMethod] public void Check_SetStoragePrice() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshotCache.CloneCache(); // Fake blockchain @@ -254,7 +254,7 @@ public void Check_SetStoragePrice() [TestMethod] public void Check_BlockAccount() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshotCache.CloneCache(); // Fake blockchain @@ -310,7 +310,7 @@ public void Check_BlockAccount() [TestMethod] public void Check_Block_UnblockAccount() { - var snapshot = _snapshot.CreateSnapshot(); + var snapshot = _snapshotCache.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..c8d5a176f2 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_RoleManagement.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_RoleManagement.cs @@ -12,6 +12,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; @@ -29,12 +30,12 @@ namespace Neo.UnitTests.SmartContract.Native [TestClass] public class UT_RoleManagement { - private DataCache _snapshot; + private DataCache _snapshotCache; [TestInitialize] public void TestSetup() { - _snapshot = TestBlockchain.GetTestSnapshot(); + _snapshotCache = TestBlockchain.GetTestSnapshotCache(); } [TestCleanup] @@ -62,7 +63,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 = _snapshotCache.CloneCache(); UInt160 committeeMultiSigAddr = NativeContract.NEO.GetCommitteeAddress(snapshot1); List notifications = new List(); EventHandler ev = (o, e) => notifications.Add(e); @@ -79,7 +80,7 @@ public void TestSetAndGet() ApplicationEngine.Notify -= ev; notifications.Count.Should().Be(1); notifications[0].EventName.Should().Be("Designation"); - var snapshot2 = _snapshot.CreateSnapshot(); + var snapshot2 = _snapshotCache.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..1dffb3d384 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_StdLib.cs @@ -9,7 +9,9 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.VM; @@ -53,12 +55,15 @@ 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] public void MemoryCompare() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using (var script = new ScriptBuilder()) { @@ -67,7 +72,7 @@ public void MemoryCompare() script.EmitDynamicCall(NativeContract.StdLib.Hash, "memoryCompare", "abc", "abc"); script.EmitDynamicCall(NativeContract.StdLib.Hash, "memoryCompare", "abc", "abcd"); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -83,13 +88,13 @@ public void MemoryCompare() [TestMethod] public void CheckDecodeEncode() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using (ScriptBuilder script = new()) { script.EmitDynamicCall(NativeContract.StdLib.Hash, "base58CheckEncode", new byte[] { 1, 2, 3 }); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -102,7 +107,7 @@ public void CheckDecodeEncode() { script.EmitDynamicCall(NativeContract.StdLib.Hash, "base58CheckDecode", "3DUz7ncyT"); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -117,7 +122,7 @@ public void CheckDecodeEncode() { script.EmitDynamicCall(NativeContract.StdLib.Hash, "base58CheckDecode", "AA"); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.FAULT); @@ -127,7 +132,7 @@ public void CheckDecodeEncode() { script.EmitDynamicCall(NativeContract.StdLib.Hash, "base58CheckDecode", null); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.FAULT); @@ -137,7 +142,7 @@ public void CheckDecodeEncode() [TestMethod] public void MemorySearch() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using (var script = new ScriptBuilder()) { @@ -147,7 +152,7 @@ public void MemorySearch() script.EmitDynamicCall(NativeContract.StdLib.Hash, "memorySearch", "abc", "c", 3); script.EmitDynamicCall(NativeContract.StdLib.Hash, "memorySearch", "abc", "d", 0); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -167,7 +172,7 @@ public void MemorySearch() script.EmitDynamicCall(NativeContract.StdLib.Hash, "memorySearch", "abc", "c", 3, false); script.EmitDynamicCall(NativeContract.StdLib.Hash, "memorySearch", "abc", "d", 0, false); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -187,7 +192,7 @@ public void MemorySearch() script.EmitDynamicCall(NativeContract.StdLib.Hash, "memorySearch", "abc", "c", 3, true); script.EmitDynamicCall(NativeContract.StdLib.Hash, "memorySearch", "abc", "d", 0, true); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -203,12 +208,12 @@ public void MemorySearch() [TestMethod] public void StringSplit() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.StdLib.Hash, "stringSplit", "a,b", ","); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -223,14 +228,14 @@ public void StringSplit() [TestMethod] public void StringElementLength() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.StdLib.Hash, "strLen", "🦆"); script.EmitDynamicCall(NativeContract.StdLib.Hash, "strLen", "ã"); script.EmitDynamicCall(NativeContract.StdLib.Hash, "strLen", "a"); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -246,13 +251,13 @@ public void TestInvalidUtf8Sequence() // Simulating invalid UTF-8 byte (0xff) decoded as a UTF-16 char const char badChar = (char)0xff; var badStr = badChar.ToString(); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.StdLib.Hash, "strLen", badStr); script.EmitDynamicCall(NativeContract.StdLib.Hash, "strLen", badStr + "ab"); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -264,7 +269,7 @@ public void TestInvalidUtf8Sequence() [TestMethod] public void Json_Deserialize() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); // Good @@ -273,7 +278,7 @@ public void Json_Deserialize() script.EmitDynamicCall(NativeContract.StdLib.Hash, "jsonDeserialize", "123"); script.EmitDynamicCall(NativeContract.StdLib.Hash, "jsonDeserialize", "null"); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -289,7 +294,7 @@ public void Json_Deserialize() { script.EmitDynamicCall(NativeContract.StdLib.Hash, "jsonDeserialize", "***"); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.FAULT); @@ -302,7 +307,7 @@ public void Json_Deserialize() { script.EmitDynamicCall(NativeContract.StdLib.Hash, "jsonDeserialize", "123.45"); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.FAULT); @@ -313,7 +318,7 @@ public void Json_Deserialize() [TestMethod] public void Json_Serialize() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); // Good @@ -333,7 +338,7 @@ public void Json_Serialize() } }); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -352,7 +357,7 @@ public void Json_Serialize() { script.EmitDynamicCall(NativeContract.StdLib.Hash, "jsonSerialize"); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.FAULT); @@ -363,7 +368,7 @@ public void Json_Serialize() [TestMethod] public void TestRuntime_Serialize() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); // Good @@ -371,7 +376,7 @@ public void TestRuntime_Serialize() script.EmitDynamicCall(NativeContract.StdLib.Hash, "serialize", 100); script.EmitDynamicCall(NativeContract.StdLib.Hash, "serialize", "test"); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -384,7 +389,7 @@ public void TestRuntime_Serialize() [TestMethod] public void TestRuntime_Deserialize() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); // Good @@ -392,7 +397,7 @@ public void TestRuntime_Deserialize() script.EmitDynamicCall(NativeContract.StdLib.Hash, "deserialize", "280474657374".HexToBytes()); script.EmitDynamicCall(NativeContract.StdLib.Hash, "deserialize", "210164".HexToBytes()); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); diff --git a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs index 0969e12776..def978596e 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs @@ -30,8 +30,8 @@ public partial class UT_ApplicationEngine [TestMethod] public void TestNotify() { - var snapshot = TestBlockchain.GetTestSnapshot(); - using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + using var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(System.Array.Empty()); ApplicationEngine.Notify += Test_Notify1; const string notifyEvent = "TestEvent"; @@ -66,9 +66,9 @@ private void Test_Notify2(object sender, NotifyEventArgs e) [TestMethod] public void TestCreateDummyBlock() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); byte[] SyscallSystemRuntimeCheckWitnessHash = new byte[] { 0x68, 0xf8, 0x27, 0xec, 0x8c }; - ApplicationEngine engine = ApplicationEngine.Run(SyscallSystemRuntimeCheckWitnessHash, snapshot, settings: TestProtocolSettings.Default); + ApplicationEngine engine = ApplicationEngine.Run(SyscallSystemRuntimeCheckWitnessHash, snapshotCache, settings: TestProtocolSettings.Default); engine.PersistingBlock.Version.Should().Be(0); engine.PersistingBlock.PrevHash.Should().Be(TestBlockchain.TheNeoSystem.GenesisBlock.Hash); engine.PersistingBlock.MerkleRoot.Should().Be(new UInt256()); @@ -111,7 +111,7 @@ public void TestCheckingHardfork() public void TestSystem_Contract_Call_Permissions() { UInt160 scriptHash; - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); // Setup: put a simple contract to the storage. using (var script = new ScriptBuilder()) @@ -123,7 +123,7 @@ public void TestSystem_Contract_Call_Permissions() // Mock contract and put it to the Managemant's storage. scriptHash = script.ToArray().ToScriptHash(); - snapshot.DeleteContract(scriptHash); + snapshotCache.DeleteContract(scriptHash); var contract = TestUtils.GetContract(script.ToArray(), TestUtils.CreateManifest("test", ContractParameterType.Any)); contract.Manifest.Abi.Methods = new[] { @@ -138,11 +138,11 @@ public void TestSystem_Contract_Call_Permissions() Parameters = new ContractParameterDefinition[]{} } }; - snapshot.AddContract(scriptHash, contract); + snapshotCache.AddContract(scriptHash, contract); } // Disallowed method call. - using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, ProtocolSettings.Default)) + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, null, ProtocolSettings.Default)) using (var script = new ScriptBuilder()) { // Build call script calling disallowed method. @@ -167,12 +167,19 @@ public void TestSystem_Contract_Call_Permissions() }; var currentScriptHash = engine.EntryScriptHash; + Assert.AreEqual("", engine.GetEngineStackInfoOnFault()); Assert.AreEqual(VMState.FAULT, engine.Execute()); Assert.IsTrue(engine.FaultException.ToString().Contains($"Cannot Call Method disallowed Of Contract {scriptHash.ToString()}")); + string traceback = engine.GetEngineStackInfoOnFault(); + Assert.IsTrue(traceback.Contains($"Cannot Call Method disallowed Of Contract {scriptHash.ToString()}")); + Assert.IsTrue(traceback.Contains("CurrentScriptHash")); + Assert.IsTrue(traceback.Contains("EntryScriptHash")); + Assert.IsTrue(traceback.Contains("InstructionPointer")); + Assert.IsTrue(traceback.Contains("OpCode SYSCALL, Script Length=")); } // Allowed method call. - using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, ProtocolSettings.Default)) + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, null, ProtocolSettings.Default)) using (var script = new ScriptBuilder()) { // Build call script. diff --git a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngineProvider.cs b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngineProvider.cs index 75fc558669..1d9da111e1 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngineProvider.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngineProvider.cs @@ -59,8 +59,8 @@ public ApplicationEngine Create(TriggerType trigger, IVerifiable container, Data class TestEngine : ApplicationEngine { - public TestEngine(TriggerType trigger, IVerifiable container, DataCache snapshot, Block persistingBlock, ProtocolSettings settings, long gas, IDiagnostic diagnostic, JumpTable jumpTable) - : base(trigger, container, snapshot, persistingBlock, settings, gas, diagnostic, jumpTable) + public TestEngine(TriggerType trigger, IVerifiable container, DataCache snapshotCache, Block persistingBlock, ProtocolSettings settings, long gas, IDiagnostic diagnostic, JumpTable jumpTable) + : base(trigger, container, snapshotCache, persistingBlock, settings, gas, diagnostic, jumpTable) { } } diff --git a/tests/Neo.UnitTests/SmartContract/UT_ContractParameter.cs b/tests/Neo.UnitTests/SmartContract/UT_ContractParameter.cs index 6618a918e7..4068837f6f 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_ContractParameter.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_ContractParameter.cs @@ -12,6 +12,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.Json; using Neo.SmartContract; using System; diff --git a/tests/Neo.UnitTests/SmartContract/UT_ContractParameterContext.cs b/tests/Neo.UnitTests/SmartContract/UT_ContractParameterContext.cs index aba90cc6f0..9452cc6ebc 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_ContractParameterContext.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_ContractParameterContext.cs @@ -12,6 +12,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.Network.P2P.Payloads; using Neo.SmartContract; using Neo.VM; @@ -43,18 +44,18 @@ public static void ClassSetUp(TestContext ctx) [TestMethod] public void TestGetComplete() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); Transaction tx = TestUtils.GetTransaction(UInt160.Parse("0x1bd5c777ec35768892bd3daab60fb7a1cb905066")); - var context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + var context = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); context.Completed.Should().BeFalse(); } [TestMethod] public void TestToString() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); Transaction tx = TestUtils.GetTransaction(UInt160.Parse("0x1bd5c777ec35768892bd3daab60fb7a1cb905066")); - var context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + var context = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); context.Add(contract, 0, new byte[] { 0x01 }); string str = context.ToString(); str.Should().Be(@"{""type"":""Neo.Network.P2P.Payloads.Transaction"",""hash"":""0x602c1fa1c08b041e4e6b87aa9a9f9c643166cd34bdd5215a3dd85778c59cce88"",""data"":""AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFmUJDLobcPtqo9vZKIdjXsd8fVGwEAARI="",""items"":{},""network"":" + TestProtocolSettings.Default.Network + "}"); @@ -63,8 +64,8 @@ public void TestToString() [TestMethod] public void TestParse() { - var snapshot = TestBlockchain.GetTestSnapshot(); - var ret = ContractParametersContext.Parse("{\"type\":\"Neo.Network.P2P.Payloads.Transaction\",\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFmUJDLobcPtqo9vZKIdjXsd8fVGwEAARI=\",\"items\":{\"0xbecaad15c0ea585211faf99738a4354014f177f2\":{\"script\":\"IQJv8DuUkkHOHa3UNRnmlg4KhbQaaaBcMoEDqivOFZTKFmh0dHaq\",\"parameters\":[{\"type\":\"Signature\",\"value\":\"AQ==\"}],\"signatures\":{\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\":\"AQ==\"}}},\"network\":" + TestProtocolSettings.Default.Network + "}", snapshot); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + var ret = ContractParametersContext.Parse("{\"type\":\"Neo.Network.P2P.Payloads.Transaction\",\"data\":\"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFmUJDLobcPtqo9vZKIdjXsd8fVGwEAARI=\",\"items\":{\"0xbecaad15c0ea585211faf99738a4354014f177f2\":{\"script\":\"IQJv8DuUkkHOHa3UNRnmlg4KhbQaaaBcMoEDqivOFZTKFmh0dHaq\",\"parameters\":[{\"type\":\"Signature\",\"value\":\"AQ==\"}],\"signatures\":{\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\":\"AQ==\"}}},\"network\":" + TestProtocolSettings.Default.Network + "}", snapshotCache); ret.ScriptHashes[0].ToString().Should().Be("0x1bd5c777ec35768892bd3daab60fb7a1cb905066"); ((Transaction)ret.Verifiable).Script.Span.ToHexString().Should().Be(new byte[] { 18 }.ToHexString()); } @@ -72,21 +73,21 @@ public void TestParse() [TestMethod] public void TestFromJson() { - var snapshot = TestBlockchain.GetTestSnapshot(); - Action action = () => ContractParametersContext.Parse("{\"type\":\"wrongType\",\"data\":\"00000000007c97764845172d827d3c863743293931a691271a0000000000000000000000000000000000000000000100\",\"items\":{\"0x1bd5c777ec35768892bd3daab60fb7a1cb905066\":{\"script\":\"21026ff03b949241ce1dadd43519e6960e0a85b41a69a05c328103aa2bce1594ca1650680a906ad4\",\"parameters\":[{\"type\":\"Signature\",\"value\":\"01\"}]}}}", snapshot); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + Action action = () => ContractParametersContext.Parse("{\"type\":\"wrongType\",\"data\":\"00000000007c97764845172d827d3c863743293931a691271a0000000000000000000000000000000000000000000100\",\"items\":{\"0x1bd5c777ec35768892bd3daab60fb7a1cb905066\":{\"script\":\"21026ff03b949241ce1dadd43519e6960e0a85b41a69a05c328103aa2bce1594ca1650680a906ad4\",\"parameters\":[{\"type\":\"Signature\",\"value\":\"01\"}]}}}", snapshotCache); action.Should().Throw(); } [TestMethod] public void TestAdd() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); Transaction tx = TestUtils.GetTransaction(UInt160.Zero); - var context1 = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + var context1 = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); context1.Add(contract, 0, new byte[] { 0x01 }).Should().BeFalse(); tx = TestUtils.GetTransaction(UInt160.Parse("0x902e0d38da5e513b6d07c1c55b85e77d3dce8063")); - var context2 = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + var context2 = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); context2.Add(contract, 0, new byte[] { 0x01 }).Should().BeTrue(); //test repeatlly createItem context2.Add(contract, 0, new byte[] { 0x01 }).Should().BeTrue(); @@ -95,9 +96,9 @@ public void TestAdd() [TestMethod] public void TestGetParameter() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); Transaction tx = TestUtils.GetTransaction(UInt160.Parse("0x902e0d38da5e513b6d07c1c55b85e77d3dce8063")); - var context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + var context = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); context.GetParameter(tx.Sender, 0).Should().BeNull(); context.Add(contract, 0, new byte[] { 0x01 }); @@ -108,9 +109,9 @@ public void TestGetParameter() [TestMethod] public void TestGetWitnesses() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); Transaction tx = TestUtils.GetTransaction(UInt160.Parse("0x902e0d38da5e513b6d07c1c55b85e77d3dce8063")); - var context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + var context = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); context.Add(contract, 0, new byte[] { 0x01 }); Witness[] witnesses = context.GetWitnesses(); witnesses.Length.Should().Be(1); @@ -121,18 +122,18 @@ public void TestGetWitnesses() [TestMethod] public void TestAddSignature() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var singleSender = UInt160.Parse("0x902e0d38da5e513b6d07c1c55b85e77d3dce8063"); Transaction tx = TestUtils.GetTransaction(singleSender); //singleSign - var context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + var context = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); context.AddSignature(contract, key.PublicKey, new byte[] { 0x01 }).Should().BeTrue(); var contract1 = Contract.CreateSignatureContract(key.PublicKey); contract1.ParameterList = Array.Empty(); - context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + context = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); context.AddSignature(contract1, key.PublicKey, new byte[] { 0x01 }).Should().BeFalse(); contract1.ParameterList = new[] { ContractParameterType.Signature, ContractParameterType.Signature }; @@ -154,16 +155,16 @@ public void TestAddSignature() }); var multiSender = UInt160.Parse("0xf76b51bc6605ac3cfcd188173af0930507f51210"); tx = TestUtils.GetTransaction(multiSender); - context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + context = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); context.AddSignature(multiSignContract, key.PublicKey, new byte[] { 0x01 }).Should().BeTrue(); context.AddSignature(multiSignContract, key2.PublicKey, new byte[] { 0x01 }).Should().BeTrue(); tx = TestUtils.GetTransaction(singleSender); - context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + context = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); context.AddSignature(multiSignContract, key.PublicKey, new byte[] { 0x01 }).Should().BeFalse(); tx = TestUtils.GetTransaction(multiSender); - context = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); + context = new ContractParametersContext(snapshotCache, tx, TestProtocolSettings.Default.Network); byte[] privateKey3 = new byte[] { 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, diff --git a/tests/Neo.UnitTests/SmartContract/UT_InteropPrices.cs b/tests/Neo.UnitTests/SmartContract/UT_InteropPrices.cs index 581bf42751..dfc685f335 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_InteropPrices.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_InteropPrices.cs @@ -64,11 +64,11 @@ public void ApplicationEngineRegularPut() StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(System.Array.Empty()); - var snapshot = TestBlockchain.GetTestSnapshot(); - snapshot.Add(skey, sItem); - snapshot.AddContract(script.ToScriptHash(), contractState); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + snapshotCache.Add(skey, sItem); + snapshotCache.AddContract(script.ToScriptHash(), contractState); - using ApplicationEngine ae = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + using ApplicationEngine ae = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache); Debugger debugger = new(ae); ae.LoadScript(script); debugger.StepInto(); @@ -95,11 +95,11 @@ public void ApplicationEngineReusedStorage_FullReuse() StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(value); - var snapshot = TestBlockchain.GetTestSnapshot(); - snapshot.Add(skey, sItem); - snapshot.AddContract(script.ToScriptHash(), contractState); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + snapshotCache.Add(skey, sItem); + snapshotCache.AddContract(script.ToScriptHash(), contractState); - using ApplicationEngine applicationEngine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + using ApplicationEngine applicationEngine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache); Debugger debugger = new(applicationEngine); applicationEngine.LoadScript(script); debugger.StepInto(); @@ -128,11 +128,11 @@ public void ApplicationEngineReusedStorage_PartialReuse() StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(oldValue); - var snapshot = TestBlockchain.GetTestSnapshot(); - snapshot.Add(skey, sItem); - snapshot.AddContract(script.ToScriptHash(), contractState); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + snapshotCache.Add(skey, sItem); + snapshotCache.AddContract(script.ToScriptHash(), contractState); - using ApplicationEngine ae = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + using ApplicationEngine ae = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache); Debugger debugger = new(ae); ae.LoadScript(script); debugger.StepInto(); @@ -162,11 +162,11 @@ public void ApplicationEngineReusedStorage_PartialReuseTwice() StorageKey skey = TestUtils.GetStorageKey(contractState.Id, key); StorageItem sItem = TestUtils.GetStorageItem(oldValue); - var snapshot = TestBlockchain.GetTestSnapshot(); - snapshot.Add(skey, sItem); - snapshot.AddContract(script.ToScriptHash(), contractState); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + snapshotCache.Add(skey, sItem); + snapshotCache.AddContract(script.ToScriptHash(), contractState); - using ApplicationEngine ae = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + using ApplicationEngine ae = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache); Debugger debugger = new(ae); ae.LoadScript(script); debugger.StepInto(); //push value diff --git a/tests/Neo.UnitTests/SmartContract/UT_InteropService.NEO.cs b/tests/Neo.UnitTests/SmartContract/UT_InteropService.NEO.cs index 4f0767bf2c..e0ccdd7dcc 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_InteropService.NEO.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_InteropService.NEO.cs @@ -12,6 +12,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P; using Neo.SmartContract; @@ -113,7 +114,7 @@ public void TestCrypto_CheckMultiSig() [TestMethod] public void TestContract_Create() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var nef = new NefFile() { Script = Enumerable.Repeat((byte)OpCode.RET, byte.MaxValue).ToArray(), @@ -124,9 +125,9 @@ public void TestContract_Create() nef.CheckSum = NefFile.ComputeChecksum(nef); var nefFile = nef.ToArray(); var manifest = TestUtils.CreateDefaultManifest(); - Assert.ThrowsException(() => snapshot.DeployContract(null, nefFile, manifest.ToJson().ToByteArray(false))); - Assert.ThrowsException(() => snapshot.DeployContract(UInt160.Zero, nefFile, new byte[ContractManifest.MaxLength + 1])); - Assert.ThrowsException(() => snapshot.DeployContract(UInt160.Zero, nefFile, manifest.ToJson().ToByteArray(true), 10000000)); + Assert.ThrowsException(() => snapshotCache.DeployContract(null, nefFile, manifest.ToJson().ToByteArray(false))); + Assert.ThrowsException(() => snapshotCache.DeployContract(UInt160.Zero, nefFile, new byte[ContractManifest.MaxLength + 1])); + Assert.ThrowsException(() => snapshotCache.DeployContract(UInt160.Zero, nefFile, manifest.ToJson().ToByteArray(true), 10000000)); var script_exceedMaxLength = new NefFile() { @@ -138,29 +139,29 @@ public void TestContract_Create() script_exceedMaxLength.CheckSum = NefFile.ComputeChecksum(script_exceedMaxLength); Assert.ThrowsException(() => script_exceedMaxLength.ToArray().AsSerializable()); - Assert.ThrowsException(() => snapshot.DeployContract(UInt160.Zero, script_exceedMaxLength.ToArray(), manifest.ToJson().ToByteArray(true))); + Assert.ThrowsException(() => snapshotCache.DeployContract(UInt160.Zero, script_exceedMaxLength.ToArray(), manifest.ToJson().ToByteArray(true))); var script_zeroLength = System.Array.Empty(); - Assert.ThrowsException(() => snapshot.DeployContract(UInt160.Zero, script_zeroLength, manifest.ToJson().ToByteArray(true))); + Assert.ThrowsException(() => snapshotCache.DeployContract(UInt160.Zero, script_zeroLength, manifest.ToJson().ToByteArray(true))); var manifest_zeroLength = System.Array.Empty(); - Assert.ThrowsException(() => snapshot.DeployContract(UInt160.Zero, nefFile, manifest_zeroLength)); + Assert.ThrowsException(() => snapshotCache.DeployContract(UInt160.Zero, nefFile, manifest_zeroLength)); manifest = TestUtils.CreateDefaultManifest(); - var ret = snapshot.DeployContract(UInt160.Zero, nefFile, manifest.ToJson().ToByteArray(false)); + var ret = snapshotCache.DeployContract(UInt160.Zero, nefFile, manifest.ToJson().ToByteArray(false)); ret.Hash.ToString().Should().Be("0x7b37d4bd3d87f53825c3554bd1a617318235a685"); - Assert.ThrowsException(() => snapshot.DeployContract(UInt160.Zero, nefFile, manifest.ToJson().ToByteArray(false))); + Assert.ThrowsException(() => snapshotCache.DeployContract(UInt160.Zero, nefFile, manifest.ToJson().ToByteArray(false))); var state = TestUtils.GetContract(); - snapshot.AddContract(state.Hash, state); + snapshotCache.AddContract(state.Hash, state); - Assert.ThrowsException(() => snapshot.DeployContract(UInt160.Zero, nefFile, manifest.ToJson().ToByteArray(false))); + Assert.ThrowsException(() => snapshotCache.DeployContract(UInt160.Zero, nefFile, manifest.ToJson().ToByteArray(false))); } [TestMethod] public void TestContract_Update() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var nef = new NefFile() { Script = new[] { (byte)OpCode.RET }, @@ -169,7 +170,7 @@ public void TestContract_Update() Tokens = Array.Empty() }; nef.CheckSum = NefFile.ComputeChecksum(nef); - Assert.ThrowsException(() => snapshot.UpdateContract(null, nef.ToArray(), new byte[0])); + Assert.ThrowsException(() => snapshotCache.UpdateContract(null, nef.ToArray(), new byte[0])); var manifest = TestUtils.CreateDefaultManifest(); byte[] privkey = { 0x01,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, @@ -197,12 +198,12 @@ public void TestContract_Update() Id = state.Id, Key = new byte[] { 0x01 } }; - snapshot.AddContract(state.Hash, state); - snapshot.Add(storageKey, storageItem); + snapshotCache.AddContract(state.Hash, state); + snapshotCache.Add(storageKey, storageItem); state.UpdateCounter.Should().Be(0); - snapshot.UpdateContract(state.Hash, nef.ToArray(), manifest.ToJson().ToByteArray(false)); - var ret = NativeContract.ContractManagement.GetContract(snapshot, state.Hash); - snapshot.Find(BitConverter.GetBytes(state.Id)).ToList().Count().Should().Be(1); + snapshotCache.UpdateContract(state.Hash, nef.ToArray(), manifest.ToJson().ToByteArray(false)); + var ret = NativeContract.ContractManagement.GetContract(snapshotCache, state.Hash); + snapshotCache.Find(BitConverter.GetBytes(state.Id)).ToList().Count().Should().Be(1); ret.UpdateCounter.Should().Be(1); ret.Id.Should().Be(state.Id); ret.Manifest.ToJson().ToString().Should().Be(manifest.ToJson().ToString()); @@ -221,11 +222,11 @@ public void TestContract_Update_Invalid() }; nefFile.CheckSum = NefFile.ComputeChecksum(nefFile); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); - Assert.ThrowsException(() => snapshot.UpdateContract(null, null, new byte[] { 0x01 })); - Assert.ThrowsException(() => snapshot.UpdateContract(null, nefFile.ToArray(), null)); - Assert.ThrowsException(() => snapshot.UpdateContract(null, null, null)); + Assert.ThrowsException(() => snapshotCache.UpdateContract(null, null, new byte[] { 0x01 })); + Assert.ThrowsException(() => snapshotCache.UpdateContract(null, nefFile.ToArray(), null)); + Assert.ThrowsException(() => snapshotCache.UpdateContract(null, null, null)); nefFile = new NefFile() { @@ -236,14 +237,14 @@ public void TestContract_Update_Invalid() }; nefFile.CheckSum = NefFile.ComputeChecksum(nefFile); - Assert.ThrowsException(() => snapshot.UpdateContract(null, nefFile.ToArray(), new byte[] { 0x01 })); - Assert.ThrowsException(() => snapshot.UpdateContract(null, nefFile.ToArray(), new byte[0])); + Assert.ThrowsException(() => snapshotCache.UpdateContract(null, nefFile.ToArray(), new byte[] { 0x01 })); + Assert.ThrowsException(() => snapshotCache.UpdateContract(null, nefFile.ToArray(), new byte[0])); } [TestMethod] public void TestStorage_Find() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var state = TestUtils.GetContract(); var storageItem = new StorageItem @@ -255,9 +256,9 @@ public void TestStorage_Find() Id = state.Id, Key = new byte[] { 0x01 } }; - snapshot.AddContract(state.Hash, state); - snapshot.Add(storageKey, storageItem); - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + snapshotCache.AddContract(state.Hash, state); + snapshotCache.Add(storageKey, storageItem); + var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache); engine.LoadScript(new byte[] { 0x01 }); var iterator = engine.Find(new StorageContext diff --git a/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs b/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs index 06290922da..35dd3dbe37 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_InteropService.cs @@ -10,10 +10,12 @@ // modifications are permitted. using Akka.TestKit.Xunit2; +using Akka.Util.Internal; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; @@ -37,7 +39,7 @@ public partial class UT_InteropService : TestKit public void Runtime_GetNotifications_Test() { UInt160 scriptHash2; - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using (var script = new ScriptBuilder()) { @@ -55,7 +57,7 @@ public void Runtime_GetNotifications_Test() scriptHash2 = script.ToArray().ToScriptHash(); - snapshot.DeleteContract(scriptHash2); + snapshotCache.DeleteContract(scriptHash2); var contract = TestUtils.GetContract(script.ToArray(), TestUtils.CreateManifest("test", ContractParameterType.Any, ContractParameterType.Integer, ContractParameterType.Integer)); contract.Manifest.Abi.Events = new[] { @@ -79,12 +81,12 @@ public void Runtime_GetNotifications_Test() Methods = WildcardContainer.Create(new string[]{"test"}) } }; - snapshot.AddContract(scriptHash2, contract); + snapshotCache.AddContract(scriptHash2, contract); } // Wrong length - using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, ProtocolSettings.Default)) + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, null, ProtocolSettings.Default)) using (var script = new ScriptBuilder()) { // Retrive @@ -101,7 +103,7 @@ public void Runtime_GetNotifications_Test() // All test - using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, ProtocolSettings.Default)) + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, null, ProtocolSettings.Default)) using (var script = new ScriptBuilder()) { // Notification @@ -178,7 +180,7 @@ public void Runtime_GetNotifications_Test() // Script notifications - using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, ProtocolSettings.Default)) + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, null, ProtocolSettings.Default)) using (var script = new ScriptBuilder()) { // Notification @@ -254,7 +256,7 @@ public void Runtime_GetNotifications_Test() // Clean storage - snapshot.DeleteContract(scriptHash2); + snapshotCache.DeleteContract(scriptHash2); } private static void AssertNotification(StackItem stackItem, UInt160 scriptHash, string notification) @@ -290,7 +292,7 @@ public void TestExecutionEngine_GetCallingScriptHash() var contract = TestUtils.GetContract(scriptA.ToArray(), TestUtils.CreateManifest("test", ContractParameterType.Any, ContractParameterType.String, ContractParameterType.Integer)); engine = GetEngine(true, true, addScript: false); - engine.Snapshot.AddContract(contract.Hash, contract); + engine.SnapshotCache.AddContract(contract.Hash, contract); using ScriptBuilder scriptB = new(); scriptB.EmitDynamicCall(contract.Hash, "test", "0", 1); @@ -438,7 +440,7 @@ public void TestCrypto_Verify() public void TestBlockchain_GetHeight() { var engine = GetEngine(true, true); - NativeContract.Ledger.CurrentIndex(engine.Snapshot).Should().Be(0); + NativeContract.Ledger.CurrentIndex(engine.SnapshotCache).Should().Be(0); } [TestMethod] @@ -446,14 +448,14 @@ public void TestBlockchain_GetBlock() { var engine = GetEngine(true, true); - NativeContract.Ledger.GetBlock(engine.Snapshot, UInt256.Zero).Should().BeNull(); + NativeContract.Ledger.GetBlock(engine.SnapshotCache, UInt256.Zero).Should().BeNull(); var data1 = new byte[] { 0x01, 0x01, 0x01 ,0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; - NativeContract.Ledger.GetBlock(engine.Snapshot, new UInt256(data1)).Should().BeNull(); - NativeContract.Ledger.GetBlock(engine.Snapshot, TestBlockchain.TheNeoSystem.GenesisBlock.Hash).Should().NotBeNull(); + NativeContract.Ledger.GetBlock(engine.SnapshotCache, new UInt256(data1)).Should().BeNull(); + NativeContract.Ledger.GetBlock(engine.SnapshotCache, TestBlockchain.TheNeoSystem.GenesisBlock.Hash).Should().NotBeNull(); } [TestMethod] @@ -464,7 +466,7 @@ public void TestBlockchain_GetTransaction() 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}; - NativeContract.Ledger.GetTransaction(engine.Snapshot, new UInt256(data1)).Should().BeNull(); + NativeContract.Ledger.GetTransaction(engine.SnapshotCache, new UInt256(data1)).Should().BeNull(); } [TestMethod] @@ -476,7 +478,7 @@ public void TestBlockchain_GetTransactionHeight() BlockIndex = 0, Transaction = TestUtils.CreateRandomHashTransaction() }; - TestUtils.TransactionAdd(engine.Snapshot, state); + TestUtils.TransactionAdd(engine.SnapshotCache, state); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.Ledger.Hash, "getTransactionHeight", state.Transaction.Hash); @@ -497,14 +499,49 @@ public void TestBlockchain_GetContract() 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 }; - NativeContract.ContractManagement.GetContract(engine.Snapshot, new UInt160(data1)).Should().BeNull(); + NativeContract.ContractManagement.GetContract(engine.SnapshotCache, new UInt160(data1)).Should().BeNull(); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var state = TestUtils.GetContract(); - snapshot.AddContract(state.Hash, state); - engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + snapshotCache.AddContract(state.Hash, state); + engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache); engine.LoadScript(new byte[] { 0x01 }); - NativeContract.ContractManagement.GetContract(engine.Snapshot, state.Hash).Hash.Should().Be(state.Hash); + NativeContract.ContractManagement.GetContract(engine.SnapshotCache, state.Hash).Hash.Should().Be(state.Hash); + } + + [TestMethod] + public void TestBlockchain_GetContractById() + { + var engine = GetEngine(true, true); + var contract = NativeContract.ContractManagement.GetContractById(engine.SnapshotCache, -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.SnapshotCache, NativeContract.NEO.Hash, "symbol", 0).Should().Be(true); + NativeContract.ContractManagement.HasMethod(engine.SnapshotCache, NativeContract.NEO.Hash, "transfer", 4).Should().Be(true); + } + + [TestMethod] + public void TestBlockchain_ListContracts() + { + var engine = GetEngine(true, true); + var list = NativeContract.ContractManagement.ListContracts(engine.SnapshotCache); + list.ForEach(p => p.Id.Should().BeLessThan(0)); + + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + var state = TestUtils.GetContract(); + snapshotCache.AddContract(state.Hash, state); + engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache); + engine.LoadScript(new byte[] { 0x01 }); + NativeContract.ContractManagement.GetContract(engine.SnapshotCache, state.Hash).Hash.Should().Be(state.Hash); + + var list2 = NativeContract.ContractManagement.ListContracts(engine.SnapshotCache); + list2.Count().Should().Be(list.Count() + 1); } [TestMethod] @@ -512,7 +549,7 @@ public void TestStorage_GetContext() { var engine = GetEngine(false, true); var state = TestUtils.GetContract(); - engine.Snapshot.AddContract(state.Hash, state); + engine.SnapshotCache.AddContract(state.Hash, state); engine.LoadScript(state.Script); engine.GetStorageContext().IsReadOnly.Should().BeFalse(); } @@ -522,7 +559,7 @@ public void TestStorage_GetReadOnlyContext() { var engine = GetEngine(false, true); var state = TestUtils.GetContract(); - engine.Snapshot.AddContract(state.Hash, state); + engine.SnapshotCache.AddContract(state.Hash, state); engine.LoadScript(state.Script); engine.GetReadOnlyContext().IsReadOnly.Should().BeTrue(); } @@ -530,7 +567,7 @@ public void TestStorage_GetReadOnlyContext() [TestMethod] public void TestStorage_Get() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var state = TestUtils.GetContract(); var storageKey = new StorageKey @@ -543,9 +580,9 @@ public void TestStorage_Get() { Value = new byte[] { 0x01, 0x02, 0x03, 0x04 } }; - snapshot.AddContract(state.Hash, state); - snapshot.Add(storageKey, storageItem); - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + snapshotCache.AddContract(state.Hash, state); + snapshotCache.Add(storageKey, storageItem); + var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache); engine.LoadScript(new byte[] { 0x01 }); engine.Get(new StorageContext @@ -588,7 +625,7 @@ public void TestStorage_Put() Assert.ThrowsException(() => engine.Put(storageContext, key, value)); //storage value is constant - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var storageKey = new StorageKey { @@ -599,9 +636,9 @@ public void TestStorage_Put() { Value = new byte[] { 0x01, 0x02, 0x03, 0x04 } }; - snapshot.AddContract(state.Hash, state); - snapshot.Add(storageKey, storageItem); - engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + snapshotCache.AddContract(state.Hash, state); + snapshotCache.Add(storageKey, storageItem); + engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache); engine.LoadScript(new byte[] { 0x01 }); key = new byte[] { 0x01 }; value = new byte[] { 0x02 }; @@ -618,7 +655,7 @@ public void TestStorage_Put() public void TestStorage_Delete() { var engine = GetEngine(false, true); - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var state = TestUtils.GetContract(); var storageKey = new StorageKey { @@ -629,9 +666,9 @@ public void TestStorage_Delete() { Value = new byte[] { 0x01, 0x02, 0x03, 0x04 } }; - snapshot.AddContract(state.Hash, state); - snapshot.Add(storageKey, storageItem); - engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + snapshotCache.AddContract(state.Hash, state); + snapshotCache.Add(storageKey, storageItem); + engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache); engine.LoadScript(new byte[] { 0x01 }); var key = new byte[] { 0x01 }; var storageContext = new StorageContext @@ -661,38 +698,38 @@ public void TestStorageContext_AsReadOnly() [TestMethod] public void TestContract_Call() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var method = "method"; var args = new VM.Types.Array { 0, 1 }; var state = TestUtils.GetContract(method, args.Count); - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, ProtocolSettings.Default); + var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, null, ProtocolSettings.Default); engine.LoadScript(new byte[] { 0x01 }); - engine.Snapshot.AddContract(state.Hash, state); + engine.SnapshotCache.AddContract(state.Hash, state); engine.CallContract(state.Hash, method, CallFlags.All, args); engine.CurrentContext.EvaluationStack.Pop().Should().Be(args[0]); engine.CurrentContext.EvaluationStack.Pop().Should().Be(args[1]); state.Manifest.Permissions[0].Methods = WildcardContainer.Create("a"); - engine.Snapshot.DeleteContract(state.Hash); - engine.Snapshot.AddContract(state.Hash, state); + engine.SnapshotCache.DeleteContract(state.Hash); + engine.SnapshotCache.AddContract(state.Hash, state); Assert.ThrowsException(() => engine.CallContract(state.Hash, method, CallFlags.All, args)); state.Manifest.Permissions[0].Methods = WildcardContainer.CreateWildcard(); - engine.Snapshot.DeleteContract(state.Hash); - engine.Snapshot.AddContract(state.Hash, state); + engine.SnapshotCache.DeleteContract(state.Hash); + engine.SnapshotCache.AddContract(state.Hash, state); engine.CallContract(state.Hash, method, CallFlags.All, args); - engine.Snapshot.DeleteContract(state.Hash); - engine.Snapshot.AddContract(state.Hash, state); + engine.SnapshotCache.DeleteContract(state.Hash); + engine.SnapshotCache.AddContract(state.Hash, state); Assert.ThrowsException(() => engine.CallContract(UInt160.Zero, method, CallFlags.All, args)); } [TestMethod] public void TestContract_Destroy() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var state = TestUtils.GetContract(); var scriptHash = UInt160.Parse("0xcb9f3b7c6fb1cf2c13a40637c189bdd066a272b4"); var storageItem = new StorageItem @@ -705,16 +742,16 @@ public void TestContract_Destroy() Id = 0x43000000, Key = new byte[] { 0x01 } }; - snapshot.AddContract(scriptHash, state); - snapshot.Add(storageKey, storageItem); - snapshot.DestroyContract(scriptHash); - snapshot.Find(BitConverter.GetBytes(0x43000000)).Any().Should().BeFalse(); + snapshotCache.AddContract(scriptHash, state); + snapshotCache.Add(storageKey, storageItem); + snapshotCache.DestroyContract(scriptHash); + snapshotCache.Find(BitConverter.GetBytes(0x43000000)).Any().Should().BeFalse(); //storages are removed state = TestUtils.GetContract(); - snapshot.AddContract(scriptHash, state); - snapshot.DestroyContract(scriptHash); - snapshot.Find(BitConverter.GetBytes(0x43000000)).Any().Should().BeFalse(); + snapshotCache.AddContract(scriptHash, state); + snapshotCache.DestroyContract(scriptHash); + snapshotCache.Find(BitConverter.GetBytes(0x43000000)).Any().Should().BeFalse(); } [TestMethod] @@ -733,11 +770,98 @@ public static void LogEvent(object sender, LogEventArgs args) 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 snapshotCache = hasSnapshot ? TestBlockchain.GetTestSnapshotCache() : null; var block = hasBlock ? new Block { Header = new Header() } : null; - var engine = ApplicationEngine.Create(TriggerType.Application, tx, snapshot, block, TestBlockchain.TheNeoSystem.Settings, gas: gas); + var engine = ApplicationEngine.Create(TriggerType.Application, tx, snapshotCache, block, TestBlockchain.TheNeoSystem.Settings, gas: gas); 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 snapshotCache = GetEngine(true, true).SnapshotCache; + var hash = LedgerContract.Ledger.GetBlockHash(snapshotCache, 0); + var hash2 = LedgerContract.Ledger.GetBlock(snapshotCache, 0).Hash; + var hash3 = LedgerContract.Ledger.GetHeader(snapshotCache, 0).Hash; + hash.ToString().Should().Be(hash2.ToString()); + hash.ToString().Should().Be(hash3.ToString()); + hash.ToString().Should().Be("0x1f4d1defa46faa5e7b9b8d3f79a06bec777d7c26c4aa5f6f5899a291daa87c15"); + LedgerContract.Ledger.ContainsBlock(snapshotCache, hash).Should().BeTrue(); + } + + [TestMethod] + public void TestGetCandidateVote() + { + var snapshotCache = GetEngine(true, true).SnapshotCache; + var vote = LedgerContract.NEO.GetCandidateVote(snapshotCache, 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_KeyBuilder.cs b/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs index 3ff52c1324..a3514c7f5a 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_KeyBuilder.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.SmartContract; namespace Neo.UnitTests.SmartContract diff --git a/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs b/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs index c1161213ce..d223c747f6 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; @@ -124,9 +125,9 @@ public void TestIsStandardContract() [TestMethod] public void TestVerifyWitnesses() { - var snapshot1 = TestBlockchain.GetTestSnapshot().CreateSnapshot(); + var snapshotCache1 = TestBlockchain.GetTestSnapshotCache().CreateSnapshot(); UInt256 index1 = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"); - TestUtils.BlocksAdd(snapshot1, index1, new TrimmedBlock() + TestUtils.BlocksAdd(snapshotCache1, index1, new TrimmedBlock() { Header = new Header { @@ -138,10 +139,10 @@ public void TestVerifyWitnesses() }, Hashes = new UInt256[1] { UInt256.Zero }, }); - TestUtils.BlocksDelete(snapshot1, index1); - Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(new Header() { PrevHash = index1 }, TestProtocolSettings.Default, snapshot1, 100)); + TestUtils.BlocksDelete(snapshotCache1, index1); + Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(new Header() { PrevHash = index1 }, TestProtocolSettings.Default, snapshotCache1, 100)); - var snapshot2 = TestBlockchain.GetTestSnapshot(); + var snapshotCache2 = TestBlockchain.GetTestSnapshotCache(); UInt256 index2 = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"); TrimmedBlock block2 = new() { @@ -155,14 +156,14 @@ public void TestVerifyWitnesses() }, Hashes = new UInt256[1] { UInt256.Zero }, }; - TestUtils.BlocksAdd(snapshot2, index2, block2); + TestUtils.BlocksAdd(snapshotCache2, index2, block2); Header header2 = new() { PrevHash = index2, Witness = new Witness { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } }; - snapshot2.AddContract(UInt160.Zero, new ContractState()); - snapshot2.DeleteContract(UInt160.Zero); - Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(header2, TestProtocolSettings.Default, snapshot2, 100)); + snapshotCache2.AddContract(UInt160.Zero, new ContractState()); + snapshotCache2.DeleteContract(UInt160.Zero); + Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(header2, TestProtocolSettings.Default, snapshotCache2, 100)); - var snapshot3 = TestBlockchain.GetTestSnapshot(); + var snapshotCache3 = TestBlockchain.GetTestSnapshotCache(); UInt256 index3 = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"); TrimmedBlock block3 = new() { @@ -176,7 +177,7 @@ public void TestVerifyWitnesses() }, Hashes = new UInt256[1] { UInt256.Zero }, }; - TestUtils.BlocksAdd(snapshot3, index3, block3); + TestUtils.BlocksAdd(snapshotCache3, index3, block3); Header header3 = new() { PrevHash = index3, @@ -186,13 +187,13 @@ public void TestVerifyWitnesses() VerificationScript = Array.Empty() } }; - snapshot3.AddContract(UInt160.Zero, new ContractState() + snapshotCache3.AddContract(UInt160.Zero, new ContractState() { Nef = new NefFile { Script = Array.Empty() }, Hash = Array.Empty().ToScriptHash(), Manifest = TestUtils.CreateManifest("verify", ContractParameterType.Boolean, ContractParameterType.Signature), }); - Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(header3, TestProtocolSettings.Default, snapshot3, 100)); + Assert.AreEqual(false, Neo.SmartContract.Helper.VerifyWitnesses(header3, TestProtocolSettings.Default, snapshotCache3, 100)); // Smart contract verification @@ -202,13 +203,13 @@ public void TestVerifyWitnesses() Hash = "11".HexToBytes().ToScriptHash(), Manifest = TestUtils.CreateManifest("verify", ContractParameterType.Boolean, ContractParameterType.Signature), // Offset = 0 }; - snapshot3.AddContract(contract.Hash, contract); + snapshotCache3.AddContract(contract.Hash, contract); var tx = new Nep17NativeContractExtensions.ManualWitness(contract.Hash) { Witnesses = new Witness[] { new Witness() { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } } }; - Assert.AreEqual(true, Neo.SmartContract.Helper.VerifyWitnesses(tx, TestProtocolSettings.Default, snapshot3, 1000)); + Assert.AreEqual(true, Neo.SmartContract.Helper.VerifyWitnesses(tx, TestProtocolSettings.Default, snapshotCache3, 1000)); } } } 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 76448855d7..a864c5d427 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_Syscalls.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_Syscalls.cs @@ -62,14 +62,14 @@ public void System_Blockchain_GetBlock() Hashes = new[] { tx.Hash } }; - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using ScriptBuilder script = new(); script.EmitDynamicCall(NativeContract.Ledger.Hash, "getBlock", block.Hash.ToArray()); // Without block - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -81,17 +81,18 @@ public void System_Blockchain_GetBlock() const byte Prefix_Transaction = 11; const byte Prefix_CurrentBlock = 12; - var height = snapshot[NativeContract.Ledger.CreateStorageKey(Prefix_CurrentBlock)].GetInteroperable(); + TestUtils.BlocksAdd(snapshotCache, block.Hash, block); + + var height = snapshotCache[NativeContract.Ledger.CreateStorageKey(Prefix_CurrentBlock)].GetInteroperable(); height.Index = block.Index + TestProtocolSettings.Default.MaxTraceableBlocks; - TestUtils.BlocksAdd(snapshot, block.Hash, block); - snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, tx.Hash), new StorageItem(new TransactionState + snapshotCache.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, tx.Hash), new StorageItem(new TransactionState { BlockIndex = block.Index, Transaction = tx })); - engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -100,10 +101,10 @@ public void System_Blockchain_GetBlock() // With block - height = snapshot[NativeContract.Ledger.CreateStorageKey(Prefix_CurrentBlock)].GetInteroperable(); + height = snapshotCache[NativeContract.Ledger.CreateStorageKey(Prefix_CurrentBlock)].GetInteroperable(); height.Index = block.Index; - engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, settings: TestBlockchain.TheNeoSystem.Settings); + engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, settings: TestBlockchain.TheNeoSystem.Settings); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -116,13 +117,13 @@ public void System_Blockchain_GetBlock() [TestMethod] public void System_ExecutionEngine_GetScriptContainer() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using ScriptBuilder script = new(); script.EmitSysCall(ApplicationEngine.System_Runtime_GetScriptContainer); // Without tx - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.FAULT); @@ -152,7 +153,7 @@ public void System_ExecutionEngine_GetScriptContainer() Witnesses = new Witness[] { new Witness() { VerificationScript = new byte[] { 0x07 } } }, }; - engine = ApplicationEngine.Create(TriggerType.Application, tx, snapshot); + engine = ApplicationEngine.Create(TriggerType.Application, tx, snapshotCache); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -165,7 +166,7 @@ public void System_ExecutionEngine_GetScriptContainer() [TestMethod] public void System_Runtime_GasLeft() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); using (var script = new ScriptBuilder()) { @@ -180,7 +181,7 @@ public void System_Runtime_GasLeft() // Execute - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, gas: 100_000_000); + var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, gas: 100_000_000); engine.LoadScript(script.ToArray()); Assert.AreEqual(engine.Execute(), VMState.HALT); @@ -201,7 +202,7 @@ public void System_Runtime_GasLeft() // Execute - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot); + var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache); engine.LoadScript(script.ToArray()); // Check the results @@ -217,7 +218,7 @@ public void System_Runtime_GasLeft() public void System_Runtime_GetInvocationCounter() { ContractState contractA, contractB, contractC; - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); // Create dummy contracts @@ -235,15 +236,15 @@ public void System_Runtime_GetInvocationCounter() // Init A,B,C contracts // First two drops is for drop method and arguments - snapshot.DeleteContract(contractA.Hash); - snapshot.DeleteContract(contractB.Hash); - snapshot.DeleteContract(contractC.Hash); + snapshotCache.DeleteContract(contractA.Hash); + snapshotCache.DeleteContract(contractB.Hash); + snapshotCache.DeleteContract(contractC.Hash); contractA.Manifest = TestUtils.CreateManifest("dummyMain", ContractParameterType.Any, ContractParameterType.String, ContractParameterType.Integer); contractB.Manifest = TestUtils.CreateManifest("dummyMain", ContractParameterType.Any, ContractParameterType.String, ContractParameterType.Integer); contractC.Manifest = TestUtils.CreateManifest("dummyMain", ContractParameterType.Any, ContractParameterType.String, ContractParameterType.Integer); - snapshot.AddContract(contractA.Hash, contractA); - snapshot.AddContract(contractB.Hash, contractB); - snapshot.AddContract(contractC.Hash, contractC); + snapshotCache.AddContract(contractA.Hash, contractA); + snapshotCache.AddContract(contractB.Hash, contractB); + snapshotCache.AddContract(contractC.Hash, contractC); } // Call A,B,B,C @@ -257,7 +258,7 @@ public void System_Runtime_GetInvocationCounter() // Execute - var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshot, null, ProtocolSettings.Default); + var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, null, ProtocolSettings.Default); engine.LoadScript(script.ToArray()); Assert.AreEqual(VMState.HALT, engine.Execute()); diff --git a/tests/Neo.UnitTests/TestBlockchain.cs b/tests/Neo.UnitTests/TestBlockchain.cs index f7e06d0595..0596c629d2 100644 --- a/tests/Neo.UnitTests/TestBlockchain.cs +++ b/tests/Neo.UnitTests/TestBlockchain.cs @@ -41,9 +41,11 @@ internal static void ResetStore() TheNeoSystem.Blockchain.Ask(new Blockchain.Initialize()).Wait(); } - internal static DataCache GetTestSnapshot() + internal static SnapshotCache GetTestSnapshotCache(bool reset = true) { - return TheNeoSystem.GetSnapshot().CreateSnapshot(); + if (reset) + ResetStore(); + return TheNeoSystem.GetSnapshot(); } } } 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 index 4f663fec2d..dd1ad77625 100644 --- a/tests/Neo.UnitTests/TestUtils.Block.cs +++ b/tests/Neo.UnitTests/TestUtils.Block.cs @@ -11,6 +11,7 @@ using Akka.Util.Internal; using Neo.Cryptography; +using Neo.Extensions; using Neo.IO; using Neo.Network.P2P.Payloads; using Neo.Persistence; @@ -30,10 +31,12 @@ 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 @@ -42,12 +45,15 @@ public partial class TestUtils /// 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) + 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(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.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 @@ -57,10 +63,10 @@ public static void SetupHeaderWithValues(Header header, UInt256 val256, out UInt }; } - 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 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(header, val256, out merkRootVal, out val160, out timestampVal, out nonceVal, out indexVal, out scriptVal); + SetupHeaderWithValues(snapshot, header, val256, out merkRootVal, out val160, out timestampVal, out nonceVal, out indexVal, out scriptVal); transactionsVal = new Transaction[numberOfTransactions]; if (numberOfTransactions > 0) @@ -79,21 +85,32 @@ public static void SetupBlockWithValues(Block block, UInt256 val256, out UInt256 public static Block CreateBlockWithValidTransactions(DataCache snapshot, NEP6Wallet wallet, WalletAccount account, int numberOfTransactions) { - var block = new Block(); - 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(); - SetupHeaderWithValues(header, RandomUInt256(), out _, out _, out _, out _, out _, out _); + 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.ToArray(); + 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; } @@ -115,6 +132,10 @@ public static void BlocksAdd(DataCache snapshot, UInt256 hash, TrimmedBlock bloc { 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) @@ -129,8 +150,42 @@ public static void BlocksAdd(DataCache snapshot, UInt256 hash, Block block) }; 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) diff --git a/tests/Neo.UnitTests/TestUtils.Contract.cs b/tests/Neo.UnitTests/TestUtils.Contract.cs new file mode 100644 index 0000000000..da3139561c --- /dev/null +++ b/tests/Neo.UnitTests/TestUtils.Contract.cs @@ -0,0 +1,105 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestUtils.Contract.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using System; +using System.Linq; + +namespace Neo.UnitTests; + +partial class TestUtils +{ + public static ContractManifest CreateDefaultManifest() + { + return new ContractManifest + { + Name = "testManifest", + Groups = [], + SupportedStandards = [], + Abi = new ContractAbi + { + Events = [], + Methods = + [ + new ContractMethodDescriptor + { + Name = "testMethod", + Parameters = [], + ReturnType = ContractParameterType.Void, + Offset = 0, + Safe = true + } + ] + }, + Permissions = [ContractPermission.DefaultPermission], + Trusts = WildcardContainer.Create(), + Extra = null + }; + } + + public static ContractManifest CreateManifest(string method, ContractParameterType returnType, params ContractParameterType[] parameterTypes) + { + var manifest = CreateDefaultManifest(); + manifest.Abi.Methods = + [ + new ContractMethodDescriptor() + { + Name = method, + Parameters = parameterTypes.Select((p, i) => new ContractParameterDefinition + { + Name = $"p{i}", + Type = p + }).ToArray(), + ReturnType = returnType + } + ]; + return manifest; + } + + public static ContractState GetContract(string method = "test", int parametersCount = 0) + { + NefFile nef = new() + { + Compiler = "", + Source = "", + Tokens = [], + Script = new byte[] { 0x01, 0x01, 0x01, 0x01 } + }; + nef.CheckSum = NefFile.ComputeChecksum(nef); + return new ContractState + { + Id = 0x43000000, + Nef = nef, + Hash = nef.Script.Span.ToScriptHash(), + Manifest = CreateManifest(method, ContractParameterType.Any, Enumerable.Repeat(ContractParameterType.Any, parametersCount).ToArray()) + }; + } + + internal static ContractState GetContract(byte[] script, ContractManifest manifest = null) + { + NefFile nef = new() + { + Compiler = "", + Source = "", + Tokens = [], + Script = script + }; + nef.CheckSum = NefFile.ComputeChecksum(nef); + return new ContractState + { + Id = 1, + Hash = script.ToScriptHash(), + Nef = nef, + Manifest = manifest ?? CreateDefaultManifest() + }; + } +} diff --git a/tests/Neo.UnitTests/TestUtils.Transaction.cs b/tests/Neo.UnitTests/TestUtils.Transaction.cs new file mode 100644 index 0000000000..70ac264a19 --- /dev/null +++ b/tests/Neo.UnitTests/TestUtils.Transaction.cs @@ -0,0 +1,232 @@ +// 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.Cryptography.ECC; +using Neo.Extensions; +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; +using System.Numerics; + +namespace Neo.UnitTests; + +public partial class TestUtils +{ + 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; + tx.Signers = [new Signer { Account = account, Scopes = WitnessScope.CalledByEntry }]; + 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; + tx.Signers = [new Signer { Account = account, Scopes = WitnessScope.CalledByEntry }]; + 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 CreateRandomHashTransaction() + { + var randomBytes = new byte[16]; + TestRandom.NextBytes(randomBytes); + return new Transaction + { + Script = randomBytes, + Attributes = [], + Signers = [new Signer { Account = UInt160.Zero }], + Witnesses = + [ + new Witness + { + InvocationScript = Array.Empty(), + VerificationScript = Array.Empty() + } + ] + }; + } + + public static Transaction GetTransaction(UInt160 sender) + { + return new Transaction + { + Script = new[] { (byte)OpCode.PUSH2 }, + Attributes = [], + Signers = + [ + new Signer + { + Account = sender, + Scopes = WitnessScope.CalledByEntry, + AllowedContracts = [], + AllowedGroups = [], + Rules = [], + } + ], + Witnesses = + [ + new Witness + { + InvocationScript = Array.Empty(), + VerificationScript = Array.Empty() + } + ] + }; + } + + 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 55492d882c..a9ad4d8ff7 100644 --- a/tests/Neo.UnitTests/TestUtils.cs +++ b/tests/Neo.UnitTests/TestUtils.cs @@ -24,6 +24,7 @@ using Neo.Wallets; using Neo.Wallets.NEP6; using System; +using System.Collections.Generic; using System.Linq; using System.Numerics; @@ -47,53 +48,6 @@ public static UInt160 RandomUInt160() return new UInt160(data); } - public static ContractManifest CreateDefaultManifest() - { - return new ContractManifest() - { - Name = "testManifest", - Groups = new ContractGroup[0], - SupportedStandards = Array.Empty(), - Abi = new ContractAbi() - { - Events = new ContractEventDescriptor[0], - Methods = new[] - { - new ContractMethodDescriptor - { - Name = "testMethod", - Parameters = new ContractParameterDefinition[0], - ReturnType = ContractParameterType.Void, - Offset = 0, - Safe = true - } - } - }, - Permissions = new[] { ContractPermission.DefaultPermission }, - Trusts = WildcardContainer.Create(), - Extra = null - }; - } - - public static ContractManifest CreateManifest(string method, ContractParameterType returnType, params ContractParameterType[] parameterTypes) - { - ContractManifest manifest = CreateDefaultManifest(); - manifest.Abi.Methods = new ContractMethodDescriptor[] - { - new ContractMethodDescriptor() - { - Name = method, - Parameters = parameterTypes.Select((p, i) => new ContractParameterDefinition - { - Name = $"p{i}", - Type = p - }).ToArray(), - ReturnType = returnType - } - }; - return manifest; - } - public static StorageKey CreateStorageKey(this NativeContract contract, byte prefix, ISerializable key = null) { var k = new KeyBuilder(contract.Id, prefix); @@ -129,97 +83,6 @@ 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[] - { - 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 GetTransaction(UInt160 sender) - { - return new Transaction - { - Script = new byte[] { (byte)OpCode.PUSH2 }, - Attributes = Array.Empty(), - Signers = new[]{ new Signer() - { - Account = sender, - Scopes = WitnessScope.CalledByEntry, - AllowedContracts = Array.Empty(), - AllowedGroups = Array.Empty(), - Rules = Array.Empty(), - } }, - Witnesses = new Witness[]{ new Witness - { - InvocationScript = Array.Empty(), - VerificationScript = Array.Empty() - } } - }; - } - - public static ContractState GetContract(string method = "test", int parametersCount = 0) - { - NefFile nef = new() - { - Compiler = "", - Source = "", - Tokens = Array.Empty(), - Script = new byte[] { 0x01, 0x01, 0x01, 0x01 } - }; - nef.CheckSum = NefFile.ComputeChecksum(nef); - return new ContractState - { - Id = 0x43000000, - Nef = nef, - Hash = nef.Script.Span.ToScriptHash(), - Manifest = CreateManifest(method, ContractParameterType.Any, Enumerable.Repeat(ContractParameterType.Any, parametersCount).ToArray()) - }; - } - - internal static ContractState GetContract(byte[] script, ContractManifest manifest = null) - { - NefFile nef = new() - { - Compiler = "", - Source = "", - Tokens = Array.Empty(), - Script = script - }; - nef.CheckSum = NefFile.ComputeChecksum(nef); - return new ContractState - { - Id = 1, - Hash = script.ToScriptHash(), - Nef = nef, - Manifest = manifest ?? CreateDefaultManifest() - }; - } - internal static StorageItem GetStorageItem(byte[] value) { return new StorageItem @@ -246,24 +109,13 @@ public static void StorageItemAdd(DataCache snapshot, int id, byte[] keyValue, b }, new StorageItem(value)); } - public static Transaction CreateRandomHashTransaction() + public static void FillMemoryPool(DataCache snapshot, NeoSystem system, NEP6Wallet wallet, WalletAccount account) { - var randomBytes = new byte[16]; - TestRandom.NextBytes(randomBytes); - return new Transaction + for (int i = 0; i < system.Settings.MemoryPoolMaxTransactions; i++) { - Script = randomBytes, - Attributes = Array.Empty(), - Signers = new Signer[] { new Signer() { Account = UInt160.Zero } }, - Witnesses = new[] - { - new Witness - { - InvocationScript = new byte[0], - VerificationScript = new byte[0] - } - } - }; + var tx = CreateValidTx(snapshot, wallet, account); + system.MemPool.TryAdd(tx, snapshot); + } } public static T CopyMsgBySerialization(T serializableObj, T newObj) where T : ISerializable diff --git a/tests/Neo.UnitTests/UT_DataCache.cs b/tests/Neo.UnitTests/UT_DataCache.cs index 008f166b6f..5bc5b4085c 100644 --- a/tests/Neo.UnitTests/UT_DataCache.cs +++ b/tests/Neo.UnitTests/UT_DataCache.cs @@ -22,8 +22,8 @@ public class UT_DataCache [TestMethod] public void TestCachedFind_Between() { - var snapshot = TestBlockchain.GetTestSnapshot(); - var storages = snapshot.CreateSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + var storages = snapshotCache.CreateSnapshot(); var cache = new ClonedCache(storages); storages.Add @@ -61,8 +61,8 @@ public void TestCachedFind_Between() [TestMethod] public void TestCachedFind_Last() { - var snapshot = TestBlockchain.GetTestSnapshot(); - var storages = snapshot.CreateSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + var storages = snapshotCache.CreateSnapshot(); var cache = new ClonedCache(storages); storages.Add @@ -93,8 +93,8 @@ public void TestCachedFind_Last() [TestMethod] public void TestCachedFind_Empty() { - var snapshot = TestBlockchain.GetTestSnapshot(); - var storages = snapshot.CreateSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + var storages = snapshotCache.CreateSnapshot(); 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..6cf1605a24 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; @@ -50,91 +51,6 @@ public void ToScriptHash() res.Should().Be(UInt160.Parse("2d3b96ae1bcc5a585e075e3b81920210dec16302")); } - [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 TestHexToBytes() - { - string nullStr = null; - _ = nullStr.HexToBytes().ToHexString().Should().Be(Array.Empty().ToHexString()); - string emptyStr = ""; - emptyStr.HexToBytes().ToHexString().Should().Be(Array.Empty().ToHexString()); - string str1 = "hab"; - Action action = () => str1.HexToBytes(); - action.Should().Throw(); - string str2 = "0102"; - byte[] bytes = str2.HexToBytes(); - bytes.ToHexString().Should().Be(new byte[] { 0x01, 0x02 }.ToHexString()); - } - - [TestMethod] - public void TestRemoveHashsetDictionary() - { - var a = new HashSet - { - 1, - 2, - 3 - }; - - var b = new Dictionary - { - [2] = null - }; - - a.Remove(b); - - CollectionAssert.AreEqual(new int[] { 1, 3 }, a.ToArray()); - - b[4] = null; - b[5] = null; - b[1] = null; - a.Remove(b); - - CollectionAssert.AreEqual(new int[] { 3 }, a.ToArray()); - } - - [TestMethod] - public void TestRemoveHashsetSet() - { - var a = new HashSet - { - 1, - 2, - 3 - }; - - var b = new SortedSet() - { - 2 - }; - - a.Remove(b); - - CollectionAssert.AreEqual(new int[] { 1, 3 }, a.ToArray()); - - b.Add(4); - b.Add(5); - b.Add(1); - a.Remove(b); - - CollectionAssert.AreEqual(new int[] { 3 }, a.ToArray()); - } - [TestMethod] public void TestRemoveHashsetHashSetCache() { @@ -161,77 +77,5 @@ public void TestRemoveHashsetHashSetCache() CollectionAssert.AreEqual(new int[] { 3 }, a.ToArray()); } - - [TestMethod] - public void TestToHexString() - { - byte[] nullStr = null; - Assert.ThrowsException(() => nullStr.ToHexString()); - byte[] empty = Array.Empty(); - empty.ToHexString().Should().Be(""); - empty.ToHexString(false).Should().Be(""); - empty.ToHexString(true).Should().Be(""); - - byte[] str1 = new byte[] { (byte)'n', (byte)'e', (byte)'o' }; - str1.ToHexString().Should().Be("6e656f"); - str1.ToHexString(false).Should().Be("6e656f"); - str1.ToHexString(true).Should().Be("6f656e"); - } - - [TestMethod] - public void TestGetVersion() - { - // assembly without version - - var asm = AppDomain.CurrentDomain.GetAssemblies() - .Where(u => u.FullName == "Anonymously Hosted DynamicMethods Assembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null") - .FirstOrDefault(); - string version = asm?.GetVersion() ?? ""; - 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() - { - Random ran = new(); - Action action1 = () => ran.NextBigInteger(-1); - action1.Should().Throw(); - - ran.NextBigInteger(0).Should().Be(0); - ran.NextBigInteger(8).Should().NotBeNull(); - ran.NextBigInteger(9).Should().NotBeNull(); - } - - [TestMethod] - public void TestUnmapForIPAddress() - { - var addr = new IPAddress(new byte[] { 127, 0, 0, 1 }); - addr.Unmap().Should().Be(addr); - - var addr2 = addr.MapToIPv6(); - addr2.Unmap().Should().Be(addr); - } - - [TestMethod] - public void TestUnmapForIPEndPoin() - { - var addr = new IPAddress(new byte[] { 127, 0, 0, 1 }); - var endPoint = new IPEndPoint(addr, 8888); - endPoint.Unmap().Should().Be(endPoint); - - var addr2 = addr.MapToIPv6(); - var endPoint2 = new IPEndPoint(addr2, 8888); - endPoint2.Unmap().Should().Be(endPoint); - } } } 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 @" { diff --git a/tests/Neo.UnitTests/UT_UInt160.cs b/tests/Neo.UnitTests/UT_UInt160.cs index 4799a80d19..3502c7cf90 100644 --- a/tests/Neo.UnitTests/UT_UInt160.cs +++ b/tests/Neo.UnitTests/UT_UInt160.cs @@ -9,11 +9,10 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -#pragma warning disable CS1718 - using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; +using System.Security.Cryptography; namespace Neo.UnitTests.IO { @@ -36,8 +35,16 @@ public void TestGernerator1() [TestMethod] public void TestGernerator2() { - UInt160 uInt160 = new UInt160(new byte[20]); + UInt160 uInt160 = new byte[20]; + Assert.IsNotNull(uInt160); + } + + [TestMethod] + public void TestGernerator3() + { + UInt160 uInt160 = "0xff00000000000000000000000000000000000001"; Assert.IsNotNull(uInt160); + Assert.IsTrue(uInt160.ToString() == "0xff00000000000000000000000000000000000001"); } [TestMethod] @@ -57,9 +64,13 @@ public void TestEquals() byte[] temp = new byte[20]; temp[19] = 0x01; UInt160 result = new UInt160(temp); - Assert.AreEqual(true, UInt160.Zero.Equals(UInt160.Zero)); - Assert.AreEqual(false, UInt160.Zero.Equals(result)); - Assert.AreEqual(false, result.Equals(null)); + Assert.IsTrue(UInt160.Zero.Equals(UInt160.Zero)); + Assert.IsFalse(UInt160.Zero.Equals(result)); + Assert.IsFalse(result.Equals(null)); + Assert.IsTrue(UInt160.Zero == UInt160.Zero); + Assert.IsFalse(UInt160.Zero != UInt160.Zero); + Assert.IsTrue(UInt160.Zero == "0x0000000000000000000000000000000000000000"); + Assert.IsFalse(UInt160.Zero == "0x0000000000000000000000000000000000000001"); } [TestMethod] @@ -92,24 +103,28 @@ public void TestTryParse() public void TestOperatorLarger() { Assert.AreEqual(false, UInt160.Zero > UInt160.Zero); + Assert.IsFalse(UInt160.Zero > "0x0000000000000000000000000000000000000000"); } [TestMethod] public void TestOperatorLargerAndEqual() { Assert.AreEqual(true, UInt160.Zero >= UInt160.Zero); + Assert.IsTrue(UInt160.Zero >= "0x0000000000000000000000000000000000000000"); } [TestMethod] public void TestOperatorSmaller() { Assert.AreEqual(false, UInt160.Zero < UInt160.Zero); + Assert.IsFalse(UInt160.Zero < "0x0000000000000000000000000000000000000000"); } [TestMethod] public void TestOperatorSmallerAndEqual() { Assert.AreEqual(true, UInt160.Zero <= UInt160.Zero); + Assert.IsTrue(UInt160.Zero >= "0x0000000000000000000000000000000000000000"); } } } diff --git a/tests/Neo.UnitTests/VM/UT_Helper.cs b/tests/Neo.UnitTests/VM/UT_Helper.cs index c5bf61d152..db47555dbd 100644 --- a/tests/Neo.UnitTests/VM/UT_Helper.cs +++ b/tests/Neo.UnitTests/VM/UT_Helper.cs @@ -12,11 +12,13 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.VM; using Neo.VM.Types; +using Org.BouncyCastle.Asn1.Tsp; using System; using System.Collections.Generic; using System.Linq; @@ -104,6 +106,30 @@ public void TestEmitArray() Assert.AreEqual(0, engine2.ResultStack.Pop().Count); } + [TestMethod] + public void TestEmitStruct() + { + var expected = new BigInteger[] { 1, 2, 3 }; + var sb = new ScriptBuilder(); + sb.CreateStruct(expected); + + using var engine = ApplicationEngine.Create(TriggerType.Application, null, null); + engine.LoadScript(sb.ToArray()); + Assert.AreEqual(VMState.HALT, engine.Execute()); + + CollectionAssert.AreEqual(expected, engine.ResultStack.Pop().Select(u => u.GetInteger()).ToArray()); + + expected = new BigInteger[] { }; + sb = new ScriptBuilder(); + sb.CreateStruct(expected); + + using var engine2 = ApplicationEngine.Create(TriggerType.Application, null, null); + engine2.LoadScript(sb.ToArray()); + Assert.AreEqual(VMState.HALT, engine2.Execute()); + + Assert.AreEqual(0, engine2.ResultStack.Pop().Count); + } + [TestMethod] public void TestEmitMap() { @@ -151,7 +177,7 @@ public void TestEmitAppCall3() sb.EmitDynamicCall(UInt160.Zero, "AAAAA", true); byte[] tempArray = new byte[38]; tempArray[0] = (byte)OpCode.PUSHT; - tempArray[1] = (byte)OpCode.PUSH1;//arg.Length + tempArray[1] = (byte)OpCode.PUSH1;//arg.Length tempArray[2] = (byte)OpCode.PACK; tempArray[3] = (byte)OpCode.PUSH15;//(byte)CallFlags.All; tempArray[4] = (byte)OpCode.PUSHDATA1; @@ -400,6 +426,7 @@ public void TestEmitPush3() TestEmitPush3Byte(); TestEmitPush3Short(); TestEmitPush3Ushort(); + TestEmitPush3Char(); TestEmitPush3Int(); TestEmitPush3Uint(); TestEmitPush3Long(); @@ -471,6 +498,16 @@ private void TestEmitPush3Ushort() CollectionAssert.AreEqual(tempArray, sb.ToArray()); } + private void TestEmitPush3Char() + { + ScriptBuilder sb = new ScriptBuilder(); + char temp = char.MinValue; + VM.Helper.EmitPush(sb, temp); + byte[] tempArray = new byte[1]; + tempArray[0] = (byte)OpCode.PUSH0; + CollectionAssert.AreEqual(tempArray, sb.ToArray()); + } + private void TestEmitPush3Short() { ScriptBuilder sb = new ScriptBuilder(); @@ -629,5 +666,28 @@ private void TestToParaMeter2VMArray() Assert.AreEqual(ContractParameterType.Array, parameter.Type); Assert.AreEqual(0, ((List)parameter.Value).Count); } + + [TestMethod] + public void TestCharAsUInt16() + { + Assert.AreEqual(ushort.MaxValue, char.MaxValue); + Assert.AreEqual(ushort.MinValue, char.MinValue); + + // test every char in a loop + for (int i = ushort.MinValue; i < char.MinValue; i++) + { + var c = Convert.ToChar(i); + Assert.AreEqual(i, c); + } + + for (int i = ushort.MinValue; i < ushort.MaxValue; i++) + { + using var sbUInt16 = new ScriptBuilder(); + using var sbChar = new ScriptBuilder(); + sbUInt16.EmitPush((ushort)i); + sbChar.EmitPush(Convert.ToChar(i)); + CollectionAssert.AreEqual(sbUInt16.ToArray(), sbChar.ToArray()); + } + } } } diff --git a/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs index 65ec90ab7e..599cd418a1 100644 --- a/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs +++ b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Account.cs @@ -60,7 +60,7 @@ public void TestChangePassword() _account.ChangePasswordPrepare("b", "Satoshi").Should().BeTrue(); _account.ChangePasswordCommit(); _account.ChangePasswordPrepare("Satoshi", "b").Should().BeTrue(); - _account.ChangePasswordRoolback(); + _account.ChangePasswordRollback(); _account.VerifyPassword("Satoshi").Should().BeTrue(); } diff --git a/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Contract.cs b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Contract.cs index 37f816c240..bac6c0d4eb 100644 --- a/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Contract.cs +++ b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Contract.cs @@ -11,6 +11,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.Json; using Neo.SmartContract; using Neo.Wallets.NEP6; diff --git a/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs index 71914c86f0..3f3a61ca03 100644 --- a/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs +++ b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs @@ -11,6 +11,7 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.Json; using Neo.Network.P2P.Payloads; using Neo.SmartContract; @@ -89,10 +90,10 @@ public void TestCreateAccount() Script = new byte[1], Signers = new Signer[] { new Signer() { Account = acc.ScriptHash } }, }; - var ctx = new ContractParametersContext(TestBlockchain.GetTestSnapshot(), tx, TestProtocolSettings.Default.Network); + var ctx = new ContractParametersContext(TestBlockchain.GetTestSnapshotCache(), tx, TestProtocolSettings.Default.Network); Assert.IsTrue(uut.Sign(ctx)); tx.Witnesses = ctx.GetWitnesses(); - Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, TestBlockchain.GetTestSnapshot(), long.MaxValue)); + Assert.IsTrue(tx.VerifyWitnesses(TestProtocolSettings.Default, TestBlockchain.GetTestSnapshotCache(), long.MaxValue)); Assert.ThrowsException(() => uut.CreateAccount((byte[])null)); Assert.ThrowsException(() => uut.CreateAccount("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551".HexToBytes())); } diff --git a/tests/Neo.UnitTests/Wallets/UT_AssetDescriptor.cs b/tests/Neo.UnitTests/Wallets/UT_AssetDescriptor.cs index 8d938492bc..ff2386c466 100644 --- a/tests/Neo.UnitTests/Wallets/UT_AssetDescriptor.cs +++ b/tests/Neo.UnitTests/Wallets/UT_AssetDescriptor.cs @@ -22,10 +22,10 @@ public class UT_AssetDescriptor [TestMethod] public void TestConstructorWithNonexistAssetId() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); Action action = () => { - var descriptor = new Neo.Wallets.AssetDescriptor(snapshot, TestProtocolSettings.Default, UInt160.Parse("01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4")); + var descriptor = new Neo.Wallets.AssetDescriptor(snapshotCache, TestProtocolSettings.Default, UInt160.Parse("01ff00ff00ff00ff00ff00ff00ff00ff00ff00a4")); }; action.Should().Throw(); } @@ -33,8 +33,8 @@ public void TestConstructorWithNonexistAssetId() [TestMethod] public void Check_GAS() { - var snapshot = TestBlockchain.GetTestSnapshot(); - var descriptor = new Neo.Wallets.AssetDescriptor(snapshot, TestProtocolSettings.Default, NativeContract.GAS.Hash); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + var descriptor = new Neo.Wallets.AssetDescriptor(snapshotCache, TestProtocolSettings.Default, NativeContract.GAS.Hash); descriptor.AssetId.Should().Be(NativeContract.GAS.Hash); descriptor.AssetName.Should().Be(nameof(GasToken)); descriptor.ToString().Should().Be(nameof(GasToken)); @@ -45,8 +45,8 @@ public void Check_GAS() [TestMethod] public void Check_NEO() { - var snapshot = TestBlockchain.GetTestSnapshot(); - var descriptor = new Neo.Wallets.AssetDescriptor(snapshot, TestProtocolSettings.Default, NativeContract.NEO.Hash); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + var descriptor = new Neo.Wallets.AssetDescriptor(snapshotCache, TestProtocolSettings.Default, NativeContract.NEO.Hash); descriptor.AssetId.Should().Be(NativeContract.NEO.Hash); descriptor.AssetName.Should().Be(nameof(NeoToken)); descriptor.ToString().Should().Be(nameof(NeoToken)); diff --git a/tests/Neo.UnitTests/Wallets/UT_Wallet.cs b/tests/Neo.UnitTests/Wallets/UT_Wallet.cs index d134c6aed3..bba7ff05a1 100644 --- a/tests/Neo.UnitTests/Wallets/UT_Wallet.cs +++ b/tests/Neo.UnitTests/Wallets/UT_Wallet.cs @@ -217,14 +217,14 @@ public void TestGetAvailable() account.Lock = false; // Fake balance - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var key = NativeContract.GAS.CreateStorageKey(20, account.ScriptHash); - var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + var entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; - wallet.GetAvailable(snapshot, NativeContract.GAS.Hash).Should().Be(new BigDecimal(new BigInteger(1000000000000M), 8)); + wallet.GetAvailable(snapshotCache, NativeContract.GAS.Hash).Should().Be(new BigDecimal(new BigInteger(1000000000000M), 8)); - entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 0; } @@ -237,15 +237,15 @@ public void TestGetBalance() account.Lock = false; // Fake balance - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); var key = NativeContract.GAS.CreateStorageKey(20, account.ScriptHash); - var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + var entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; - wallet.GetBalance(snapshot, UInt160.Zero, new UInt160[] { account.ScriptHash }).Should().Be(new BigDecimal(BigInteger.Zero, 0)); - wallet.GetBalance(snapshot, NativeContract.GAS.Hash, new UInt160[] { account.ScriptHash }).Should().Be(new BigDecimal(new BigInteger(1000000000000M), 8)); + wallet.GetBalance(snapshotCache, UInt160.Zero, new UInt160[] { account.ScriptHash }).Should().Be(new BigDecimal(BigInteger.Zero, 0)); + wallet.GetBalance(snapshotCache, NativeContract.GAS.Hash, new UInt160[] { account.ScriptHash }).Should().Be(new BigDecimal(new BigInteger(1000000000000M), 8)); - entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 0; } @@ -290,13 +290,13 @@ public void TestImport2() [TestMethod] public void TestMakeTransaction1() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); MyWallet wallet = new(); Contract contract = Contract.Create(new ContractParameterType[] { ContractParameterType.Boolean }, new byte[] { 1 }); WalletAccount account = wallet.CreateAccount(contract, glkey.PrivateKey); account.Lock = false; - Action action = () => wallet.MakeTransaction(snapshot, new TransferOutput[] + Action action = () => wallet.MakeTransaction(snapshotCache, new TransferOutput[] { new TransferOutput() { @@ -308,7 +308,7 @@ public void TestMakeTransaction1() }, UInt160.Zero); action.Should().Throw(); - action = () => wallet.MakeTransaction(snapshot, new TransferOutput[] + action = () => wallet.MakeTransaction(snapshotCache, new TransferOutput[] { new TransferOutput() { @@ -320,7 +320,7 @@ public void TestMakeTransaction1() }, account.ScriptHash); action.Should().Throw(); - action = () => wallet.MakeTransaction(snapshot, new TransferOutput[] + action = () => wallet.MakeTransaction(snapshotCache, new TransferOutput[] { new TransferOutput() { @@ -334,14 +334,14 @@ public void TestMakeTransaction1() // Fake balance var key = NativeContract.GAS.CreateStorageKey(20, account.ScriptHash); - var entry1 = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + var entry1 = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry1.GetInteroperable().Balance = 10000 * NativeContract.GAS.Factor; key = NativeContract.NEO.CreateStorageKey(20, account.ScriptHash); - var entry2 = snapshot.GetAndChange(key, () => new StorageItem(new NeoToken.NeoAccountState())); + var entry2 = snapshotCache.GetAndChange(key, () => new StorageItem(new NeoToken.NeoAccountState())); entry2.GetInteroperable().Balance = 10000 * NativeContract.NEO.Factor; - var tx = wallet.MakeTransaction(snapshot, new TransferOutput[] + var tx = wallet.MakeTransaction(snapshotCache, new TransferOutput[] { new TransferOutput() { @@ -352,7 +352,7 @@ public void TestMakeTransaction1() }); tx.Should().NotBeNull(); - tx = wallet.MakeTransaction(snapshot, new TransferOutput[] + tx = wallet.MakeTransaction(snapshotCache, new TransferOutput[] { new TransferOutput() { @@ -364,8 +364,8 @@ public void TestMakeTransaction1() }); tx.Should().NotBeNull(); - entry1 = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); - entry2 = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + entry1 = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); + entry2 = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry1.GetInteroperable().Balance = 0; entry2.GetInteroperable().Balance = 0; } @@ -373,9 +373,9 @@ public void TestMakeTransaction1() [TestMethod] public void TestMakeTransaction2() { - var snapshot = TestBlockchain.GetTestSnapshot(); + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); MyWallet wallet = new(); - Action action = () => wallet.MakeTransaction(snapshot, Array.Empty(), null, null, Array.Empty()); + Action action = () => wallet.MakeTransaction(snapshotCache, Array.Empty(), null, null, Array.Empty()); action.Should().Throw(); Contract contract = Contract.Create(new ContractParameterType[] { ContractParameterType.Boolean }, new byte[] { 1 }); @@ -384,10 +384,10 @@ public void TestMakeTransaction2() // Fake balance var key = NativeContract.GAS.CreateStorageKey(20, account.ScriptHash); - var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + var entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 1000000 * NativeContract.GAS.Factor; - var tx = wallet.MakeTransaction(snapshot, Array.Empty(), account.ScriptHash, new[]{ new Signer() + var tx = wallet.MakeTransaction(snapshotCache, Array.Empty(), account.ScriptHash, new[]{ new Signer() { Account = account.ScriptHash, Scopes = WitnessScope.CalledByEntry @@ -395,10 +395,10 @@ public void TestMakeTransaction2() tx.Should().NotBeNull(); - tx = wallet.MakeTransaction(snapshot, Array.Empty(), null, null, Array.Empty()); + tx = wallet.MakeTransaction(snapshotCache, Array.Empty(), null, null, Array.Empty()); tx.Should().NotBeNull(); - entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + entry = snapshotCache.GetAndChange(key, () => new StorageItem(new AccountState())); entry.GetInteroperable().Balance = 0; } diff --git a/tests/Neo.VM.Tests/Neo.VM.Tests.csproj b/tests/Neo.VM.Tests/Neo.VM.Tests.csproj index 53c16ed4af..2766ce05d2 100644 --- a/tests/Neo.VM.Tests/Neo.VM.Tests.csproj +++ b/tests/Neo.VM.Tests/Neo.VM.Tests.csproj @@ -17,9 +17,9 @@ - - - + + + diff --git a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODMUL.json b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODMUL.json index e3b93bfb56..1c2a9d9dab 100644 --- a/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODMUL.json +++ b/tests/Neo.VM.Tests/Tests/OpCodes/Arithmetic/MODMUL.json @@ -136,6 +136,242 @@ } } ] + }, + { + "name": "Real test (-3 * 4 % 5)", + "script": [ + "PUSH3", + "NEGATE", + "PUSH4", + "PUSH5", + "MODMUL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "MODMUL", + "evaluationStack": [ + { + "type": "Integer", + "value": 5 + }, + { + "type": "Integer", + "value": 4 + }, + { + "type": "Integer", + "value": -3 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": -2 + } + ] + } + } + ] + }, + { + "name": "Real test (3 * 4 % -5)", + "script": [ + "PUSH3", + "PUSH4", + "PUSH5", + "NEGATE", + "MODMUL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 4, + "nextInstruction": "MODMUL", + "evaluationStack": [ + { + "type": "Integer", + "value": -5 + }, + { + "type": "Integer", + "value": 4 + }, + { + "type": "Integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": 2 + } + ] + } + } + ] + }, + { + "name": "Real test (-3 * 4 % -5)", + "script": [ + "PUSH3", + "NEGATE", + "PUSH4", + "PUSH5", + "NEGATE", + "MODMUL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "MODMUL", + "evaluationStack": [ + { + "type": "Integer", + "value": -5 + }, + { + "type": "Integer", + "value": 4 + }, + { + "type": "Integer", + "value": -3 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": -2 + } + ] + } + } + ] + }, + { + "name": "Real test (3 * -4 % -5)", + "script": [ + "PUSH3", + "PUSH4", + "NEGATE", + "PUSH5", + "NEGATE", + "MODMUL" + ], + "steps": [ + { + "actions": [ + "stepInto", + "stepInto", + "stepInto", + "stepInto", + "stepInto" + ], + "result": { + "state": "BREAK", + "invocationStack": [ + { + "instructionPointer": 5, + "nextInstruction": "MODMUL", + "evaluationStack": [ + { + "type": "Integer", + "value": -5 + }, + { + "type": "Integer", + "value": -4 + }, + { + "type": "Integer", + "value": 3 + } + ] + } + ] + } + }, + { + "actions": [ + "execute" + ], + "result": { + "state": "HALT", + "invocationStack": [], + "resultStack": [ + { + "type": "Integer", + "value": -2 + } + ] + } + } + ] } ] } diff --git a/tests/Neo.VM.Tests/Types/TestEngine.cs b/tests/Neo.VM.Tests/Types/TestEngine.cs index cf99314892..2602163315 100644 --- a/tests/Neo.VM.Tests/Types/TestEngine.cs +++ b/tests/Neo.VM.Tests/Types/TestEngine.cs @@ -15,7 +15,7 @@ namespace Neo.Test.Types { - class TestEngine : ExecutionEngine + public class TestEngine : ExecutionEngine { public Exception FaultException { get; private set; } diff --git a/tests/Neo.VM.Tests/UT_ReferenceCounter.cs b/tests/Neo.VM.Tests/UT_ReferenceCounter.cs index f974670805..61f7788dd8 100644 --- a/tests/Neo.VM.Tests/UT_ReferenceCounter.cs +++ b/tests/Neo.VM.Tests/UT_ReferenceCounter.cs @@ -12,6 +12,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.VM; using Neo.VM.Types; +using System; +using Array = Neo.VM.Types.Array; namespace Neo.Test { @@ -240,5 +242,22 @@ public void TestArrayNoPush() Assert.AreEqual(VMState.HALT, engine.Execute()); Assert.AreEqual(array.Count, engine.ReferenceCounter.Count); } + + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public void TestInvalidReferenceStackItem() + { + var reference = new ReferenceCounter(); + var arr = new Array(reference); + var arr2 = new Array(); + + for (var i = 0; i < 10; i++) + { + arr2.Add(i); + } + + arr.Add(arr2); + Assert.AreEqual(11, reference.Count); + } } } diff --git a/tests/Neo.VM.Tests/UT_ScriptBuilder.cs b/tests/Neo.VM.Tests/UT_ScriptBuilder.cs index faa57f3345..6d5b4e6f2d 100644 --- a/tests/Neo.VM.Tests/UT_ScriptBuilder.cs +++ b/tests/Neo.VM.Tests/UT_ScriptBuilder.cs @@ -143,41 +143,63 @@ public void TestEmitJump() [TestMethod] public void TestEmitPushBigInteger() { - using (ScriptBuilder script = new()) + // Test small integers (-1 to 16) + for (var i = -1; i <= 16; i++) { - script.EmitPush(BigInteger.MinusOne); - CollectionAssert.AreEqual(new byte[] { 0x0F }, script.ToArray()); + using ScriptBuilder script = new(); + script.EmitPush(new BigInteger(i)); + CollectionAssert.AreEqual(new[] { (byte)(OpCode.PUSH0 + (byte)i) }, script.ToArray()); } - using (ScriptBuilder script = new()) - { - script.EmitPush(BigInteger.Zero); - CollectionAssert.AreEqual(new byte[] { 0x10 }, script.ToArray()); - } + // Test -1 + Assert.AreEqual("0x0f", new ScriptBuilder().EmitPush(BigInteger.MinusOne).ToArray().ToHexString()); - for (byte x = 1; x <= 16; x++) - { - using ScriptBuilder script = new(); - script.EmitPush(new BigInteger(x)); - CollectionAssert.AreEqual(new byte[] { (byte)(OpCode.PUSH0 + x) }, script.ToArray()); - } + // Test edge cases for different sizes + // PUSHINT8 + Assert.AreEqual("0x0080", new ScriptBuilder().EmitPush(sbyte.MinValue).ToArray().ToHexString()); + Assert.AreEqual("0x007f", new ScriptBuilder().EmitPush(sbyte.MaxValue).ToArray().ToHexString()); + + // PUSHINT16 + Assert.AreEqual("0x010080", new ScriptBuilder().EmitPush(short.MinValue).ToArray().ToHexString()); + Assert.AreEqual("0x01ff7f", new ScriptBuilder().EmitPush(short.MaxValue).ToArray().ToHexString()); + + // PUSHINT32 + Assert.AreEqual("0x0200000080", new ScriptBuilder().EmitPush(int.MinValue).ToArray().ToHexString()); + Assert.AreEqual("0x02ffffff7f", new ScriptBuilder().EmitPush(int.MaxValue).ToArray().ToHexString()); + + // PUSHINT64 + Assert.AreEqual("0x030000000000000080", new ScriptBuilder().EmitPush(long.MinValue).ToArray().ToHexString()); + Assert.AreEqual("0x03ffffffffffffff7f", new ScriptBuilder().EmitPush(long.MaxValue).ToArray().ToHexString()); + + // PUSHINT128 + Assert.AreEqual("0x04ffffffffffffffff0000000000000000", new ScriptBuilder().EmitPush(new BigInteger(ulong.MaxValue)).ToArray().ToHexString()); + Assert.AreEqual("0x0400000000000000000100000000000000", new ScriptBuilder().EmitPush(new BigInteger(ulong.MaxValue) + 1).ToArray().ToHexString()); + + // PUSHINT256, case from https://en.wikipedia.org/wiki/256-bit_computing#:~:text=The%20range%20of%20a%20signed,%2C%E2%80%8B819%2C%E2%80%8B967. + Assert.AreEqual("0x050000000000000000000000000000000000000000000000000000000000000080", + new ScriptBuilder().EmitPush(BigInteger.Parse("-57896044618658097711785492504343953926634992332820282019728792003956564819968")).ToArray().ToHexString()); + + Assert.AreEqual("0x05ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + new ScriptBuilder().EmitPush(BigInteger.Parse("57896044618658097711785492504343953926634992332820282019728792003956564819967")).ToArray().ToHexString()); + + // Test exceeding 256-bit value (2^256) + Assert.ThrowsException(() => + new ScriptBuilder().EmitPush(BigInteger.Parse("115792089237316195423570985008687907853269984665640564039457584007913129639936"))); + + // Test negative numbers + Assert.AreEqual("0x00fe", new ScriptBuilder().EmitPush(new BigInteger(-2)).ToArray().ToHexString()); + Assert.AreEqual("0x0100ff", new ScriptBuilder().EmitPush(new BigInteger(-256)).ToArray().ToHexString()); + + // Test numbers that are exactly at the boundary + Assert.AreEqual("0x04ffffffffffffffff0000000000000000", new ScriptBuilder().EmitPush(BigInteger.Parse("18446744073709551615")).ToArray().ToHexString()); + Assert.AreEqual("0x0400000000000000000100000000000000", new ScriptBuilder().EmitPush(BigInteger.Parse("18446744073709551616")).ToArray().ToHexString()); - CollectionAssert.AreEqual("0080".FromHexString(), new ScriptBuilder().EmitPush(sbyte.MinValue).ToArray()); - CollectionAssert.AreEqual("007f".FromHexString(), new ScriptBuilder().EmitPush(sbyte.MaxValue).ToArray()); - CollectionAssert.AreEqual("01ff00".FromHexString(), new ScriptBuilder().EmitPush(byte.MaxValue).ToArray()); - CollectionAssert.AreEqual("010080".FromHexString(), new ScriptBuilder().EmitPush(short.MinValue).ToArray()); - CollectionAssert.AreEqual("01ff7f".FromHexString(), new ScriptBuilder().EmitPush(short.MaxValue).ToArray()); - CollectionAssert.AreEqual("02ffff0000".FromHexString(), new ScriptBuilder().EmitPush(ushort.MaxValue).ToArray()); - CollectionAssert.AreEqual("0200000080".FromHexString(), new ScriptBuilder().EmitPush(int.MinValue).ToArray()); - CollectionAssert.AreEqual("02ffffff7f".FromHexString(), new ScriptBuilder().EmitPush(int.MaxValue).ToArray()); - CollectionAssert.AreEqual("03ffffffff00000000".FromHexString(), new ScriptBuilder().EmitPush(uint.MaxValue).ToArray()); - CollectionAssert.AreEqual("030000000000000080".FromHexString(), new ScriptBuilder().EmitPush(long.MinValue).ToArray()); - CollectionAssert.AreEqual("03ffffffffffffff7f".FromHexString(), new ScriptBuilder().EmitPush(long.MaxValue).ToArray()); - CollectionAssert.AreEqual("04ffffffffffffffff0000000000000000".FromHexString(), new ScriptBuilder().EmitPush(ulong.MaxValue).ToArray()); - CollectionAssert.AreEqual("050100000000000000feffffffffffffff00000000000000000000000000000000".FromHexString(), new ScriptBuilder().EmitPush(new BigInteger(ulong.MaxValue) * new BigInteger(ulong.MaxValue)).ToArray()); + // Test very large negative number + Assert.AreEqual("0x040000000000000000ffffffffffffffff", new ScriptBuilder().EmitPush(BigInteger.Parse("-18446744073709551616")).ToArray().ToHexString()); + // Test exception for too large BigInteger Assert.ThrowsException(() => new ScriptBuilder().EmitPush( - new BigInteger("050100000000000000feffffffffffffff0100000000000000feffffffffffffff00000000000000000000000000000000".FromHexString()))); + BigInteger.Parse("115792089237316195423570985008687907853269984665640564039457584007913129639937"))); } [TestMethod]