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
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