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 dca46dbbd6..22cf4d08df 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,11 +24,30 @@ jobs: - name: Check Format (*.cs) run: dotnet format --verify-no-changes --verbosity diagnostic - - name: Build (Neo.CLI) + Test-Everything: + needs: [Format] + timeout-minutes: 15 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Build (Everything) + run: dotnet build + + - name: Install dependencies run: | - dotnet build ./src/Neo.CLI \ - --output ./out/Neo.CLI + sudo apt-get install libleveldb-dev expect + 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.exp + Test: needs: [Format] timeout-minutes: 15 @@ -39,15 +58,19 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: ${{ env.DOTNET_VERSION }} + - name: Test 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' run: | @@ -58,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 @@ -65,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' @@ -82,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/') @@ -101,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: | @@ -121,7 +142,7 @@ jobs: --source https://nuget.pkg.github.com/neo-project/index.json \ --api-key "${{ secrets.GITHUB_TOKEN }}" \ --disable-buffering \ - --no-service-endpoint; + --no-service-endpoint - name: Publish to myGet working-directory: ./out @@ -130,83 +151,4 @@ jobs: --source https://www.myget.org/F/neo/api/v3/index.json \ --api-key "${{ secrets.MYGET_TOKEN }}" \ --disable-buffering \ - --no-service-endpoint; - - Release: - if: github.ref == 'refs/heads/master' && startsWith(github.repository, 'neo-project/') - needs: [Test] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Get version - id: get_version - run: | - sudo apt install xmlstarlet - find src -name Directory.Build.props | xargs xmlstarlet sel -N i=http://schemas.microsoft.com/developer/msbuild/2003 -t -v "concat('::set-output name=version::v',//i:VersionPrefix/text())" | xargs echo - - name: Check tag - id: check_tag - run: curl -s -I ${{ format('https://github.com/{0}/releases/tag/{1}', github.repository, steps.get_version.outputs.version) }} | head -n 1 | cut -d$' ' -f2 | xargs printf "::set-output name=statusCode::%s" | xargs echo - - name: Create release - if: steps.check_tag.outputs.statusCode == '404' - id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ steps.get_version.outputs.version }} - release_name: ${{ steps.get_version.outputs.version }} - prerelease: ${{ contains(steps.get_version.outputs.version, '-') }} - - name: Setup .NET - if: steps.check_tag.outputs.statusCode == '404' - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ env.DOTNET_VERSION }} - - name : Pack (Neo) - if: steps.check_tag.outputs.statusCode == '404' - run: | - dotnet pack ./src/Neo \ - --configuration Release \ - --output ./out - - name : Pack (Neo.IO) - if: steps.check_tag.outputs.statusCode == '404' - run: | - dotnet pack ./src/Neo.IO \ - --configuration Release \ - --output ./out - - name : Pack (Neo.Extensions) - if: steps.check_tag.outputs.statusCode == '404' - run: | - dotnet pack ./src/Neo.Extensions \ - --configuration Release \ - --output ./out - - name : Pack (Neo.Json) - if: steps.check_tag.outputs.statusCode == '404' - run: | - dotnet pack ./src/Neo.Json \ - --configuration Release \ - --output ./out - - name : Pack (Neo.VM) - if: steps.check_tag.outputs.statusCode == '404' - run: | - dotnet pack ./src/Neo.VM \ - --configuration Release \ - --output ./out - - name : Pack (Neo.ConsoleService) - if: steps.check_tag.outputs.statusCode == '404' - run: | - dotnet pack ./src/Neo.ConsoleService \ - --configuration Release \ - --output ./out - - name : Pack (Neo.Cryptography.BLS12_381) - if: steps.check_tag.outputs.statusCode == '404' - run: | - dotnet pack ./src/Neo.Cryptography.BLS12_381 \ - --configuration Release \ - --output ./out - - name: Publish to NuGet - if: steps.check_tag.outputs.statusCode == '404' - run: | - dotnet nuget push out/*.nupkg -s https://api.nuget.org/v3/index.json -k ${NUGET_TOKEN} --skip-duplicate - env: - NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }} + --no-service-endpoint diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml new file mode 100644 index 0000000000..3fa6cc4f5e --- /dev/null +++ b/.github/workflows/nuget.yml @@ -0,0 +1,46 @@ +name: Release (nuget) + +# 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 + +jobs: + nuget-release: + runs-on: ubuntu-latest + 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: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Pack NuGet Packages + run: | + dotnet pack ./neo.sln \ + --configuration Release \ + --output ./sbin \ + --verbosity normal \ + -p:VersionPrefix=${{ env.APP_VERSION }} + + - name: Publish to NuGet.org + run: | + dotnet nuget push ./sbin/*.nupkg \ + --source https://api.nuget.org/v3/index.json \ + --api-key ${{ secrets.NUGET_TOKEN }} \ + --skip-duplicate diff --git a/.github/workflows/pkgs-delete.yml b/.github/workflows/pkgs-delete.yml index cf3471b551..2dc580953d 100644 --- a/.github/workflows/pkgs-delete.yml +++ b/.github/workflows/pkgs-delete.yml @@ -102,7 +102,7 @@ jobs: delete-only-pre-release-versions: "true" token: "${{ secrets.GITHUB_TOKEN }}" - - name: Delete Neo Package + - name: Delete Neo Package (nuget) uses: actions/delete-package-versions@v4 with: package-name: Neo @@ -110,6 +110,16 @@ jobs: min-versions-to-keep: 3 delete-only-pre-release-versions: "true" token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Delete Neo Package (docker) + uses: actions/delete-package-versions@v4 + with: + package-name: Neo + package-type: docker + min-versions-to-keep: 1 + delete-only-pre-release-versions: "true" + token: "${{ secrets.GITHUB_TOKEN }}" + - name: Delete Neo.ConsoleService Package uses: actions/delete-package-versions@v4 with: @@ -118,6 +128,7 @@ jobs: min-versions-to-keep: 3 delete-only-pre-release-versions: "true" token: "${{ secrets.GITHUB_TOKEN }}" + - name: Delete Neo.Extensions Package uses: actions/delete-package-versions@v4 with: 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/benchmarks/Directory.Build.props b/benchmarks/Directory.Build.props new file mode 100644 index 0000000000..8ef2d9e6bb --- /dev/null +++ b/benchmarks/Directory.Build.props @@ -0,0 +1,8 @@ + + + + + false + + + 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/Neo.Benchmarks.csproj b/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj index 476ef93060..c4b1a35da9 100644 --- a/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj +++ b/benchmarks/Neo.Benchmarks/Neo.Benchmarks.csproj @@ -5,11 +5,11 @@ net8.0 Neo enable - false + diff --git a/benchmarks/Neo.Benchmarks/Program.cs b/benchmarks/Neo.Benchmarks/Program.cs index 9d4125bb9f..e39ef23ff7 100644 --- a/benchmarks/Neo.Benchmarks/Program.cs +++ b/benchmarks/Neo.Benchmarks/Program.cs @@ -9,10 +9,7 @@ // 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(); diff --git a/benchmarks/Neo.VM.Benchmarks/Benchmarks.cs b/benchmarks/Neo.VM.Benchmarks/Benchmarks.POC.cs similarity index 87% rename from benchmarks/Neo.VM.Benchmarks/Benchmarks.cs rename to benchmarks/Neo.VM.Benchmarks/Benchmarks.POC.cs index 6eab691a7d..1a5dac4b97 100644 --- a/benchmarks/Neo.VM.Benchmarks/Benchmarks.cs +++ b/benchmarks/Neo.VM.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,13 +9,15 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using BenchmarkDotNet.Attributes; using System.Diagnostics; -namespace Neo.VM +namespace Neo.VM.Benchmark { - public static class Benchmarks + public class Benchmarks_PoCs { - public static void NeoIssue2528() + [Benchmark] + public void NeoIssue2528() { // https://github.com/neo-project/neo/issues/2528 // L01: INITSLOT 1, 0 @@ -47,7 +49,8 @@ public static void NeoIssue2528() Run(nameof(NeoIssue2528), "VwEAwkpKAfsHdwARwG8AnXcAbwAl9////xHAzwJwlAAAdwAQzm8AnXcAbwAl9////0U="); } - public static void NeoVMIssue418() + [Benchmark] + public void NeoVMIssue418() { // https://github.com/neo-project/neo-vm/issues/418 // L00: NEWARRAY0 @@ -81,7 +84,8 @@ public static void NeoVMIssue418() Run(nameof(NeoVMIssue418), "whBNEcARTRHAVgEB/gGdYBFNEU0SwFMSwFhKJPNFUUU="); } - public static void NeoIssue2723() + [Benchmark] + public void NeoIssue2723() { // L00: INITSSLOT 1 // L01: PUSHINT32 130000 @@ -102,11 +106,8 @@ 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/Benchmarks.Types.cs b/benchmarks/Neo.VM.Benchmarks/Benchmarks.Types.cs new file mode 100644 index 0000000000..3685e45f12 --- /dev/null +++ b/benchmarks/Neo.VM.Benchmarks/Benchmarks.Types.cs @@ -0,0 +1,122 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// Benchmarks.Types.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_Types +{ + 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, ReferenceCounter? 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, ReferenceCounter 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/benchmarks/Neo.VM.Benchmarks/Neo.VM.Benchmarks.csproj b/benchmarks/Neo.VM.Benchmarks/Neo.VM.Benchmarks.csproj index 7737c3ec0c..ae717e8254 100644 --- a/benchmarks/Neo.VM.Benchmarks/Neo.VM.Benchmarks.csproj +++ b/benchmarks/Neo.VM.Benchmarks/Neo.VM.Benchmarks.csproj @@ -6,11 +6,11 @@ Neo.VM enable enable - false + diff --git a/benchmarks/Neo.VM.Benchmarks/Program.cs b/benchmarks/Neo.VM.Benchmarks/Program.cs index a9c86f405d..028dff9460 100644 --- a/benchmarks/Neo.VM.Benchmarks/Program.cs +++ b/benchmarks/Neo.VM.Benchmarks/Program.cs @@ -9,10 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. -using Neo.VM; -using System.Reflection; +using BenchmarkDotNet.Running; +using Neo.VM.Benchmark; -foreach (var method in typeof(Benchmarks).GetMethods(BindingFlags.Public | BindingFlags.Static)) -{ - method.CreateDelegate().Invoke(); -} +BenchmarkRunner.Run(); diff --git a/benchmarks/Neo.VM.Benchmarks/TestArray.cs b/benchmarks/Neo.VM.Benchmarks/TestArray.cs new file mode 100644 index 0000000000..62fedfed11 --- /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(ReferenceCounter? 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..5a9541f1e0 --- /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(ReferenceCounter? 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/docs/ReferenceCounter.md b/docs/ReferenceCounter.md new file mode 100644 index 0000000000..87c9bd489e --- /dev/null +++ b/docs/ReferenceCounter.md @@ -0,0 +1,241 @@ +# ReferenceCounter + +`ReferenceCounter` is a reference counting manager for use in a virtual machine (VM). It is designed to track and manage the reference counts of objects to ensure they are correctly cleaned up when no longer referenced, thereby preventing memory leaks. + +## Purpose + +In a virtual machine, managing object memory is crucial. The main purposes of `ReferenceCounter` are: + +1. **Tracking Object References**: Manage and track the reference relationships between objects. +2. **Memory Management**: Ensure that objects are correctly released when they are no longer referenced. +3. **Preventing Memory Leaks**: Avoid memory leaks by using reference counting and detecting circular references. + +## Technical Principles + +### Reference Counting + +Reference counting is a memory management technique used to track the number of references to each object. When an object's reference count drops to zero, it indicates that the object is no longer in use and can be safely cleaned up. `ReferenceCounter` uses the principles of reference counting to manage the lifecycle of objects: + +- **Increment Reference Count**: Increase the reference count when an object is referenced. +- **Decrement Reference Count**: Decrease the reference count when a reference is removed. +- **Cleanup Object**: Cleanup the object when its reference count drops to zero. + +### What is Tracked + +In the Neo VM, the `ReferenceCounter` class is used to count references to objects to track and manage `StackItem` references. The `reference_count` calculates the total number of current references, including stack references and object references. Specifically, the `reference_count` increases or decreases in the following situations: + +#### Increment Reference + +Use the `AddReference` method to increment the reference count of an object: + +```csharp +internal void AddReference(StackItem item, CompoundType parent) +{ + references_count++; + if (!NeedTrack(item)) return; + cached_components = null; + tracked_items.Add(item); + item.ObjectReferences ??= new(ReferenceEqualityComparer.Instance); + if (!item.ObjectReferences.TryGetValue(parent, out var pEntry)) + { + pEntry = new(parent); + item.ObjectReferences.Add(parent, pEntry); + } + pEntry.References++; +} +``` + +#### Decrement Reference + +Use the `RemoveReference` method to decrement the reference count of an object: + +```csharp +internal void RemoveReference(StackItem item, CompoundType parent) +{ + references_count--; + if (!NeedTrack(item)) return; + cached_components = null; + item.ObjectReferences![parent].References--; + if (item.StackReferences == 0) + zero_referred.Add(item); +} +``` + +#### Increment Stack Reference + +Use the `AddStackReference` method to increment the stack reference count of an object: + +```csharp +internal void AddStackReference(StackItem item, int count = 1) +{ + references_count += count; + if (!NeedTrack(item)) return; + if (tracked_items.Add(item)) + cached_components?.AddLast(new HashSet(ReferenceEqualityComparer.Instance) { item }); + item.StackReferences += count; + zero_referred.Remove(item); +} +``` + +#### Decrement Stack Reference + +Use the `RemoveStackReference` method to decrement the stack reference count of an object: + +```csharp +internal void RemoveStackReference(StackItem item) +{ + references_count--; + if (!NeedTrack(item)) return; + if (--item.StackReferences == 0) + zero_referred.Add(item); +} +``` + +### Circular References + +Circular references occur when objects reference each other, preventing their reference counts from dropping to zero, which can lead to memory leaks. `ReferenceCounter` addresses circular references using the following methods: + +1. **Mark and Sweep**: Detect and clean up strongly connected components when circular references are identified using algorithms like Tarjan's algorithm. +2. **Recursive Reference Management**: Recursively manage the reference counts of nested objects to ensure all reference relationships are correctly handled. + +### Tarjan's Algorithm + +Tarjan's algorithm is a graph theory algorithm for finding strongly connected components (SCCs) in a directed graph. An SCC is a maximal subgraph where every vertex is reachable from every other vertex in the subgraph. In the context of `ReferenceCounter`, Tarjan's algorithm is used to detect circular references, allowing for efficient memory management and cleanup of objects that are no longer reachable. + +#### How Tarjan's Algorithm Works + +1. **Initialization**: + - Each node (object) in the graph is initially unvisited. The algorithm uses a stack to keep track of the current path and arrays (or lists) to store the discovery time (`DFN`) and the lowest point reachable (`LowLink`) for each node. + +2. **Depth-First Search (DFS)**: + - Starting from an unvisited node, the algorithm performs a DFS. Each node visited is assigned a discovery time and a `LowLink` value, both initially set to the node's discovery time. + +3. **Update LowLink**: + - For each node, the algorithm updates the `LowLink` value based on the nodes reachable from its descendants. If a descendant node points back to an ancestor in the current path (stack), the `LowLink` value of the current node is updated to the minimum of its own `LowLink` and the descendant's `LowLink`. + +4. **Identify SCCs**: + - When a node's `LowLink` value is equal to its discovery time, it indicates the root of an SCC. The algorithm then pops nodes from the stack until it reaches the current node, forming an SCC. + +5. **Cleanup**: + - Once SCCs are identified, nodes that have no remaining references are cleaned up, preventing memory leaks caused by circular references. + +### Tarjan's Algorithm in `ReferenceCounter` + +The `CheckZeroReferred` method in `ReferenceCounter` uses Tarjan's algorithm to detect and handle circular references. Here’s a detailed breakdown of the algorithm as used in `CheckZeroReferred`: + +```csharp +internal int CheckZeroReferred() +{ + // If there are items with zero references, process them. + if (zero_referred.Count > 0) + { + // Clear the zero_referred set since we are going to process all of them. + zero_referred.Clear(); + + // If cached components are null, we need to recompute the strongly connected components (SCCs). + if (cached_components is null) + { + // Create a new Tarjan object and invoke it to find all SCCs in the tracked_items graph. + Tarjan tarjan = new(tracked_items); + cached_components = tarjan.Invoke(); + } + + // Reset all tracked items' Tarjan algorithm-related fields (DFN, LowLink, and OnStack). + foreach (StackItem item in tracked_items) + item.Reset(); + + // Process each SCC in the cached_components list. + for (var node = cached_components.First; node != null;) + { + var component = node.Value; + bool on_stack = false; + + // Check if any item in the SCC is still on the stack. + foreach (StackItem item in component) + { + // An item is considered 'on stack' if it has stack references or if its parent items are still on stack. + if (item.StackReferences > 0 || item.ObjectReferences?.Values.Any(p => p.References > 0 && p.Item.OnStack) == true) + { + on_stack = true; + break; + } + } + + // If any item in the component is on stack, mark all items in the component as on stack. + if (on_stack) + { + foreach (StackItem item in component) + item.OnStack = true; + node = node.Next; + } + else + { + // Otherwise, remove the component and clean up the items. + foreach (StackItem item in component) + { + tracked_items.Remove(item); + + // If the item is a CompoundType, adjust the reference count and clean up its sub-items. + if (item is CompoundType compound) + { + // Decrease the reference count by the number of sub-items. + references_count -= compound.SubItemsCount; + foreach (StackItem subitem in compound.SubItems) + { + // Skip sub-items that are in the same component or don't need tracking. + if (component.Contains(subitem)) continue; + if (!NeedTrack(subitem)) continue; + + // Remove the parent reference from the sub-item. + subitem.ObjectReferences!.Remove(compound); + } + } + + // Perform cleanup for the item. + item.Cleanup(); + } + + // Move to the next component and remove the current one from the cached_components list. + var nodeToRemove = node; + node = node.Next; + cached_components.Remove(nodeToRemove); + } + } + } + + // Return the current total reference count. + return references_count; +} +``` + +### Detailed Explanation + +1. **Initialization and Check for Zero References**: + - The method starts by checking if there are any items in `zero_referred`. If there are, it clears the `zero_referred` set. + +2. **Compute Strongly Connected Components (SCCs)**: + - If there are no cached SCCs, it recomputes them using Tarjan's algorithm. This involves creating a `Tarjan` object, passing the `tracked_items` to it, and invoking the algorithm to get the SCCs. + +3. **Reset Tarjan-related Fields**: + - It resets the Tarjan-related fields (`DFN`, `LowLink`, `OnStack`) of all tracked items to prepare for processing SCCs. + +4. **Process Each SCC**: + - It iterates through each SCC (component) in the cached components list. For each component, it checks if any item is still on the stack by looking at its `StackReferences` or if any of its parent items are on the stack. + +5. **Mark Items as On Stack**: + - If any item in the component is still on the stack, it marks all items in the component as on the stack and moves to the next component. + +6. **Remove and Clean Up Items**: + - If no items in the component are on the stack, it removes the component and cleans up all items within it. For `CompoundType` items, it adjusts the reference count and removes parent references from their sub-items. It then performs cleanup on each item and removes the component from the cached components list. + +7. **Return Reference Count**: + - Finally, it returns the current total reference count. + +## Features + +`ReferenceCounter` provides the following features: + +1. **Increment Reference Count**: Increment the reference count of objects. +2. **Decrement Reference Count**: Decrement the reference count of objects. +3. **Check Zero-Referenced Objects**: Detect and clean up objects with a reference count of zero. +4. **Manage Nested References**: Recursively manage the reference counts of nested objects, supporting nested arrays of arbitrary depth. diff --git a/src/Neo.CLI/config.json.md b/docs/config.json.md similarity index 92% rename from src/Neo.CLI/config.json.md rename to docs/config.json.md index da3bbaec6e..b32b1f2f8f 100644 --- a/src/Neo.CLI/config.json.md +++ b/docs/config.json.md @@ -1,6 +1,6 @@ # README for Application and Protocol Configuration JSON File -This README provides an explanation for each field in the JSON configuration file for a NEO node. +This README provides an explanation for each field in the JSON configuration file for a Neo node. ## ApplicationConfiguration @@ -31,15 +31,15 @@ This README provides an explanation for each field in the JSON configuration fil - **NeoNameService**: Script hash of the Neo Name Service contract. MainNet is `0x50ac1c37690cc2cfc594472833cf57505d5f46de`, TestNet is `0x50ac1c37690cc2cfc594472833cf57505d5f46de`. ### Plugins -- **DownloadUrl**: URL to download plugins, typically from the NEO project's GitHub releases. Default is `https://api.github.com/repos/neo-project/neo/releases`. +- **DownloadUrl**: URL to download plugins, typically from the Neo project's GitHub releases. Default is `https://api.github.com/repos/neo-project/neo/releases`. ## ProtocolConfiguration ### Network -- **Network**: Network ID for the NEO network. MainNet is `860833102`, TestNet is `894710606` +- **Network**: Network ID for the Neo network. MainNet is `860833102`, TestNet is `894710606` ### AddressVersion -- **AddressVersion**: Version byte used in NEO address generation. Default is `53`. +- **AddressVersion**: Version byte used in Neo address generation. Default is `53`. ### MillisecondsPerBlock - **MillisecondsPerBlock**: Time interval between blocks in milliseconds. Default is `15000` (15 seconds). @@ -82,4 +82,4 @@ This README provides an explanation for each field in the JSON configuration fil - `seed4t5.neo.org:20333` - `seed5t5.neo.org:20333` -This configuration file is essential for setting up and running a NEO node, ensuring proper logging, storage, network connectivity, and consensus protocol parameters. +This configuration file is essential for setting up and running a Neo node, ensuring proper logging, storage, network connectivity, and consensus protocol parameters. diff --git a/docs/handlers.md b/docs/handlers.md new file mode 100644 index 0000000000..43d864b209 --- /dev/null +++ b/docs/handlers.md @@ -0,0 +1,73 @@ +## Neo Core Events + +### 1. Block Committing Event + +**Event Name** `Committing` + +**Handler Interface:** `ICommittingHandler` + +This event is triggered when a transaction is in the process of being committed to the blockchain. Implementing the `ICommittingHandler` interface allows you to define custom actions that should be executed during this phase. + +### 2. Block Committed Event + +**Event Name** `Committed` + +**Handler Interface:** `ICommittedHandler` + +This event occurs after a transaction has been successfully committed to the blockchain. By implementing the `ICommittedHandler` interface, you can specify custom actions to be performed after the transaction is committed. + +### 3. Logging Event +**Event Name** `Logging` + +**Handler Interface:** `ILoggingHandler` + +This event is related to logging within the blockchain system. Implement the `ILoggingHandler` interface to define custom logging behaviors for different events and actions that occur in the blockchain. + +### 4. General Log Event +**Event Name** `Log` + +**Handler Interface:** `ILogHandler` + +This event pertains to general logging activities. The `ILogHandler` interface allows you to handle logging for specific actions or errors within the blockchain system. + +### 5. Notification Event +**Event Name** `Notify` + +**Handler Interface:** `INotifyHandler` + +This event is triggered when a notification needs to be sent. By implementing the `INotifyHandler` interface, you can specify custom actions for sending notifications when certain events occur within the blockchain system. + +### 6. Service Added Event +**Event Name** `ServiceAdded` + +**Handler Interface:** `IServiceAddedHandler` + +This event occurs when a new service is added to the blockchain system. Implement the `IServiceAddedHandler` interface to define custom actions that should be executed when a new service is added. + +### 7. Transaction Added Event +**Event Name** `TransactionAdded` + +**Handler Interface:** `ITransactionAddedHandler` + +This event is triggered when a new transaction is added to the blockchain system. By implementing the `ITransactionAddedHandler` interface, you can specify custom actions to be performed when a new transaction is added. + +### 8. Transaction Removed Event +**Event Name** `TransactionRemoved` + +**Handler Interface:** `ITransactionRemovedHandler` + +This event occurs when a transaction is removed from the blockchain system. Implement the `ITransactionRemovedHandler` interface to define custom actions that should be taken when a transaction is removed. + +### 9. Wallet Changed Event +**Event Name** `WalletChanged` + +**Handler Interface:** `IWalletChangedHandler` + +This event is triggered when changes occur in the wallet, such as balance updates or new transactions. By implementing the `IWalletChangedHandler` interface, you can specify custom actions to be taken when there are changes in the wallet. + +### 10. Remote Node MessageReceived Event +**Event Name** `MessageReceived` + +**Handler Interface:** `IMessageReceivedHandler` + +This event is triggered when a new message is received from a peer remote node. 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.exp b/scripts/Neo.CLI/test-neo-cli.exp new file mode 100644 index 0000000000..e7124402c3 --- /dev/null +++ b/scripts/Neo.CLI/test-neo-cli.exp @@ -0,0 +1,86 @@ +#!/usr/bin/expect -d -f +# +# This script uses expect to test neo-cli +# +set timeout 10 +exp_internal true + +# Start neo-cli +spawn dotnet ./bin/Neo.CLI/net8.0/neo-cli.dll + +# Expect the main input prompt +expect { + "neo> " { } + "error" { exit 2 } + timeout { exit 1 } +} + +# +# Test 'create wallet' +# +send "create wallet ./bin/Neo.CLI/test-wallet1.json\n" + +expect { + "password:" { send "asd\n" } + "error" { exit 2 } + timeout { exit 1 } +} + +expect { + "password:" { send "asd\n" } + "error" { exit 2 } + timeout { exit 1 } +} + +expect { + " Address:" { } + "error" { exit 2 } + timeout { exit 1 } +} + + +# +# Test 'create wallet' +# +send "create wallet ./bin/Neo.CLI/test-wallet2.json L2ArHTuiDL4FHu4nfyhamrG8XVYB4QyRbmhj7vD6hFMB5iAMSTf6\n" + +expect { + "password:" { send "abcd\n" } + "error" { exit 2 } + timeout { exit 1 } +} + +expect { + "password:" { send "abcd\n" } + "error" { exit 2 } + timeout { exit 1 } +} + +expect { + "NUj249PQg9EMJfAuxKizdJwMG7GSBzYX2Y" { } + "error" { exit 2 } + timeout { exit 1 } +} + +# +# Test 'list address' +# +send "list address\n" + +expect { + "neo> " { } + "error" { exit 2 } + timeout { exit 1 } +} + +# +# Test 'create address' +# +send "create address\n" + +expect { + "neo> " { } + "error" { exit 2 } + timeout { exit 1 } +} +exit 0 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index c211ee34aa..fe43e16b3c 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,9 +3,9 @@ 2015-2024 The Neo Project - 3.7.4 12.0 The Neo Project + true neo.png https://github.com/neo-project/neo MIT @@ -16,6 +16,7 @@ snupkg The Neo Project true + 3.7.5 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.Logger.cs b/src/Neo.CLI/CLI/MainService.Logger.cs index 80624779c7..b54bbefa2c 100644 --- a/src/Neo.CLI/CLI/MainService.Logger.cs +++ b/src/Neo.CLI/CLI/MainService.Logger.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.ConsoleService; +using Neo.IEventHandlers; using System; using System.Collections.Generic; using System.IO; @@ -19,7 +20,7 @@ namespace Neo.CLI { - partial class MainService + partial class MainService : ILoggingHandler { private static readonly ConsoleColorSet DebugColor = new(ConsoleColor.Cyan); private static readonly ConsoleColorSet InfoColor = new(ConsoleColor.White); @@ -32,12 +33,12 @@ partial class MainService private void Initialize_Logger() { - Utility.Logging += OnLog; + Utility.Logging += ((ILoggingHandler)this).Utility_Logging_Handler; } private void Dispose_Logger() { - Utility.Logging -= OnLog; + Utility.Logging -= ((ILoggingHandler)this).Utility_Logging_Handler; } /// @@ -78,7 +79,7 @@ private static void GetErrorLogs(StringBuilder sb, Exception ex) } } - private void OnLog(string source, LogLevel level, object message) + void ILoggingHandler.Utility_Logging_Handler(string source, LogLevel level, object message) { if (!Settings.Default.Logger.Active) return; diff --git a/src/Neo.CLI/CLI/MainService.Network.cs b/src/Neo.CLI/CLI/MainService.Network.cs index 38d6fd1d28..c579b8323e 100644 --- a/src/Neo.CLI/CLI/MainService.Network.cs +++ b/src/Neo.CLI/CLI/MainService.Network.cs @@ -117,7 +117,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 eb684194b7..d02c4d6ff1 100644 --- a/src/Neo.CLI/CLI/MainService.Plugins.cs +++ b/src/Neo.CLI/CLI/MainService.Plugins.cs @@ -32,8 +32,9 @@ partial class MainService /// Process "install" command /// /// Plugin name + /// Custom plugins download url, this is optional. [ConsoleCommand("install", Category = "Plugin Commands")] - private void OnInstallCommand(string pluginName) + private void OnInstallCommand(string pluginName, string? downloadUrl = null) { if (PluginExists(pluginName)) { @@ -41,7 +42,7 @@ private void OnInstallCommand(string pluginName) return; } - var result = InstallPluginAsync(pluginName).GetAwaiter().GetResult(); + var result = InstallPluginAsync(pluginName, downloadUrl).GetAwaiter().GetResult(); if (result) { var asmName = Assembly.GetExecutingAssembly().GetName().Name; @@ -74,18 +75,19 @@ private void OnReinstallCommand(string pluginName) /// /// name of the plugin /// + /// Custom plugin download url. /// /// Downloaded content - private static async Task DownloadPluginAsync(string pluginName, Version pluginVersion, bool prerelease = false) + 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(); 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 url = customDownloadUrl == null ? Settings.Default.Plugins.DownloadUrl : new Uri(customDownloadUrl); + var json = await httpClient.GetFromJsonAsync(url) ?? throw new HttpRequestException($"Failed: {url}"); var pluginVersionString = $"v{pluginVersion.ToString(3)}"; var jsonRelease = json.AsArray() @@ -135,7 +137,6 @@ private static async Task DownloadPluginAsync(string pluginName, Version var downloadUrl = jsonPlugin["browser_download_url"]?.GetValue() ?? throw new Exception("Could not find download URL"); - return await httpClient.GetStreamAsync(downloadUrl); } @@ -143,10 +144,12 @@ private static async Task DownloadPluginAsync(string pluginName, Version /// Install plugin from stream /// /// Name of the plugin + /// Custom plugins download url. /// Dependency set /// Install by force for `update` public async Task InstallPluginAsync( string pluginName, + string? downloadUrl = null, HashSet? installed = null, bool overWrite = false) { @@ -157,14 +160,14 @@ public async Task InstallPluginAsync( try { - using var stream = await DownloadPluginAsync(pluginName, Settings.Default.Plugins.Version, Settings.Default.Plugins.Prerelease); + using var stream = await DownloadPluginAsync(pluginName, Settings.Default.Plugins.Version, downloadUrl, Settings.Default.Plugins.Prerelease); using var zip = new ZipArchive(stream, ZipArchiveMode.Read); var entry = zip.Entries.FirstOrDefault(p => p.Name == "config.json"); if (entry is not null) { await using var es = entry.Open(); - await InstallDependenciesAsync(es, installed); + await InstallDependenciesAsync(es, installed, downloadUrl); } zip.ExtractToDirectory("./", true); return true; @@ -181,7 +184,8 @@ public async Task InstallPluginAsync( /// /// plugin config path in temp /// Dependency set - private async Task InstallDependenciesAsync(Stream config, HashSet installed) + /// Custom plugin download url. + private async Task InstallDependenciesAsync(Stream config, HashSet installed, string? downloadUrl = null) { var dependency = new ConfigurationBuilder() .AddJsonStream(config) @@ -195,7 +199,7 @@ private async Task InstallDependenciesAsync(Stream config, HashSet insta foreach (var plugin in dependencies.Where(p => p is not null && !PluginExists(p))) { ConsoleHelper.Info($"Installing dependency: {plugin}"); - await InstallPluginAsync(plugin!, installed); + await InstallPluginAsync(plugin!, downloadUrl, installed); } } @@ -293,13 +297,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..60de31493a 100644 --- a/src/Neo.CLI/CLI/MainService.Tools.cs +++ b/src/Neo.CLI/CLI/MainService.Tools.cs @@ -11,9 +11,9 @@ using Neo.ConsoleService; using Neo.Cryptography.ECC; +using Neo.Extensions; using Neo.IO; using Neo.SmartContract; -using Neo.VM; using Neo.Wallets; using System; using System.Collections.Generic; @@ -56,7 +56,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 +418,27 @@ 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); - } - catch (Exception) - { - return null; - } - return ScriptsToOpCode(script); - } + var bytes = Convert.FromBase64String(base64); + var sb = new StringBuilder(); + var line = 0; - private string ScriptsToOpCode(Script script) - { - //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) - { - 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) + foreach (var instruct in new VMInstruction(bytes)) { - 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)}"); - } + if (instruct.OperandSize == 0) + sb.AppendFormat("L{0:D04}:{1:X04} {2}{3}", line, instruct.Position, instruct.OpCode, Environment.NewLine); else - { - result.Add($"{op}"); - } + 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 + { + return null; } - return Environment.NewLine + string.Join("\r\n", result.ToArray()); } /// 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 1d349df320..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; @@ -131,10 +132,10 @@ public override void RunConsole() { Console.ForegroundColor = ConsoleColor.DarkGreen; - var cliV = Assembly.GetAssembly(typeof(Program))!.GetVersion(); - var neoV = Assembly.GetAssembly(typeof(NeoSystem))!.GetVersion(); - var vmV = Assembly.GetAssembly(typeof(ExecutionEngine))!.GetVersion(); - Console.WriteLine($"{ServiceName} v{cliV} - NEO v{neoV} - NEO-VM v{vmV}"); + var cliV = Assembly.GetAssembly(typeof(Program))!.GetName().Version; + var neoV = Assembly.GetAssembly(typeof(NeoSystem))!.GetName().Version; + var vmV = Assembly.GetAssembly(typeof(ExecutionEngine))!.GetName().Version; + Console.WriteLine($"{ServiceName} v{cliV?.ToString(3)} - NEO v{neoV?.ToString(3)} - NEO-VM v{vmV?.ToString(3)}"); Console.WriteLine(); base.RunConsole(); @@ -375,7 +376,50 @@ public async void Start(CommandLineOptions options) ProtocolSettings protocol = ProtocolSettings.Load("config.json"); CustomProtocolSettings(options, protocol); CustomApplicationSettings(options, Settings.Default); - NeoSystem = new NeoSystem(protocol, Settings.Default.Storage.Engine, string.Format(Settings.Default.Storage.Path, protocol.Network.ToString("X8"))); + try + { + NeoSystem = new NeoSystem(protocol, Settings.Default.Storage.Engine, + string.Format(Settings.Default.Storage.Path, protocol.Network.ToString("X8"))); + } + catch (DllNotFoundException ex) when (ex.Message.Contains("libleveldb")) + { + if (OperatingSystem.IsWindows()) + { + if (File.Exists("libleveldb.dll")) + { + DisplayError("Dependency DLL not found, please install Microsoft Visual C++ Redistributable.", + "See https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist"); + } + else + { + DisplayError("DLL not found, please get libleveldb.dll.", + "Download from https://github.com/neo-ngd/leveldb/releases"); + } + } + 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.", + "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.", + "Download from https://github.com/neo-project/neo/releases"); + } + return; + } + catch (DllNotFoundException) + { + DisplayError("Neo CLI is broken, please reinstall it.", + "Download from https://github.com/neo-project/neo/releases"); + return; + } + NeoSystem.AddService(this); LocalNode = NeoSystem.LocalNode.Ask(new LocalNode.GetInstance()).Result; @@ -448,6 +492,17 @@ public async void Start(CommandLineOptions options) ConsoleHelper.Error(ex.GetBaseException().Message); } } + + return; + + void DisplayError(string primaryMessage, string? secondaryMessage = null) + { + ConsoleHelper.Error(primaryMessage + Environment.NewLine + + (secondaryMessage != null ? secondaryMessage + Environment.NewLine : "") + + "Press any key to exit."); + Console.ReadKey(); + Environment.Exit(-1); + } } public void Stop() diff --git a/src/Neo.CLI/Neo.CLI.csproj b/src/Neo.CLI/Neo.CLI.csproj index a9d4c9919e..f2e1359194 100644 --- a/src/Neo.CLI/Neo.CLI.csproj +++ b/src/Neo.CLI/Neo.CLI.csproj @@ -10,7 +10,8 @@ Neo.CLI neo.ico enable - $(SolutionDir)/bin/$(AssemblyTitle) + ../../bin/$(AssemblyTitle) + false @@ -30,6 +31,7 @@ + diff --git a/src/Neo.CLI/Settings.cs b/src/Neo.CLI/Settings.cs index ecc38115c3..e831d15ebd 100644 --- a/src/Neo.CLI/Settings.cs +++ b/src/Neo.CLI/Settings.cs @@ -13,6 +13,7 @@ using Neo.Network.P2P; using Neo.Persistence; using System; +using System.Linq; using System.Reflection; using System.Threading; @@ -165,7 +166,7 @@ public ContractsSettings() { } public class PluginsSettings { - public Uri DownloadUrl { get; init; } = new("https://api.github.com/repos/neo-project/neo-modules/releases"); + public Uri DownloadUrl { get; init; } = new("https://api.github.com/repos/neo-project/neo/releases"); public bool Prerelease { get; init; } = false; public Version Version { get; init; } = Assembly.GetExecutingAssembly().GetName().Version!; 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 f629dc3aac..6879232732 100644 --- a/src/Neo.CLI/config.fs.mainnet.json +++ b/src/Neo.CLI/config.fs.mainnet.json @@ -33,13 +33,14 @@ "MillisecondsPerBlock": 15000, "MaxTransactionsPerBlock": 512, "MemoryPoolMaxTransactions": 50000, - "MaxTraceableBlocks": 2102400, + "MaxTraceableBlocks": 17280, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, "Hardforks": { "HF_Aspidochelone": 3000000, "HF_Basilisk": 4500000, - "HF_Cockatrice": 5800000 + "HF_Cockatrice": 5800000, + "HF_Domovoi": 5800000 }, "StandbyCommittee": [ "026fa34ec057d74c2fdf1a18e336d0bd597ea401a0b2ad57340d5c220d09f44086", 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.CLI/config.json b/src/Neo.CLI/config.json index 87e38b2efe..772a221714 100644 --- a/src/Neo.CLI/config.json +++ b/src/Neo.CLI/config.json @@ -6,8 +6,8 @@ "Active": false }, "Storage": { - "Engine": "LevelDBStore", // Candidates [MemoryStore, LevelDBStore, RocksDBStore] - "Path": "Data_LevelDB_{0}" // {0} is a placeholder for the network id + "Engine": "LevelDBStore", + "Path": "Data_LevelDB_{0}" }, "P2P": { "Port": 10333, @@ -37,7 +37,8 @@ "Hardforks": { "HF_Aspidochelone": 1730000, "HF_Basilisk": 4120000, - "HF_Cockatrice": 5450000 + "HF_Cockatrice": 5450000, + "HF_Domovoi": 5570000 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo.CLI/config.mainnet.json b/src/Neo.CLI/config.mainnet.json index 821eb364f5..772a221714 100644 --- a/src/Neo.CLI/config.mainnet.json +++ b/src/Neo.CLI/config.mainnet.json @@ -37,7 +37,8 @@ "Hardforks": { "HF_Aspidochelone": 1730000, "HF_Basilisk": 4120000, - "HF_Cockatrice": 5450000 + "HF_Cockatrice": 5450000, + "HF_Domovoi": 5570000 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo.CLI/config.testnet.json b/src/Neo.CLI/config.testnet.json index ee28108c3e..dc102be54b 100644 --- a/src/Neo.CLI/config.testnet.json +++ b/src/Neo.CLI/config.testnet.json @@ -37,7 +37,8 @@ "Hardforks": { "HF_Aspidochelone": 210000, "HF_Basilisk": 2680000, - "HF_Cockatrice": 3967000 + "HF_Cockatrice": 3967000, + "HF_Domovoi": 4144000 }, "InitialGasDistribution": 5200000000000000, "ValidatorsCount": 7, diff --git a/src/Neo.ConsoleService/Neo.ConsoleService.csproj b/src/Neo.ConsoleService/Neo.ConsoleService.csproj index 9b3ad32dfe..b4254a4cad 100644 --- a/src/Neo.ConsoleService/Neo.ConsoleService.csproj +++ b/src/Neo.ConsoleService/Neo.ConsoleService.csproj @@ -4,7 +4,7 @@ netstandard2.1;net8.0 Neo.ConsoleService enable - $(SolutionDir)/bin/$(PackageId) + ../../bin/$(PackageId) diff --git a/src/Neo.Cryptography.BLS12_381/Neo.Cryptography.BLS12_381.csproj b/src/Neo.Cryptography.BLS12_381/Neo.Cryptography.BLS12_381.csproj index d5313d9eab..f74c9f1ec0 100644 --- a/src/Neo.Cryptography.BLS12_381/Neo.Cryptography.BLS12_381.csproj +++ b/src/Neo.Cryptography.BLS12_381/Neo.Cryptography.BLS12_381.csproj @@ -6,7 +6,7 @@ enable enable Neo.Cryptography.BLS12_381 - $(SolutionDir)/bin/$(PackageId) + ../../bin/$(PackageId) diff --git a/src/Neo.Extensions/BigIntegerExtensions.cs b/src/Neo.Extensions/BigIntegerExtensions.cs new file mode 100644 index 0000000000..38a0e60bb5 --- /dev/null +++ b/src/Neo.Extensions/BigIntegerExtensions.cs @@ -0,0 +1,91 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// BigIntegerExtensions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Runtime.CompilerServices; + +namespace Neo.Extensions +{ + public static class BigIntegerExtensions + { + public static int GetLowestSetBit(this BigInteger i) + { + if (i.Sign == 0) + return -1; + var b = i.ToByteArray(); + var w = 0; + while (b[w] == 0) + w++; + for (var x = 0; x < 8; x++) + if ((b[w] & 1 << x) > 0) + return x + w * 8; + throw new Exception(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static BigInteger Mod(this BigInteger x, BigInteger y) + { + x %= y; + if (x.Sign < 0) + x += y; + return x; + } + + public static BigInteger ModInverse(this BigInteger a, BigInteger n) + { + BigInteger i = n, v = 0, d = 1; + while (a > 0) + { + BigInteger t = i / a, x = a; + a = i % x; + i = x; + x = d; + d = v - t * x; + v = x; + } + v %= n; + if (v < 0) v = (v + n) % n; + return v; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TestBit(this BigInteger i, int index) + { + return (i & (BigInteger.One << index)) > BigInteger.Zero; + } + + /// + /// Finds the sum of the specified integers. + /// + /// The specified integers. + /// The sum of the integers. + public static BigInteger Sum(this IEnumerable source) + { + var sum = BigInteger.Zero; + foreach (var bi in source) sum += bi; + return sum; + } + + /// + /// Converts a to byte array and eliminates all the leading zeros. + /// + /// The to convert. + /// The converted byte array. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static byte[] ToByteArrayStandard(this BigInteger i) + { + if (i.IsZero) return Array.Empty(); + return i.ToByteArray(); + } + } +} diff --git a/src/Neo.Extensions/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/Neo.Extensions.csproj b/src/Neo.Extensions/Neo.Extensions.csproj index c6d549be6a..cc5325843a 100644 --- a/src/Neo.Extensions/Neo.Extensions.csproj +++ b/src/Neo.Extensions/Neo.Extensions.csproj @@ -5,17 +5,17 @@ enable Neo.Extensions NEO;Blockchain;Extensions - $(SolutionDir)/bin/$(PackageId) + ../../bin/$(PackageId) - + - + 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/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/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.GUI/Neo.GUI.csproj b/src/Neo.GUI/Neo.GUI.csproj index 014459be4c..28c7d841a5 100644 --- a/src/Neo.GUI/Neo.GUI.csproj +++ b/src/Neo.GUI/Neo.GUI.csproj @@ -11,7 +11,8 @@ Neo.GUI neo.ico false - $(SolutionDir)/bin/$(AssemblyTitle) + ../../bin/$(AssemblyTitle) + false diff --git a/src/Neo/IO/Actors/Idle.cs b/src/Neo.IO/Actors/Idle.cs similarity index 89% rename from src/Neo/IO/Actors/Idle.cs rename to src/Neo.IO/Actors/Idle.cs index e722d057ee..c372062783 100644 --- a/src/Neo/IO/Actors/Idle.cs +++ b/src/Neo.IO/Actors/Idle.cs @@ -13,6 +13,6 @@ namespace Neo.IO.Actors { internal sealed class Idle { - public static Idle Instance { get; } = new Idle(); + public static Idle Instance { get; } = new(); } } diff --git a/src/Neo/IO/Caching/Cache.cs b/src/Neo.IO/Caching/Cache.cs similarity index 80% rename from src/Neo/IO/Caching/Cache.cs rename to src/Neo.IO/Caching/Cache.cs index 7895a8ca2d..1a66e6dca5 100644 --- a/src/Neo/IO/Caching/Cache.cs +++ b/src/Neo.IO/Caching/Cache.cs @@ -17,25 +17,21 @@ namespace Neo.IO.Caching { - internal abstract class Cache : ICollection, IDisposable + internal abstract class Cache + (int max_capacity, IEqualityComparer? comparer = null) : ICollection, IDisposable + where TKey : notnull { protected class CacheItem + (TKey key, TValue value) { - public readonly TKey Key; - public readonly TValue Value; - public readonly DateTime Time; - - public CacheItem(TKey key, TValue value) - { - Key = key; - Value = value; - Time = TimeProvider.Current.UtcNow; - } + public readonly TKey Key = key; + public readonly TValue Value = value; + public readonly DateTime Time = DateTime.UtcNow; } protected readonly ReaderWriterLockSlim RwSyncRootLock = new(LockRecursionPolicy.SupportsRecursion); - protected readonly Dictionary InnerDictionary; - private readonly int max_capacity; + protected readonly Dictionary InnerDictionary = new Dictionary(comparer); + private readonly int _max_capacity = max_capacity; public TValue this[TKey key] { @@ -44,7 +40,7 @@ public TValue this[TKey key] RwSyncRootLock.EnterReadLock(); try { - if (!InnerDictionary.TryGetValue(key, out CacheItem item)) throw new KeyNotFoundException(); + if (!InnerDictionary.TryGetValue(key, out CacheItem? item)) throw new KeyNotFoundException(); OnAccess(item); return item.Value; } @@ -73,15 +69,9 @@ public int Count public bool IsReadOnly => false; - public Cache(int max_capacity, IEqualityComparer comparer = null) - { - this.max_capacity = max_capacity; - InnerDictionary = new Dictionary(comparer); - } - public void Add(TValue item) { - TKey key = GetKeyForItem(item); + var key = GetKeyForItem(item); RwSyncRootLock.EnterWriteLock(); try { @@ -95,16 +85,16 @@ public void Add(TValue item) private void AddInternal(TKey key, TValue item) { - if (InnerDictionary.TryGetValue(key, out CacheItem cacheItem)) + if (InnerDictionary.TryGetValue(key, out CacheItem? cacheItem)) { OnAccess(cacheItem); } else { - if (InnerDictionary.Count >= max_capacity) + if (InnerDictionary.Count >= _max_capacity) { //TODO: Perform a performance test on the PLINQ query to determine which algorithm is better here (parallel or not) - foreach (CacheItem item_del in InnerDictionary.Values.AsParallel().OrderBy(p => p.Time).Take(InnerDictionary.Count - max_capacity + 1)) + foreach (var item_del in InnerDictionary.Values.AsParallel().OrderBy(p => p.Time).Take(InnerDictionary.Count - _max_capacity + 1)) { RemoveInternal(item_del); } @@ -118,9 +108,9 @@ public void AddRange(IEnumerable items) RwSyncRootLock.EnterWriteLock(); try { - foreach (TValue item in items) + foreach (var item in items) { - TKey key = GetKeyForItem(item); + var key = GetKeyForItem(item); AddInternal(key, item); } } @@ -135,7 +125,7 @@ public void Clear() RwSyncRootLock.EnterWriteLock(); try { - foreach (CacheItem item_del in InnerDictionary.Values.ToArray()) + foreach (var item_del in InnerDictionary.Values.ToArray()) { RemoveInternal(item_del); } @@ -151,7 +141,7 @@ public bool Contains(TKey key) RwSyncRootLock.EnterReadLock(); try { - if (!InnerDictionary.TryGetValue(key, out CacheItem cacheItem)) return false; + if (!InnerDictionary.TryGetValue(key, out CacheItem? cacheItem)) return false; OnAccess(cacheItem); return true; } @@ -171,7 +161,7 @@ public void CopyTo(TValue[] array, int arrayIndex) if (array == null) throw new ArgumentNullException(); if (arrayIndex < 0) throw new ArgumentOutOfRangeException(); if (arrayIndex + InnerDictionary.Count > array.Length) throw new ArgumentException(); - foreach (TValue item in this) + foreach (var item in this) { array[arrayIndex++] = item; } @@ -188,7 +178,7 @@ public IEnumerator GetEnumerator() RwSyncRootLock.EnterReadLock(); try { - foreach (TValue item in InnerDictionary.Values.Select(p => p.Value)) + foreach (var item in InnerDictionary.Values.Select(p => p.Value)) { yield return item; } @@ -211,7 +201,7 @@ public bool Remove(TKey key) RwSyncRootLock.EnterWriteLock(); try { - if (!InnerDictionary.TryGetValue(key, out CacheItem cacheItem)) return false; + if (!InnerDictionary.TryGetValue(key, out CacheItem? cacheItem)) return false; RemoveInternal(cacheItem); return true; } @@ -242,7 +232,7 @@ public bool TryGet(TKey key, out TValue item) RwSyncRootLock.EnterReadLock(); try { - if (InnerDictionary.TryGetValue(key, out CacheItem cacheItem)) + if (InnerDictionary.TryGetValue(key, out CacheItem? cacheItem)) { OnAccess(cacheItem); item = cacheItem.Value; @@ -253,7 +243,7 @@ public bool TryGet(TKey key, out TValue item) { RwSyncRootLock.ExitReadLock(); } - item = default; + item = default!; return false; } } diff --git a/src/Neo/IO/Caching/FIFOCache.cs b/src/Neo.IO/Caching/FIFOCache.cs similarity index 72% rename from src/Neo/IO/Caching/FIFOCache.cs rename to src/Neo.IO/Caching/FIFOCache.cs index af3e5e469d..b7e9f39e98 100644 --- a/src/Neo/IO/Caching/FIFOCache.cs +++ b/src/Neo.IO/Caching/FIFOCache.cs @@ -13,13 +13,10 @@ namespace Neo.IO.Caching { - internal abstract class FIFOCache : Cache + internal abstract class FIFOCache + (int max_capacity, IEqualityComparer? comparer = null) : Cache(max_capacity, comparer) + where TKey : notnull { - public FIFOCache(int max_capacity, IEqualityComparer comparer = null) - : base(max_capacity, comparer) - { - } - protected override void OnAccess(CacheItem item) { } diff --git a/src/Neo/IO/Caching/HashSetCache.cs b/src/Neo.IO/Caching/HashSetCache.cs similarity index 76% rename from src/Neo/IO/Caching/HashSetCache.cs rename to src/Neo.IO/Caching/HashSetCache.cs index bdef1c5e3a..577893e924 100644 --- a/src/Neo/IO/Caching/HashSetCache.cs +++ b/src/Neo.IO/Caching/HashSetCache.cs @@ -20,17 +20,17 @@ class HashSetCache : IReadOnlyCollection where T : IEquatable /// /// Sets where the Hashes are stored /// - private readonly LinkedList> sets = new(); + private readonly LinkedList> _sets = new(); /// - /// Maximum capacity of each bucket inside each HashSet of . + /// Maximum capacity of each bucket inside each HashSet of . /// - private readonly int bucketCapacity; + private readonly int _bucketCapacity; /// /// Maximum number of buckets for the LinkedList, meaning its maximum cardinality. /// - private readonly int maxBucketCount; + private readonly int _maxBucketCount; /// /// Entry count @@ -43,32 +43,32 @@ public HashSetCache(int bucketCapacity, int maxBucketCount = 10) if (maxBucketCount <= 0) throw new ArgumentOutOfRangeException($"{nameof(maxBucketCount)} should be greater than 0"); Count = 0; - this.bucketCapacity = bucketCapacity; - this.maxBucketCount = maxBucketCount; - sets.AddFirst(new HashSet()); + _bucketCapacity = bucketCapacity; + _maxBucketCount = maxBucketCount; + _sets.AddFirst([]); } public bool Add(T item) { if (Contains(item)) return false; Count++; - if (sets.First.Value.Count < bucketCapacity) return sets.First.Value.Add(item); + if (_sets.First?.Value.Count < _bucketCapacity) return _sets.First.Value.Add(item); var newSet = new HashSet { item }; - sets.AddFirst(newSet); - if (sets.Count > maxBucketCount) + _sets.AddFirst(newSet); + if (_sets.Count > _maxBucketCount) { - Count -= sets.Last.Value.Count; - sets.RemoveLast(); + Count -= _sets.Last?.Value.Count ?? 0; + _sets.RemoveLast(); } return true; } public bool Contains(T item) { - foreach (var set in sets) + foreach (var set in _sets) { if (set.Contains(item)) return true; } @@ -77,17 +77,17 @@ public bool Contains(T item) public void ExceptWith(IEnumerable items) { - List> removeList = null; + List> removeList = default!; foreach (var item in items) { - foreach (var set in sets) + foreach (var set in _sets) { if (set.Remove(item)) { Count--; if (set.Count == 0) { - removeList ??= new List>(); + removeList ??= []; removeList.Add(set); } break; @@ -97,13 +97,13 @@ public void ExceptWith(IEnumerable items) if (removeList == null) return; foreach (var set in removeList) { - sets.Remove(set); + _sets.Remove(set); } } public IEnumerator GetEnumerator() { - foreach (var set in sets) + foreach (var set in _sets) { foreach (var item in set) { diff --git a/src/Neo/IO/Caching/IndexedQueue.cs b/src/Neo.IO/Caching/IndexedQueue.cs similarity index 95% rename from src/Neo/IO/Caching/IndexedQueue.cs rename to src/Neo.IO/Caching/IndexedQueue.cs index 54440a871b..29b39a4947 100644 --- a/src/Neo/IO/Caching/IndexedQueue.cs +++ b/src/Neo.IO/Caching/IndexedQueue.cs @@ -89,14 +89,14 @@ public void Enqueue(T item) { if (_array.Length == _count) { - int newSize = _array.Length * GrowthFactor; + var newSize = _array.Length * GrowthFactor; if (_head == 0) { Array.Resize(ref _array, newSize); } else { - T[] buffer = new T[newSize]; + var buffer = new T[newSize]; Array.Copy(_array, _head, buffer, 0, _array.Length - _head); Array.Copy(_array, 0, buffer, _array.Length - _head, _head); _array = buffer; @@ -127,7 +127,7 @@ public bool TryPeek(out T item) { if (_count == 0) { - item = default; + item = default!; return false; } else @@ -145,7 +145,7 @@ public T Dequeue() { if (_count == 0) throw new InvalidOperationException("The queue is empty"); - T result = _array[_head]; + var result = _array[_head]; ++_head; _head %= _array.Length; --_count; @@ -161,7 +161,7 @@ public bool TryDequeue(out T item) { if (_count == 0) { - item = default; + item = default!; return false; } else @@ -194,7 +194,7 @@ public void TrimExcess() } else if (_array.Length * TrimThreshold >= _count) { - T[] arr = new T[_count]; + var arr = new T[_count]; CopyTo(arr, 0); _array = arr; _head = 0; @@ -228,14 +228,14 @@ public void CopyTo(T[] array, int arrayIndex) /// An array containing the queue's items public T[] ToArray() { - T[] result = new T[_count]; + var result = new T[_count]; CopyTo(result, 0); return result; } public IEnumerator GetEnumerator() { - for (int i = 0; i < _count; i++) + for (var i = 0; i < _count; i++) yield return _array[(_head + i) % _array.Length]; } diff --git a/src/Neo/IO/Caching/KeyedCollectionSlim.cs b/src/Neo.IO/Caching/KeyedCollectionSlim.cs similarity index 61% rename from src/Neo/IO/Caching/KeyedCollectionSlim.cs rename to src/Neo.IO/Caching/KeyedCollectionSlim.cs index a24dd87e4c..f632dc0927 100644 --- a/src/Neo/IO/Caching/KeyedCollectionSlim.cs +++ b/src/Neo.IO/Caching/KeyedCollectionSlim.cs @@ -10,43 +10,46 @@ // modifications are permitted. using System; +using System.Collections; using System.Collections.Generic; namespace Neo.IO.Caching; -abstract class KeyedCollectionSlim where TKey : notnull +abstract class KeyedCollectionSlim + where TKey : notnull + where TItem : class, IStructuralEquatable, IStructuralComparable, IComparable { private readonly LinkedList _items = new(); - private readonly Dictionary> dict = new(); + private readonly Dictionary> _dict = []; - public int Count => dict.Count; - public TItem First => _items.First.Value; + public int Count => _dict.Count; + public TItem? First => _items.First?.Value; - protected abstract TKey GetKeyForItem(TItem item); + protected abstract TKey GetKeyForItem(TItem? item); public void Add(TItem item) { var key = GetKeyForItem(item); var node = _items.AddLast(item); - if (!dict.TryAdd(key, node)) + if (!_dict.TryAdd(key, node)) { _items.RemoveLast(); throw new ArgumentException("An element with the same key already exists in the collection."); } } - public bool Contains(TKey key) => dict.ContainsKey(key); + public bool Contains(TKey key) => _dict.ContainsKey(key); public void Remove(TKey key) { - if (dict.Remove(key, out var node)) + if (_dict.Remove(key, out var node)) _items.Remove(node); } public void RemoveFirst() { - var key = GetKeyForItem(_items.First.Value); - dict.Remove(key); + var key = GetKeyForItem(_items.First?.Value); + _dict.Remove(key); _items.RemoveFirst(); } } diff --git a/src/Neo/IO/Caching/ReflectionCacheAttribute.cs b/src/Neo.IO/Caching/ReflectionCacheAttribute.cs similarity index 67% rename from src/Neo/IO/Caching/ReflectionCacheAttribute.cs rename to src/Neo.IO/Caching/ReflectionCacheAttribute.cs index 20aeb91320..7071e879e4 100644 --- a/src/Neo/IO/Caching/ReflectionCacheAttribute.cs +++ b/src/Neo.IO/Caching/ReflectionCacheAttribute.cs @@ -13,21 +13,17 @@ namespace Neo.IO.Caching { + /// + /// Constructor + /// + /// Type [AttributeUsage(AttributeTargets.Field, AllowMultiple = false)] - internal class ReflectionCacheAttribute : Attribute + internal class ReflectionCacheAttribute + (Type type) : Attribute { /// /// Type /// - public Type Type { get; } - - /// - /// Constructor - /// - /// Type - public ReflectionCacheAttribute(Type type) - { - Type = type; - } + public Type Type { get; } = type; } } diff --git a/src/Neo.IO/Neo.IO.csproj b/src/Neo.IO/Neo.IO.csproj index a1a0a9ea2e..a51ec1110a 100644 --- a/src/Neo.IO/Neo.IO.csproj +++ b/src/Neo.IO/Neo.IO.csproj @@ -6,7 +6,7 @@ enable Neo.IO NEO;Blockchain;IO - $(SolutionDir)/bin/$(PackageId) + ../../bin/$(PackageId) 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 3d75276dec..8d8fd33ac9 100644 --- a/src/Neo.Json/Neo.Json.csproj +++ b/src/Neo.Json/Neo.Json.csproj @@ -6,11 +6,11 @@ enable Neo.Json NEO;JSON - $(SolutionDir)/bin/$(PackageId) + ../../bin/$(PackageId) - + diff --git a/src/Neo.VM/EvaluationStack.cs b/src/Neo.VM/EvaluationStack.cs index 34517b2197..571cb6b2db 100644 --- a/src/Neo.VM/EvaluationStack.cs +++ b/src/Neo.VM/EvaluationStack.cs @@ -26,6 +26,8 @@ public sealed class EvaluationStack : IReadOnlyList private readonly List innerList = new(); private readonly ReferenceCounter referenceCounter; + internal ReferenceCounter ReferenceCounter => referenceCounter; + internal EvaluationStack(ReferenceCounter referenceCounter) { this.referenceCounter = referenceCounter; diff --git a/src/Neo.VM/ExecutionEngine.cs b/src/Neo.VM/ExecutionEngine.cs index c7c3f86ce9..bec60c4348 100644 --- a/src/Neo.VM/ExecutionEngine.cs +++ b/src/Neo.VM/ExecutionEngine.cs @@ -235,6 +235,13 @@ public ExecutionContext LoadScript(Script script, int rvcount = -1, int initialP protected virtual void OnFault(Exception ex) { State = VMState.FAULT; + +#if VMPERF + if (ex != null) + { + Console.Error.WriteLine(ex); + } +#endif } /// diff --git a/src/Neo.VM/JumpTable/JumpTable.Compound.cs b/src/Neo.VM/JumpTable/JumpTable.Compound.cs index aeb2047825..197cc1952a 100644 --- a/src/Neo.VM/JumpTable/JumpTable.Compound.cs +++ b/src/Neo.VM/JumpTable/JumpTable.Compound.cs @@ -239,6 +239,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 +411,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) { 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..f04d045988 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..]); } 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 9315a850df..cc6fb4a3a9 100644 --- a/src/Neo.VM/Neo.VM.csproj +++ b/src/Neo.VM/Neo.VM.csproj @@ -5,12 +5,13 @@ true enable Neo.VM - $(SolutionDir)/bin/$(PackageId) + ../../bin/$(PackageId) + diff --git a/src/Neo.VM/OpCode.cs b/src/Neo.VM/OpCode.cs index dd5f1574ea..8ef5c0e538 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,164 @@ 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. + /// + /// + /// 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. + /// + /// + /// 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. + /// + /// + /// 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. + /// + /// + /// + /// Push: 0 item(s) + /// Pop: 1 item(s) + /// /// REVERSEN = 0x55, @@ -389,209 +818,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 +1325,74 @@ public enum OpCode : byte /// /// Creates a new and pushes it onto the stack. + /// + /// new Buffer(a) + /// + /// + /// Push: 1 item(s) + /// Pop: 0 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 +1401,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 +1478,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 +1820,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: 0 item(s) + /// Pop: 1 item(s) + /// /// POPITEM = 0xD4, @@ -872,16 +2041,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 +2079,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 be7844dac8..f9ea08a2e2 100644 --- a/src/Neo.VM/ReferenceCounter.cs +++ b/src/Neo.VM/ReferenceCounter.cs @@ -22,86 +22,180 @@ namespace Neo.VM /// public sealed class ReferenceCounter { + // If set to true, all items will be tracked regardless of their type. private const bool TrackAllItems = false; - private readonly HashSet tracked_items = new(ReferenceEqualityComparer.Instance); - private readonly HashSet zero_referred = new(ReferenceEqualityComparer.Instance); - private LinkedList>? cached_components; - private int references_count = 0; + // Stores items that are being tracked for references. + // Only CompoundType and Buffer items are tracked. + private readonly HashSet _trackedItems = new(ReferenceEqualityComparer.Instance); + + // Stores items that have zero references. + private readonly HashSet _zeroReferred = new(ReferenceEqualityComparer.Instance); + + // Caches strongly connected components for optimization. + private LinkedList>? _cachedComponents; + + // Keeps the total count of references. + private int _referencesCount = 0; /// - /// Indicates the number of this counter. + /// Gets the count of references. /// - public int Count => references_count; + public int Count => _referencesCount; + /// + /// Determines if an item needs to be tracked based on its type. + /// + /// The item to check. + /// True if the item needs to be tracked, otherwise false. [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool NeedTrack(StackItem item) { + // Track all items if TrackAllItems is true. #pragma warning disable CS0162 if (TrackAllItems) return true; #pragma warning restore CS0162 + + // Track the item if it is a CompoundType or Buffer. if (item is CompoundType or Buffer) return true; 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) { - references_count++; + // Increment the reference count. + _referencesCount++; + + // If the item doesn't need to be tracked, return early. + // Only track CompoundType and Buffer items. if (!NeedTrack(item)) return; - cached_components = null; - tracked_items.Add(item); - item.ObjectReferences ??= new(ReferenceEqualityComparer.Instance); + + // Invalidate the cached components since the tracked items are changing. + _cachedComponents = null; + + // Add the item to the set of tracked items. + _trackedItems.Add(item); + + // Initialize the ObjectReferences dictionary if it is null. + item.ObjectReferences ??= new Dictionary(ReferenceEqualityComparer.Instance); + + // Add the parent to the item's ObjectReferences dictionary and increment its reference count. if (!item.ObjectReferences.TryGetValue(parent, out var pEntry)) { - pEntry = new(parent); + pEntry = new StackItem.ObjectReferenceEntry(parent); item.ObjectReferences.Add(parent, pEntry); } 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) { - references_count += count; + // Increment the reference count by the specified count. + _referencesCount += count; + + // If the item doesn't need to be tracked, return early. if (!NeedTrack(item)) return; - if (tracked_items.Add(item)) - cached_components?.AddLast(new HashSet(ReferenceEqualityComparer.Instance) { item }); + + // Add the item to the set of tracked items and to the cached components if needed. + if (_trackedItems.Add(item)) + _cachedComponents?.AddLast(new HashSet(ReferenceEqualityComparer.Instance) { item }); + + // Increment the item's stack references by the specified count. item.StackReferences += count; - zero_referred.Remove(item); + + // Remove the item from the _zeroReferred set since it now has references. + _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) { - zero_referred.Add(item); + // Add the item to the _zeroReferred set. + _zeroReferred.Add(item); + + // If the item doesn't need to be tracked, return early. if (!NeedTrack(item)) return; - cached_components?.AddLast(new HashSet(ReferenceEqualityComparer.Instance) { item }); - tracked_items.Add(item); + + // Add the item to the cached components and the set of tracked items. + _cachedComponents?.AddLast(new HashSet(ReferenceEqualityComparer.Instance) { item }); + _trackedItems.Add(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. + /// It uses Tarjan's algorithm to find strongly connected components and remove those with no references. + /// + /// Use this method periodically to clean up items with zero references and free up memory. + /// + /// The current reference count. internal int CheckZeroReferred() { - if (zero_referred.Count > 0) + // If there are items with zero references, process them. + if (_zeroReferred.Count > 0) { - zero_referred.Clear(); - if (cached_components is null) + // Clear the zero_referred set since we are going to process all of them. + _zeroReferred.Clear(); + + // If cached components are null, we need to recompute the strongly connected components (SCCs). + if (_cachedComponents is null) { - //Tarjan tarjan = new(tracked_items.Where(p => p.StackReferences == 0)); - Tarjan tarjan = new(tracked_items); - cached_components = tarjan.Invoke(); + // Create a new Tarjan object and invoke it to find all SCCs in the tracked_items graph. + Tarjan tarjan = new(_trackedItems); + _cachedComponents = tarjan.Invoke(); } - foreach (StackItem item in tracked_items) + + // Reset all tracked items' Tarjan algorithm-related fields (DFN, LowLink, and OnStack). + foreach (StackItem item in _trackedItems) item.Reset(); - for (var node = cached_components.First; node != null;) + + // Process each SCC in the cached_components list. + for (var node = _cachedComponents.First; node != null;) { var component = node.Value; bool on_stack = false; + + // Check if any item in the SCC is still on the stack. foreach (StackItem item in component) { + // An item is considered 'on stack' if it has stack references or if its parent items are still on stack. if (item.StackReferences > 0 || item.ObjectReferences?.Values.Any(p => p.References > 0 && p.Item.OnStack) == true) { on_stack = true; break; } } + + // If any item in the component is on stack, mark all items in the component as on stack. if (on_stack) { foreach (StackItem item in component) @@ -110,46 +204,93 @@ internal int CheckZeroReferred() } else { + // Otherwise, remove the component and clean up the items. foreach (StackItem item in component) { - tracked_items.Remove(item); + _trackedItems.Remove(item); + + // If the item is a CompoundType, adjust the reference count and clean up its sub-items. if (item is CompoundType compound) { - references_count -= compound.SubItemsCount; + // Decrease the reference count by the number of sub-items. + _referencesCount -= compound.SubItemsCount; foreach (StackItem subitem in compound.SubItems) { + // Skip sub-items that are in the same component or don't need tracking. if (component.Contains(subitem)) continue; if (!NeedTrack(subitem)) continue; + + // Remove the parent reference from the sub-item. subitem.ObjectReferences!.Remove(compound); } } + + // Perform cleanup for the item. item.Cleanup(); } + + // Move to the next component and remove the current one from the cached_components list. var nodeToRemove = node; node = node.Next; - cached_components.Remove(nodeToRemove); + _cachedComponents.Remove(nodeToRemove); } } } - return references_count; + + // Return the current total reference count. + 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) { - references_count--; + // Decrement the reference count. + _referencesCount--; + + // If the item doesn't need to be tracked, return early. if (!NeedTrack(item)) return; - cached_components = null; + + // Invalidate the cached components since the tracked items are changing. + _cachedComponents = null; + + // Decrement the reference count for the parent in the item's ObjectReferences dictionary. item.ObjectReferences![parent].References--; + + // If the item has no stack references, add it to the zero_referred set. if (item.StackReferences == 0) - zero_referred.Add(item); + _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) { - references_count--; + // Decrement the reference count. + _referencesCount--; + + // If the item doesn't need to be tracked, return early. if (!NeedTrack(item)) return; + + // Decrement the item's stack references and add it to the zero_referred set if it has no references. if (--item.StackReferences == 0) - zero_referred.Add(item); + _zeroReferred.Add(item); } } } diff --git a/src/Neo.VM/Types/Array.cs b/src/Neo.VM/Types/Array.cs index e15358a881..76c1486778 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); } } @@ -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..daa9b05be6 100644 --- a/src/Neo.VM/Types/CompoundType.cs +++ b/src/Neo.VM/Types/CompoundType.cs @@ -24,7 +24,7 @@ public abstract class CompoundType : StackItem /// /// The reference counter used to count the items in the VM object. /// - protected readonly ReferenceCounter? ReferenceCounter; + protected internal readonly ReferenceCounter? ReferenceCounter; /// /// Create a new with the specified reference counter. diff --git a/src/Neo.VM/Types/Map.cs b/src/Neo.VM/Types/Map.cs index 5df66fb897..48155b5207 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; diff --git a/src/Neo.VM/Types/StackItem.Vertex.cs b/src/Neo.VM/Types/StackItem.Vertex.cs index a988d5db32..d3e9ed4dbd 100644 --- a/src/Neo.VM/Types/StackItem.Vertex.cs +++ b/src/Neo.VM/Types/StackItem.Vertex.cs @@ -17,23 +17,109 @@ namespace Neo.VM.Types { partial class StackItem { + /// + /// Represents an entry for an object reference. + /// + /// This class is used to keep track of references from compound types to other or . + /// It contains the referenced item and the number of references to it. + /// + /// Use this class to manage references from compound types to their child items. + /// + /// This is used to track references numbers from the same parent to the same child. + /// This is used for the purpose of determining strongly connect components. + /// + /// internal class ObjectReferenceEntry { + /// + /// The referenced StackItem. + /// public StackItem Item; + + /// + /// The number of references to the StackItem. + /// public int References; + + /// + /// Initializes a new instance of the ObjectReferenceEntry class with the specified StackItem. + /// + /// The referenced StackItem. public ObjectReferenceEntry(StackItem item) => Item = item; } + /// + /// The number of references to this StackItem from the evaluation stack. + /// + /// This field tracks how many times this item is referenced by the evaluation stack. + /// It is incremented when the item is pushed onto the stack and decremented when it is popped off. + /// + /// Use this field to manage stack references and determine when an item is no longer needed. + /// internal int StackReferences = 0; + + /// + /// A dictionary mapping compound types to their object reference entries. + /// + /// This dictionary is used to track references from compound types to their child items. + /// It allows efficient lookup and management of references. + /// + /// Use this dictionary to manage references from compound types to their children. + /// Only and will be assigned an , + /// other types will be null. + /// internal Dictionary? ObjectReferences; + + /// + /// Depth-First Number for Tarjan's algorithm. + /// internal int DFN = -1; + + /// + /// Low-link value for Tarjan's algorithm. + /// internal int LowLink = 0; + + /// + /// Indicates whether the item is currently on the stack for Tarjan's algorithm. + /// + /// + /// This should only be used for Tarjan algorithm, it can not be used to indicate + /// whether an item is on the stack or not since it can still be false if a value is + /// on the stack but the algorithm is not yet running. + /// + /// internal bool OnStack = false; + /// + /// Returns the successors of the current item based on object references. + /// + /// This property provides an enumerable of StackItems that are referenced by this item. + /// It is used by Tarjan's algorithm to find strongly connected components. + /// + /// Use this property when you need to iterate over the successors of a StackItem. + /// internal IEnumerable Successors => ObjectReferences?.Values.Where(p => p.References > 0).Select(p => p.Item) ?? System.Array.Empty(); + /// + /// Resets the strongly connected components-related fields. + /// + /// This method resets the DFN, LowLink, and OnStack fields to their default values. + /// It is used before running Tarjan's algorithm to ensure a clean state. + /// + /// Use this method to reset the state of a StackItem for strongly connected components analysis. + /// internal void Reset() => (DFN, LowLink, OnStack) = (-1, 0, false); + /// + /// Generates a hash code based on the item's span. + /// + /// This method provides a hash code for the StackItem based on its byte span. + /// It is used for efficient storage and retrieval in hash-based collections. + /// + /// Use this method when you need a hash code for a StackItem. + /// + /// The hash code for the StackItem. public override int GetHashCode() => HashCode.Combine(GetSpan().ToArray()); } 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/Crypto.cs b/src/Neo/Cryptography/Crypto.cs index 85ed0411a8..a2e89dc787 100644 --- a/src/Neo/Cryptography/Crypto.cs +++ b/src/Neo/Cryptography/Crypto.cs @@ -170,7 +170,7 @@ public static ECDsa CreateECDsa(ECC.ECPoint pubkey) { if (CacheECDsa.TryGet(pubkey, out var cache)) { - return cache.value; + return cache.Value; } var curve = pubkey.Curve == ECC.ECCurve.Secp256r1 ? ECCurve.NamedCurves.nistP256 : diff --git a/src/Neo/Cryptography/ECC/ECFieldElement.cs b/src/Neo/Cryptography/ECC/ECFieldElement.cs index 47d8bfa274..d77a8b0451 100644 --- a/src/Neo/Cryptography/ECC/ECFieldElement.cs +++ b/src/Neo/Cryptography/ECC/ECFieldElement.cs @@ -9,6 +9,7 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Neo.Extensions; using System; using System.Numerics; diff --git a/src/Neo/Cryptography/ECC/ECPoint.cs b/src/Neo/Cryptography/ECC/ECPoint.cs index 3b7bdcc885..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/Hardfork.cs b/src/Neo/Hardfork.cs index 9ef6a63c1c..7bd3cc0aef 100644 --- a/src/Neo/Hardfork.cs +++ b/src/Neo/Hardfork.cs @@ -15,6 +15,7 @@ public enum Hardfork : byte { HF_Aspidochelone, HF_Basilisk, - HF_Cockatrice + HF_Cockatrice, + HF_Domovoi } } diff --git a/src/Neo/Helper.cs b/src/Neo/Helper.cs index 00099bc148..4458d04f42 100644 --- a/src/Neo/Helper.cs +++ b/src/Neo/Helper.cs @@ -17,8 +17,6 @@ using System.Net; using System.Numerics; using System.Reflection; -using System.Runtime.CompilerServices; -using System.Text; namespace Neo { @@ -29,73 +27,6 @@ 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) @@ -157,32 +88,6 @@ public static byte[] HexToBytes(this string value) return result; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static BigInteger Mod(this BigInteger x, BigInteger y) - { - x %= y; - if (x.Sign < 0) - x += y; - return x; - } - - internal static BigInteger ModInverse(this BigInteger a, BigInteger n) - { - BigInteger i = n, v = 0, d = 1; - while (a > 0) - { - BigInteger t = i / a, x = a; - a = i % x; - i = x; - x = d; - d = v - t * x; - v = x; - } - v %= n; - if (v < 0) v = (v + n) % n; - return v; - } - internal static BigInteger NextBigInteger(this Random rand, int sizeInBits) { if (sizeInBits < 0) @@ -198,76 +103,6 @@ internal static BigInteger NextBigInteger(this Random rand, int sizeInBits) return new BigInteger(b); } - /// - /// Finds the sum of the specified integers. - /// - /// The specified integers. - /// The sum of the integers. - public static BigInteger Sum(this IEnumerable source) - { - var sum = BigInteger.Zero; - foreach (var bi in source) sum += bi; - return sum; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool TestBit(this BigInteger i, int index) - { - return (i & (BigInteger.One << index)) > BigInteger.Zero; - } - - /// - /// Converts a to byte array and eliminates all the leading zeros. - /// - /// The to convert. - /// The converted byte array. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static byte[] ToByteArrayStandard(this BigInteger i) - { - if (i.IsZero) return Array.Empty(); - return i.ToByteArray(); - } - - /// - /// Converts a byte array to hex . - /// - /// 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. /// diff --git a/src/Neo/IEventHandlers/ICommittedHandler.cs b/src/Neo/IEventHandlers/ICommittedHandler.cs new file mode 100644 index 0000000000..632e88916b --- /dev/null +++ b/src/Neo/IEventHandlers/ICommittedHandler.cs @@ -0,0 +1,26 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ICommittedHandler.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Ledger; +using Neo.Network.P2P.Payloads; + +namespace Neo.IEventHandlers; + +public interface ICommittedHandler +{ + /// + /// This is the handler of Commited event from + /// Triggered after a new block is Commited, and state has being updated. + /// + /// The object. + /// The committed . + void Blockchain_Committed_Handler(NeoSystem system, Block block); +} diff --git a/src/Neo/IEventHandlers/ICommittingHandler.cs b/src/Neo/IEventHandlers/ICommittingHandler.cs new file mode 100644 index 0000000000..3fcccccda2 --- /dev/null +++ b/src/Neo/IEventHandlers/ICommittingHandler.cs @@ -0,0 +1,30 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ICommittingHandler.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using System.Collections.Generic; + +namespace Neo.IEventHandlers; + +public interface ICommittingHandler +{ + /// + /// This is the handler of Committing event from + /// Triggered when a new block is committing, and the state is still in the cache. + /// + /// The instance associated with the event. + /// The block that is being committed. + /// The current data snapshot. + /// A list of executed applications associated with the block. + void Blockchain_Committing_Handler(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList); +} diff --git a/src/Neo/IEventHandlers/ILogHandler.cs b/src/Neo/IEventHandlers/ILogHandler.cs new file mode 100644 index 0000000000..ae0a1f9773 --- /dev/null +++ b/src/Neo/IEventHandlers/ILogHandler.cs @@ -0,0 +1,25 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ILogHandler.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; + +namespace Neo.IEventHandlers; + +public interface ILogHandler +{ + /// + /// The handler of Log event from the . + /// Triggered when a contract calls System.Runtime.Log. + /// + /// The source of the event. + /// The arguments of the log. + void ApplicationEngine_Log_Handler(object sender, LogEventArgs logEventArgs); +} diff --git a/src/Neo/IEventHandlers/ILoggingHandler.cs b/src/Neo/IEventHandlers/ILoggingHandler.cs new file mode 100644 index 0000000000..7ec03d4751 --- /dev/null +++ b/src/Neo/IEventHandlers/ILoggingHandler.cs @@ -0,0 +1,24 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ILoggingHandler.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.IEventHandlers; + +public interface ILoggingHandler +{ + /// + /// The handler of Logging event from + /// Triggered when a new log is added by calling + /// + /// The source of the log. Used to identify the producer of the log. + /// The level of the log. + /// The message of the log. + void Utility_Logging_Handler(string source, LogLevel level, object message); +} diff --git a/src/Neo/IEventHandlers/IMessageReceivedHandler.cs b/src/Neo/IEventHandlers/IMessageReceivedHandler.cs new file mode 100644 index 0000000000..e4e15a4f3f --- /dev/null +++ b/src/Neo/IEventHandlers/IMessageReceivedHandler.cs @@ -0,0 +1,25 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// IMessageReceivedHandler.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; + +namespace Neo.IEventHandlers; + +public interface IMessageReceivedHandler +{ + /// + /// The handler of MessageReceived event from + /// Triggered when a new message is received from a peer + /// + /// The object + /// The current node received from a peer + bool RemoteNode_MessageReceived_Handler(NeoSystem system, Message message); +} diff --git a/src/Neo/IEventHandlers/INotifyHandler.cs b/src/Neo/IEventHandlers/INotifyHandler.cs new file mode 100644 index 0000000000..36b9c4abe4 --- /dev/null +++ b/src/Neo/IEventHandlers/INotifyHandler.cs @@ -0,0 +1,25 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// INotifyHandler.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; + +namespace Neo.IEventHandlers; + +public interface INotifyHandler +{ + /// + /// The handler of Notify event from + /// Triggered when a contract calls System.Runtime.Notify. + /// + /// The source of the event. + /// The arguments of the notification. + void ApplicationEngine_Notify_Handler(object sender, NotifyEventArgs notifyEventArgs); +} diff --git a/src/Neo/IEventHandlers/IServiceAddedHandler.cs b/src/Neo/IEventHandlers/IServiceAddedHandler.cs new file mode 100644 index 0000000000..7c3b66a471 --- /dev/null +++ b/src/Neo/IEventHandlers/IServiceAddedHandler.cs @@ -0,0 +1,23 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// IServiceAddedHandler.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.IEventHandlers; + +public interface IServiceAddedHandler +{ + /// + /// The handler of ServiceAdded event from the . + /// Triggered when a service is added to the . + /// + /// The source of the event. + /// The service added. + void NeoSystem_ServiceAdded_Handler(object sender, object service); +} diff --git a/src/Neo/IEventHandlers/ITransactionAddedHandler.cs b/src/Neo/IEventHandlers/ITransactionAddedHandler.cs new file mode 100644 index 0000000000..e120170c3d --- /dev/null +++ b/src/Neo/IEventHandlers/ITransactionAddedHandler.cs @@ -0,0 +1,26 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ITransactionAddedHandler.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Ledger; +using Neo.Network.P2P.Payloads; + +namespace Neo.IEventHandlers; + +public interface ITransactionAddedHandler +{ + /// + /// The handler of TransactionAdded event from the . + /// Triggered when a transaction is added to the . + /// + /// The source of the event + /// The transaction added to the memory pool . + void MemoryPool_TransactionAdded_Handler(object sender, Transaction tx); +} diff --git a/src/Neo/IEventHandlers/ITransactionRemovedHandler.cs b/src/Neo/IEventHandlers/ITransactionRemovedHandler.cs new file mode 100644 index 0000000000..077aa3eccb --- /dev/null +++ b/src/Neo/IEventHandlers/ITransactionRemovedHandler.cs @@ -0,0 +1,25 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// ITransactionRemovedHandler.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Ledger; + +namespace Neo.IEventHandlers; + +public interface ITransactionRemovedHandler +{ + /// + /// Handler of TransactionRemoved event from + /// Triggered when a transaction is removed to the . + /// + /// The source of the event + /// The arguments of event that removes a transaction from the + void MemoryPool_TransactionRemoved_Handler(object sender, TransactionRemovedEventArgs tx); +} diff --git a/src/Neo/IEventHandlers/IWalletChangedHandler.cs b/src/Neo/IEventHandlers/IWalletChangedHandler.cs new file mode 100644 index 0000000000..32986e6e15 --- /dev/null +++ b/src/Neo/IEventHandlers/IWalletChangedHandler.cs @@ -0,0 +1,25 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// IWalletChangedHandler.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.Wallets; + +namespace Neo.IEventHandlers; + +public interface IWalletChangedHandler +{ + /// + /// The handler of WalletChanged event from the . + /// Triggered when a new wallet is assigned to the node. + /// + /// The source of the event + /// The new wallet being assigned to the system. + void IWalletProvider_WalletChanged_Handler(object sender, Wallet wallet); +} diff --git a/src/Neo/IO/Actors/PriorityMailbox.cs b/src/Neo/IO/Actors/PriorityMailbox.cs index 0b08c04d70..b51d5ee861 100644 --- a/src/Neo/IO/Actors/PriorityMailbox.cs +++ b/src/Neo/IO/Actors/PriorityMailbox.cs @@ -17,17 +17,11 @@ namespace Neo.IO.Actors { - internal abstract class PriorityMailbox : MailboxType, IProducesMessageQueue + internal abstract class PriorityMailbox + (Settings settings, Config config) : MailboxType(settings, config), IProducesMessageQueue { - public PriorityMailbox(Akka.Actor.Settings settings, Config config) - : base(settings, config) - { - } - - public override IMessageQueue Create(IActorRef owner, ActorSystem system) - { - return new PriorityMessageQueue(ShallDrop, IsHighPriority); - } + public override IMessageQueue Create(IActorRef owner, ActorSystem system) => + new PriorityMessageQueue(ShallDrop, IsHighPriority); internal protected virtual bool IsHighPriority(object message) => false; internal protected virtual bool ShallDrop(object message, IEnumerable queue) => false; diff --git a/src/Neo/IO/Actors/PriorityMessageQueue.cs b/src/Neo/IO/Actors/PriorityMessageQueue.cs index 225720ec97..8723416adf 100644 --- a/src/Neo/IO/Actors/PriorityMessageQueue.cs +++ b/src/Neo/IO/Actors/PriorityMessageQueue.cs @@ -20,22 +20,17 @@ namespace Neo.IO.Actors { - internal class PriorityMessageQueue : IMessageQueue, IUnboundedMessageQueueSemantics + internal class PriorityMessageQueue + (Func dropper, Func priority_generator) : IMessageQueue, IUnboundedMessageQueueSemantics { - private readonly ConcurrentQueue high = new(); - private readonly ConcurrentQueue low = new(); - private readonly Func dropper; - private readonly Func priority_generator; - private int idle = 1; + private readonly ConcurrentQueue _high = new(); + private readonly ConcurrentQueue _low = new(); + private readonly Func _dropper = dropper; + private readonly Func _priority_generator = priority_generator; + private int _idle = 1; - public bool HasMessages => !high.IsEmpty || !low.IsEmpty; - public int Count => high.Count + low.Count; - - public PriorityMessageQueue(Func dropper, Func priority_generator) - { - this.dropper = dropper; - this.priority_generator = priority_generator; - } + public bool HasMessages => !_high.IsEmpty || !_low.IsEmpty; + public int Count => _high.Count + _low.Count; public void CleanUp(IActorRef owner, IMessageQueue deadletters) { @@ -43,19 +38,19 @@ public void CleanUp(IActorRef owner, IMessageQueue deadletters) public void Enqueue(IActorRef receiver, Envelope envelope) { - Interlocked.Increment(ref idle); + Interlocked.Increment(ref _idle); if (envelope.Message is Idle) return; - if (dropper(envelope.Message, high.Concat(low).Select(p => p.Message))) + if (_dropper(envelope.Message, _high.Concat(_low).Select(p => p.Message))) return; - ConcurrentQueue queue = priority_generator(envelope.Message) ? high : low; + var queue = _priority_generator(envelope.Message) ? _high : _low; queue.Enqueue(envelope); } public bool TryDequeue(out Envelope envelope) { - if (high.TryDequeue(out envelope)) return true; - if (low.TryDequeue(out envelope)) return true; - if (Interlocked.Exchange(ref idle, 0) > 0) + if (_high.TryDequeue(out envelope)) return true; + if (_low.TryDequeue(out envelope)) return true; + if (Interlocked.Exchange(ref _idle, 0) > 0) { envelope = new Envelope(Idle.Instance, ActorRefs.NoSender); return true; diff --git a/src/Neo/IO/Caching/ECDsaCache.cs b/src/Neo/IO/Caching/ECDsaCache.cs index b25f29da8e..b41a058b61 100644 --- a/src/Neo/IO/Caching/ECDsaCache.cs +++ b/src/Neo/IO/Caching/ECDsaCache.cs @@ -14,7 +14,8 @@ namespace Neo.IO.Caching { - record ECDsaCacheItem(Cryptography.ECC.ECPoint key, ECDsa value); + record ECDsaCacheItem(Cryptography.ECC.ECPoint Key, ECDsa Value); + internal class ECDsaCache : FIFOCache { public ECDsaCache(int max_capacity = 20000) : base(max_capacity, EqualityComparer.Default) @@ -23,7 +24,7 @@ public ECDsaCache(int max_capacity = 20000) : base(max_capacity, EqualityCompare protected override Cryptography.ECC.ECPoint GetKeyForItem(ECDsaCacheItem item) { - return item.key; + return item.Key; } } } diff --git a/src/Neo/IO/Caching/ReflectionCache.cs b/src/Neo/IO/Caching/ReflectionCache.cs index 2fd8f5fceb..5007b67740 100644 --- a/src/Neo/IO/Caching/ReflectionCache.cs +++ b/src/Neo/IO/Caching/ReflectionCache.cs @@ -15,29 +15,30 @@ namespace Neo.IO.Caching { - internal static class ReflectionCache where T : Enum + internal static class ReflectionCache + where T : Enum { - private static readonly Dictionary dictionary = new(); + private static readonly Dictionary s_dictionary = []; - public static int Count => dictionary.Count; + public static int Count => s_dictionary.Count; static ReflectionCache() { - foreach (FieldInfo field in typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static)) + foreach (var field in typeof(T).GetFields(BindingFlags.Public | BindingFlags.Static)) { // Get attribute - ReflectionCacheAttribute attribute = field.GetCustomAttribute(); + var attribute = field.GetCustomAttribute(); if (attribute == null) continue; // Append to cache - dictionary.Add((T)field.GetValue(null), attribute.Type); + s_dictionary.Add((T)field.GetValue(null), attribute.Type); } } public static object CreateInstance(T key, object def = null) { // Get Type from cache - if (dictionary.TryGetValue(key, out Type t)) + if (s_dictionary.TryGetValue(key, out var t)) return Activator.CreateInstance(t); // return null @@ -46,7 +47,7 @@ public static object CreateInstance(T key, object def = null) public static ISerializable CreateSerializable(T key, ReadOnlyMemory data) { - if (dictionary.TryGetValue(key, out Type t)) + if (s_dictionary.TryGetValue(key, out var t)) return data.AsSerializable(t); return null; } diff --git a/src/Neo/IO/Caching/RelayCache.cs b/src/Neo/IO/Caching/RelayCache.cs index 0f617f4d24..56466d9a7f 100644 --- a/src/Neo/IO/Caching/RelayCache.cs +++ b/src/Neo/IO/Caching/RelayCache.cs @@ -13,13 +13,9 @@ namespace Neo.IO.Caching { - internal class RelayCache : FIFOCache + internal class RelayCache + (int max_capacity) : FIFOCache(max_capacity) { - public RelayCache(int max_capacity) - : base(max_capacity) - { - } - protected override UInt256 GetKeyForItem(IInventory item) { return item.Hash; diff --git a/src/Neo/Ledger/Blockchain.cs b/src/Neo/Ledger/Blockchain.cs index ff9c8e29d8..f675226303 100644 --- a/src/Neo/Ledger/Blockchain.cs +++ b/src/Neo/Ledger/Blockchain.cs @@ -16,6 +16,7 @@ using Neo.Network.P2P; using Neo.Network.P2P.Payloads; using Neo.Persistence; +using Neo.Plugins; using Neo.SmartContract; using Neo.SmartContract.Native; using Neo.VM; @@ -24,6 +25,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Linq; +using System.Runtime.CompilerServices; namespace Neo.Ledger { @@ -417,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; @@ -435,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) { @@ -449,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); @@ -468,10 +470,10 @@ private void Persist(Block block) Context.System.EventStream.Publish(application_executed); all_application_executed.Add(application_executed); } - Committing?.Invoke(system, block, snapshot, all_application_executed); + InvokeCommitting(system, block, snapshot, all_application_executed); snapshot.Commit(); } - Committed?.Invoke(system, block); + InvokeCommitted(system, block); system.MemPool.UpdatePoolForBlockPersisted(block, system.StoreView); extensibleWitnessWhiteList = null; block_cache.Remove(block.PrevHash); @@ -480,6 +482,56 @@ private void Persist(Block block) Debug.Assert(header.Index == block.Index); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void InvokeCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + { + InvokeHandlers(Committing?.GetInvocationList(), h => ((CommittingHandler)h)(system, block, snapshot, applicationExecutedList)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void InvokeCommitted(NeoSystem system, Block block) + { + InvokeHandlers(Committed?.GetInvocationList(), h => ((CommittedHandler)h)(system, block)); + } + + private static void InvokeHandlers(Delegate[] handlers, Action handlerAction) + { + if (handlers == null) return; + + foreach (var handler in handlers) + { + try + { + // skip stopped plugin. + if (handler.Target is Plugin { IsStopped: true }) + { + 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: + throw; + case UnhandledExceptionPolicy.StopPlugin: + //Stop plugin on exception + plugin.IsStopped = true; + break; + case UnhandledExceptionPolicy.Ignore: + // Log the exception and continue with the next handler + break; + default: + throw new InvalidCastException( + $"The exception policy {plugin.ExceptionPolicy} is not valid."); + } + } + } + } + /// /// Gets a object used for creating the actor. /// diff --git a/src/Neo/Ledger/MemoryPool.cs b/src/Neo/Ledger/MemoryPool.cs index 23eb711e87..8f9437641b 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,7 +443,7 @@ private void RemoveConflictsOfVerified(PoolItem item) } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal bool TryRemoveUnVerified(UInt256 hash, out PoolItem item) + internal bool TryRemoveUnVerified(UInt256 hash, [MaybeNullWhen(false)] out PoolItem? item) { if (!_unverifiedTransactions.TryGetValue(hash, out item)) return false; @@ -653,5 +659,24 @@ internal bool ReVerifyTopUnverifiedTransactionsIfNeeded(int maxToVerify, DataCac return _unverifiedTransactions.Count > 0; } + + // This method is only for test purpose + // Do not use this method outside of unit tests + internal void Clear() + { + _txRwLock.EnterReadLock(); + try + { + _unsortedTransactions.Clear(); + _conflicts.Clear(); + _sortedTransactions.Clear(); + _unverifiedTransactions.Clear(); + _unverifiedSortedTransactions.Clear(); + } + finally + { + _txRwLock.ExitReadLock(); + } + } } } diff --git a/src/Neo/Neo.csproj b/src/Neo/Neo.csproj index 707b94fa4c..7c6478c33e 100644 --- a/src/Neo/Neo.csproj +++ b/src/Neo/Neo.csproj @@ -5,16 +5,16 @@ true Neo NEO;AntShares;Blockchain;Smart Contract - $(SolutionDir)/bin/$(PackageId) + ../../bin/$(PackageId) - + - + @@ -30,6 +30,7 @@ + diff --git a/src/Neo/NeoSystem.cs b/src/Neo/NeoSystem.cs index b752f16712..3121432db5 100644 --- a/src/Neo/NeoSystem.cs +++ b/src/Neo/NeoSystem.cs @@ -275,12 +275,24 @@ public void SuspendNodeStartup() /// /// Gets a snapshot of the blockchain storage. /// - /// + /// An instance of + [Obsolete("This method is obsolete, use GetSnapshotCache instead.")] public SnapshotCache GetSnapshot() { return new SnapshotCache(store.GetSnapshot()); } + /// + /// Gets a snapshot of the blockchain storage with an execution cache. + /// With the snapshot, we have the latest state of the blockchain, with the cache, + /// we can run transactions in a sandboxed environment. + /// + /// An instance of + public SnapshotCache GetSnapshotCache() + { + return new SnapshotCache(store.GetSnapshot()); + } + /// /// Determines whether the specified transaction exists in the memory pool or storage. /// diff --git a/src/Neo/Network/P2P/LocalNode.cs b/src/Neo/Network/P2P/LocalNode.cs index 78a967047c..313e7afd16 100644 --- a/src/Neo/Network/P2P/LocalNode.cs +++ b/src/Neo/Network/P2P/LocalNode.cs @@ -78,7 +78,7 @@ static LocalNode() { Random rand = new(); Nonce = (uint)rand.Next(); - UserAgent = $"/{Assembly.GetExecutingAssembly().GetName().Name}:{Assembly.GetExecutingAssembly().GetVersion()}/"; + UserAgent = $"/{Assembly.GetExecutingAssembly().GetName().Name}:{Assembly.GetExecutingAssembly().GetName().Version?.ToString(3)}/"; } /// diff --git a/src/Neo/Network/P2P/Payloads/Conditions/CalledByGroupCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/CalledByGroupCondition.cs index 82dab60fcf..d28cd9f8bf 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)); } diff --git a/src/Neo/Network/P2P/Payloads/Conditions/GroupCondition.cs b/src/Neo/Network/P2P/Payloads/Conditions/GroupCondition.cs index ee937aff8b..3187b62988 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)); } diff --git a/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs b/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs index 4606723cd0..4e7861a562 100644 --- a/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs +++ b/src/Neo/Network/P2P/RemoteNode.ProtocolHandler.cs @@ -30,9 +30,9 @@ namespace Neo.Network.P2P partial class RemoteNode { private class Timer { } - private class PendingKnownHashesCollection : KeyedCollectionSlim + private class PendingKnownHashesCollection : KeyedCollectionSlim> { - protected override UInt256 GetKeyForItem((UInt256, DateTime) item) + protected override UInt256 GetKeyForItem(Tuple item) { return item.Item1; } @@ -354,7 +354,7 @@ private void OnInvMessageReceived(InvPayload payload) } if (hashes.Length == 0) return; foreach (UInt256 hash in hashes) - pendingKnownHashes.Add((hash, TimeProvider.Current.UtcNow)); + pendingKnownHashes.Add(Tuple.Create(hash, TimeProvider.Current.UtcNow)); system.TaskManager.Tell(new TaskManager.NewTasks { Payload = InvPayload.Create(payload.Type, hashes) }); } diff --git a/src/Neo/Persistence/DataCache.cs b/src/Neo/Persistence/DataCache.cs index 86fbd69961..29c610c6c8 100644 --- a/src/Neo/Persistence/DataCache.cs +++ b/src/Neo/Persistence/DataCache.cs @@ -152,11 +152,21 @@ public virtual void Commit() /// Creates a snapshot, which uses this instance as the underlying storage. /// /// The snapshot of this instance. + [Obsolete("CreateSnapshot is deprecated, please use CloneCache instead.")] public DataCache CreateSnapshot() { return new ClonedCache(this); } + /// + /// Creates a clone of the snapshot cache, which uses this instance as the underlying storage. + /// + /// The of this instance. + public DataCache CloneCache() + { + return new ClonedCache(this); + } + /// /// Deletes an entry from the cache. /// diff --git a/src/Neo/Plugins/Plugin.cs b/src/Neo/Plugins/Plugin.cs index 248301af56..7b85d85d92 100644 --- a/src/Neo/Plugins/Plugin.cs +++ b/src/Neo/Plugins/Plugin.cs @@ -33,7 +33,8 @@ public abstract class Plugin : IDisposable /// /// The directory containing the plugin folders. Files can be contained in any subdirectory. /// - public static readonly string PluginsDirectory = Combine(GetDirectoryName(System.AppContext.BaseDirectory), "Plugins"); + public static readonly string PluginsDirectory = + Combine(GetDirectoryName(System.AppContext.BaseDirectory), "Plugins"); private static readonly FileSystemWatcher configWatcher; @@ -67,6 +68,18 @@ public abstract class Plugin : IDisposable /// public virtual Version Version => GetType().Assembly.GetName().Version; + /// + /// If the plugin should be stopped when an exception is thrown. + /// Default is StopNode. + /// + protected internal virtual UnhandledExceptionPolicy ExceptionPolicy { get; init; } = UnhandledExceptionPolicy.StopNode; + + /// + /// The plugin will be stopped if an exception is thrown. + /// But it also depends on . + /// + internal bool IsStopped { get; set; } + static Plugin() { if (!Directory.Exists(PluginsDirectory)) return; @@ -74,7 +87,8 @@ static Plugin() { EnableRaisingEvents = true, IncludeSubdirectories = true, - NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.Size, + NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.CreationTime | + NotifyFilters.LastWrite | NotifyFilters.Size, }; configWatcher.Changed += ConfigWatcher_Changed; configWatcher.Created += ConfigWatcher_Changed; @@ -106,7 +120,8 @@ private static void ConfigWatcher_Changed(object sender, FileSystemEventArgs e) { case ".json": case ".dll": - Utility.Log(nameof(Plugin), LogLevel.Warning, $"File {e.Name} is {e.ChangeType}, please restart node."); + Utility.Log(nameof(Plugin), LogLevel.Warning, + $"File {e.Name} is {e.ChangeType}, please restart node."); break; } } @@ -119,7 +134,8 @@ private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEven AssemblyName an = new(args.Name); Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.FullName == args.Name) ?? - AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name == an.Name); + AppDomain.CurrentDomain.GetAssemblies() + .FirstOrDefault(a => a.GetName().Name == an.Name); if (assembly != null) return assembly; string filename = an.Name + ".dll"; @@ -150,7 +166,8 @@ public virtual void Dispose() /// The content of the configuration file read. protected IConfigurationSection GetConfiguration() { - return new ConfigurationBuilder().AddJsonFile(ConfigFile, optional: true).Build().GetSection("PluginConfiguration"); + return new ConfigurationBuilder().AddJsonFile(ConfigFile, optional: true).Build() + .GetSection("PluginConfiguration"); } private static void LoadPlugin(Assembly assembly) @@ -187,6 +204,7 @@ internal static void LoadPlugins() catch { } } } + foreach (Assembly assembly in assemblies) { LoadPlugin(assembly); @@ -229,7 +247,46 @@ protected internal virtual void OnSystemLoaded(NeoSystem system) /// if the is handled by a plugin; otherwise, . public static bool SendMessage(object message) { - return Plugins.Any(plugin => plugin.OnMessage(message)); + foreach (var plugin in Plugins) + { + if (plugin.IsStopped) + { + continue; + } + + bool result; + try + { + result = plugin.OnMessage(message); + } + catch (Exception ex) + { + Utility.Log(nameof(Plugin), LogLevel.Error, ex); + + switch (plugin.ExceptionPolicy) + { + case UnhandledExceptionPolicy.StopNode: + throw; + case UnhandledExceptionPolicy.StopPlugin: + plugin.IsStopped = true; + break; + case UnhandledExceptionPolicy.Ignore: + break; + default: + throw new InvalidCastException($"The exception policy {plugin.ExceptionPolicy} is not valid."); + } + + 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 new file mode 100644 index 0000000000..66549a390e --- /dev/null +++ b/src/Neo/Plugins/PluginSettings.cs @@ -0,0 +1,33 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// PluginSettings.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.Extensions.Configuration; +using Org.BouncyCastle.Security; +using System; + +namespace Neo.Plugins; + +public abstract class PluginSettings(IConfigurationSection section) +{ + public UnhandledExceptionPolicy ExceptionPolicy + { + get + { + var policyString = section.GetValue(nameof(UnhandledExceptionPolicy), nameof(UnhandledExceptionPolicy.StopNode)); + if (Enum.TryParse(policyString, true, out UnhandledExceptionPolicy policy)) + { + return policy; + } + + throw new InvalidParameterException($"{policyString} is not a valid UnhandledExceptionPolicy"); + } + } +} diff --git a/src/Neo/Plugins/UnhandledExceptionPolicy.cs b/src/Neo/Plugins/UnhandledExceptionPolicy.cs new file mode 100644 index 0000000000..0422f2c5e7 --- /dev/null +++ b/src/Neo/Plugins/UnhandledExceptionPolicy.cs @@ -0,0 +1,20 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UnhandledExceptionPolicy.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +namespace Neo.Plugins +{ + public enum UnhandledExceptionPolicy : byte + { + Ignore = 0, + StopPlugin = 1, + StopNode = 2, + } +} 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.Runtime.cs b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs index b39fd55a01..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); } /// @@ -407,14 +407,19 @@ protected internal void SendNotification(UInt160 hash, string eventName, Array s /// /// The hash of the specified contract. It can be set to to get all notifications. /// The notifications sent during the execution. - protected internal NotifyEventArgs[] GetNotifications(UInt160 hash) + protected internal Array GetNotifications(UInt160 hash) { IEnumerable notifications = Notifications; if (hash != null) // must filter by scriptHash notifications = notifications.Where(p => p.ScriptHash == hash); - NotifyEventArgs[] array = notifications.ToArray(); + var array = notifications.ToArray(); if (array.Length > Limits.MaxStackSize) throw new InvalidOperationException(); - return array; + Array notifyArray = new(ReferenceCounter); + foreach (var notify in array) + { + notifyArray.Add(notify.ToStackItem(ReferenceCounter, this)); + } + return notifyArray; } /// 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 8602eb054d..38430f3632 100644 --- a/src/Neo/SmartContract/ApplicationEngine.cs +++ b/src/Neo/SmartContract/ApplicationEngine.cs @@ -55,7 +55,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 +95,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 +170,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) { @@ -274,7 +280,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,17 +289,21 @@ 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; + ExecutionContextState state = currentContext.GetState(); if (method.Safe) { flags &= ~(CallFlags.WriteStates | CallFlags.AllowNotify); } else { - ContractState currentContract = NativeContract.ContractManagement.GetContract(Snapshot, CurrentScriptHash); - if (currentContract?.CanCall(contract, method.Name) == false) + 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(SnapshotCache, CurrentScriptHash); + if (executingContract?.CanCall(contract, method.Name) == false) throw new InvalidOperationException($"Cannot Call Method {method.Name} Of Contract {contract.Hash} From Contract {CurrentScriptHash}"); } @@ -306,8 +316,6 @@ private ExecutionContext CallContractInternal(ContractState contract, ContractMe invocationCounter[contract.Hash] = 1; } - ExecutionContext currentContext = CurrentContext; - ExecutionContextState state = currentContext.GetState(); CallFlags callingFlags = state.CallFlags; if (args.Count != method.Parameters.Length) throw new InvalidOperationException($"Method {method} Expects {method.Parameters.Length} Arguments But Receives {args.Count} Arguments"); @@ -350,7 +358,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(); @@ -458,7 +466,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 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/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/Manifest/ContractAbi.cs b/src/Neo/SmartContract/Manifest/ContractAbi.cs index 3660d1a99e..cedd431d62 100644 --- a/src/Neo/SmartContract/Manifest/ContractAbi.cs +++ b/src/Neo/SmartContract/Manifest/ContractAbi.cs @@ -62,8 +62,8 @@ public static ContractAbi FromJson(JObject json) { ContractAbi abi = new() { - Methods = ((JArray)json["methods"]).Select(u => ContractMethodDescriptor.FromJson((JObject)u)).ToArray(), - Events = ((JArray)json["events"]).Select(u => ContractEventDescriptor.FromJson((JObject)u)).ToArray() + Methods = ((JArray)json!["methods"])?.Select(u => ContractMethodDescriptor.FromJson((JObject)u)).ToArray() ?? [], + Events = ((JArray)json!["events"])?.Select(u => ContractEventDescriptor.FromJson((JObject)u)).ToArray() ?? [] }; if (abi.Methods.Length == 0) throw new FormatException(); return abi; diff --git a/src/Neo/SmartContract/Manifest/ContractManifest.cs b/src/Neo/SmartContract/Manifest/ContractManifest.cs index 078e8fd35e..b1bd317e41 100644 --- a/src/Neo/SmartContract/Manifest/ContractManifest.cs +++ b/src/Neo/SmartContract/Manifest/ContractManifest.cs @@ -112,20 +112,21 @@ public static ContractManifest FromJson(JObject json) { ContractManifest manifest = new() { - Name = json["name"].GetString(), - Groups = ((JArray)json["groups"]).Select(u => ContractGroup.FromJson((JObject)u)).ToArray(), - SupportedStandards = ((JArray)json["supportedstandards"]).Select(u => u.GetString()).ToArray(), + Name = json["name"]!.GetString(), + Groups = ((JArray)json["groups"])?.Select(u => ContractGroup.FromJson((JObject)u)).ToArray() ?? [], + SupportedStandards = ((JArray)json["supportedstandards"])?.Select(u => u.GetString()).ToArray() ?? [], Abi = ContractAbi.FromJson((JObject)json["abi"]), - Permissions = ((JArray)json["permissions"]).Select(u => ContractPermission.FromJson((JObject)u)).ToArray(), + Permissions = ((JArray)json["permissions"])?.Select(u => ContractPermission.FromJson((JObject)u)).ToArray() ?? [], Trusts = WildcardContainer.FromJson(json["trusts"], u => ContractPermissionDescriptor.FromJson((JString)u)), Extra = (JObject)json["extra"] }; + if (string.IsNullOrEmpty(manifest.Name)) throw new FormatException(); _ = manifest.Groups.ToDictionary(p => p.PubKey); if (json["features"] is not JObject features || features.Count != 0) throw new FormatException(); - if (manifest.SupportedStandards.Any(p => string.IsNullOrEmpty(p))) + if (manifest.SupportedStandards.Any(string.IsNullOrEmpty)) throw new FormatException(); _ = manifest.SupportedStandards.ToDictionary(p => p); _ = manifest.Permissions.ToDictionary(p => p.Contract); diff --git a/src/Neo/SmartContract/Manifest/ContractPermissionDescriptor.cs b/src/Neo/SmartContract/Manifest/ContractPermissionDescriptor.cs index f1f22d99d8..d950d25c62 100644 --- a/src/Neo/SmartContract/Manifest/ContractPermissionDescriptor.cs +++ b/src/Neo/SmartContract/Manifest/ContractPermissionDescriptor.cs @@ -114,7 +114,8 @@ public bool Equals(ContractPermissionDescriptor other) if (this == other) return true; if (IsWildcard == other.IsWildcard) return true; if (IsHash) return Hash.Equals(other.Hash); - else return Group.Equals(other.Group); + if (IsGroup) return Group.Equals(other.Group); + return false; } public override int GetHashCode() diff --git a/src/Neo/SmartContract/Native/ContractEventAttribute.cs b/src/Neo/SmartContract/Native/ContractEventAttribute.cs index 656ecef725..b2ff30891a 100644 --- a/src/Neo/SmartContract/Native/ContractEventAttribute.cs +++ b/src/Neo/SmartContract/Native/ContractEventAttribute.cs @@ -17,11 +17,12 @@ namespace Neo.SmartContract.Native { [DebuggerDisplay("{Descriptor.Name}")] [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = true)] - internal class ContractEventAttribute : Attribute + internal class ContractEventAttribute : Attribute, IHardforkActivable { public int Order { get; init; } public ContractEventDescriptor Descriptor { get; set; } public Hardfork? ActiveIn { get; init; } = null; + public Hardfork? DeprecatedIn { get; init; } = null; public ContractEventAttribute(Hardfork activeIn, int order, string name, string arg1Name, ContractParameterType arg1Value) : this(order, name, arg1Name, arg1Value) @@ -29,23 +30,35 @@ public ContractEventAttribute(Hardfork activeIn, int order, string name, ActiveIn = activeIn; } + public ContractEventAttribute(Hardfork activeIn, int order, string name, + string arg1Name, ContractParameterType arg1Value, Hardfork deprecatedIn) : this(activeIn, order, name, arg1Name, arg1Value) + { + DeprecatedIn = deprecatedIn; + } + public ContractEventAttribute(int order, string name, string arg1Name, ContractParameterType arg1Value) { Order = order; Descriptor = new ContractEventDescriptor() { Name = name, - Parameters = new ContractParameterDefinition[] - { + Parameters = + [ new ContractParameterDefinition() { Name = arg1Name, Type = arg1Value } - } + ] }; } + public ContractEventAttribute(int order, string name, string arg1Name, ContractParameterType arg1Value, Hardfork deprecatedIn) + : this(order, name, arg1Name, arg1Value) + { + DeprecatedIn = deprecatedIn; + } + public ContractEventAttribute(Hardfork activeIn, int order, string name, string arg1Name, ContractParameterType arg1Value, string arg2Name, ContractParameterType arg2Value) : this(order, name, arg1Name, arg1Value, arg2Name, arg2Value) @@ -53,6 +66,13 @@ public ContractEventAttribute(Hardfork activeIn, int order, string name, ActiveIn = activeIn; } + public ContractEventAttribute(Hardfork activeIn, int order, string name, + string arg1Name, ContractParameterType arg1Value, + string arg2Name, ContractParameterType arg2Value, Hardfork deprecatedIn) : this(activeIn, order, name, arg1Name, arg1Value, arg2Name, arg2Value) + { + DeprecatedIn = deprecatedIn; + } + public ContractEventAttribute(int order, string name, string arg1Name, ContractParameterType arg1Value, string arg2Name, ContractParameterType arg2Value) @@ -61,8 +81,8 @@ public ContractEventAttribute(int order, string name, Descriptor = new ContractEventDescriptor() { Name = name, - Parameters = new ContractParameterDefinition[] - { + Parameters = + [ new ContractParameterDefinition() { Name = arg1Name, @@ -73,10 +93,18 @@ public ContractEventAttribute(int order, string name, Name = arg2Name, Type = arg2Value } - } + ] }; } + public ContractEventAttribute(int order, string name, + string arg1Name, ContractParameterType arg1Value, + string arg2Name, ContractParameterType arg2Value, Hardfork deprecatedIn) : this(order, name, arg1Name, arg1Value, arg2Name, arg2Value) + { + DeprecatedIn = deprecatedIn; + } + + public ContractEventAttribute(Hardfork activeIn, int order, string name, string arg1Name, ContractParameterType arg1Value, string arg2Name, ContractParameterType arg2Value, @@ -95,8 +123,8 @@ public ContractEventAttribute(int order, string name, Descriptor = new ContractEventDescriptor() { Name = name, - Parameters = new ContractParameterDefinition[] - { + Parameters = + [ new ContractParameterDefinition() { Name = arg1Name, @@ -112,7 +140,7 @@ public ContractEventAttribute(int order, string name, Name = arg3Name, Type = arg3Value } - } + ] }; } @@ -136,8 +164,8 @@ public ContractEventAttribute(int order, string name, Descriptor = new ContractEventDescriptor() { Name = name, - Parameters = new ContractParameterDefinition[] - { + Parameters = + [ new ContractParameterDefinition() { Name = arg1Name, @@ -158,7 +186,7 @@ public ContractEventAttribute(int order, string name, Name = arg4Name, Type = arg4Value } - } + ] }; } } diff --git a/src/Neo/SmartContract/Native/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/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/LedgerContract.cs b/src/Neo/SmartContract/Native/LedgerContract.cs index ea757ba348..329e0565d5 100644 --- a/src/Neo/SmartContract/Native/LedgerContract.cs +++ b/src/Neo/SmartContract/Native/LedgerContract.cs @@ -42,22 +42,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 +67,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 +188,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 +280,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 +314,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 99ecd41e29..40f63355df 100644 --- a/src/Neo/SmartContract/Native/NeoToken.cs +++ b/src/Neo/SmartContract/Native/NeoToken.cs @@ -86,11 +86,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 +107,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 +184,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 +198,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 +232,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 +251,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 +266,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 +287,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 +332,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 +349,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 +364,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 +386,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; @@ -421,7 +421,7 @@ private async ContractTask Vote(ApplicationEngine engine, UInt160 account, /// The snapshot used to read data. /// All the registered candidates. [ContractMethod(CpuFee = 1 << 22, RequiredCallFlags = CallFlags.ReadStates)] - private (ECPoint PublicKey, BigInteger Votes)[] GetCandidates(DataCache snapshot) + internal (ECPoint PublicKey, BigInteger Votes)[] GetCandidates(DataCache snapshot) { return GetCandidatesInternal(snapshot) .Select(p => (p.PublicKey, p.State.Votes)) @@ -536,7 +536,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); } /// diff --git a/src/Neo/SmartContract/Native/OracleContract.cs b/src/Neo/SmartContract/Native/OracleContract.cs index 13a7264660..be4765581a 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); 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..d0437594c9 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 })); } diff --git a/src/Neo/SmartContract/NotifyEventArgs.cs b/src/Neo/SmartContract/NotifyEventArgs.cs index 93c124ea88..257efb3a66 100644 --- a/src/Neo/SmartContract/NotifyEventArgs.cs +++ b/src/Neo/SmartContract/NotifyEventArgs.cs @@ -66,11 +66,31 @@ public void FromStackItem(StackItem stackItem) public StackItem ToStackItem(ReferenceCounter referenceCounter) { return new Array(referenceCounter) + { + ScriptHash.ToArray(), + EventName, + State + }; + } + + public StackItem ToStackItem(ReferenceCounter referenceCounter, ApplicationEngine engine) + { + if (engine.IsHardforkEnabled(Hardfork.HF_Domovoi)) { - ScriptHash.ToArray(), - EventName, - State - }; + return new Array(referenceCounter) + { + ScriptHash.ToArray(), + EventName, + State.OnStack ? State : State.DeepCopy(true) + }; + } + + return new Array(referenceCounter) + { + ScriptHash.ToArray(), + EventName, + State + }; } } } 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..db317ef7e5 100644 --- a/src/Neo/UInt160.cs +++ b/src/Neo/UInt160.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/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..c6423ac1f1 100644 --- a/src/Neo/VM/Helper.cs +++ b/src/Neo/VM/Helper.cs @@ -217,6 +217,9 @@ public static ScriptBuilder EmitPush(this ScriptBuilder builder, object obj) case short data: builder.EmitPush(data); break; + case char data: + builder.EmitPush((ushort)data); + break; case ushort data: builder.EmitPush(data); break; diff --git a/src/Neo/Wallets/Helper.cs b/src/Neo/Wallets/Helper.cs index 1273bafc50..b8427d078d 100644 --- a/src/Neo/Wallets/Helper.cs +++ b/src/Neo/Wallets/Helper.cs @@ -133,7 +133,7 @@ public static long CalculateNetworkFee(this Transaction tx, DataCache snapshot, size += Array.Empty().GetVarSize() + invSize; // Check verify cost - using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CreateSnapshot(), settings: settings, gas: maxExecutionCost); + using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CloneCache(), settings: settings, gas: maxExecutionCost); engine.LoadContract(contract, md, CallFlags.ReadOnly); if (invocationScript != null) engine.LoadScript(invocationScript, configureState: p => p.CallFlags = CallFlags.None); if (engine.Execute() == VMState.FAULT) throw new ArgumentException($"Smart contract {contract.Hash} verification fault."); diff --git a/src/Neo/Wallets/Wallet.cs b/src/Neo/Wallets/Wallet.cs index aca30b008f..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 4529b946af..b0c31a7180 100644 --- a/src/Plugins/ApplicationLogs/ApplicationLogs.csproj +++ b/src/Plugins/ApplicationLogs/ApplicationLogs.csproj @@ -4,7 +4,7 @@ Neo.Plugins.ApplicationLogs Neo.Plugins enable - $(SolutionDir)/bin/$(PackageId) + ../../../bin/$(PackageId) @@ -14,9 +14,14 @@ runtime + PreserveNewest + + + + diff --git a/src/Plugins/ApplicationLogs/ApplicationLogs.json b/src/Plugins/ApplicationLogs/ApplicationLogs.json index af601bc81e..2664665dd2 100644 --- a/src/Plugins/ApplicationLogs/ApplicationLogs.json +++ b/src/Plugins/ApplicationLogs/ApplicationLogs.json @@ -3,7 +3,8 @@ "Path": "ApplicationLogs_{0}", "Network": 860833102, "MaxStackSize": 65535, - "Debug": false + "Debug": false, + "UnhandledExceptionPolicy": "StopPlugin" }, "Dependency": [ "RpcServer" diff --git a/src/Plugins/ApplicationLogs/LogReader.cs b/src/Plugins/ApplicationLogs/LogReader.cs index 6a8682ab5e..b3f767fecb 100644 --- a/src/Plugins/ApplicationLogs/LogReader.cs +++ b/src/Plugins/ApplicationLogs/LogReader.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.ConsoleService; +using Neo.IEventHandlers; using Neo.Json; using Neo.Ledger; using Neo.Network.P2P.Payloads; @@ -25,7 +26,7 @@ namespace Neo.Plugins.ApplicationLogs { - public class LogReader : Plugin + public class LogReader : Plugin, ICommittingHandler, ICommittedHandler, ILogHandler { #region Globals @@ -37,14 +38,15 @@ public class LogReader : Plugin public override string Name => "ApplicationLogs"; public override string Description => "Synchronizes smart contract VM executions and notifications (NotifyLog) on blockchain."; + protected override UnhandledExceptionPolicy ExceptionPolicy => Settings.Default.ExceptionPolicy; #region Ctor public LogReader() { _logEvents = new(); - Blockchain.Committing += OnCommitting; - Blockchain.Committed += OnCommitted; + Blockchain.Committing += ((ICommittingHandler)this).Blockchain_Committing_Handler; + Blockchain.Committed += ((ICommittedHandler)this).Blockchain_Committed_Handler; } #endregion @@ -55,10 +57,10 @@ public LogReader() public override void Dispose() { - Blockchain.Committing -= OnCommitting; - Blockchain.Committed -= OnCommitted; + Blockchain.Committing -= ((ICommittingHandler)this).Blockchain_Committing_Handler; + Blockchain.Committed -= ((ICommittedHandler)this).Blockchain_Committed_Handler; if (Settings.Default.Debug) - ApplicationEngine.Log -= OnApplicationEngineLog; + ApplicationEngine.Log -= ((ILogHandler)this).ApplicationEngine_Log_Handler; GC.SuppressFinalize(this); } @@ -78,7 +80,7 @@ protected override void OnSystemLoaded(NeoSystem system) RpcServerPlugin.RegisterMethods(this, Settings.Default.Network); if (Settings.Default.Debug) - ApplicationEngine.Log += OnApplicationEngineLog; + ApplicationEngine.Log += ((ILogHandler)this).ApplicationEngine_Log_Handler; } #endregion @@ -141,12 +143,11 @@ private void OnGetBlockCommand(string blockHashOrIndex, string eventName = null) _neostore.GetBlockLog(blockhash, TriggerType.PostPersist) : _neostore.GetBlockLog(blockhash, TriggerType.PostPersist, eventName); - if (blockOnPersist == null && blockOnPersist == null) + if (blockOnPersist == null) ConsoleHelper.Error($"No logs."); - if (blockOnPersist != null) - PrintExecutionToConsole(blockOnPersist); - if (blockPostPersist != null) + else { + PrintExecutionToConsole(blockOnPersist); ConsoleHelper.Info("--------------------------------"); PrintExecutionToConsole(blockPostPersist); } @@ -195,7 +196,7 @@ private void OnGetContractCommand(UInt160 scripthash, uint page = 1, uint pageSi #region Blockchain Events - private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + void ICommittingHandler.Blockchain_Committing_Handler(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) { if (system.Settings.Network != Settings.Default.Network) return; @@ -216,7 +217,7 @@ private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IRe } } - private void OnCommitted(NeoSystem system, Block block) + void ICommittedHandler.Blockchain_Committed_Handler(NeoSystem system, Block block) { if (system.Settings.Network != Settings.Default.Network) return; @@ -225,7 +226,7 @@ private void OnCommitted(NeoSystem system, Block block) _neostore.CommitBlockLog(); } - private void OnApplicationEngineLog(object sender, LogEventArgs e) + void ILogHandler.ApplicationEngine_Log_Handler(object sender, LogEventArgs e) { if (Settings.Default.Debug == false) return; @@ -271,8 +272,9 @@ private void PrintExecutionToConsole(BlockchainExecutionModel model) ConsoleHelper.Info(" ScriptHash: ", $"{notifyItem.ScriptHash}"); ConsoleHelper.Info(" Event Name: ", $"{notifyItem.EventName}"); ConsoleHelper.Info(" State Parameters:"); - for (int i = 0; i < notifyItem.State.Length; i++) - ConsoleHelper.Info($" {GetMethodParameterName(notifyItem.ScriptHash, notifyItem.EventName, i)}: ", $"{notifyItem.State[i].ToJson()}"); + var ncount = (uint)notifyItem.State.Length; + for (var i = 0; i < ncount; i++) + ConsoleHelper.Info($" {GetMethodParameterName(notifyItem.ScriptHash, notifyItem.EventName, ncount, i)}: ", $"{notifyItem.State[i].ToJson()}"); } } if (Settings.Default.Debug) @@ -300,18 +302,21 @@ private void PrintEventModelToConsole(IReadOnlyCollection<(BlockchainEventModel ConsoleHelper.Info(); ConsoleHelper.Info(" Event Name: ", $"{notifyItem.EventName}"); ConsoleHelper.Info(" State Parameters:"); - for (int i = 0; i < notifyItem.State.Length; i++) - ConsoleHelper.Info($" {GetMethodParameterName(notifyItem.ScriptHash, notifyItem.EventName, i)}: ", $"{notifyItem.State[i].ToJson()}"); + var ncount = (uint)notifyItem.State.Length; + for (var i = 0; i < ncount; i++) + ConsoleHelper.Info($" {GetMethodParameterName(notifyItem.ScriptHash, notifyItem.EventName, ncount, i)}: ", $"{notifyItem.State[i].ToJson()}"); ConsoleHelper.Info("--------------------------------"); } } - private string GetMethodParameterName(UInt160 scriptHash, string methodName, int parameterIndex) + private string GetMethodParameterName(UInt160 scriptHash, string methodName, uint ncount, int parameterIndex) { var contract = NativeContract.ContractManagement.GetContract(_neosystem.StoreView, scriptHash); if (contract == null) return $"{parameterIndex}"; - var contractEvent = contract.Manifest.Abi.Events.SingleOrDefault(s => s.Name == methodName); + var contractEvent = contract.Manifest.Abi.Events.SingleOrDefault(s => s.Name == methodName && (uint)s.Parameters.Length == ncount); + if (contractEvent == null) + return $"{parameterIndex}"; return contractEvent.Parameters[parameterIndex].Name; } diff --git a/src/Plugins/ApplicationLogs/Settings.cs b/src/Plugins/ApplicationLogs/Settings.cs index 8f2a0da1e1..6a5f238272 100644 --- a/src/Plugins/ApplicationLogs/Settings.cs +++ b/src/Plugins/ApplicationLogs/Settings.cs @@ -13,7 +13,7 @@ namespace Neo.Plugins.ApplicationLogs { - internal class Settings + internal class Settings : PluginSettings { public string Path { get; } public uint Network { get; } @@ -23,7 +23,7 @@ internal class Settings public static Settings Default { get; private set; } - private Settings(IConfigurationSection section) + private Settings(IConfigurationSection section) : base(section) { Path = section.GetValue("Path", "ApplicationLogs_{0}"); Network = section.GetValue("Network", 5195086u); diff --git a/src/Plugins/ApplicationLogs/Store/LogStorageStore.cs b/src/Plugins/ApplicationLogs/Store/LogStorageStore.cs index 249c5d3bd1..147a80034b 100644 --- a/src/Plugins/ApplicationLogs/Store/LogStorageStore.cs +++ b/src/Plugins/ApplicationLogs/Store/LogStorageStore.cs @@ -159,7 +159,7 @@ public Guid PutStackItemState(StackItem stackItem) { _snapshot.Put(key, BinarySerializer.Serialize(stackItem, ExecutionEngineLimits.Default with { MaxItemSize = (uint)Settings.Default.MaxStackSize })); } - catch (NotSupportedException) + catch { _snapshot.Put(key, BinarySerializer.Serialize(StackItem.Null, ExecutionEngineLimits.Default with { MaxItemSize = (uint)Settings.Default.MaxStackSize })); } diff --git a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs index ee3b8a7747..f68f004c3c 100644 --- a/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs +++ b/src/Plugins/DBFTPlugin/Consensus/ConsensusContext.MakePayload.cs @@ -166,11 +166,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.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/DBFTPlugin.cs b/src/Plugins/DBFTPlugin/DBFTPlugin.cs index 9ca44adc74..65fc5011dc 100644 --- a/src/Plugins/DBFTPlugin/DBFTPlugin.cs +++ b/src/Plugins/DBFTPlugin/DBFTPlugin.cs @@ -11,15 +11,15 @@ using Akka.Actor; using Neo.ConsoleService; +using Neo.IEventHandlers; using Neo.Network.P2P; using Neo.Network.P2P.Payloads; -using Neo.Plugins; using Neo.Plugins.DBFTPlugin.Consensus; using Neo.Wallets; namespace Neo.Plugins.DBFTPlugin { - public class DBFTPlugin : Plugin + public class DBFTPlugin : Plugin, IServiceAddedHandler, IMessageReceivedHandler, IWalletChangedHandler { private IWalletProvider walletProvider; private IActorRef consensus; @@ -31,9 +31,11 @@ public class DBFTPlugin : Plugin public override string ConfigFile => System.IO.Path.Combine(RootPath, "DBFTPlugin.json"); + protected override UnhandledExceptionPolicy ExceptionPolicy => settings.ExceptionPolicy; + public DBFTPlugin() { - RemoteNode.MessageReceived += RemoteNode_MessageReceived; + RemoteNode.MessageReceived += ((IMessageReceivedHandler)this).RemoteNode_MessageReceived_Handler; } public DBFTPlugin(Settings settings) : this() @@ -43,7 +45,7 @@ public DBFTPlugin(Settings settings) : this() public override void Dispose() { - RemoteNode.MessageReceived -= RemoteNode_MessageReceived; + RemoteNode.MessageReceived -= ((IMessageReceivedHandler)this).RemoteNode_MessageReceived_Handler; } protected override void Configure() @@ -55,23 +57,23 @@ protected override void OnSystemLoaded(NeoSystem system) { if (system.Settings.Network != settings.Network) return; neoSystem = system; - neoSystem.ServiceAdded += NeoSystem_ServiceAdded; + neoSystem.ServiceAdded += ((IServiceAddedHandler)this).NeoSystem_ServiceAdded_Handler; } - private void NeoSystem_ServiceAdded(object sender, object service) + void IServiceAddedHandler.NeoSystem_ServiceAdded_Handler(object sender, object service) { if (service is not IWalletProvider provider) return; walletProvider = provider; - neoSystem.ServiceAdded -= NeoSystem_ServiceAdded; + neoSystem.ServiceAdded -= ((IServiceAddedHandler)this).NeoSystem_ServiceAdded_Handler; if (settings.AutoStart) { - walletProvider.WalletChanged += WalletProvider_WalletChanged; + walletProvider.WalletChanged += ((IWalletChangedHandler)this).IWalletProvider_WalletChanged_Handler; } } - private void WalletProvider_WalletChanged(object sender, Wallet wallet) + void IWalletChangedHandler.IWalletProvider_WalletChanged_Handler(object sender, Wallet wallet) { - walletProvider.WalletChanged -= WalletProvider_WalletChanged; + walletProvider.WalletChanged -= ((IWalletChangedHandler)this).IWalletProvider_WalletChanged_Handler; Start(wallet); } @@ -89,7 +91,7 @@ public void Start(Wallet wallet) consensus.Tell(new ConsensusService.Start()); } - private bool RemoteNode_MessageReceived(NeoSystem system, Message message) + bool IMessageReceivedHandler.RemoteNode_MessageReceived_Handler(NeoSystem system, Message message) { if (message.Command == MessageCommand.Transaction) { diff --git a/src/Plugins/DBFTPlugin/DBFTPlugin.csproj b/src/Plugins/DBFTPlugin/DBFTPlugin.csproj index 68595be53a..93c77ad1f8 100644 --- a/src/Plugins/DBFTPlugin/DBFTPlugin.csproj +++ b/src/Plugins/DBFTPlugin/DBFTPlugin.csproj @@ -4,7 +4,7 @@ net8.0 Neo.Consensus.DBFT Neo.Consensus - $(SolutionDir)/bin/$(PackageId) + ../../../bin/$(PackageId) diff --git a/src/Plugins/DBFTPlugin/DBFTPlugin.json b/src/Plugins/DBFTPlugin/DBFTPlugin.json index 2e2b710ba3..705b2b77cb 100644 --- a/src/Plugins/DBFTPlugin/DBFTPlugin.json +++ b/src/Plugins/DBFTPlugin/DBFTPlugin.json @@ -5,6 +5,7 @@ "AutoStart": false, "Network": 860833102, "MaxBlockSize": 2097152, - "MaxBlockSystemFee": 150000000000 + "MaxBlockSystemFee": 150000000000, + "UnhandledExceptionPolicy": "StopNode" } } diff --git a/src/Plugins/DBFTPlugin/Settings.cs b/src/Plugins/DBFTPlugin/Settings.cs index 28ad21f37a..1f37feaf16 100644 --- a/src/Plugins/DBFTPlugin/Settings.cs +++ b/src/Plugins/DBFTPlugin/Settings.cs @@ -13,7 +13,7 @@ namespace Neo.Plugins.DBFTPlugin { - public class Settings + public class Settings : PluginSettings { public string RecoveryLogs { get; } public bool IgnoreRecoveryLogs { get; } @@ -22,7 +22,7 @@ public class Settings public uint MaxBlockSize { get; } public long MaxBlockSystemFee { get; } - public Settings(IConfigurationSection section) + public Settings(IConfigurationSection section) : base(section) { RecoveryLogs = section.GetValue("RecoveryLogs", "ConsensusState"); IgnoreRecoveryLogs = section.GetValue("IgnoreRecoveryLogs", false); diff --git a/src/Plugins/Directory.Build.props b/src/Plugins/Directory.Build.props index 72e96f0300..ecd9a2735f 100644 --- a/src/Plugins/Directory.Build.props +++ b/src/Plugins/Directory.Build.props @@ -3,11 +3,6 @@ - - - - - diff --git a/src/Plugins/LevelDBStore/LevelDBStore.csproj b/src/Plugins/LevelDBStore/LevelDBStore.csproj index ef605a7afe..23cf469640 100644 --- a/src/Plugins/LevelDBStore/LevelDBStore.csproj +++ b/src/Plugins/LevelDBStore/LevelDBStore.csproj @@ -5,7 +5,7 @@ Neo.Plugins.Storage.LevelDBStore Neo.Plugins.Storage true - $(SolutionDir)/bin/$(PackageId) + ../../../bin/$(PackageId) 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/MPTTrie/MPTTrie.csproj b/src/Plugins/MPTTrie/MPTTrie.csproj index ef9e45cc51..3134f7ae5b 100644 --- a/src/Plugins/MPTTrie/MPTTrie.csproj +++ b/src/Plugins/MPTTrie/MPTTrie.csproj @@ -5,7 +5,7 @@ Neo.Cryptography.MPT Neo.Cryptography true - $(SolutionDir)/bin/$(PackageId) + ../../../bin/$(PackageId) diff --git a/src/Plugins/OracleService/OracleService.cs b/src/Plugins/OracleService/OracleService.cs index e9787b3d4c..27ced4e04c 100644 --- a/src/Plugins/OracleService/OracleService.cs +++ b/src/Plugins/OracleService/OracleService.cs @@ -14,6 +14,8 @@ using Neo.ConsoleService; using Neo.Cryptography; using Neo.Cryptography.ECC; +using Neo.Extensions; +using Neo.IEventHandlers; using Neo.IO; using Neo.Json; using Neo.Ledger; @@ -37,7 +39,7 @@ namespace Neo.Plugins.OracleService { - public class OracleService : Plugin + public class OracleService : Plugin, ICommittingHandler, IServiceAddedHandler, IWalletChangedHandler { private const int RefreshIntervalMilliSeconds = 1000 * 60 * 3; @@ -61,11 +63,13 @@ public class OracleService : Plugin public override string Description => "Built-in oracle plugin"; + protected override UnhandledExceptionPolicy ExceptionPolicy => Settings.Default.ExceptionPolicy; + public override string ConfigFile => System.IO.Path.Combine(RootPath, "OracleService.json"); public OracleService() { - Blockchain.Committing += OnCommitting; + Blockchain.Committing += ((ICommittingHandler)this).Blockchain_Committing_Handler; } protected override void Configure() @@ -79,32 +83,33 @@ protected override void OnSystemLoaded(NeoSystem system) { if (system.Settings.Network != Settings.Default.Network) return; _system = system; - _system.ServiceAdded += NeoSystem_ServiceAdded; + _system.ServiceAdded += ((IServiceAddedHandler)this).NeoSystem_ServiceAdded_Handler; RpcServerPlugin.RegisterMethods(this, Settings.Default.Network); } - private void NeoSystem_ServiceAdded(object sender, object service) + + void IServiceAddedHandler.NeoSystem_ServiceAdded_Handler(object sender, object service) { if (service is IWalletProvider) { walletProvider = service as IWalletProvider; - _system.ServiceAdded -= NeoSystem_ServiceAdded; + _system.ServiceAdded -= ((IServiceAddedHandler)this).NeoSystem_ServiceAdded_Handler; if (Settings.Default.AutoStart) { - walletProvider.WalletChanged += WalletProvider_WalletChanged; + walletProvider.WalletChanged += ((IWalletChangedHandler)this).IWalletProvider_WalletChanged_Handler; } } } - private void WalletProvider_WalletChanged(object sender, Wallet wallet) + void IWalletChangedHandler.IWalletProvider_WalletChanged_Handler(object sender, Wallet wallet) { - walletProvider.WalletChanged -= WalletProvider_WalletChanged; + walletProvider.WalletChanged -= ((IWalletChangedHandler)this).IWalletProvider_WalletChanged_Handler; Start(wallet); } public override void Dispose() { - Blockchain.Committing -= OnCommitting; + Blockchain.Committing -= ((ICommittingHandler)this).Blockchain_Committing_Handler; OnStop(); while (status != OracleStatus.Stopped) Thread.Sleep(100); @@ -166,7 +171,7 @@ private void OnShow() ConsoleHelper.Info($"Oracle status: ", $"{status}"); } - private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + void ICommittingHandler.Blockchain_Committing_Handler(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) { if (system.Settings.Network != Settings.Default.Network) return; @@ -232,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); } @@ -260,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,7 +329,7 @@ private async void 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)) @@ -427,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; @@ -528,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(); diff --git a/src/Plugins/OracleService/OracleService.csproj b/src/Plugins/OracleService/OracleService.csproj index 1fa5c5602f..bb5cde6754 100644 --- a/src/Plugins/OracleService/OracleService.csproj +++ b/src/Plugins/OracleService/OracleService.csproj @@ -3,7 +3,7 @@ net8.0 Neo.Plugins.OracleService - $(SolutionDir)/bin/$(PackageId) + ../../../bin/$(PackageId) diff --git a/src/Plugins/OracleService/OracleService.json b/src/Plugins/OracleService/OracleService.json index 1ab0d93399..49bf1153b3 100644 --- a/src/Plugins/OracleService/OracleService.json +++ b/src/Plugins/OracleService/OracleService.json @@ -6,6 +6,7 @@ "MaxOracleTimeout": 10000, "AllowPrivateHost": false, "AllowedContentTypes": [ "application/json" ], + "UnhandledExceptionPolicy": "Ignore", "Https": { "Timeout": 5000 }, diff --git a/src/Plugins/OracleService/Settings.cs b/src/Plugins/OracleService/Settings.cs index 952ea0c27b..db93c1c400 100644 --- a/src/Plugins/OracleService/Settings.cs +++ b/src/Plugins/OracleService/Settings.cs @@ -37,7 +37,7 @@ public NeoFSSettings(IConfigurationSection section) } } - class Settings + class Settings : PluginSettings { public uint Network { get; } public Uri[] Nodes { get; } @@ -51,7 +51,7 @@ class Settings public static Settings Default { get; private set; } - private Settings(IConfigurationSection section) + private Settings(IConfigurationSection section) : base(section) { Network = section.GetValue("Network", 5195086u); Nodes = section.GetSection("Nodes").GetChildren().Select(p => new Uri(p.Get(), UriKind.Absolute)).ToArray(); diff --git a/src/Plugins/RocksDBStore/RocksDBStore.csproj b/src/Plugins/RocksDBStore/RocksDBStore.csproj index 441b17306e..90ed2e841a 100644 --- a/src/Plugins/RocksDBStore/RocksDBStore.csproj +++ b/src/Plugins/RocksDBStore/RocksDBStore.csproj @@ -4,11 +4,11 @@ net8.0 Neo.Plugins.Storage.RocksDBStore Neo.Plugins.Storage - $(SolutionDir)/bin/$(PackageId) + ../../../bin/$(PackageId) - + 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.csproj b/src/Plugins/RpcClient/RpcClient.csproj index c43c71ef8a..bc6161e3cb 100644 --- a/src/Plugins/RpcClient/RpcClient.csproj +++ b/src/Plugins/RpcClient/RpcClient.csproj @@ -4,7 +4,7 @@ net8.0 Neo.Network.RPC.RpcClient Neo.Network.RPC - $(SolutionDir)/bin/$(PackageId) + ../../../bin/$(PackageId) 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/RpcServer.Blockchain.cs b/src/Plugins/RpcServer/RpcServer.Blockchain.cs index 1c8eecb99f..8f73fa3c78 100644 --- a/src/Plugins/RpcServer/RpcServer.Blockchain.cs +++ b/src/Plugins/RpcServer/RpcServer.Blockchain.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; @@ -24,18 +25,32 @@ namespace Neo.Plugins.RpcServer { partial class RpcServer { + /// + /// Gets the hash of the best (most recent) block. + /// + /// An empty array; no parameters are required. + /// The hash of the best block as a . [RpcMethod] - protected virtual JToken GetBestBlockHash(JArray _params) + protected internal virtual JToken GetBestBlockHash(JArray _params) { return NativeContract.Ledger.CurrentHash(system.StoreView).ToString(); } + /// + /// Gets a block by its hash or index. + /// + /// + /// An array containing the block hash or index as the first element, + /// and an optional boolean indicating whether to return verbose information. + /// + /// The block data as a . If the second item of _params is true, then + /// block data is json format, otherwise, the return type is Base64-encoded byte array. [RpcMethod] - protected virtual JToken GetBlock(JArray _params) + protected internal virtual JToken GetBlock(JArray _params) { JToken key = Result.Ok_Or(() => _params[0], RpcError.InvalidParams.WithData($"Invalid Block Hash or Index: {_params[0]}")); bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); - using var snapshot = system.GetSnapshot(); + using var snapshot = system.GetSnapshotCache(); Block block; if (key is JNumber) { @@ -44,7 +59,7 @@ protected virtual JToken GetBlock(JArray _params) } else { - UInt256 hash = UInt256.Parse(key.AsString()); + UInt256 hash = Result.Ok_Or(() => UInt256.Parse(key.AsString()), RpcError.InvalidParams.WithData($"Invalid block hash {_params[0]}")); block = NativeContract.Ledger.GetBlock(snapshot, hash); } block.NotNull_Or(RpcError.UnknownBlock); @@ -60,20 +75,35 @@ protected virtual JToken GetBlock(JArray _params) return Convert.ToBase64String(block.ToArray()); } + /// + /// Gets the number of block headers in the blockchain. + /// + /// An empty array; no parameters are required. + /// The count of block headers as a . [RpcMethod] internal virtual JToken GetBlockHeaderCount(JArray _params) { return (system.HeaderCache.Last?.Index ?? NativeContract.Ledger.CurrentIndex(system.StoreView)) + 1; } + /// + /// Gets the number of blocks in the blockchain. + /// + /// An empty array; no parameters are required. + /// The count of blocks as a . [RpcMethod] - protected virtual JToken GetBlockCount(JArray _params) + protected internal virtual JToken GetBlockCount(JArray _params) { return NativeContract.Ledger.CurrentIndex(system.StoreView) + 1; } + /// + /// Gets the hash of the block at the specified height. + /// + /// An array containing the block height as the first element. + /// The hash of the block at the specified height as a . [RpcMethod] - protected virtual JToken GetBlockHash(JArray _params) + protected internal virtual JToken GetBlockHash(JArray _params) { uint height = Result.Ok_Or(() => uint.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Height: {_params[0]}")); var snapshot = system.StoreView; @@ -84,8 +114,16 @@ protected virtual JToken GetBlockHash(JArray _params) throw new RpcException(RpcError.UnknownHeight); } + /// + /// Gets a block header by its hash or index. + /// + /// + /// An array containing the block header hash or index as the first element, + /// and an optional boolean indicating whether to return verbose information. + /// + /// The block header data as a . In json format if the second item of _params is true, otherwise Base64-encoded byte array. [RpcMethod] - protected virtual JToken GetBlockHeader(JArray _params) + protected internal virtual JToken GetBlockHeader(JArray _params) { JToken key = _params[0]; bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); @@ -98,7 +136,7 @@ protected virtual JToken GetBlockHeader(JArray _params) } else { - UInt256 hash = UInt256.Parse(key.AsString()); + 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); } if (verbose) @@ -114,8 +152,13 @@ protected virtual JToken GetBlockHeader(JArray _params) return Convert.ToBase64String(header.ToArray()); } + /// + /// Gets the state of a contract by its ID or script hash or (only for native contracts) by case-insensitive name. + /// + /// An array containing the contract ID or script hash or case-insensitive native contract name as the first element. + /// The contract state in json format as a . [RpcMethod] - protected virtual JToken GetContractState(JArray _params) + protected internal virtual JToken GetContractState(JArray _params) { if (int.TryParse(_params[0].AsString(), out int contractId)) { @@ -123,7 +166,7 @@ protected virtual JToken GetContractState(JArray _params) return contractState.NotNull_Or(RpcError.UnknownContract).ToJson(); } - var scriptHash = ToScriptHash(_params[0].AsString()); + 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); return contract.NotNull_Or(RpcError.UnknownContract).ToJson(); } @@ -139,8 +182,13 @@ private static UInt160 ToScriptHash(string keyword) return UInt160.Parse(keyword); } + /// + /// Gets the current memory pool transactions. + /// + /// An array containing an optional boolean indicating whether to include unverified transactions. + /// The memory pool transactions in json format as a . [RpcMethod] - protected virtual JToken GetRawMemPool(JArray _params) + protected internal virtual JToken GetRawMemPool(JArray _params) { bool shouldGetUnverified = _params.Count >= 1 && _params[0].AsBoolean(); if (!shouldGetUnverified) @@ -156,8 +204,16 @@ protected virtual JToken GetRawMemPool(JArray _params) return json; } + /// + /// Gets a transaction by its hash. + /// + /// + /// An array containing the transaction hash as the first element, + /// and an optional boolean indicating whether to return verbose information. + /// + /// The transaction data as a . In json format if the second item of _params is true, otherwise base64string. [RpcMethod] - protected virtual JToken GetRawTransaction(JArray _params) + protected internal virtual JToken GetRawTransaction(JArray _params) { UInt256 hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Transaction Hash: {_params[0]}")); bool verbose = _params.Count >= 2 && _params[1].AsBoolean(); @@ -179,13 +235,21 @@ protected virtual JToken GetRawTransaction(JArray _params) return json; } + /// + /// Gets the storage item by contract ID or script hash and key. + /// + /// + /// An array containing the contract ID or script hash as the first element, + /// and the storage key as the second element. + /// + /// The storage item as a . [RpcMethod] - protected virtual JToken GetStorage(JArray _params) + protected internal virtual JToken GetStorage(JArray _params) { - using var snapshot = system.GetSnapshot(); + using var snapshot = system.GetSnapshotCache(); if (!int.TryParse(_params[0].AsString(), out int id)) { - UInt160 hash = UInt160.Parse(_params[0].AsString()); + 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); id = contract.Id; } @@ -198,18 +262,27 @@ protected virtual JToken GetStorage(JArray _params) return Convert.ToBase64String(item.Value.Span); } + /// + /// Finds storage items by contract ID or script hash and prefix. + /// + /// + /// An array containing the contract ID or script hash as the first element, + /// the Base64-encoded storage key prefix as the second element, + /// and an optional start index as the third element. + /// + /// The found storage items as a . [RpcMethod] - protected virtual JToken FindStorage(JArray _params) + protected internal virtual JToken FindStorage(JArray _params) { - using var snapshot = system.GetSnapshot(); + using var snapshot = system.GetSnapshotCache(); if (!int.TryParse(_params[0].AsString(), out int id)) { - UInt160 hash = UInt160.Parse(_params[0].AsString()); + 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); id = contract.Id; } - byte[] prefix = Convert.FromBase64String(_params[1].AsString()); + 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)) @@ -247,8 +320,13 @@ protected virtual JToken FindStorage(JArray _params) return json; } + /// + /// Gets the height of a transaction by its hash. + /// + /// An array containing the transaction hash as the first element. + /// The height of the transaction as a . [RpcMethod] - protected virtual JToken GetTransactionHeight(JArray _params) + protected internal virtual JToken GetTransactionHeight(JArray _params) { UInt256 hash = Result.Ok_Or(() => UInt256.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid Transaction Hash: {_params[0]}")); uint? height = NativeContract.Ledger.GetTransactionState(system.StoreView, hash)?.BlockIndex; @@ -256,10 +334,15 @@ protected virtual JToken GetTransactionHeight(JArray _params) throw new RpcException(RpcError.UnknownTransaction); } + /// + /// Gets the next block validators. + /// + /// An empty array; no parameters are required. + /// The next block validators as a . [RpcMethod] - protected virtual JToken GetNextBlockValidators(JArray _params) + protected internal virtual JToken GetNextBlockValidators(JArray _params) { - using var snapshot = system.GetSnapshot(); + using var snapshot = system.GetSnapshotCache(); var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, system.Settings.ValidatorsCount); return validators.Select(p => { @@ -270,10 +353,15 @@ protected virtual JToken GetNextBlockValidators(JArray _params) }).ToArray(); } + /// + /// Gets the list of candidates for the next block validators. + /// + /// An empty array; no parameters are required. + /// The candidates public key list as a JToken. [RpcMethod] - protected virtual JToken GetCandidates(JArray _params) + protected internal virtual JToken GetCandidates(JArray _params) { - using var snapshot = system.GetSnapshot(); + using var snapshot = system.GetSnapshotCache(); byte[] script; using (ScriptBuilder sb = new()) { @@ -322,14 +410,24 @@ protected virtual JToken GetCandidates(JArray _params) return json; } + /// + /// Gets the list of committee members. + /// + /// An empty array; no parameters are required. + /// The committee members publickeys as a . [RpcMethod] - protected virtual JToken GetCommittee(JArray _params) + protected internal virtual JToken GetCommittee(JArray _params) { return new JArray(NativeContract.NEO.GetCommittee(system.StoreView).Select(p => (JToken)p.ToString())); } + /// + /// Gets the list of native contracts. + /// + /// An empty array; no parameters are required. + /// The native contract states as a . [RpcMethod] - protected virtual JToken GetNativeContracts(JArray _params) + protected internal virtual JToken GetNativeContracts(JArray _params) { return new JArray(NativeContract.Contracts.Select(p => NativeContract.ContractManagement.GetContract(system.StoreView, p.Hash).ToJson())); } diff --git a/src/Plugins/RpcServer/RpcServer.Node.cs b/src/Plugins/RpcServer/RpcServer.Node.cs index 79a8884a0f..716125e2cf 100644 --- a/src/Plugins/RpcServer/RpcServer.Node.cs +++ b/src/Plugins/RpcServer/RpcServer.Node.cs @@ -110,7 +110,7 @@ private static JObject GetRelayResult(VerifyResult reason, UInt256 hash) } [RpcMethod] - protected virtual JToken GetVersion(JArray _params) + protected internal virtual JToken GetVersion(JArray _params) { JObject json = new(); json["tcpport"] = localNode.ListenerTcpPort; @@ -139,6 +139,8 @@ 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; @@ -150,7 +152,7 @@ private static string StripPrefix(string s, string prefix) } [RpcMethod] - protected virtual JToken SendRawTransaction(JArray _params) + protected internal virtual JToken SendRawTransaction(JArray _params) { Transaction tx = Result.Ok_Or(() => Convert.FromBase64String(_params[0].AsString()).AsSerializable(), RpcError.InvalidParams.WithData($"Invalid Transaction Format: {_params[0]}")); RelayResult reason = system.Blockchain.Ask(tx).Result; @@ -158,7 +160,7 @@ protected virtual JToken SendRawTransaction(JArray _params) } [RpcMethod] - protected virtual JToken SubmitBlock(JArray _params) + protected internal virtual JToken SubmitBlock(JArray _params) { Block block = Result.Ok_Or(() => Convert.FromBase64String(_params[0].AsString()).AsSerializable(), RpcError.InvalidParams.WithData($"Invalid Block Format: {_params[0]}")); RelayResult reason = system.Blockchain.Ask(block).Result; diff --git a/src/Plugins/RpcServer/RpcServer.SmartContract.cs b/src/Plugins/RpcServer/RpcServer.SmartContract.cs index 2fe49d4965..70edc4cedb 100644 --- a/src/Plugins/RpcServer/RpcServer.SmartContract.cs +++ b/src/Plugins/RpcServer/RpcServer.SmartContract.cs @@ -92,7 +92,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(); @@ -213,7 +213,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 +231,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 +241,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 +262,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 +278,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.Wallet.cs b/src/Plugins/RpcServer/RpcServer.Wallet.cs index f1adb277ce..50f3c7a1e0 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,23 +101,35 @@ 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 = UInt160.Parse(_params[0].AsString()); + UInt160 asset_id = Result.Ok_Or(() => UInt160.Parse(_params[0].AsString()), RpcError.InvalidParams.WithData($"Invalid asset id: {_params[0]}")); JObject json = new(); json["balance"] = wallet.GetAvailable(system.StoreView, asset_id).Value.ToString(); 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 = UInt160.Parse(_params[0].AsString()); + 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,9 +482,17 @@ 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)); (md.ReturnType == ContractParameterType.Boolean).True_Or(RpcErrorFactory.InvalidContractVerification("The verify method doesn't return boolean value.")); @@ -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.csproj b/src/Plugins/RpcServer/RpcServer.csproj index 3fb7a7bb9a..c5b72e7a61 100644 --- a/src/Plugins/RpcServer/RpcServer.csproj +++ b/src/Plugins/RpcServer/RpcServer.csproj @@ -3,7 +3,7 @@ net8.0 Neo.Plugins.RpcServer - $(SolutionDir)/bin/$(PackageId) + ../../../bin/$(PackageId) diff --git a/src/Plugins/RpcServer/RpcServer.json b/src/Plugins/RpcServer/RpcServer.json index 8f6905dead..dc9c25b8da 100644 --- a/src/Plugins/RpcServer/RpcServer.json +++ b/src/Plugins/RpcServer/RpcServer.json @@ -1,5 +1,6 @@ { "PluginConfiguration": { + "UnhandledExceptionPolicy": "Ignore", "Servers": [ { "Network": 860833102, diff --git a/src/Plugins/RpcServer/RpcServerPlugin.cs b/src/Plugins/RpcServer/RpcServerPlugin.cs index c22462d139..03416c1be5 100644 --- a/src/Plugins/RpcServer/RpcServerPlugin.cs +++ b/src/Plugins/RpcServer/RpcServerPlugin.cs @@ -24,6 +24,7 @@ public class RpcServerPlugin : Plugin private static readonly Dictionary> handlers = new(); public override string ConfigFile => System.IO.Path.Combine(RootPath, "RpcServer.json"); + protected override UnhandledExceptionPolicy ExceptionPolicy => settings.ExceptionPolicy; protected override void Configure() { diff --git a/src/Plugins/RpcServer/Session.cs b/src/Plugins/RpcServer/Session.cs index 1dd8808dde..60be3e19b1 100644 --- a/src/Plugins/RpcServer/Session.cs +++ b/src/Plugins/RpcServer/Session.cs @@ -29,7 +29,7 @@ class Session : IDisposable public Session(NeoSystem system, byte[] script, Signer[] signers, Witness[] witnesses, long datoshi, Diagnostic diagnostic) { Random random = new(); - Snapshot = system.GetSnapshot(); + Snapshot = system.GetSnapshotCache(); Transaction tx = signers == null ? null : new Transaction { Version = 0, diff --git a/src/Plugins/RpcServer/Settings.cs b/src/Plugins/RpcServer/Settings.cs index ad624d9082..2cf7b72fb8 100644 --- a/src/Plugins/RpcServer/Settings.cs +++ b/src/Plugins/RpcServer/Settings.cs @@ -18,11 +18,11 @@ namespace Neo.Plugins.RpcServer { - class Settings + class Settings : PluginSettings { public IReadOnlyList Servers { get; init; } - public Settings(IConfigurationSection section) + public Settings(IConfigurationSection section) : base(section) { Servers = section.GetSection(nameof(Servers)).GetChildren().Select(p => RpcServerSettings.Load(p)).ToArray(); } diff --git a/src/Plugins/SQLiteWallet/SQLiteWallet.csproj b/src/Plugins/SQLiteWallet/SQLiteWallet.csproj index 4c9029040f..b8a1646e65 100644 --- a/src/Plugins/SQLiteWallet/SQLiteWallet.csproj +++ b/src/Plugins/SQLiteWallet/SQLiteWallet.csproj @@ -5,11 +5,11 @@ Neo.Wallets.SQLite Neo.Wallets.SQLite enable - $(SolutionDir)/bin/$(PackageId) + ../../../bin/$(PackageId) - + diff --git a/src/Plugins/StateService/Settings.cs b/src/Plugins/StateService/Settings.cs index 8557866bc1..a425b57d7e 100644 --- a/src/Plugins/StateService/Settings.cs +++ b/src/Plugins/StateService/Settings.cs @@ -13,7 +13,7 @@ namespace Neo.Plugins.StateService { - internal class Settings + internal class Settings : PluginSettings { public string Path { get; } public bool FullState { get; } @@ -23,7 +23,7 @@ internal class Settings public static Settings Default { get; private set; } - private Settings(IConfigurationSection section) + private Settings(IConfigurationSection section) : base(section) { Path = section.GetValue("Path", "Data_MPT_{0}"); FullState = section.GetValue("FullState", false); diff --git a/src/Plugins/StateService/StatePlugin.cs b/src/Plugins/StateService/StatePlugin.cs index 1c28e92193..03dcc55aac 100644 --- a/src/Plugins/StateService/StatePlugin.cs +++ b/src/Plugins/StateService/StatePlugin.cs @@ -12,6 +12,7 @@ using Akka.Actor; using Neo.ConsoleService; using Neo.Cryptography.MPTTrie; +using Neo.IEventHandlers; using Neo.IO; using Neo.Json; using Neo.Ledger; @@ -33,13 +34,15 @@ namespace Neo.Plugins.StateService { - public class StatePlugin : Plugin + public class StatePlugin : Plugin, ICommittingHandler, ICommittedHandler, IWalletChangedHandler, IServiceAddedHandler { public const string StatePayloadCategory = "StateService"; public override string Name => "StateService"; public override string Description => "Enables MPT for the node"; public override string ConfigFile => System.IO.Path.Combine(RootPath, "StateService.json"); + protected override UnhandledExceptionPolicy ExceptionPolicy => Settings.Default.ExceptionPolicy; + internal IActorRef Store; internal IActorRef Verifier; @@ -48,8 +51,8 @@ public class StatePlugin : Plugin public StatePlugin() { - Blockchain.Committing += OnCommitting; - Blockchain.Committed += OnCommitted; + Blockchain.Committing += ((ICommittingHandler)this).Blockchain_Committing_Handler; + Blockchain.Committed += ((ICommittedHandler)this).Blockchain_Committed_Handler; } protected override void Configure() @@ -62,45 +65,45 @@ protected override void OnSystemLoaded(NeoSystem system) if (system.Settings.Network != Settings.Default.Network) return; _system = system; Store = _system.ActorSystem.ActorOf(StateStore.Props(this, string.Format(Settings.Default.Path, system.Settings.Network.ToString("X8")))); - _system.ServiceAdded += NeoSystem_ServiceAdded; + _system.ServiceAdded += ((IServiceAddedHandler)this).NeoSystem_ServiceAdded_Handler; RpcServerPlugin.RegisterMethods(this, Settings.Default.Network); } - private void NeoSystem_ServiceAdded(object sender, object service) + void IServiceAddedHandler.NeoSystem_ServiceAdded_Handler(object sender, object service) { if (service is IWalletProvider) { walletProvider = service as IWalletProvider; - _system.ServiceAdded -= NeoSystem_ServiceAdded; + _system.ServiceAdded -= ((IServiceAddedHandler)this).NeoSystem_ServiceAdded_Handler; if (Settings.Default.AutoVerify) { - walletProvider.WalletChanged += WalletProvider_WalletChanged; + walletProvider.WalletChanged += ((IWalletChangedHandler)this).IWalletProvider_WalletChanged_Handler; } } } - private void WalletProvider_WalletChanged(object sender, Wallet wallet) + void IWalletChangedHandler.IWalletProvider_WalletChanged_Handler(object sender, Wallet wallet) { - walletProvider.WalletChanged -= WalletProvider_WalletChanged; + walletProvider.WalletChanged -= ((IWalletChangedHandler)this).IWalletProvider_WalletChanged_Handler; Start(wallet); } public override void Dispose() { base.Dispose(); - Blockchain.Committing -= OnCommitting; - Blockchain.Committed -= OnCommitted; + Blockchain.Committing -= ((ICommittingHandler)this).Blockchain_Committing_Handler; + Blockchain.Committed -= ((ICommittedHandler)this).Blockchain_Committed_Handler; if (Store is not null) _system.EnsureStopped(Store); if (Verifier is not null) _system.EnsureStopped(Verifier); } - private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + void ICommittingHandler.Blockchain_Committing_Handler(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) { if (system.Settings.Network != Settings.Default.Network) return; StateStore.Singleton.UpdateLocalStateRootSnapshot(block.Index, snapshot.GetChangeSet().Where(p => p.State != TrackState.None).Where(p => p.Key.Id != NativeContract.Ledger.Id).ToList()); } - private void OnCommitted(NeoSystem system, Block block) + void ICommittedHandler.Blockchain_Committed_Handler(NeoSystem system, Block block) { if (system.Settings.Network != Settings.Default.Network) return; StateStore.Singleton.UpdateLocalStateRoot(block.Index); diff --git a/src/Plugins/StateService/StateService.csproj b/src/Plugins/StateService/StateService.csproj index 2bf85f1465..58c18ac498 100644 --- a/src/Plugins/StateService/StateService.csproj +++ b/src/Plugins/StateService/StateService.csproj @@ -4,7 +4,7 @@ net8.0 Neo.Plugins.StateService true - $(SolutionDir)/bin/$(PackageId) + ../../../bin/$(PackageId) diff --git a/src/Plugins/StateService/StateService.json b/src/Plugins/StateService/StateService.json index 265436fc30..cadd2da5fd 100644 --- a/src/Plugins/StateService/StateService.json +++ b/src/Plugins/StateService/StateService.json @@ -4,7 +4,8 @@ "FullState": false, "Network": 860833102, "AutoVerify": false, - "MaxFindResultItems": 100 + "MaxFindResultItems": 100, + "UnhandledExceptionPolicy": "StopPlugin" }, "Dependency": [ "RpcServer" diff --git a/src/Plugins/StorageDumper/Settings.cs b/src/Plugins/StorageDumper/Settings.cs index c2761ce6b9..e645cd7074 100644 --- a/src/Plugins/StorageDumper/Settings.cs +++ b/src/Plugins/StorageDumper/Settings.cs @@ -14,7 +14,7 @@ namespace Neo.Plugins.StorageDumper { - internal class Settings + internal class Settings : PluginSettings { /// /// Amount of storages states (heights) to be dump in a given json file @@ -32,7 +32,7 @@ internal class Settings public static Settings? Default { get; private set; } - private Settings(IConfigurationSection section) + private Settings(IConfigurationSection section) : base(section) { // Geting settings for storage changes state dumper BlockCacheSize = section.GetValue("BlockCacheSize", 1000u); diff --git a/src/Plugins/StorageDumper/StorageDumper.cs b/src/Plugins/StorageDumper/StorageDumper.cs index d8987cdcaa..6f5498b3a2 100644 --- a/src/Plugins/StorageDumper/StorageDumper.cs +++ b/src/Plugins/StorageDumper/StorageDumper.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Neo.ConsoleService; +using Neo.IEventHandlers; using Neo.IO; using Neo.Json; using Neo.Ledger; @@ -19,7 +20,7 @@ namespace Neo.Plugins.StorageDumper { - public class StorageDumper : Plugin + public class StorageDumper : Plugin, ICommittingHandler, ICommittedHandler { private readonly Dictionary systems = new Dictionary(); @@ -29,7 +30,7 @@ public class StorageDumper : Plugin /// private JObject? _currentBlock; private string? _lastCreateDirectory; - + protected override UnhandledExceptionPolicy ExceptionPolicy => Settings.Default?.ExceptionPolicy ?? UnhandledExceptionPolicy.Ignore; public override string Description => "Exports Neo-CLI status data"; @@ -37,14 +38,14 @@ public class StorageDumper : Plugin public StorageDumper() { - Blockchain.Committing += OnCommitting; - Blockchain.Committed += OnCommitted; + Blockchain.Committing += ((ICommittingHandler)this).Blockchain_Committing_Handler; + Blockchain.Committed += ((ICommittedHandler)this).Blockchain_Committed_Handler; } public override void Dispose() { - Blockchain.Committing -= OnCommitting; - Blockchain.Committed -= OnCommitted; + Blockchain.Committing -= ((ICommittingHandler)this).Blockchain_Committing_Handler; + Blockchain.Committed -= ((ICommittedHandler)this).Blockchain_Committed_Handler; } protected override void Configure() @@ -85,7 +86,7 @@ private void OnDumpStorage(uint network, UInt160? contractHash = null) $"{path}"); } - private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + void ICommittingHandler.Blockchain_Committing_Handler(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) { InitFileWriter(system.Settings.Network, snapshot); OnPersistStorage(system.Settings.Network, snapshot); @@ -132,7 +133,7 @@ private void OnPersistStorage(uint network, DataCache snapshot) } - private void OnCommitted(NeoSystem system, Block block) + void ICommittedHandler.Blockchain_Committed_Handler(NeoSystem system, Block block) { OnCommitStorage(system.Settings.Network, system.StoreView); } diff --git a/src/Plugins/StorageDumper/StorageDumper.csproj b/src/Plugins/StorageDumper/StorageDumper.csproj index 7805140f3e..ebadda6136 100644 --- a/src/Plugins/StorageDumper/StorageDumper.csproj +++ b/src/Plugins/StorageDumper/StorageDumper.csproj @@ -5,7 +5,7 @@ Neo.Plugins.StorageDumper enable enable - $(SolutionDir)/bin/$(PackageId) + ../../../bin/$(PackageId) diff --git a/src/Plugins/StorageDumper/StorageDumper.json b/src/Plugins/StorageDumper/StorageDumper.json index b327c37e0c..0c314cf262 100644 --- a/src/Plugins/StorageDumper/StorageDumper.json +++ b/src/Plugins/StorageDumper/StorageDumper.json @@ -3,6 +3,7 @@ "BlockCacheSize": 1000, "HeightToBegin": 0, "StoragePerFolder": 100000, - "Exclude": [ -4 ] + "Exclude": [ -4 ], + "UnhandledExceptionPolicy": "Ignore" } } diff --git a/src/Plugins/TokensTracker/TokensTracker.cs b/src/Plugins/TokensTracker/TokensTracker.cs index eacd0de6e4..34fe0a2c95 100644 --- a/src/Plugins/TokensTracker/TokensTracker.cs +++ b/src/Plugins/TokensTracker/TokensTracker.cs @@ -10,18 +10,20 @@ // modifications are permitted. using Microsoft.Extensions.Configuration; +using Neo.IEventHandlers; using Neo.Ledger; using Neo.Network.P2P.Payloads; using Neo.Persistence; using Neo.Plugins.RpcServer; using Neo.Plugins.Trackers; +using System; using System.Collections.Generic; using System.Linq; using static System.IO.Path; namespace Neo.Plugins { - public class TokensTracker : Plugin + public class TokensTracker : Plugin, ICommittingHandler, ICommittedHandler { private string _dbPath; private bool _shouldTrackHistory; @@ -29,8 +31,10 @@ public class TokensTracker : Plugin private uint _network; private string[] _enabledTrackers; private IStore _db; + private UnhandledExceptionPolicy _exceptionPolicy; private NeoSystem neoSystem; private readonly List trackers = new(); + protected override UnhandledExceptionPolicy ExceptionPolicy => _exceptionPolicy; public override string Description => "Enquiries balances and transaction history of accounts through RPC"; @@ -38,14 +42,14 @@ public class TokensTracker : Plugin public TokensTracker() { - Blockchain.Committing += OnCommitting; - Blockchain.Committed += OnCommitted; + Blockchain.Committing += ((ICommittingHandler)this).Blockchain_Committing_Handler; + Blockchain.Committed += ((ICommittedHandler)this).Blockchain_Committed_Handler; } public override void Dispose() { - Blockchain.Committing -= OnCommitting; - Blockchain.Committed -= OnCommitted; + Blockchain.Committing -= ((ICommittingHandler)this).Blockchain_Committing_Handler; + Blockchain.Committed -= ((ICommittedHandler)this).Blockchain_Committed_Handler; } protected override void Configure() @@ -56,6 +60,11 @@ protected override void Configure() _maxResults = config.GetValue("MaxResults", 1000u); _network = config.GetValue("Network", 860833102u); _enabledTrackers = config.GetSection("EnabledTrackers").GetChildren().Select(p => p.Value).ToArray(); + var policyString = config.GetValue(nameof(UnhandledExceptionPolicy), nameof(UnhandledExceptionPolicy.StopNode)); + if (Enum.TryParse(policyString, true, out UnhandledExceptionPolicy policy)) + { + _exceptionPolicy = policy; + } } protected override void OnSystemLoaded(NeoSystem system) @@ -80,7 +89,7 @@ private void ResetBatch() } } - private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + void ICommittingHandler.Blockchain_Committing_Handler(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) { if (system.Settings.Network != _network) return; // Start freshly with a new DBCache for each block. @@ -91,7 +100,7 @@ private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IRe } } - private void OnCommitted(NeoSystem system, Block block) + void ICommittedHandler.Blockchain_Committed_Handler(NeoSystem system, Block block) { if (system.Settings.Network != _network) return; foreach (var tracker in trackers) diff --git a/src/Plugins/TokensTracker/TokensTracker.csproj b/src/Plugins/TokensTracker/TokensTracker.csproj index bdacd3839c..5f17120159 100644 --- a/src/Plugins/TokensTracker/TokensTracker.csproj +++ b/src/Plugins/TokensTracker/TokensTracker.csproj @@ -3,7 +3,7 @@ net8.0 Neo.Plugins.TokensTracker - $(SolutionDir)/bin/$(PackageId) + ../../../bin/$(PackageId) diff --git a/src/Plugins/TokensTracker/TokensTracker.json b/src/Plugins/TokensTracker/TokensTracker.json index ca63183b68..dbdbecfd40 100644 --- a/src/Plugins/TokensTracker/TokensTracker.json +++ b/src/Plugins/TokensTracker/TokensTracker.json @@ -4,7 +4,8 @@ "TrackHistory": true, "MaxResults": 1000, "Network": 860833102, - "EnabledTrackers": [ "NEP-11", "NEP-17" ] + "EnabledTrackers": [ "NEP-11", "NEP-17" ], + "UnhandledExceptionPolicy": "StopPlugin" }, "Dependency": [ "RpcServer" 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/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..05592d366b 100644 --- a/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Trie.cs +++ b/tests/Neo.Cryptography.MPTTrie.Tests/Cryptography/MPTTrie/UT_Trie.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.IO; using Neo.Persistence; using System; @@ -26,7 +27,7 @@ class TestSnapshot : ISnapshot private byte[] StoreKey(byte[] key) { - return Concat(key); + return [.. key]; } public void Put(byte[] key, byte[] value) @@ -65,7 +66,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/Neo.Extensions.Tests.csproj b/tests/Neo.Extensions.Tests/Neo.Extensions.Tests.csproj new file mode 100644 index 0000000000..3c13dd0953 --- /dev/null +++ b/tests/Neo.Extensions.Tests/Neo.Extensions.Tests.csproj @@ -0,0 +1,23 @@ + + + + net8.0 + enable + + + + + + + + + + + + + + + + + + diff --git a/tests/Neo.Extensions.Tests/UT_BigIntegerExtensions.cs b/tests/Neo.Extensions.Tests/UT_BigIntegerExtensions.cs new file mode 100644 index 0000000000..b887d1c973 --- /dev/null +++ b/tests/Neo.Extensions.Tests/UT_BigIntegerExtensions.cs @@ -0,0 +1,47 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_BigIntegerExtensions.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using FluentAssertions; +using Neo.Extensions; +using System.Numerics; + +namespace Neo.Extensions.Tests +{ + [TestClass] + public class UT_BigIntegerExtensions + { + [TestMethod] + public void TestGetLowestSetBit() + { + var big1 = new BigInteger(0); + big1.GetLowestSetBit().Should().Be(-1); + + var big2 = new BigInteger(512); + big2.GetLowestSetBit().Should().Be(9); + + var big3 = new BigInteger(int.MinValue); + big3.GetLowestSetBit().Should().Be(31); + + var big4 = new BigInteger(long.MinValue); + big4.GetLowestSetBit().Should().Be(63); + } + + [TestMethod] + public void TestToByteArrayStandard() + { + BigInteger number = BigInteger.Zero; + Assert.AreEqual("", number.ToByteArrayStandard().ToHexString()); + + number = BigInteger.One; + Assert.AreEqual("01", number.ToByteArrayStandard().ToHexString()); + } + } +} diff --git a/tests/Neo.Extensions.Tests/UT_ByteExtensions.cs b/tests/Neo.Extensions.Tests/UT_ByteExtensions.cs new file mode 100644 index 0000000000..a66d462694 --- /dev/null +++ b/tests/Neo.Extensions.Tests/UT_ByteExtensions.cs @@ -0,0 +1,37 @@ +// 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"); + } + } +} 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..c2e4f4b5a2 100644 --- a/tests/Neo.Network.RPC.Tests/RpcTestCases.json +++ b/tests/Neo.Network.RPC.Tests/RpcTestCases.json @@ -2496,6 +2496,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_Nep17API.cs b/tests/Neo.Network.RPC.Tests/UT_Nep17API.cs index 3bfb4e87ff..863d424405 100644 --- a/tests/Neo.Network.RPC.Tests/UT_Nep17API.cs +++ b/tests/Neo.Network.RPC.Tests/UT_Nep17API.cs @@ -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_Utility.cs b/tests/Neo.Network.RPC.Tests/UT_Utility.cs index fa410c5437..53e381b836 100644 --- a/tests/Neo.Network.RPC.Tests/UT_Utility.cs +++ b/tests/Neo.Network.RPC.Tests/UT_Utility.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Extensions; using Neo.SmartContract; using Neo.Wallets; using System; 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..e8a46d3022 --- /dev/null +++ b/tests/Neo.Plugins.ApplicationLogs.Tests/Neo.Plugins.ApplicationLogs.Tests.csproj @@ -0,0 +1,19 @@ + + + + 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_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/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..a4cc2f6fbf 100644 --- a/tests/Neo.Plugins.OracleService.Tests/TestBlockchain.cs +++ b/tests/Neo.Plugins.OracleService.Tests/TestBlockchain.cs @@ -9,6 +9,8 @@ // Redistribution and use in source and binary forms with or without // modifications are permitted. +using Akka.Actor; +using Neo.Ledger; using Neo.Persistence; using System; @@ -16,21 +18,36 @@ namespace Neo.Plugins.OracleService.Tests { public static class TestBlockchain { - public static readonly NeoSystem TheNeoSystem; + private static readonly NeoSystem s_theNeoSystem; + private static readonly MemoryStore s_store = new(); + + 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()); + s_theNeoSystem = new NeoSystem(ProtocolSettings.Load("config.json"), new StoreProvider()); } public static void InitializeMockNeoSystem() { } - internal static DataCache GetTestSnapshot() + internal static void ResetStore() + { + s_store.Reset(); + s_theNeoSystem.Blockchain.Ask(new Blockchain.Initialize()).Wait(); + } + + internal static SnapshotCache GetTestSnapshotCache() { - return TheNeoSystem.GetSnapshot().CreateSnapshot(); + ResetStore(); + return s_theNeoSystem.GetSnapshot(); } } } diff --git a/tests/Neo.Plugins.OracleService.Tests/UT_OracleService.cs b/tests/Neo.Plugins.OracleService.Tests/UT_OracleService.cs index 9622ceb9e2..33d82c618c 100644 --- a/tests/Neo.Plugins.OracleService.Tests/UT_OracleService.cs +++ b/tests/Neo.Plugins.OracleService.Tests/UT_OracleService.cs @@ -70,11 +70,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 +85,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 +98,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 +108,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 b4800b80ea..67bc3a62fa 100644 --- a/tests/Neo.Plugins.OracleService.Tests/config.json +++ b/tests/Neo.Plugins.OracleService.Tests/config.json @@ -24,7 +24,7 @@ "NeoNameService": "0x50ac1c37690cc2cfc594472833cf57505d5f46de" }, "Plugins": { - "DownloadUrl": "https://api.github.com/repos/neo-project/neo-modules/releases" + "DownloadUrl": "https://api.github.com/repos/neo-project/neo/releases" } }, "ProtocolConfiguration": { diff --git a/tests/Neo.Plugins.RpcServer.Tests/MockNeoSystem.cs b/tests/Neo.Plugins.RpcServer.Tests/MockNeoSystem.cs deleted file mode 100644 index 0f45a63d98..0000000000 --- a/tests/Neo.Plugins.RpcServer.Tests/MockNeoSystem.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (C) 2015-2024 The Neo Project. -// -// MockNeoSystem.cs file belongs to the neo project and is free -// software distributed under the MIT software license, see the -// accompanying file LICENSE in the main directory of the -// repository or http://www.opensource.org/licenses/mit-license.php -// for more details. -// -// Redistribution and use in source and binary forms with or without -// modifications are permitted. - -using Neo.Ledger; -using Neo.Persistence; - -namespace Neo.Plugins.RpcServer.Tests -{ - public class MockNeoSystem : NeoSystem - { - public SnapshotCache SnapshotCache { get; } - public MemoryPool MemoryPool { get; } - - public MockNeoSystem(SnapshotCache snapshotCache, MemoryPool memoryPool) - : base(TestProtocolSettings.Default, new TestBlockchain.StoreProvider()) - { - SnapshotCache = snapshotCache; - MemoryPool = memoryPool; - } - - public SnapshotCache GetSnapshot() - { - return SnapshotCache; - } - } -} diff --git a/tests/Neo.Plugins.RpcServer.Tests/Neo.Plugins.RpcServer.Tests.csproj b/tests/Neo.Plugins.RpcServer.Tests/Neo.Plugins.RpcServer.Tests.csproj index 5b693c96b8..b7e2ec91ac 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/Neo.Plugins.RpcServer.Tests.csproj +++ b/tests/Neo.Plugins.RpcServer.Tests/Neo.Plugins.RpcServer.Tests.csproj @@ -4,18 +4,20 @@ net8.0 Neo.Plugins.RpcServer.Tests Neo.Plugins.RpcServer.Tests + true - + - - + + + \ No newline at end of file diff --git a/tests/Neo.Plugins.RpcServer.Tests/TestBlockchain.cs b/tests/Neo.Plugins.RpcServer.Tests/TestBlockchain.cs index f6e35cf695..3ed48e982c 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/TestBlockchain.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/TestBlockchain.cs @@ -12,6 +12,7 @@ using Akka.Actor; using Neo.Ledger; using Neo.Persistence; +using Neo.UnitTests; using System; namespace Neo.Plugins.RpcServer.Tests @@ -43,7 +44,7 @@ internal static void ResetStore() internal static DataCache GetTestSnapshot() { - return TheNeoSystem.GetSnapshot().CreateSnapshot(); + return TheNeoSystem.GetSnapshotCache().CloneCache(); } } } diff --git a/tests/Neo.Plugins.RpcServer.Tests/TestMemoryStoreProvider.cs b/tests/Neo.Plugins.RpcServer.Tests/TestMemoryStoreProvider.cs new file mode 100644 index 0000000000..b39b4aeae5 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/TestMemoryStoreProvider.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestMemoryStoreProvider.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Persistence; + +namespace Neo.Plugins.RpcServer.Tests; + +public class TestMemoryStoreProvider(MemoryStore memoryStore) : IStoreProvider +{ + public MemoryStore MemoryStore { get; init; } = memoryStore; + public string Name => nameof(MemoryStore); + public IStore GetStore(string path) => MemoryStore; +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/TestProtocolSettings.cs b/tests/Neo.Plugins.RpcServer.Tests/TestProtocolSettings.cs deleted file mode 100644 index edf2df39fb..0000000000 --- a/tests/Neo.Plugins.RpcServer.Tests/TestProtocolSettings.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (C) 2015-2024 The Neo Project. -// -// TestProtocolSettings.cs file belongs to the neo project and is free -// software distributed under the MIT software license, see the -// accompanying file LICENSE in the main directory of the -// repository or http://www.opensource.org/licenses/mit-license.php -// for more details. -// -// Redistribution and use in source and binary forms with or without -// modifications are permitted. - -using Neo.Cryptography.ECC; - -namespace Neo.Plugins.RpcServer.Tests -{ - public static class TestProtocolSettings - { - public static readonly ProtocolSettings Default = new() - { - Network = 0x334F454Eu, - AddressVersion = ProtocolSettings.Default.AddressVersion, - StandbyCommittee = new[] - { - //Validators - ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", ECCurve.Secp256r1), - ECPoint.Parse("02df48f60e8f3e01c48ff40b9b7f1310d7a8b2a193188befe1c2e3df740e895093", ECCurve.Secp256r1), - ECPoint.Parse("03b8d9d5771d8f513aa0869b9cc8d50986403b78c6da36890638c3d46a5adce04a", ECCurve.Secp256r1), - ECPoint.Parse("02ca0e27697b9c248f6f16e085fd0061e26f44da85b58ee835c110caa5ec3ba554", ECCurve.Secp256r1), - ECPoint.Parse("024c7b7fb6c310fccf1ba33b082519d82964ea93868d676662d4a59ad548df0e7d", ECCurve.Secp256r1), - ECPoint.Parse("02aaec38470f6aad0042c6e877cfd8087d2676b0f516fddd362801b9bd3936399e", ECCurve.Secp256r1), - ECPoint.Parse("02486fd15702c4490a26703112a5cc1d0923fd697a33406bd5a1c00e0013b09a70", ECCurve.Secp256r1), - //Other Members - ECPoint.Parse("023a36c72844610b4d34d1968662424011bf783ca9d984efa19a20babf5582f3fe", ECCurve.Secp256r1), - ECPoint.Parse("03708b860c1de5d87f5b151a12c2a99feebd2e8b315ee8e7cf8aa19692a9e18379", ECCurve.Secp256r1), - ECPoint.Parse("03c6aa6e12638b36e88adc1ccdceac4db9929575c3e03576c617c49cce7114a050", ECCurve.Secp256r1), - ECPoint.Parse("03204223f8c86b8cd5c89ef12e4f0dbb314172e9241e30c9ef2293790793537cf0", ECCurve.Secp256r1), - ECPoint.Parse("02a62c915cf19c7f19a50ec217e79fac2439bbaad658493de0c7d8ffa92ab0aa62", ECCurve.Secp256r1), - ECPoint.Parse("03409f31f0d66bdc2f70a9730b66fe186658f84a8018204db01c106edc36553cd0", ECCurve.Secp256r1), - ECPoint.Parse("0288342b141c30dc8ffcde0204929bb46aed5756b41ef4a56778d15ada8f0c6654", ECCurve.Secp256r1), - ECPoint.Parse("020f2887f41474cfeb11fd262e982051c1541418137c02a0f4961af911045de639", ECCurve.Secp256r1), - ECPoint.Parse("0222038884bbd1d8ff109ed3bdef3542e768eef76c1247aea8bc8171f532928c30", ECCurve.Secp256r1), - ECPoint.Parse("03d281b42002647f0113f36c7b8efb30db66078dfaaa9ab3ff76d043a98d512fde", ECCurve.Secp256r1), - ECPoint.Parse("02504acbc1f4b3bdad1d86d6e1a08603771db135a73e61c9d565ae06a1938cd2ad", ECCurve.Secp256r1), - ECPoint.Parse("0226933336f1b75baa42d42b71d9091508b638046d19abd67f4e119bf64a7cfb4d", ECCurve.Secp256r1), - ECPoint.Parse("03cdcea66032b82f5c30450e381e5295cae85c5e6943af716cc6b646352a6067dc", ECCurve.Secp256r1), - ECPoint.Parse("02cd5a5547119e24feaa7c2a0f37b8c9366216bab7054de0065c9be42084003c8a", ECCurve.Secp256r1) - }, - ValidatorsCount = 7, - SeedList = new[] - { - "seed1.neo.org:10333", - "seed2.neo.org:10333", - "seed3.neo.org:10333", - "seed4.neo.org:10333", - "seed5.neo.org:10333" - }, - MillisecondsPerBlock = ProtocolSettings.Default.MillisecondsPerBlock, - MaxTransactionsPerBlock = ProtocolSettings.Default.MaxTransactionsPerBlock, - MemoryPoolMaxTransactions = ProtocolSettings.Default.MemoryPoolMaxTransactions, - MaxTraceableBlocks = ProtocolSettings.Default.MaxTraceableBlocks, - InitialGasDistribution = ProtocolSettings.Default.InitialGasDistribution, - Hardforks = ProtocolSettings.Default.Hardforks - }; - } -} diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs new file mode 100644 index 0000000000..ab5693937f --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Blockchain.cs @@ -0,0 +1,622 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RpcServer.Blockchain.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Actor; +using Akka.Util.Internal; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.Json; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.UnitTests; +using Neo.UnitTests.Extensions; +using System; +using System.Linq; + +namespace Neo.Plugins.RpcServer.Tests +{ + public partial class UT_RpcServer + { + + [TestMethod] + public void TestGetBestBlockHash() + { + var key = NativeContract.Ledger.CreateStorageKey(12); + var expectedHash = UInt256.Zero; + + var snapshot = _neoSystem.GetSnapshotCache(); + var b = snapshot.GetAndChange(key, () => new StorageItem(new HashIndexState())).GetInteroperable(); + b.Hash = UInt256.Zero; + b.Index = 100; + snapshot.Commit(); + + var result = _rpcServer.GetBestBlockHash([]); + // Assert + Assert.AreEqual(expectedHash.ToString(), result.AsString()); + } + + [TestMethod] + public void TestGetBlockByHash() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(block.Hash.ToString(), false); + var result = _rpcServer.GetBlock(parameters); + var blockArr = Convert.FromBase64String(result.AsString()); + var block2 = blockArr.AsSerializable(); + block2.Transactions.ForEach(tx => + { + Assert.AreEqual(VerifyResult.Succeed, tx.VerifyStateIndependent(UnitTests.TestProtocolSettings.Default)); + }); + } + + [TestMethod] + public void TestGetBlockByIndex() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(block.Index, false); + var result = _rpcServer.GetBlock(parameters); + var blockArr = Convert.FromBase64String(result.AsString()); + var block2 = blockArr.AsSerializable(); + block2.Transactions.ForEach(tx => + { + Assert.AreEqual(VerifyResult.Succeed, tx.VerifyStateIndependent(UnitTests.TestProtocolSettings.Default)); + }); + } + + [TestMethod] + public void TestGetBlockCount() + { + var expectedCount = 1; + var result = _rpcServer.GetBlockCount(new JArray()); + Assert.AreEqual(expectedCount, result.AsNumber()); + } + + [TestMethod] + public void TestGetBlockHeaderCount() + { + var expectedCount = 1; + var result = _rpcServer.GetBlockHeaderCount(new JArray()); + Assert.AreEqual(expectedCount, result.AsNumber()); + } + + [TestMethod] + public void TestGetBlockHash() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + // TestUtils.BlocksAdd(snapshot, block.Hash, block); + // snapshot.Commit(); + var reason = _neoSystem.Blockchain.Ask(block).Result; + var expectedHash = block.Hash.ToString(); + var result = _rpcServer.GetBlockHash(new JArray(block.Index)); + Assert.AreEqual(expectedHash, result.AsString()); + } + + [TestMethod] + public void TestGetBlockHeader() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + var parameters = new JArray(block.Hash.ToString(), true); + var result = _rpcServer.GetBlockHeader(parameters); + var header = block.Header.ToJson(_neoSystem.Settings); + header["confirmations"] = NativeContract.Ledger.CurrentIndex(snapshot) - block.Index + 1; + Assert.AreEqual(header.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetContractState() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var contractState = TestUtils.GetContract(); + snapshot.AddContract(contractState.Hash, contractState); + snapshot.Commit(); + var result = _rpcServer.GetContractState(new JArray(contractState.Hash.ToString())); + + Assert.AreEqual(contractState.ToJson().ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetRawMemPool() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount); + snapshot.Commit(); + _neoSystem.MemPool.TryAdd(tx, snapshot); + + var result = _rpcServer.GetRawMemPool(new JArray()); + + Assert.IsTrue(((JArray)result).Any(p => p.AsString() == tx.Hash.ToString())); + } + + [TestMethod] + public void TestGetRawTransaction() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var tx = TestUtils.CreateValidTx(snapshot, _wallet, _walletAccount); + _neoSystem.MemPool.TryAdd(tx, snapshot); + var parameters = new JArray(tx.Hash.ToString(), true); + snapshot.Commit(); + var result = _rpcServer.GetRawTransaction(parameters); + + var json = Utility.TransactionToJson(tx, _neoSystem.Settings); + Assert.AreEqual(json.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetStorage() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var contractState = TestUtils.GetContract(); + snapshot.AddContract(contractState.Hash, contractState); + var key = new byte[] { 0x01 }; + var value = new byte[] { 0x02 }; + TestUtils.StorageItemAdd(snapshot, contractState.Id, key, value); + snapshot.Commit(); + + var result = _rpcServer.GetStorage(new JArray(contractState.Hash.ToString(), Convert.ToBase64String(key))); + Assert.AreEqual(Convert.ToBase64String(value), result.AsString()); + } + + [TestMethod] + public void TestFindStorage() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var contractState = TestUtils.GetContract(); + snapshot.AddContract(contractState.Hash, contractState); + var key = new byte[] { 0x01 }; + var value = new byte[] { 0x02 }; + TestUtils.StorageItemAdd(snapshot, contractState.Id, key, value); + snapshot.Commit(); + var result = _rpcServer.FindStorage(new JArray(contractState.Hash.ToString(), Convert.ToBase64String(key), 0)); + + var json = new JObject(); + var jarr = new JArray(); + var j = new JObject(); + j["key"] = Convert.ToBase64String(key); + j["value"] = Convert.ToBase64String(value); + jarr.Add(j); + json["truncated"] = false; + json["next"] = 1; + json["results"] = jarr; + Assert.AreEqual(json.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetTransactionHeight() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 1); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + var tx = block.Transactions[0]; + var result = _rpcServer.GetTransactionHeight(new JArray(tx.Hash.ToString())); + Assert.AreEqual(block.Index, result.AsNumber()); + } + + [TestMethod] + public void TestGetNextBlockValidators() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var result = _rpcServer.GetNextBlockValidators(new JArray()); + + var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, _neoSystem.Settings.ValidatorsCount); + var expected = validators.Select(p => + { + var validator = new JObject(); + validator["publickey"] = p.ToString(); + validator["votes"] = (int)NativeContract.NEO.GetCandidateVote(snapshot, p); + return validator; + }).ToArray(); + Assert.AreEqual(new JArray(expected).ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetCandidates() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var result = _rpcServer.GetCandidates(new JArray()); + var json = new JArray(); + var validators = NativeContract.NEO.GetNextBlockValidators(snapshot, _neoSystem.Settings.ValidatorsCount); + snapshot.Commit(); + var candidates = NativeContract.NEO.GetCandidates(_neoSystem.GetSnapshotCache()); + + foreach (var candidate in candidates) + { + var item = new JObject(); + item["publickey"] = candidate.PublicKey.ToString(); + item["votes"] = candidate.Votes.ToString(); + item["active"] = validators.Contains(candidate.PublicKey); + json.Add(item); + } + Assert.AreEqual(json.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetCommittee() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var result = _rpcServer.GetCommittee(new JArray()); + var committee = NativeContract.NEO.GetCommittee(snapshot); + var expected = new JArray(committee.Select(p => (JToken)p.ToString())); + Assert.AreEqual(expected.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetNativeContracts() + { + var result = _rpcServer.GetNativeContracts(new JArray()); + var contracts = new JArray(NativeContract.Contracts.Select(p => NativeContract.ContractManagement.GetContract(_neoSystem.GetSnapshotCache(), p.Hash).ToJson())); + Assert.AreEqual(contracts.ToString(), result.ToString()); + } + + [TestMethod] + public void TestGetBlockByUnknownIndex() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(int.MaxValue, false); + try + { + _rpcServer.GetBlock(parameters); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownBlock.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockByUnknownHash() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(TestUtils.RandomUInt256().ToString(), false); + try + { + _rpcServer.GetBlock(parameters); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownBlock.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockByUnKnownIndex() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(int.MaxValue, false); + try + { + _rpcServer.GetBlock(parameters); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownBlock.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockByUnKnownHash() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + + var parameters = new JArray(TestUtils.RandomUInt256().ToString(), false); + try + { + _rpcServer.GetBlock(parameters); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownBlock.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockHashInvalidIndex() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var block = TestUtils.CreateBlockWithValidTransactions(snapshot, _wallet, _walletAccount, 3); + TestUtils.BlocksAdd(snapshot, block.Hash, block); + snapshot.Commit(); + Assert.ThrowsException(() => _rpcServer.GetBlockHash(new JArray(block.Index + 1))); + } + + [TestMethod] + public void TestGetContractStateUnknownContract() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var randomHash = TestUtils.RandomUInt160(); + try + { + _rpcServer.GetContractState(new JArray(randomHash.ToString())); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownContract.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetStorageUnknownContract() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var randomHash = TestUtils.RandomUInt160(); + var key = new byte[] { 0x01 }; + try + { + _rpcServer.GetStorage(new JArray(randomHash.ToString(), Convert.ToBase64String(key))); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownContract.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetStorageUnknownStorageItem() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var contractState = TestUtils.GetContract(); + snapshot.AddContract(contractState.Hash, contractState); + snapshot.Commit(); + + var key = new byte[] { 0x01 }; + try + { + _rpcServer.GetStorage(new JArray(contractState.Hash.ToString(), Convert.ToBase64String(key))); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownStorageItem.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetTransactionHeightUnknownTransaction() + { + var randomHash = TestUtils.RandomUInt256(); + try + { + _rpcServer.GetTransactionHeight(new JArray(randomHash.ToString())); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownTransaction.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetRawTransactionUnknownTransaction() + { + var randomHash = TestUtils.RandomUInt256(); + try + { + _rpcServer.GetRawTransaction(new JArray(randomHash.ToString(), true)); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownTransaction.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockInvalidParams() + { + try + { + _rpcServer.GetBlock(new JArray("invalid_hash", false)); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + catch (FormatException) + { + } + catch + { + Assert.Fail("Unexpected exception"); + } + } + + [TestMethod] + public void TestGetBlockHashInvalidParams() + { + try + { + _rpcServer.GetBlockHash(new JArray("invalid_index")); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetBlockHeaderInvalidParams() + { + try + { + _rpcServer.GetBlockHeader(new JArray("invalid_hash", true)); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + catch (FormatException) + { + } + catch + { + Assert.Fail("Unexpected exception"); + } + } + + [TestMethod] + public void TestGetContractStateInvalidParams() + { + try + { + _rpcServer.GetContractState(new JArray("invalid_hash")); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + catch (FormatException) + { + } + catch + { + Assert.Fail("Unexpected exception"); + } + } + + [TestMethod] + public void TestGetStorageInvalidParams() + { + try + { + _rpcServer.GetStorage(new JArray("invalid_hash", "invalid_key")); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + catch (FormatException) + { + } + catch + { + Assert.Fail("Unexpected exception"); + } + } + + [TestMethod] + public void TestFindStorageInvalidParams() + { + try + { + _rpcServer.FindStorage(new JArray("invalid_hash", "invalid_prefix", "invalid_start")); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + catch (FormatException) + { + } + catch + { + Assert.Fail("Unexpected exception"); + } + } + + [TestMethod] + public void TestGetTransactionHeightInvalidParams() + { + try + { + _rpcServer.GetTransactionHeight(new JArray("invalid_hash")); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + } + + [TestMethod] + public void TestGetRawTransactionInvalidParams() + { + try + { + _rpcServer.GetRawTransaction(new JArray("invalid_hash", true)); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InvalidParams.Code, ex.HResult); + } + } + + [TestMethod] + public void TestInternalServerError() + { + _memoryStore.Reset(); + try + { + _rpcServer.GetCandidates(new JArray()); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.InternalServerError.Code, ex.HResult); + } + } + + [TestMethod] + public void TestUnknownHeight() + { + try + { + _rpcServer.GetBlockHash(new JArray(int.MaxValue)); + Assert.Fail("Expected RpcException was not thrown."); + } + catch (RpcException ex) + { + Assert.AreEqual(RpcError.UnknownHeight.Code, ex.HResult); + } + } + } +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Node.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Node.cs new file mode 100644 index 0000000000..c8b655024c --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Node.cs @@ -0,0 +1,293 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_RpcServer.Node.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; +using Neo.Json; +using Neo.Network.P2P.Payloads; +using Neo.SmartContract.Native; +using Neo.UnitTests; +using System; + +namespace Neo.Plugins.RpcServer.Tests +{ + partial class UT_RpcServer + { + [TestMethod] + public void TestGetVersion() + { + var result = _rpcServer.GetVersion(new JArray()); + Assert.IsInstanceOfType(result, typeof(JObject)); + + var json = (JObject)result; + Assert.IsTrue(json.ContainsProperty("tcpport")); + Assert.IsTrue(json.ContainsProperty("nonce")); + Assert.IsTrue(json.ContainsProperty("useragent")); + + Assert.IsTrue(json.ContainsProperty("protocol")); + var protocol = (JObject)json["protocol"]; + Assert.IsTrue(protocol.ContainsProperty("addressversion")); + Assert.IsTrue(protocol.ContainsProperty("network")); + Assert.IsTrue(protocol.ContainsProperty("validatorscount")); + Assert.IsTrue(protocol.ContainsProperty("msperblock")); + Assert.IsTrue(protocol.ContainsProperty("maxtraceableblocks")); + Assert.IsTrue(protocol.ContainsProperty("maxvaliduntilblockincrement")); + Assert.IsTrue(protocol.ContainsProperty("maxtransactionsperblock")); + Assert.IsTrue(protocol.ContainsProperty("memorypoolmaxtransactions")); + 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(new JArray(txString)); + Assert.IsInstanceOfType(result, typeof(JObject)); + Assert.IsTrue(((JObject)result).ContainsProperty("hash")); + } + + [TestMethod] + public void TestSendRawTransaction_InvalidTransactionFormat() + { + Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray("invalid_transaction_string")), + "Should throw RpcException for invalid transaction format"); + } + + [TestMethod] + public void TestSendRawTransaction_InsufficientBalance() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.InsufficientBalance); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray(txString)), + "Should throw RpcException for insufficient balance"); + Assert.AreEqual(RpcError.InsufficientFunds.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_InvalidSignature() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.InvalidSignature); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray(txString)), + "Should throw RpcException for invalid signature"); + Assert.AreEqual(RpcError.InvalidSignature.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_InvalidScript() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.InvalidScript); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray(txString)), + "Should throw RpcException for invalid script"); + Assert.AreEqual(RpcError.InvalidScript.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_InvalidAttribute() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.InvalidAttribute); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray(txString)), + "Should throw RpcException for invalid attribute"); + // Transaction with invalid attribute can not pass the Transaction deserialization + // and will throw invalid params exception. + Assert.AreEqual(RpcError.InvalidParams.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_Oversized() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.Oversized); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray(txString)), + "Should throw RpcException for invalid format transaction"); + // Oversized transaction will not pass the deserialization. + Assert.AreEqual(RpcError.InvalidParams.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_Expired() + { + var snapshot = _neoSystem.GetSnapshotCache(); + var tx = TestUtils.CreateInvalidTransaction(snapshot, _wallet, _walletAccount, TestUtils.InvalidTransactionType.Expired); + var txString = Convert.ToBase64String(tx.ToArray()); + + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray(txString)), + "Should throw RpcException for expired transaction"); + Assert.AreEqual(RpcError.ExpiredTransaction.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_PolicyFailed() + { + var snapshot = _neoSystem.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(new JArray(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(new JArray(txString)), + "Should throw RpcException for transaction already in memory pool"); + Assert.AreEqual(RpcError.AlreadyInPool.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_AlreadyInBlockchain() + { + var snapshot = _neoSystem.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(new JArray(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(new JArray(blockString)); + Assert.IsInstanceOfType(result, typeof(JObject)); + Assert.IsTrue(((JObject)result).ContainsProperty("hash")); + } + + [TestMethod] + public void TestSubmitBlock_InvalidBlockFormat() + { + string invalidBlockString = TestUtils.CreateInvalidBlockFormat(); + + var exception = Assert.ThrowsException(() => + _rpcServer.SubmitBlock(new JArray(invalidBlockString)), + "Should throw RpcException for invalid block format"); + + Assert.AreEqual(RpcError.InvalidParams.Code, exception.HResult); + StringAssert.Contains(exception.Message, "Invalid Block Format"); + } + + [TestMethod] + public void TestSubmitBlock_AlreadyExists() + { + var snapshot = _neoSystem.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(new JArray(blockString)), + "Should throw RpcException when block already exists"); + Assert.AreEqual(RpcError.AlreadyExists.Code, exception.HResult); + } + + [TestMethod] + public void TestSubmitBlock_InvalidBlock() + { + var snapshot = _neoSystem.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(new JArray(blockString)), + "Should throw RpcException for invalid block"); + Assert.AreEqual(RpcError.VerificationFailed.Code, exception.HResult); + } + + #endregion + + #region Edge Cases and Error Handling + + [TestMethod] + public void TestSendRawTransaction_NullInput() + { + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray((string)null)), + "Should throw RpcException for null input"); + Assert.AreEqual(RpcError.InvalidParams.Code, exception.HResult); + } + + [TestMethod] + public void TestSendRawTransaction_EmptyInput() + { + var exception = Assert.ThrowsException(() => + _rpcServer.SendRawTransaction(new JArray(string.Empty)), + "Should throw RpcException for empty input"); + Assert.AreEqual(RpcError.InvalidParams.Code, exception.HResult); + } + + [TestMethod] + public void TestSubmitBlock_NullInput() + { + var exception = Assert.ThrowsException(() => + _rpcServer.SubmitBlock(new JArray((string)null)), + "Should throw RpcException for null input"); + Assert.AreEqual(RpcError.InvalidParams.Code, exception.HResult); + } + + [TestMethod] + public void TestSubmitBlock_EmptyInput() + { + var exception = Assert.ThrowsException(() => + _rpcServer.SubmitBlock(new JArray(string.Empty)), + "Should throw RpcException for empty input"); + Assert.AreEqual(RpcError.InvalidParams.Code, exception.HResult); + } + + #endregion + } +} diff --git a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs new file mode 100644 index 0000000000..2db71ae570 --- /dev/null +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.Wallet.cs @@ -0,0 +1,403 @@ +// 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.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(); + } + + [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); + } + + [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); + } + + [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(); + } + + [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); + } + + + 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 7bca550b83..2561171c81 100644 --- a/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs +++ b/tests/Neo.Plugins.RpcServer.Tests/UT_RpcServer.cs @@ -11,9 +11,13 @@ using Microsoft.AspNetCore.Http; using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; using Neo.Ledger; using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.UnitTests; +using Neo.Wallets; +using Neo.Wallets.NEP6; using System; using System.Text; @@ -22,39 +26,50 @@ namespace Neo.Plugins.RpcServer.Tests [TestClass] public partial class UT_RpcServer { - private Mock _systemMock; - private SnapshotCache _snapshotCache; - private MemoryPool _memoryPool; - private RpcServerSettings _settings; + private NeoSystem _neoSystem; private RpcServer _rpcServer; + private TestMemoryStoreProvider _memoryStoreProvider; + private MemoryStore _memoryStore; + private readonly NEP6Wallet _wallet = TestUtils.GenerateTestWallet("123"); + private WalletAccount _walletAccount; [TestInitialize] public void TestSetup() { - // Mock IReadOnlyStore - var mockStore = new Mock(); - - // Initialize SnapshotCache with the mock IReadOnlyStore - _snapshotCache = new SnapshotCache(mockStore.Object); - - // Initialize NeoSystem - var neoSystem = new NeoSystem(TestProtocolSettings.Default, new TestBlockchain.StoreProvider()); - - // Initialize MemoryPool with the NeoSystem - _memoryPool = new MemoryPool(neoSystem); - - // Set up the mock system with the correct constructor arguments - _systemMock = new Mock(_snapshotCache, _memoryPool); + _memoryStore = new MemoryStore(); + _memoryStoreProvider = new TestMemoryStoreProvider(_memoryStore); + _neoSystem = new NeoSystem(TestProtocolSettings.SoleNode, _memoryStoreProvider); + _rpcServer = new RpcServer(_neoSystem, RpcServerSettings.Default); + _walletAccount = _wallet.Import("KxuRSsHgJMb3AMSN6B9P3JHNGMFtxmuimqgR9MmXPcv3CLLfusTd"); + var key = new KeyBuilder(NativeContract.GAS.Id, 20).Add(_walletAccount.ScriptHash); + var snapshot = _neoSystem.GetSnapshotCache(); + var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + entry.GetInteroperable().Balance = 100_000_000 * NativeContract.GAS.Factor; + snapshot.Commit(); + } - _rpcServer = new RpcServer(_systemMock.Object, RpcServerSettings.Default); + [TestCleanup] + public void TestCleanup() + { + // Please build and test in debug mode + _neoSystem.MemPool.Clear(); + _memoryStore.Reset(); + var snapshot = _neoSystem.GetSnapshotCache(); + var key = new KeyBuilder(NativeContract.GAS.Id, 20).Add(_walletAccount.ScriptHash); + var entry = snapshot.GetAndChange(key, () => new StorageItem(new AccountState())); + entry.GetInteroperable().Balance = 100_000_000 * NativeContract.GAS.Factor; + snapshot.Commit(); } [TestMethod] public void TestCheckAuth_ValidCredentials_ReturnsTrue() { + // Arrange var context = new DefaultHttpContext(); context.Request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes("testuser:testpass")); + // Act var result = _rpcServer.CheckAuth(context); + // Assert Assert.IsTrue(result); } } diff --git a/tests/Neo.Plugins.Storage.Tests/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..189e212bc2 100644 --- a/tests/Neo.Plugins.Storage.Tests/StoreTest.cs +++ b/tests/Neo.Plugins.Storage.Tests/StoreTest.cs @@ -21,9 +21,19 @@ public class StoreTest { private const string path_leveldb = "Data_LevelDB_UT"; private const string path_rocksdb = "Data_RocksDB_UT"; + private static LevelDBStore levelDbStore; + private static RocksDBStore rocksDBStore; [TestInitialize] public void OnStart() + { + levelDbStore ??= new LevelDBStore(); + rocksDBStore ??= new RocksDBStore(); + OnEnd(); + } + + [TestCleanup] + public void OnEnd() { if (Directory.Exists(path_leveldb)) Directory.Delete(path_leveldb, true); if (Directory.Exists(path_rocksdb)) Directory.Delete(path_rocksdb, true); @@ -49,35 +59,138 @@ public void TestMemory() [TestMethod] public void TestLevelDb() { - using var plugin = new LevelDBStore(); - TestPersistenceDelete(plugin.GetStore(path_leveldb)); + TestPersistenceDelete(levelDbStore.GetStore(path_leveldb)); // Test all with the same store - TestStorage(plugin.GetStore(path_leveldb)); + TestStorage(levelDbStore.GetStore(path_leveldb)); // Test with different storages - TestPersistenceWrite(plugin.GetStore(path_leveldb)); - TestPersistenceRead(plugin.GetStore(path_leveldb), true); - TestPersistenceDelete(plugin.GetStore(path_leveldb)); - TestPersistenceRead(plugin.GetStore(path_leveldb), false); + TestPersistenceWrite(levelDbStore.GetStore(path_leveldb)); + TestPersistenceRead(levelDbStore.GetStore(path_leveldb), true); + TestPersistenceDelete(levelDbStore.GetStore(path_leveldb)); + TestPersistenceRead(levelDbStore.GetStore(path_leveldb), false); + } + + [TestMethod] + public void TestLevelDbSnapshot() + { + using var store = levelDbStore.GetStore(path_leveldb); + + var snapshot = store.GetSnapshot(); + + var testKey = new byte[] { 0x01, 0x02, 0x03 }; + var testValue = new byte[] { 0x04, 0x05, 0x06 }; + + snapshot.Put(testKey, testValue); + // Data saved to the leveldb snapshot shall not be visible to the store + Assert.IsNull(snapshot.TryGet(testKey)); + + // Value is in the write batch, not visible to the store and snapshot + Assert.AreEqual(false, snapshot.Contains(testKey)); + Assert.AreEqual(false, store.Contains(testKey)); + + snapshot.Commit(); + + // After commit, the data shall be visible to the store but not to the snapshot + Assert.IsNull(snapshot.TryGet(testKey)); + CollectionAssert.AreEqual(testValue, store.TryGet(testKey)); + Assert.AreEqual(false, snapshot.Contains(testKey)); + Assert.AreEqual(true, store.Contains(testKey)); + + snapshot.Dispose(); + } + + [TestMethod] + public void TestLevelDbMultiSnapshot() + { + using var store = levelDbStore.GetStore(path_leveldb); + + var snapshot = store.GetSnapshot(); + + var testKey = new byte[] { 0x01, 0x02, 0x03 }; + var testValue = new byte[] { 0x04, 0x05, 0x06 }; + + snapshot.Put(testKey, testValue); + snapshot.Commit(); + CollectionAssert.AreEqual(testValue, store.TryGet(testKey)); + + var snapshot2 = store.GetSnapshot(); + + // Data saved to the leveldb from snapshot1 shall be visible to snapshot2 but not visible to snapshot1 + CollectionAssert.AreEqual(testValue, snapshot2.TryGet(testKey)); + Assert.IsNull(snapshot.TryGet(testKey)); + + snapshot.Dispose(); + snapshot2.Dispose(); } [TestMethod] public void TestRocksDb() { - using var plugin = new RocksDBStore(); - TestPersistenceDelete(plugin.GetStore(path_rocksdb)); + TestPersistenceDelete(rocksDBStore.GetStore(path_rocksdb)); // Test all with the same store - TestStorage(plugin.GetStore(path_rocksdb)); + TestStorage(rocksDBStore.GetStore(path_rocksdb)); // Test with different storages - TestPersistenceWrite(plugin.GetStore(path_rocksdb)); - TestPersistenceRead(plugin.GetStore(path_rocksdb), true); - TestPersistenceDelete(plugin.GetStore(path_rocksdb)); - TestPersistenceRead(plugin.GetStore(path_rocksdb), false); + TestPersistenceWrite(rocksDBStore.GetStore(path_rocksdb)); + TestPersistenceRead(rocksDBStore.GetStore(path_rocksdb), true); + TestPersistenceDelete(rocksDBStore.GetStore(path_rocksdb)); + TestPersistenceRead(rocksDBStore.GetStore(path_rocksdb), false); + } + + [TestMethod] + public void TestRocksDbSnapshot() + { + using var store = rocksDBStore.GetStore(path_leveldb); + + var snapshot = store.GetSnapshot(); + + var testKey = new byte[] { 0x01, 0x02, 0x03 }; + var testValue = new byte[] { 0x04, 0x05, 0x06 }; + + snapshot.Put(testKey, testValue); + // Data saved to the leveldb snapshot shall not be visible + Assert.IsNull(snapshot.TryGet(testKey)); + Assert.IsNull(store.TryGet(testKey)); + + // Value is in the write batch, not visible to the store and snapshot + Assert.AreEqual(false, snapshot.Contains(testKey)); + Assert.AreEqual(false, store.Contains(testKey)); + + snapshot.Commit(); + + // After commit, the data shall be visible to the store but not to the snapshot + Assert.IsNull(snapshot.TryGet(testKey)); + CollectionAssert.AreEqual(testValue, store.TryGet(testKey)); + Assert.AreEqual(false, snapshot.Contains(testKey)); + Assert.AreEqual(true, store.Contains(testKey)); + + snapshot.Dispose(); + } + + [TestMethod] + public void TestRocksDbMultiSnapshot() + { + using var store = rocksDBStore.GetStore(path_leveldb); + + var snapshot = store.GetSnapshot(); + + var testKey = new byte[] { 0x01, 0x02, 0x03 }; + var testValue = new byte[] { 0x04, 0x05, 0x06 }; + + snapshot.Put(testKey, testValue); + snapshot.Commit(); + CollectionAssert.AreEqual(testValue, store.TryGet(testKey)); + + var snapshot2 = store.GetSnapshot(); + // Data saved to the leveldb from snapshot1 shall only be visible to snapshot2 + CollectionAssert.AreEqual(testValue, snapshot2.TryGet(testKey)); + + snapshot.Dispose(); + snapshot2.Dispose(); } /// diff --git a/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs b/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs index 907a5df2b9..f7bc1789dd 100644 --- a/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs +++ b/tests/Neo.UnitTests/Cryptography/UT_BloomFilter.cs @@ -39,6 +39,8 @@ public void TestBloomFIlterConstructorGetKMTweak() uint nTweak = 123456; Action action = () => new BloomFilter(m, n, nTweak); action.Should().Throw(); + action = () => new BloomFilter(m, n, nTweak, new byte[] { 0, 1, 2, 3, 4 }); + action.Should().Throw(); m = 7; n = -10; diff --git a/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs b/tests/Neo.UnitTests/Cryptography/UT_Cryptography_Helper.cs index 01022baab9..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/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/UT_IOHelper.cs b/tests/Neo.UnitTests/IO/UT_IOHelper.cs index 4b74a495b5..48453c992a 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; diff --git a/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs b/tests/Neo.UnitTests/Ledger/UT_Blockchain.cs index 491b4fe21e..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(); @@ -70,7 +70,7 @@ public void TestValidTransaction() // Make transaction - var tx = CreateValidTx(snapshot, walletA, acc.ScriptHash, 0); + var tx = TestUtils.CreateValidTx(snapshot, walletA, acc.ScriptHash, 0); senderProbe.Send(system.Blockchain, tx); senderProbe.ExpectMsg(p => p.Result == VerifyResult.Succeed); @@ -91,35 +91,11 @@ internal static StorageKey CreateStorageKey(byte prefix, byte[] key = null) }; } - private static Transaction CreateValidTx(DataCache snapshot, NEP6Wallet wallet, UInt160 account, uint nonce) - { - var tx = wallet.MakeTransaction(snapshot, new TransferOutput[] - { - new TransferOutput() - { - AssetId = NativeContract.GAS.Hash, - ScriptHash = account, - Value = new BigDecimal(BigInteger.One,8) - } - }, - account); - - tx.Nonce = nonce; - - var data = new ContractParametersContext(snapshot, tx, TestProtocolSettings.Default.Network); - Assert.IsNull(data.GetSignatures(tx.Sender)); - Assert.IsTrue(wallet.Sign(data)); - Assert.IsTrue(data.Completed); - Assert.AreEqual(1, data.GetSignatures(tx.Sender).Count()); - - tx.Witnesses = data.GetWitnesses(); - return tx; - } [TestMethod] public void TestMaliciousOnChainConflict() { - var snapshot = TestBlockchain.TheNeoSystem.GetSnapshot(); + var snapshot = TestBlockchain.TheNeoSystem.GetSnapshotCache(); var walletA = TestUtils.GenerateTestWallet("123"); var accA = walletA.CreateAccount(); var walletB = TestUtils.GenerateTestWallet("456"); @@ -141,9 +117,9 @@ public void TestMaliciousOnChainConflict() // Create transactions: // tx1 conflicts with tx2 and has the same sender (thus, it's a valid conflict and must prevent tx2 from entering the chain); // tx2 conflicts with tx3 and has different sender (thus, this conflict is invalid and must not prevent tx3 from entering the chain). - var tx1 = CreateValidTx(snapshot, walletA, accA.ScriptHash, 0); - var tx2 = CreateValidTx(snapshot, walletA, accA.ScriptHash, 1); - var tx3 = CreateValidTx(snapshot, walletB, accB.ScriptHash, 2); + var tx1 = TestUtils.CreateValidTx(snapshot, walletA, accA.ScriptHash, 0); + var tx2 = TestUtils.CreateValidTx(snapshot, walletA, accA.ScriptHash, 1); + var tx3 = TestUtils.CreateValidTx(snapshot, walletB, accB.ScriptHash, 2); tx1.Attributes = new TransactionAttribute[] { new Conflicts() { Hash = tx2.Hash }, new Conflicts() { Hash = tx3.Hash } }; @@ -170,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(); @@ -186,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_MemoryPool.cs b/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs index 1063413073..45cdee05c7 100644 --- a/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs +++ b/tests/Neo.UnitTests/Ledger/UT_MemoryPool.cs @@ -47,7 +47,7 @@ public static void TestSetup(TestContext ctx) private static DataCache GetSnapshot() { - return testBlockchain.StoreView.CreateSnapshot(); + return testBlockchain.StoreView.CloneCache(); } [TestInitialize] @@ -225,7 +225,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 +244,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 +266,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 +298,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 +306,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 +337,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 +372,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 +436,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 +457,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 } 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 7942847f94..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 }; - UT_SmartContractHelper.TransactionAdd(snapshot, state1, state2); + TestUtils.TransactionAdd(snapshotCache, state1, state2); TrimmedBlock tblock = GetTrimmedBlockWithNoTransaction(); tblock.Hashes = new UInt256[] { tx1.Hash, tx2.Hash }; - UT_SmartContractHelper.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 df33505965..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() }; - UT_SmartContractHelper.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_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_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/TestMemoryStoreProvider.cs b/tests/Neo.UnitTests/Persistence/TestMemoryStoreProvider.cs new file mode 100644 index 0000000000..59b8ac885d --- /dev/null +++ b/tests/Neo.UnitTests/Persistence/TestMemoryStoreProvider.cs @@ -0,0 +1,21 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestMemoryStoreProvider.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Persistence; + +namespace Neo.UnitTests.Persistence; + +public class TestMemoryStoreProvider(MemoryStore memoryStore) : IStoreProvider +{ + public MemoryStore MemoryStore { get; set; } = memoryStore; + public string Name => nameof(MemoryStore); + public IStore GetStore(string path) => MemoryStore; +} diff --git a/tests/Neo.UnitTests/Persistence/UT_MemoryClonedCache.cs b/tests/Neo.UnitTests/Persistence/UT_MemoryClonedCache.cs new file mode 100644 index 0000000000..56afdc31f1 --- /dev/null +++ b/tests/Neo.UnitTests/Persistence/UT_MemoryClonedCache.cs @@ -0,0 +1,127 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_MemoryClonedCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Persistence; +using Neo.SmartContract; + +namespace Neo.UnitTests.Persistence; + +/// +/// When adding data to `datacache` , +/// it gets passed to `snapshotcache` during commit. +/// If `snapshotcache` commits, the data is then passed +/// to the underlying store . +/// However, because snapshots are immutable, the new data +/// cannot be retrieved from the snapshot . +/// +/// When deleting data from `datacache` , +/// it won't exist in `datacache` upon commit, and therefore will be removed from `snapshotcache` . +/// Upon `snapshotcache` commit, the data is deleted from the store . +/// However, since the snapshot remains unchanged, the data still exists in the snapshot. +/// If you attempt to read this data from `datacache` or `snapshotcache` , +/// which do not have the data, they will retrieve it from the snapshot instead of the store. +/// Thus, they can still access data that has been deleted. +/// +[TestClass] +public class UT_MemoryClonedCache +{ + private MemoryStore _memoryStore; + private MemorySnapshot _snapshot; + private SnapshotCache _snapshotCache; + private DataCache _dataCache; + + [TestInitialize] + public void Setup() + { + _memoryStore = new MemoryStore(); + _snapshot = _memoryStore.GetSnapshot() as MemorySnapshot; + _snapshotCache = new SnapshotCache(_snapshot); + _dataCache = _snapshotCache.CreateSnapshot(); + } + + [TestCleanup] + public void CleanUp() + { + _dataCache.Commit(); + _snapshotCache.Commit(); + _memoryStore.Reset(); + } + + [TestMethod] + public void SingleSnapshotCacheTest() + { + var key1 = new KeyBuilder(0, 1); + var value1 = new StorageItem([0x03, 0x04]); + + Assert.IsFalse(_dataCache.Contains(key1)); + _dataCache.Add(key1, value1); + + Assert.IsTrue(_dataCache.Contains(key1)); + Assert.IsFalse(_snapshotCache.Contains(key1)); + Assert.IsFalse(_snapshot.Contains(key1.ToArray())); + Assert.IsFalse(_memoryStore.Contains(key1.ToArray())); + + // After the data cache is committed, it should be dropped + // so its value after the commit is meaningless and should not be used. + _dataCache.Commit(); + + Assert.IsTrue(_dataCache.Contains(key1)); + Assert.IsTrue(_snapshotCache.Contains(key1)); + Assert.IsFalse(_snapshot.Contains(key1.ToArray())); + Assert.IsFalse(_memoryStore.Contains(key1.ToArray())); + + // After the snapshot is committed, it should be dropped + // so its value after the commit is meaningless and should not be used. + _snapshotCache.Commit(); + + Assert.IsTrue(_dataCache.Contains(key1)); + Assert.IsTrue(_snapshotCache.Contains(key1)); + Assert.IsFalse(_snapshot.Contains(key1.ToArray())); + Assert.IsTrue(_memoryStore.Contains(key1.ToArray())); + + // Test delete + + // Reset the snapshot to make it accessible to the new value. + _snapshot = _memoryStore.GetSnapshot() as MemorySnapshot; + _snapshotCache = new SnapshotCache(_snapshot); + _dataCache = _snapshotCache.CreateSnapshot(); + + Assert.IsTrue(_dataCache.Contains(key1)); + _dataCache.Delete(key1); + + Assert.IsFalse(_dataCache.Contains(key1)); + Assert.IsTrue(_snapshotCache.Contains(key1)); + Assert.IsTrue(_snapshot.Contains(key1.ToArray())); + Assert.IsTrue(_memoryStore.Contains(key1.ToArray())); + + // After the data cache is committed, it should be dropped + // so its value after the commit is meaningless and should not be used. + _dataCache.Commit(); + + Assert.IsFalse(_dataCache.Contains(key1)); + Assert.IsFalse(_snapshotCache.Contains(key1)); + Assert.IsTrue(_snapshot.Contains(key1.ToArray())); + Assert.IsTrue(_memoryStore.Contains(key1.ToArray())); + + + // After the snapshot cache is committed, it should be dropped + // so its value after the commit is meaningless and should not be used. + _snapshotCache.Commit(); + + // The reason that datacache, snapshotcache still contains key1 is because + // they can not find the value from its cache, so they fetch it from the snapshot of the store. + Assert.IsTrue(_dataCache.Contains(key1)); + Assert.IsTrue(_snapshotCache.Contains(key1)); + Assert.IsTrue(_snapshot.Contains(key1.ToArray())); + Assert.IsFalse(_memoryStore.Contains(key1.ToArray())); + } +} diff --git a/tests/Neo.UnitTests/Persistence/UT_MemorySnapshot.cs b/tests/Neo.UnitTests/Persistence/UT_MemorySnapshot.cs new file mode 100644 index 0000000000..629e2bc374 --- /dev/null +++ b/tests/Neo.UnitTests/Persistence/UT_MemorySnapshot.cs @@ -0,0 +1,121 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_MemorySnapshot.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Persistence; +using System.Linq; + +namespace Neo.UnitTests.Persistence; + +[TestClass] +public class UT_MemorySnapshot +{ + private MemoryStore _memoryStore; + private MemorySnapshot _snapshot; + + [TestInitialize] + public void Setup() + { + _memoryStore = new MemoryStore(); + _snapshot = _memoryStore.GetSnapshot() as MemorySnapshot; + } + + [TestCleanup] + public void CleanUp() + { + _memoryStore.Reset(); + } + + [TestMethod] + public void SingleSnapshotTest() + { + var key1 = new byte[] { 0x01, 0x02 }; + var value1 = new byte[] { 0x03, 0x04 }; + + _snapshot.Delete(key1); + Assert.IsNull(_snapshot.TryGet(key1)); + + // Both Store and Snapshot can not get the value that are cached in the snapshot + _snapshot.Put(key1, value1); + Assert.IsNull(_snapshot.TryGet(key1)); + Assert.IsNull(_memoryStore.TryGet(key1)); + + _snapshot.Commit(); + + // After commit the snapshot, the value can be get from the store but still can not get from the snapshot + CollectionAssert.AreEqual(value1, _memoryStore.TryGet(key1)); + Assert.IsNull(_snapshot.TryGet(key1)); + + _snapshot.Delete(key1); + + // Deleted value can not be found from the snapshot but can still get from the store + // This is because snapshot has no key1 at all. + Assert.IsFalse(_snapshot.Contains(key1)); + Assert.IsTrue(_memoryStore.Contains(key1)); + + _snapshot.Commit(); + + // After commit the snapshot, the value can not be found from the store + Assert.IsFalse(_memoryStore.Contains(key1)); + + // Test seek in order + _snapshot.Put([0x00, 0x00, 0x04], [0x04]); + _snapshot.Put([0x00, 0x00, 0x00], [0x00]); + _snapshot.Put([0x00, 0x00, 0x01], [0x01]); + _snapshot.Put([0x00, 0x00, 0x02], [0x02]); + _snapshot.Put([0x00, 0x00, 0x03], [0x03]); + + // Can not get anything from the snapshot + var entries = _snapshot.Seek([0x00, 0x00, 0x02]).ToArray(); + Assert.AreEqual(0, entries.Length); + } + + [TestMethod] + public void MultiSnapshotTest() + { + var key1 = new byte[] { 0x01, 0x02 }; + var value1 = new byte[] { 0x03, 0x04 }; + + _snapshot.Delete(key1); + Assert.IsNull(_snapshot.TryGet(key1)); + + // Both Store and Snapshot can not get the value that are cached in the snapshot + _snapshot.Put(key1, value1); + // After commit the snapshot, the value can be get from the store but still can not get from the snapshot + // But can get the value from a new snapshot + _snapshot.Commit(); + var snapshot2 = _memoryStore.GetSnapshot(); + CollectionAssert.AreEqual(value1, _memoryStore.TryGet(key1)); + Assert.IsNull(_snapshot.TryGet(key1)); + CollectionAssert.AreEqual(value1, snapshot2.TryGet(key1)); + + _snapshot.Delete(key1); + + // Deleted value can not being found from the snapshot but can still get from the store and snapshot2 + Assert.IsFalse(_snapshot.Contains(key1)); + Assert.IsTrue(_memoryStore.Contains(key1)); + Assert.IsTrue(snapshot2.Contains(key1)); + + _snapshot.Commit(); + + // After commit the snapshot, the value can not be found from the store, but can be found in snapshots + // Cause snapshot1 or store can not change the status of snapshot2. + Assert.IsFalse(_memoryStore.Contains(key1)); + Assert.IsTrue(snapshot2.Contains(key1)); + Assert.IsFalse(_snapshot.Contains(key1)); + + // Add value via snapshot2 will not affect snapshot1 at all + snapshot2.Put(key1, value1); + snapshot2.Commit(); + Assert.IsNull(_snapshot.TryGet(key1)); + CollectionAssert.AreEqual(value1, snapshot2.TryGet(key1)); + } +} diff --git a/tests/Neo.UnitTests/Persistence/UT_MemorySnapshotCache.cs b/tests/Neo.UnitTests/Persistence/UT_MemorySnapshotCache.cs new file mode 100644 index 0000000000..8c4843c3f3 --- /dev/null +++ b/tests/Neo.UnitTests/Persistence/UT_MemorySnapshotCache.cs @@ -0,0 +1,134 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_MemorySnapshotCache.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Persistence; +using Neo.SmartContract; +using System.Linq; + +namespace Neo.UnitTests.Persistence; + +[TestClass] +public class UT_MemorySnapshotCache +{ + private MemoryStore _memoryStore; + private MemorySnapshot _snapshot; + private SnapshotCache _snapshotCache; + + [TestInitialize] + public void Setup() + { + _memoryStore = new MemoryStore(); + _snapshot = _memoryStore.GetSnapshot() as MemorySnapshot; + _snapshotCache = new SnapshotCache(_snapshot); + } + + [TestCleanup] + public void CleanUp() + { + _snapshotCache.Commit(); + _memoryStore.Reset(); + } + + [TestMethod] + public void SingleSnapshotCacheTest() + { + var key1 = new KeyBuilder(0, 1); + var value1 = new StorageItem([0x03, 0x04]); + + _snapshotCache.Delete(key1); + Assert.IsNull(_snapshotCache.TryGet(key1)); + + // Adding value to the snapshot cache will not affect the snapshot or the store + // But the snapshot cache itself can see the added item right after it is added. + _snapshotCache.Add(key1, value1); + + Assert.AreEqual(value1.Value, _snapshotCache.TryGet(key1).Value); + Assert.IsNull(_snapshot.TryGet(key1.ToArray())); + Assert.IsNull(_memoryStore.TryGet(key1.ToArray())); + + // After commit the snapshot cache, it works the same as commit the snapshot. + // the value can be get from the snapshot cache and store but still can not get from the snapshot + _snapshotCache.Commit(); + + Assert.AreEqual(value1.Value, _snapshotCache.TryGet(key1).Value); + Assert.IsFalse(_snapshot.Contains(key1.ToArray())); + Assert.IsTrue(_memoryStore.Contains(key1.ToArray())); + + // Test delete + + // Reset the snapshot to make it accessible to the new value. + _snapshot = _memoryStore.GetSnapshot() as MemorySnapshot; + _snapshotCache = new SnapshotCache(_snapshot); + + // Delete value to the snapshot cache will not affect the snapshot or the store + // But the snapshot cache itself can not see the added item. + _snapshotCache.Delete(key1); + + // Value is removed from the snapshot cache immediately + Assert.IsNull(_snapshotCache.TryGet(key1)); + // But the underline snapshot will not be changed. + Assert.IsTrue(_snapshot.Contains(key1.ToArray())); + // And the store is also not affected. + Assert.IsNotNull(_memoryStore.TryGet(key1.ToArray())); + + // commit the snapshot cache + _snapshotCache.Commit(); + + // Value is removed from both the store, but the snapshot and snapshot cache remains the same. + Assert.IsTrue(_snapshotCache.Contains(key1)); + Assert.IsTrue(_snapshot.Contains(key1.ToArray())); + Assert.IsFalse(_memoryStore.Contains(key1.ToArray())); + } + + [TestMethod] + public void MultiSnapshotCacheTest() + { + var key1 = new KeyBuilder(0, 1); + var value1 = new StorageItem([0x03, 0x04]); + + _snapshotCache.Delete(key1); + Assert.IsNull(_snapshotCache.TryGet(key1)); + + // Adding value to the snapshot cache will not affect the snapshot or the store + // But the snapshot cache itself can see the added item. + _snapshotCache.Add(key1, value1); + + // After commit the snapshot cache, it works the same as commit the snapshot. + // the value can be get from the snapshot cache but still can not get from the snapshot + _snapshotCache.Commit(); + + // Get a new snapshot cache to test if the value can be seen from the new snapshot cache + var snapshotCache2 = new SnapshotCache(_snapshot); + Assert.IsNull(snapshotCache2.TryGet(key1)); + Assert.IsFalse(_snapshot.Contains(key1.ToArray())); + + // Test delete + + // Reset the snapshot to make it accessible to the new value. + _snapshot = _memoryStore.GetSnapshot() as MemorySnapshot; + _snapshotCache = new SnapshotCache(_snapshot); + + // Delete value to the snapshot cache will affect the snapshot + // But the snapshot and store itself can still see the item. + _snapshotCache.Delete(key1); + + // Commiting the snapshot cache will change the store, but the existing snapshot remains same. + _snapshotCache.Commit(); + + // reset the snapshotcache2 to snapshot + snapshotCache2 = new SnapshotCache(_snapshot); + // Value is removed from the store, but the snapshot remains the same. + // thus the snapshot cache from the snapshot will remain the same. + Assert.IsNotNull(snapshotCache2.TryGet(key1)); + Assert.IsNull(_memoryStore.TryGet(key1.ToArray())); + } +} diff --git a/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs b/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs index b0f61791f6..133d9eb66c 100644 --- a/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs +++ b/tests/Neo.UnitTests/Persistence/UT_MemoryStore.cs @@ -10,6 +10,7 @@ // modifications are permitted. using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.IO; using Neo.Persistence; using Neo.SmartContract; using System; @@ -21,6 +22,22 @@ namespace Neo.UnitTests.Persistence [TestClass] public class UT_MemoryStore { + private NeoSystem _neoSystem; + private MemoryStore _memoryStore; + + [TestInitialize] + public void Setup() + { + _memoryStore = new MemoryStore(); + _neoSystem = new NeoSystem(TestProtocolSettings.Default, new TestMemoryStoreProvider(_memoryStore)); + } + + [TestCleanup] + public void CleanUp() + { + _memoryStore.Reset(); + } + [TestMethod] public void LoadStoreTest() { @@ -58,16 +75,59 @@ public void StoreTest() [TestMethod] public void NeoSystemStoreViewTest() { - var neoSystem = new NeoSystem(TestProtocolSettings.Default, new MemoryStoreProvider()); - Assert.IsNotNull(neoSystem.StoreView); - var store = neoSystem.StoreView; + Assert.IsNotNull(_neoSystem.StoreView); + 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. + Assert.IsNull(_memoryStore.TryGet(key.ToArray())); } + [TestMethod] + public void NeoSystemStoreAddTest() + { + var storeCache = _neoSystem.GetSnapshotCache(); + var key = new KeyBuilder(0, 0); + storeCache.Add(key, new StorageItem(UInt256.Zero.ToArray())); + storeCache.Commit(); + + CollectionAssert.AreEqual(UInt256.Zero.ToArray(), storeCache.TryGet(key).ToArray()); + } + + [TestMethod] + public void NeoSystemStoreGetAndChange() + { + var storeView = _neoSystem.GetSnapshotCache(); + var key = new KeyBuilder(1, 1); + var item = new StorageItem([1, 2, 3]); + storeView.Delete(key); + Assert.AreEqual(null, storeView.TryGet(key)); + storeView.Add(key, item); + CollectionAssert.AreEqual(new byte[] { 1, 2, 3 }, storeView.TryGet(key).ToArray()); + + var key2 = new KeyBuilder(1, 2); + var item2 = new StorageItem([4, 5, 6]); + storeView.Add(key2, item2); + CollectionAssert.AreEqual(key2.ToArray(), storeView.Seek(key2.ToArray(), SeekDirection.Backward).Select(u => u.Key).First().ToArray()); + CollectionAssert.AreEqual(key.ToArray(), storeView.Seek(key.ToArray(), SeekDirection.Backward).Select(u => u.Key).First().ToArray()); + + storeView.Delete(key); + storeView.Delete(key2); + + storeView.Add(new KeyBuilder(1, 0x000000), new StorageItem([0x00])); + storeView.Add(new KeyBuilder(1, 0x000001), new StorageItem([0x01])); + storeView.Add(new KeyBuilder(1, 0x000002), new StorageItem([0x02])); + storeView.Add(new KeyBuilder(1, 0x000003), new StorageItem([0x03])); + storeView.Add(new KeyBuilder(1, 0x000004), new StorageItem([0x04])); + + var entries = storeView.Seek([], SeekDirection.Backward).ToArray(); + // Memory store has different seek behavior than the snapshot + Assert.AreEqual(entries.Length, 37); + } } } diff --git a/tests/Neo.UnitTests/Plugins/TestPlugin.cs b/tests/Neo.UnitTests/Plugins/TestPlugin.cs index dde500b927..d25d1ca3f3 100644 --- a/tests/Neo.UnitTests/Plugins/TestPlugin.cs +++ b/tests/Neo.UnitTests/Plugins/TestPlugin.cs @@ -10,15 +10,60 @@ // modifications are permitted. using Microsoft.Extensions.Configuration; +using Neo.Ledger; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; using Neo.Plugins; +using System; +using System.Collections.Generic; namespace Neo.UnitTests.Plugins { - public class TestPlugin : Plugin + + internal class TestPluginSettings(IConfigurationSection section) : PluginSettings(section) + { + public static TestPluginSettings Default { get; private set; } + public static void Load(IConfigurationSection section) + { + Default = new TestPluginSettings(section); + } + } + internal class TestNonPlugin + { + public TestNonPlugin() + { + Blockchain.Committing += OnCommitting; + Blockchain.Committed += OnCommitted; + } + + private static void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + { + throw new NotImplementedException("Test exception from OnCommitting"); + } + + private static void OnCommitted(NeoSystem system, Block block) + { + throw new NotImplementedException("Test exception from OnCommitted"); + } + } + + + internal class TestPlugin : Plugin { - public TestPlugin() : base() { } + private readonly UnhandledExceptionPolicy _exceptionPolicy; + protected internal override UnhandledExceptionPolicy ExceptionPolicy => _exceptionPolicy; - protected override void Configure() { } + public TestPlugin(UnhandledExceptionPolicy exceptionPolicy = UnhandledExceptionPolicy.StopPlugin) + { + Blockchain.Committing += OnCommitting; + Blockchain.Committed += OnCommitted; + _exceptionPolicy = exceptionPolicy; + } + + protected override void Configure() + { + TestPluginSettings.Load(GetConfiguration()); + } public void LogMessage(string message) { @@ -36,5 +81,15 @@ public IConfigurationSection TestGetConfiguration() } protected override bool OnMessage(object message) => true; + + private void OnCommitting(NeoSystem system, Block block, DataCache snapshot, IReadOnlyList applicationExecutedList) + { + throw new NotImplementedException(); + } + + private void OnCommitted(NeoSystem system, Block block) + { + throw new NotImplementedException(); + } } } diff --git a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs index e5e8cb6e49..a76119f11b 100644 --- a/tests/Neo.UnitTests/Plugins/UT_Plugin.cs +++ b/tests/Neo.UnitTests/Plugins/UT_Plugin.cs @@ -11,15 +11,61 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Ledger; using Neo.Plugins; using System; +using System.Reflection; 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() + { + ClearEventHandlers(); + } + + [TestCleanup] + public void TestCleanup() + { + ClearEventHandlers(); + } + + private static void ClearEventHandlers() + { + ClearEventHandler("Committing"); + ClearEventHandler("Committed"); + } + + private static void ClearEventHandler(string eventName) + { + var eventInfo = typeof(Blockchain).GetEvent(eventName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + if (eventInfo == null) + { + return; + } + + var fields = typeof(Blockchain).GetFields(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public); + foreach (var field in fields) + { + if (field.FieldType == typeof(MulticastDelegate) || field.FieldType.BaseType == typeof(MulticastDelegate)) + { + var eventDelegate = (MulticastDelegate)field.GetValue(null); + if (eventDelegate != null && field.Name.Contains(eventName)) + { + foreach (var handler in eventDelegate.GetInvocationList()) + { + eventInfo.RemoveEventHandler(null, handler); + } + break; + } + } + } + } [TestMethod] public void TestGetConfigFile() @@ -47,7 +93,7 @@ public void TestGetVersion() [TestMethod] public void TestSendMessage() { - lock (locker) + lock (s_locker) { Plugin.Plugins.Clear(); Plugin.SendMessage("hey1").Should().BeFalse(); @@ -63,5 +109,109 @@ public void TestGetConfiguration() var pp = new TestPlugin(); pp.TestGetConfiguration().Key.Should().Be("PluginConfiguration"); } + + [TestMethod] + public void TestOnException() + { + _ = new TestPlugin(); + // Ensure no exception is thrown + try + { + Blockchain.InvokeCommitting(null, null, null, null); + Blockchain.InvokeCommitted(null, null); + } + catch (Exception ex) + { + Assert.Fail($"InvokeCommitting or InvokeCommitted threw an exception: {ex.Message}"); + } + + // Register TestNonPlugin that throws exceptions + _ = new TestNonPlugin(); + + // Ensure exception is thrown + Assert.ThrowsException(() => + { + Blockchain.InvokeCommitting(null, null, null, null); + }); + + Assert.ThrowsException(() => + { + Blockchain.InvokeCommitted(null, null); + }); + } + + [TestMethod] + public void TestOnPluginStopped() + { + var pp = new TestPlugin(); + Assert.AreEqual(false, pp.IsStopped); + // Ensure no exception is thrown + try + { + Blockchain.InvokeCommitting(null, null, null, null); + Blockchain.InvokeCommitted(null, null); + } + catch (Exception ex) + { + Assert.Fail($"InvokeCommitting or InvokeCommitted threw an exception: {ex.Message}"); + } + + Assert.AreEqual(true, pp.IsStopped); + } + + [TestMethod] + public void TestOnPluginStopOnException() + { + // pp will stop on exception. + var pp = new TestPlugin(); + Assert.AreEqual(false, pp.IsStopped); + // Ensure no exception is thrown + try + { + Blockchain.InvokeCommitting(null, null, null, null); + Blockchain.InvokeCommitted(null, null); + } + catch (Exception ex) + { + Assert.Fail($"InvokeCommitting or InvokeCommitted threw an exception: {ex.Message}"); + } + + Assert.AreEqual(true, pp.IsStopped); + + // pp2 will not stop on exception. + var pp2 = new TestPlugin(UnhandledExceptionPolicy.Ignore); + Assert.AreEqual(false, pp2.IsStopped); + // Ensure no exception is thrown + try + { + Blockchain.InvokeCommitting(null, null, null, null); + Blockchain.InvokeCommitted(null, null); + } + catch (Exception ex) + { + Assert.Fail($"InvokeCommitting or InvokeCommitted threw an exception: {ex.Message}"); + } + + Assert.AreEqual(false, pp2.IsStopped); + } + + [TestMethod] + public void TestOnNodeStopOnPluginException() + { + // node will stop on pp exception. + var pp = new TestPlugin(UnhandledExceptionPolicy.StopNode); + Assert.AreEqual(false, pp.IsStopped); + Assert.ThrowsException(() => + { + Blockchain.InvokeCommitting(null, null, null, null); + }); + + Assert.ThrowsException(() => + { + 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_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 7639377fe1..a670e3b4b7 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_ApplicationEngine.cs @@ -12,6 +12,9 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.SmartContract; +using Neo.SmartContract.Manifest; +using Neo.UnitTests.Extensions; +using Neo.VM; using System; using System.Collections.Immutable; using System.Linq; @@ -27,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"; @@ -63,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()); @@ -103,5 +106,103 @@ public void TestCheckingHardfork() (setting[sortedHardforks[i]] > setting[sortedHardforks[i + 1]]).Should().Be(false); } } + + [TestMethod] + public void TestSystem_Contract_Call_Permissions() + { + UInt160 scriptHash; + var snapshotCache = TestBlockchain.GetTestSnapshotCache(); + + // Setup: put a simple contract to the storage. + using (var script = new ScriptBuilder()) + { + // Push True on stack and return. + script.EmitPush(true); + script.Emit(OpCode.RET); + + // Mock contract and put it to the Managemant's storage. + scriptHash = script.ToArray().ToScriptHash(); + + snapshotCache.DeleteContract(scriptHash); + var contract = TestUtils.GetContract(script.ToArray(), TestUtils.CreateManifest("test", ContractParameterType.Any)); + contract.Manifest.Abi.Methods = new[] + { + new ContractMethodDescriptor + { + Name = "disallowed", + Parameters = new ContractParameterDefinition[]{} + }, + new ContractMethodDescriptor + { + Name = "test", + Parameters = new ContractParameterDefinition[]{} + } + }; + snapshotCache.AddContract(scriptHash, contract); + } + + // Disallowed method call. + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, null, ProtocolSettings.Default)) + using (var script = new ScriptBuilder()) + { + // Build call script calling disallowed method. + script.EmitDynamicCall(scriptHash, "disallowed"); + + // Mock executing state to be a contract-based. + engine.LoadScript(script.ToArray()); + engine.CurrentContext.GetState().Contract = new() + { + Manifest = new() + { + Abi = new() { }, + Permissions = new ContractPermission[] + { + new ContractPermission + { + Contract = ContractPermissionDescriptor.Create(scriptHash), + Methods = WildcardContainer.Create(new string[]{"test"}) // allowed to call only "test" method of the target contract. + } + } + } + }; + var currentScriptHash = engine.EntryScriptHash; + + Assert.AreEqual(VMState.FAULT, engine.Execute()); + Assert.IsTrue(engine.FaultException.ToString().Contains($"Cannot Call Method disallowed Of Contract {scriptHash.ToString()}")); + } + + // Allowed method call. + using (var engine = ApplicationEngine.Create(TriggerType.Application, null, snapshotCache, null, ProtocolSettings.Default)) + using (var script = new ScriptBuilder()) + { + // Build call script. + script.EmitDynamicCall(scriptHash, "test"); + + // Mock executing state to be a contract-based. + engine.LoadScript(script.ToArray()); + engine.CurrentContext.GetState().Contract = new() + { + Manifest = new() + { + Abi = new() { }, + Permissions = new ContractPermission[] + { + new ContractPermission + { + Contract = ContractPermissionDescriptor.Create(scriptHash), + Methods = WildcardContainer.Create(new string[]{"test"}) // allowed to call only "test" method of the target contract. + } + } + } + }; + var currentScriptHash = engine.EntryScriptHash; + + Assert.AreEqual(VMState.HALT, engine.Execute()); + Assert.AreEqual(1, engine.ResultStack.Count); + Assert.IsInstanceOfType(engine.ResultStack.Peek(), typeof(VM.Types.Boolean)); + var res = (VM.Types.Boolean)engine.ResultStack.Pop(); + Assert.IsTrue(res.GetBoolean()); + } + } } } 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 0e9916b7d5..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[] { @@ -71,12 +73,20 @@ public void Runtime_GetNotifications_Test() } } }; - snapshot.AddContract(scriptHash2, contract); + contract.Manifest.Permissions = new ContractPermission[] + { + new ContractPermission + { + Contract = ContractPermissionDescriptor.Create(scriptHash2), + Methods = WildcardContainer.Create(new string[]{"test"}) + } + }; + 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 @@ -93,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 @@ -133,6 +143,14 @@ public void Runtime_GetNotifications_Test() Parameters = System.Array.Empty() } } + }, + Permissions = new ContractPermission[] + { + new ContractPermission + { + Contract = ContractPermissionDescriptor.Create(scriptHash2), + Methods = WildcardContainer.Create(new string[]{"test"}) + } } } }; @@ -162,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 @@ -202,6 +220,14 @@ public void Runtime_GetNotifications_Test() Parameters = System.Array.Empty() } } + }, + Permissions = new ContractPermission[] + { + new ContractPermission + { + Contract = ContractPermissionDescriptor.Create(scriptHash2), + Methods = WildcardContainer.Create(new string[]{"test"}) + } } } }; @@ -230,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) @@ -266,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); @@ -414,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] @@ -422,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] @@ -440,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] @@ -452,7 +478,7 @@ public void TestBlockchain_GetTransactionHeight() BlockIndex = 0, Transaction = TestUtils.CreateRandomHashTransaction() }; - UT_SmartContractHelper.TransactionAdd(engine.Snapshot, state); + TestUtils.TransactionAdd(engine.SnapshotCache, state); using var script = new ScriptBuilder(); script.EmitDynamicCall(NativeContract.Ledger.Hash, "getTransactionHeight", state.Transaction.Hash); @@ -473,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] @@ -488,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(); } @@ -498,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(); } @@ -506,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 @@ -519,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 @@ -564,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 { @@ -575,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 }; @@ -594,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 { @@ -605,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 @@ -637,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); + 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 @@ -681,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] @@ -709,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_NotifyEventArgs.cs b/tests/Neo.UnitTests/SmartContract/UT_NotifyEventArgs.cs index fdaeb9106e..59afbbb760 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_NotifyEventArgs.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_NotifyEventArgs.cs @@ -13,6 +13,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Network.P2P.Payloads; using Neo.SmartContract; +using Neo.VM; +using Neo.VM.Types; namespace Neo.UnitTests.SmartContract { @@ -27,5 +29,37 @@ public void TestGetScriptContainer() NotifyEventArgs args = new NotifyEventArgs(container, script_hash, "Test", null); args.ScriptContainer.Should().Be(container); } + + + [TestMethod] + public void TestIssue3300() // https://github.com/neo-project/neo/issues/3300 + { + using var engine = ApplicationEngine.Create(TriggerType.Application, null, null, settings: TestProtocolSettings.Default, gas: 1100_00000000); + using (var script = new ScriptBuilder()) + { + // Build call script calling disallowed method. + script.Emit(OpCode.NOP); + // Mock executing state to be a contract-based. + engine.LoadScript(script.ToArray()); + } + + var ns = new Array(engine.ReferenceCounter); + for (var i = 0; i < 500; i++) + { + ns.Add(""); + }; + + var hash = UInt160.Parse("0x179ab5d297fd34ecd48643894242fc3527f42853"); + engine.SendNotification(hash, "Test", ns); + // This should have being 0, but we have optimized the vm to not clean the reference counter + // unless it is necessary, so the reference counter will be 1000. + // Same reason why its 1504 instead of 504. + Assert.AreEqual(1000, engine.ReferenceCounter.Count); + // This will make a deepcopy for the notification, along with the 500 state items. + engine.GetNotifications(hash); + // With the fix of issue 3300, the reference counter calculates not only + // the notifaction items, but also the subitems of the notification state. + Assert.AreEqual(1504, engine.ReferenceCounter.Count); + } } } diff --git a/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs b/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs index c14b15c241..42308d91f2 100644 --- a/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs +++ b/tests/Neo.UnitTests/SmartContract/UT_SmartContractHelper.cs @@ -27,9 +27,6 @@ namespace Neo.UnitTests.SmartContract [TestClass] public class UT_SmartContractHelper { - const byte Prefix_Block = 5; - const byte Prefix_BlockHash = 9; - const byte Prefix_Transaction = 11; [TestMethod] public void TestIsMultiSigContract() @@ -127,9 +124,9 @@ public void TestIsStandardContract() [TestMethod] public void TestVerifyWitnesses() { - var snapshot1 = TestBlockchain.GetTestSnapshot().CreateSnapshot(); + var snapshotCache1 = TestBlockchain.GetTestSnapshotCache().CreateSnapshot(); UInt256 index1 = UInt256.Parse("0xa400ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff01"); - BlocksAdd(snapshot1, index1, new TrimmedBlock() + TestUtils.BlocksAdd(snapshotCache1, index1, new TrimmedBlock() { Header = new Header { @@ -141,10 +138,10 @@ public void TestVerifyWitnesses() }, Hashes = new UInt256[1] { UInt256.Zero }, }); - 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() { @@ -158,14 +155,14 @@ public void TestVerifyWitnesses() }, Hashes = new UInt256[1] { UInt256.Zero }, }; - 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() { @@ -179,7 +176,7 @@ public void TestVerifyWitnesses() }, Hashes = new UInt256[1] { UInt256.Zero }, }; - BlocksAdd(snapshot3, index3, block3); + TestUtils.BlocksAdd(snapshotCache3, index3, block3); Header header3 = new() { PrevHash = index3, @@ -189,13 +186,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 @@ -205,33 +202,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)); - } - - private static void BlocksDelete(DataCache snapshot, UInt256 hash) - { - snapshot.Delete(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, hash)); - snapshot.Delete(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash)); - } - - public static void TransactionAdd(DataCache snapshot, params TransactionState[] txs) - { - foreach (TransactionState tx in txs) - { - snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, tx.Transaction.Hash), new StorageItem(tx)); - } - } - - public static void BlocksAdd(DataCache snapshot, UInt256 hash, TrimmedBlock block) - { - snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, block.Index), new StorageItem(hash.ToArray())); - snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash), new StorageItem(block.ToArray())); + 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 0a98b5fd66..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; - UT_SmartContractHelper.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); + 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 new file mode 100644 index 0000000000..bed85131e6 --- /dev/null +++ b/tests/Neo.UnitTests/TestUtils.Block.cs @@ -0,0 +1,198 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TestUtils.Block.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Akka.Util.Internal; +using Neo.Cryptography; +using Neo.IO; +using Neo.Network.P2P.Payloads; +using Neo.Persistence; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using Neo.Wallets.NEP6; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Neo.UnitTests; + +public partial class TestUtils +{ + const byte Prefix_Block = 5; + const byte Prefix_BlockHash = 9; + const byte Prefix_Transaction = 11; + const byte Prefix_CurrentBlock = 12; + + /// + /// Test Util function SetupHeaderWithValues + /// + /// The snapshot of the current storage provider. Can be null. + /// The header to be assigned + /// PrevHash + /// MerkleRoot + /// NextConsensus + /// Timestamp + /// Index + /// Nonce + /// Witness + public static void SetupHeaderWithValues(DataCache snapshot, Header header, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out ulong nonceVal, out uint indexVal, out Witness scriptVal) + { + header.PrevHash = val256; + header.MerkleRoot = merkRootVal = UInt256.Parse("0x6226416a0e5aca42b5566f5a19ab467692688ba9d47986f6981a7f747bba2772"); + header.Timestamp = timestampVal = new DateTime(2024, 06, 05, 0, 33, 1, 001, DateTimeKind.Utc).ToTimestampMS(); + if (snapshot != null) + header.Index = indexVal = NativeContract.Ledger.CurrentIndex(snapshot) + 1; + else + header.Index = indexVal = 0; + header.Nonce = nonceVal = 0; + header.NextConsensus = val160 = UInt160.Zero; + header.Witness = scriptVal = new Witness + { + InvocationScript = Array.Empty(), + VerificationScript = new[] { (byte)OpCode.PUSH1 } + }; + } + + public static void SetupBlockWithValues(DataCache snapshot, Block block, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out ulong nonceVal, out uint indexVal, out Witness scriptVal, out Transaction[] transactionsVal, int numberOfTransactions) + { + Header header = new Header(); + SetupHeaderWithValues(snapshot, header, val256, out merkRootVal, out val160, out timestampVal, out nonceVal, out indexVal, out scriptVal); + + transactionsVal = new Transaction[numberOfTransactions]; + if (numberOfTransactions > 0) + { + for (int i = 0; i < numberOfTransactions; i++) + { + transactionsVal[i] = GetTransaction(UInt160.Zero); + } + } + + block.Header = header; + block.Transactions = transactionsVal; + + header.MerkleRoot = merkRootVal = MerkleTree.ComputeRoot(block.Transactions.Select(p => p.Hash).ToArray()); + } + + public static Block CreateBlockWithValidTransactions(DataCache snapshot, NEP6Wallet wallet, WalletAccount account, int numberOfTransactions) + { + var transactions = new List(); + for (var i = 0; i < numberOfTransactions; i++) + { + transactions.Add(CreateValidTx(snapshot, wallet, account)); + } + + return CreateBlockWithValidTransactions(snapshot, account, transactions.ToArray()); + } + + public static Block CreateBlockWithValidTransactions(DataCache snapshot, WalletAccount account, Transaction[] transactions) + { + var block = new Block(); + var header = new Header(); + var state = snapshot.TryGet(NativeContract.Ledger.CreateStorageKey(Prefix_CurrentBlock)).GetInteroperable(); + SetupHeaderWithValues(snapshot, header, state.Hash, out _, out _, out _, out _, out _, out _); + + block.Header = header; + block.Transactions = transactions; + + header.MerkleRoot = MerkleTree.ComputeRoot(block.Transactions.Select(p => p.Hash).ToArray()); + var contract = Contract.CreateMultiSigContract(1, TestProtocolSettings.SoleNode.StandbyCommittee); + var sc = new ContractParametersContext(snapshot, header, TestProtocolSettings.SoleNode.Network); + var signature = header.Sign(account.GetKey(), TestProtocolSettings.SoleNode.Network); + sc.AddSignature(contract, TestProtocolSettings.SoleNode.StandbyCommittee[0], signature.ToArray()); + block.Header.Witness = sc.GetWitnesses()[0]; + + return block; + } + + public static void BlocksDelete(DataCache snapshot, UInt256 hash) + { + snapshot.Delete(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, hash)); + snapshot.Delete(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash)); + } + + public static void TransactionAdd(DataCache snapshot, params TransactionState[] txs) + { + foreach (var tx in txs) + { + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Transaction, tx.Transaction.Hash), new StorageItem(tx)); + } + } + + public static void BlocksAdd(DataCache snapshot, UInt256 hash, TrimmedBlock block) + { + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, block.Index), new StorageItem(hash.ToArray())); + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash), new StorageItem(block.ToArray())); + + var state = snapshot.GetAndChange(NativeContract.Ledger.CreateStorageKey(Prefix_CurrentBlock), () => new StorageItem(new HashIndexState())).GetInteroperable(); + state.Hash = hash; + state.Index = block.Index; + } + + public static void BlocksAdd(DataCache snapshot, UInt256 hash, Block block) + { + + block.Transactions.ForEach(tx => + { + var state = new TransactionState + { + BlockIndex = block.Index, + Transaction = tx + }; + TransactionAdd(snapshot, state); + }); + + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_BlockHash, block.Index), new StorageItem(hash.ToArray())); + snapshot.Add(NativeContract.Ledger.CreateStorageKey(Prefix_Block, hash), new StorageItem(block.ToTrimmedBlock().ToArray())); + var state = snapshot.GetAndChange(NativeContract.Ledger.CreateStorageKey(Prefix_CurrentBlock), () => new StorageItem(new HashIndexState())).GetInteroperable(); + state.Hash = hash; + state.Index = block.Index; + } + + public static string CreateInvalidBlockFormat() + { + // Create a valid block + var validBlock = new Block + { + Header = new Header + { + Version = 0, + PrevHash = UInt256.Zero, + MerkleRoot = UInt256.Zero, + Timestamp = 0, + Index = 0, + NextConsensus = UInt160.Zero, + Witness = new Witness { InvocationScript = Array.Empty(), VerificationScript = Array.Empty() } + }, + Transactions = [] + }; + + // Serialize the valid block + byte[] validBlockBytes = validBlock.ToArray(); + + // Corrupt the serialized data + // For example, we can truncate the data by removing the last few bytes + byte[] invalidBlockBytes = new byte[validBlockBytes.Length - 5]; + Array.Copy(validBlockBytes, invalidBlockBytes, invalidBlockBytes.Length); + + // Convert the corrupted data to a Base64 string + return Convert.ToBase64String(invalidBlockBytes); + } + + public static TrimmedBlock ToTrimmedBlock(this Block block) + { + return new TrimmedBlock + { + Header = block.Header, + Hashes = block.Transactions.Select(p => p.Hash).ToArray() + }; + } +} diff --git a/tests/Neo.UnitTests/TestUtils.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..f6d6ebd4b5 --- /dev/null +++ b/tests/Neo.UnitTests/TestUtils.Transaction.cs @@ -0,0 +1,231 @@ +// 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.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 f8f63de53a..a9ad4d8ff7 100644 --- a/tests/Neo.UnitTests/TestUtils.cs +++ b/tests/Neo.UnitTests/TestUtils.cs @@ -10,70 +10,42 @@ // modifications are permitted. using FluentAssertions; +using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Cryptography; using Neo.Cryptography.ECC; using Neo.IO; using Neo.Json; using Neo.Network.P2P.Payloads; +using Neo.Persistence; using Neo.SmartContract; using Neo.SmartContract.Manifest; using Neo.SmartContract.Native; using Neo.VM; +using Neo.Wallets; using Neo.Wallets.NEP6; using System; +using System.Collections.Generic; using System.Linq; +using System.Numerics; namespace Neo.UnitTests { - public static class TestUtils + public static partial class TestUtils { public static readonly Random TestRandom = new Random(1337); // use fixed seed for guaranteed determinism - public static ContractManifest CreateDefaultManifest() + public static UInt256 RandomUInt256() { - 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 - }; + byte[] data = new byte[32]; + TestRandom.NextBytes(data); + return new UInt256(data); } - public static ContractManifest CreateManifest(string method, ContractParameterType returnType, params ContractParameterType[] parameterTypes) + public static UInt160 RandomUInt160() { - 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; + byte[] data = new byte[20]; + TestRandom.NextBytes(data); + return new UInt160(data); } public static StorageKey CreateStorageKey(this NativeContract contract, byte prefix, ISerializable key = null) @@ -111,66 +83,6 @@ public static NEP6Wallet GenerateTestWallet(string password) return new NEP6Wallet(null, password, TestProtocolSettings.Default, wallet); } - 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() - } } - }; - } - - internal 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 @@ -188,70 +100,22 @@ internal static StorageKey GetStorageKey(int id, byte[] keyValue) }; } - /// - /// Test Util function SetupHeaderWithValues - /// - /// The header to be assigned - /// PrevHash - /// MerkleRoot - /// NextConsensus - /// Timestamp - /// Index - /// Nonce - /// Witness - public static void SetupHeaderWithValues(Header header, UInt256 val256, out UInt256 merkRootVal, out UInt160 val160, out ulong timestampVal, out ulong nonceVal, out uint indexVal, out Witness scriptVal) + public static void StorageItemAdd(DataCache snapshot, int id, byte[] keyValue, byte[] value) { - header.PrevHash = val256; - header.MerkleRoot = merkRootVal = UInt256.Parse("0x6226416a0e5aca42b5566f5a19ab467692688ba9d47986f6981a7f747bba2772"); - header.Timestamp = timestampVal = new DateTime(1980, 06, 01, 0, 0, 1, 001, DateTimeKind.Utc).ToTimestampMS(); // GMT: Sunday, June 1, 1980 12:00:01.001 AM - header.Index = indexVal = 0; - header.Nonce = nonceVal = 0; - header.NextConsensus = val160 = UInt160.Zero; - header.Witness = scriptVal = new Witness + snapshot.Add(new StorageKey { - InvocationScript = new byte[0], - VerificationScript = new[] { (byte)OpCode.PUSH1 } - }; + Id = id, + Key = keyValue + }, new StorageItem(value)); } - 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 FillMemoryPool(DataCache snapshot, NeoSystem system, NEP6Wallet wallet, WalletAccount account) { - Header header = new Header(); - SetupHeaderWithValues(header, val256, out merkRootVal, out val160, out timestampVal, out nonceVal, out indexVal, out scriptVal); - - transactionsVal = new Transaction[numberOfTransactions]; - if (numberOfTransactions > 0) + for (int i = 0; i < system.Settings.MemoryPoolMaxTransactions; i++) { - for (int i = 0; i < numberOfTransactions; i++) - { - transactionsVal[i] = GetTransaction(UInt160.Zero); - } + var tx = CreateValidTx(snapshot, wallet, account); + system.MemPool.TryAdd(tx, snapshot); } - - block.Header = header; - block.Transactions = transactionsVal; - - header.MerkleRoot = merkRootVal = MerkleTree.ComputeRoot(block.Transactions.Select(p => p.Hash).ToArray()); - } - - public static Transaction CreateRandomHashTransaction() - { - var randomBytes = new byte[16]; - TestRandom.NextBytes(randomBytes); - return new Transaction - { - 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] - } - } - }; } 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..413c9dcf31 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,22 +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() { @@ -162,22 +147,6 @@ 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() { @@ -190,16 +159,6 @@ public void TestGetVersion() version.Should().Be("0.0.0"); } - [TestMethod] - public void TestToByteArrayStandard() - { - BigInteger number = BigInteger.Zero; - Assert.AreEqual("", number.ToByteArrayStandard().ToHexString()); - - number = BigInteger.One; - Assert.AreEqual("01", number.ToByteArrayStandard().ToHexString()); - } - [TestMethod] public void TestNextBigIntegerForRandom() { diff --git a/tests/Neo.UnitTests/UT_ProtocolSettings.cs b/tests/Neo.UnitTests/UT_ProtocolSettings.cs index e5d829ef8b..97a36e5907 100644 --- a/tests/Neo.UnitTests/UT_ProtocolSettings.cs +++ b/tests/Neo.UnitTests/UT_ProtocolSettings.cs @@ -52,7 +52,7 @@ public void TestGetMillisecondsPerBlock() [TestMethod] public void HardForkTestBAndNotA() { - string json = CreateHKSettings("\"HF_Basilisk\": 4120000"); + string json = CreateHFSettings("\"HF_Basilisk\": 4120000"); var file = Path.GetTempFileName(); File.WriteAllText(file, json); @@ -74,7 +74,7 @@ public void HardForkTestBAndNotA() [TestMethod] public void HardForkTestAAndNotB() { - string json = CreateHKSettings("\"HF_Aspidochelone\": 0"); + string json = CreateHFSettings("\"HF_Aspidochelone\": 0"); var file = Path.GetTempFileName(); File.WriteAllText(file, json); @@ -96,7 +96,7 @@ public void HardForkTestAAndNotB() [TestMethod] public void HardForkTestNone() { - string json = CreateHKSettings(""); + string json = CreateHFSettings(""); var file = Path.GetTempFileName(); File.WriteAllText(file, json); @@ -117,14 +117,14 @@ public void HardForkTestNone() [TestMethod] public void HardForkTestAMoreThanB() { - string json = CreateHKSettings("\"HF_Aspidochelone\": 4120001, \"HF_Basilisk\": 4120000"); + string json = CreateHFSettings("\"HF_Aspidochelone\": 4120001, \"HF_Basilisk\": 4120000"); var file = Path.GetTempFileName(); File.WriteAllText(file, json); Assert.ThrowsException(() => ProtocolSettings.Load(file, false)); File.Delete(file); } - internal static string CreateHKSettings(string hf) + internal static string CreateHFSettings(string hf) { return @" { diff --git a/tests/Neo.UnitTests/VM/UT_Helper.cs b/tests/Neo.UnitTests/VM/UT_Helper.cs index c5bf61d152..3e83256669 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; @@ -151,7 +153,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 +402,7 @@ public void TestEmitPush3() TestEmitPush3Byte(); TestEmitPush3Short(); TestEmitPush3Ushort(); + TestEmitPush3Char(); TestEmitPush3Int(); TestEmitPush3Uint(); TestEmitPush3Long(); @@ -471,6 +474,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 +642,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_NEP6Wallet.cs b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs index 71914c86f0..a914b235b7 100644 --- a/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs +++ b/tests/Neo.UnitTests/Wallets/NEP6/UT_NEP6Wallet.cs @@ -89,10 +89,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/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); + } } }