diff --git a/.editorconfig b/.editorconfig index b54df7ecd37..1b960db343d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,7 +9,7 @@ indent_style = space insert_final_newline = true trim_trailing_whitespace = true -file_header_template = SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited\nSPDX-License-Identifier: LGPL-3.0-only +file_header_template = SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited\nSPDX-License-Identifier: LGPL-3.0-only [*.py] indent_size = 4 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index e1724408447..00000000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,251 +0,0 @@ -# Copilot Instructions for Nethermind - -## Repository Overview - -Nethermind is an industry-leading Ethereum execution client built on .NET, designed for high-performance syncing and tip-of-chain processing. This enterprise-grade blockchain client features a modular architecture with plugin system support, serving multiple networks including Ethereum, Gnosis, Optimism, Base, Taiko, World Chain, Linea, and Energy Web. - -**Repository Characteristics:** -- **Size:** Large-scale enterprise codebase with 100+ C# projects -- **Language:** C# with .NET 9.0 target framework -- **Architecture:** Modular design with plugin-based extensibility -- **Type:** High-performance blockchain execution client -- **License:** LGPL-3.0-only - -## General - -- Make only high confidence suggestions when reviewing code changes. -- Always use the latest version C#, currently C# 13 features. -- Never change global.json unless explicitly asked to. -- Never change package.json or package-lock.json files unless explicitly asked to. -- Never change NuGet.config files unless explicitly asked to. -- Always trim trailing whitespace, and do not have whitespace on otherwise empty lines. - -**Any code you commit SHOULD compile, and new and existing tests related to the change SHOULD pass.** - -You MUST make your best effort to ensure your changes satisfy those criteria before committing. If for any reason you were unable to build or test the changes, you MUST report that. You MUST NOT claim success unless all builds and tests pass as described above. - -You MUST follow all code-formatting and naming conventions defined in [`.editorconfig`](/.editorconfig). - -In addition to the rules enforced by `.editorconfig`, you SHOULD: - -- Prefer file-scoped namespace declarations and single-line using directives; however do not change the type of namespace format in an existing file unless specifically asked. -- Ensure that the final return statement of a method is on its own line. -- Use pattern matching and switch expressions wherever possible. -- Use `nameof` instead of string literals when referring to member names. -- Always use `is null` or `is not null` instead of `== null` or `!= null`. -- Trust the C# null annotations and don't add null checks when the type system says a value cannot be null. -- Prefer `?.` if applicable (e.g. `scope?.Dispose()`). -- Use `ObjectDisposedException.ThrowIf` where applicable. -- When adding new unit tests, strongly prefer to add them to existing test code files rather than creating new code files. -- If you add new code files, ensure they are listed in the csproj file (if other files in that folder are listed there) so they build. -- When running tests, if possible use filters and check test run counts, or look at test logs, to ensure they actually ran. -- Do not finish work with any tests commented out or disabled that were not previously commented out or disabled. -- When writing tests, do not emit "Act", "Arrange" or "Assert" comments. -- Copy existing style in nearby files for test method names and capitalization. -- Provide code comments when helpful to explain why something is being done; however do not comment what is obvious and just a repeation of the code line. -- Ensure that XML doc comments are created for any public APIs. -- Do NOT use #regions. -- Perfer low allocation and higher performance code. - ---- - -## Build Requirements and Setup - -### Prerequisites - -**CRITICAL:** Always install .NET SDK 9.0.x before building. The project requires specific .NET version compatibility. - -```bash -# Install .NET 9.0 (if not available via package manager) -curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel 9.0 -export PATH="$HOME/.dotnet:$PATH" - -# Verify installation -dotnet --version # Should show 9.0.x -``` - -### Build Instructions - -**Complete build sequence (always follow this order):** - -```bash -# 1. Navigate to main source directory -cd src/Nethermind - -# 2. Build main solution (takes ~4 minutes) -dotnet build Nethermind.slnx -c Release - -# 3. Build Ethereum Foundation tests (if needed) -dotnet build EthereumTests.slnx -c Release - -# 4. Build benchmarks (if needed) -dotnet build Benchmarks.slnx -c Release -``` - -### Testing - -**Run tests in this order to ensure dependencies are built:** - -```bash -# Core functionality tests (fast, ~15 seconds) -dotnet test Nethermind.Core.Test/Nethermind.Core.Test.csproj - -# Full Nethermind test suite -dotnet test Nethermind.slnx -c Release - -# Ethereum Foundation tests (comprehensive, takes longer) -dotnet test EthereumTests.slnx -c Release -``` - -### Code Formatting and Validation - -**ALWAYS run before creating PRs:** - -```bash -# Check code formatting (required by CI) -dotnet format whitespace src/Nethermind/ --folder --verify-no-changes - -# Fix formatting issues -dotnet format whitespace src/Nethermind/ --folder -``` - -### Running the Application - -```bash -# From repository root -cd src/Nethermind/Nethermind.Runner - -# Run with mainnet configuration -dotnet run -c release -- -c mainnet --data-dir path/to/data/dir - -# Debug mode -dotnet run -c debug -- -c mainnet -``` - -## Project Architecture and Layout - -### Solution Structure - -The codebase is organized into three main solutions: - -- **`src/Nethermind/Nethermind.slnx`** - Main application and libraries -- **`src/Nethermind/EthereumTests.slnx`** - Ethereum Foundation test suite -- **`src/Nethermind/Benchmarks.slnx`** - Performance benchmarking tools - -### Key Directories - -``` -src/Nethermind/ -├── Nethermind.Runner/ # Main executable entry point -├── Nethermind.Core/ # Core types and utilities -├── Nethermind.Blockchain/ # Block processing logic -├── Nethermind.Consensus.* # Consensus mechanisms (Ethash, AuRa, Clique) -├── Nethermind.Synchronization/ # Node synchronization -├── Nethermind.Network*/ # P2P networking stack -├── Nethermind.JsonRpc/ # JSON-RPC API layer -├── Nethermind.State/ # State management -├── Nethermind.TxPool/ # Transaction pool -├── Nethermind.Evm/ # Ethereum Virtual Machine -└── *Test/ # Test projects (suffix pattern) -``` - -### Configuration Files - -- **`global.json`** - .NET SDK version requirement (9.0.x) -- **`Directory.Build.props`** - MSBuild properties and compilation settings -- **`.editorconfig`** - Code style rules (enforced in CI) -- **`nuget.config`** - NuGet package source configuration -- **`src/Nethermind/Directory.Build.props`** - Project-specific build properties - -### Critical Build Dependencies - -1. **TreatWarningsAsErrors:** Project configured to treat warnings as errors -2. **InvariantGlobalization:** Enabled for consistent behavior across locales -3. **UseArtifactsOutput:** Build outputs go to `artifacts/` directory -4. **Git submodules:** May be required for some test suites (`git clone --recursive`) - -## Continuous Integration Validation - -The project uses GitHub Actions with these key workflows: - -### Pre-commit Checks -```bash -# Replicate CI formatting check locally -dotnet format whitespace src/Nethermind/ --folder --verify-no-changes - -# Replicate CI build checks -dotnet build src/Nethermind/Nethermind.slnx -c Release -dotnet build src/Nethermind/Nethermind.slnx -c Debug -``` - -### Test Validation -```bash -# Core test matrix (replicates CI) -dotnet test Nethermind.Core.Test/Nethermind.Core.Test.csproj -dotnet test EthereumTests.slnx -c Release # Comprehensive test suite -``` - -## Common Development Workflows - -### Making Code Changes - -1. **Always run formatting first:** `dotnet format whitespace src/Nethermind/ --folder` -2. **Build incrementally:** Start with specific project if working on single component -3. **Test locally:** Run relevant test project before full suite -4. **Validate CI requirements:** Run formatting check before committing - -### Performance Considerations - -- **Full build time:** ~4 minutes for Release configuration -- **Test execution:** Core tests ~15 seconds, full suite several minutes -- **Memory usage:** Large solution requires adequate system memory -- **Parallel builds:** MSBuild automatically parallelizes where possible - -### File Headers - -All source files must include this header: -```csharp -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only -``` - -## Troubleshooting Common Issues - -### .NET SDK Version Mismatch -``` -Error: A compatible .NET SDK was not found. Requested SDK version: 9.0.2 -``` -**Solution:** Install .NET 9.0.x SDK as shown in prerequisites section. - -### Build Timeout in CI -**Cause:** Full builds take 4+ minutes -**Solution:** Use `timeout: 600` for build commands in automation - -### Memory Issues During Build -**Cause:** Large solution with many projects -**Solution:** Ensure adequate system memory (8GB+ recommended) - -### Code Formatting Failures -``` -Error: Fix whitespace formatting by running: dotnet format whitespace -``` -**Solution:** Run `dotnet format whitespace src/Nethermind/ --folder` before committing - -## Docker Development - -```bash -# Build Docker image directly from repository -docker build https://github.com/nethermindeth/nethermind.git -t nethermind - -# Run containerized -docker run -d nethermind -c mainnet -``` - -## Trust These Instructions - -These instructions are validated against the current codebase. Only search for additional information if: -- Instructions are incomplete for your specific task -- You encounter errors not covered in troubleshooting -- Working with components not mentioned in the architecture section - -When in doubt, start with the build sequence and test a small component first before attempting larger changes. \ No newline at end of file diff --git a/.github/workflows/build-nethermind-packages.yml b/.github/workflows/build-nethermind-packages.yml index 030e1905e2c..a3e8263dacf 100644 --- a/.github/workflows/build-nethermind-packages.yml +++ b/.github/workflows/build-nethermind-packages.yml @@ -11,7 +11,6 @@ jobs: PACKAGE_DIR: pkg PACKAGE_RETENTION: 7 PUB_DIR: pub - SCRIPTS_PATH: ${{ github.workspace }}/scripts/build steps: - name: Check out repository uses: actions/checkout@v6 @@ -29,16 +28,14 @@ jobs: - name: Build Nethermind.Runner run: | - mkdir $GITHUB_WORKSPACE/$PUB_DIR - - docker build . -t nethermind-build -f $SCRIPTS_PATH/Dockerfile \ + mkdir $PUB_DIR + docker build . -t nethermind-build -f scripts/build/Dockerfile \ --build-arg COMMIT_HASH=$GITHUB_SHA \ --build-arg SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) - - docker run --rm --mount type=bind,source=$GITHUB_WORKSPACE/$PUB_DIR,target=/output nethermind-build + docker run --rm --mount type=bind,source=./$PUB_DIR,target=/output nethermind-build - name: Archive packages - run: $SCRIPTS_PATH/archive.sh + run: scripts/build/archive.sh - name: Upload Nethermind Linux arm64 package uses: actions/upload-artifact@v4 diff --git a/.github/workflows/ci-taiko.yml b/.github/workflows/ci-taiko.yml new file mode 100644 index 00000000000..d01e2069241 --- /dev/null +++ b/.github/workflows/ci-taiko.yml @@ -0,0 +1,173 @@ +name: "Nethermind/Ethereum Taiko Client CI Tests" + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths: + - "src/Nethermind/Nethermind.Taiko/**" + - ".github/workflows/ci-taiko.yml" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + integration_tests: + if: >- + ${{ github.event.pull_request.draft == false + && !startsWith(github.head_ref, 'release-please') }} + name: Integration tests + runs-on: [ubuntu-latest] + timeout-minutes: 30 + env: + OLD_FORK_TAIKO_MONO_DIR: old-fork-taiko-mono + TAIKO_MONO_MAIN_DIR: taiko-mono-main + PACAYA_FORK_TAIKO_MONO_DIR: pacaya-fork-taiko-mono + SHASTA_FORK_TAIKO_MONO_DIR: shasta-fork-taiko-mono + + strategy: + matrix: + execution_node: [l2_nmc] + + steps: + - uses: actions/checkout@v6 + + - uses: actions/checkout@v6 + with: + repository: NethermindEth/surge-taiko-mono + path: ${{ env.TAIKO_MONO_MAIN_DIR }} + ref: surge-shasta + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: ${{ env.TAIKO_MONO_MAIN_DIR }}/go.mod + cache: true + + - name: Set up Git to use HTTPS + shell: bash + run: | + git config --global url."https://github.com/".insteadOf "git@github.com:" + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + run_install: false + + - name: Install Node.js + uses: actions/setup-node@v5 + with: + node-version: 20 + cache: pnpm + cache-dependency-path: ${{ env.TAIKO_MONO_MAIN_DIR }}/pnpm-lock.yaml + + - name: Install dependencies + working-directory: ${{ env.TAIKO_MONO_MAIN_DIR }} + shell: bash + run: pnpm install + + - uses: actions/checkout@v6 + with: + repository: taikoxyz/taiko-mono + path: >- + ${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, + env.PACAYA_FORK_TAIKO_MONO_DIR) }} + ref: taiko-alethia-protocol-v2.3.0-devnet-shasta-test + + - uses: actions/checkout@v6 + with: + repository: taikoxyz/taiko-mono + path: >- + ${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, + env.SHASTA_FORK_TAIKO_MONO_DIR) }} + ref: taiko-alethia-protocol-v3.0.0 + + - name: Install pnpm dependencies for pacaya fork taiko-mono + working-directory: >- + ${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, + env.PACAYA_FORK_TAIKO_MONO_DIR) }} + run: cd ./packages/protocol && pnpm install + + - name: Install pnpm dependencies for shasta fork taiko-mono + working-directory: >- + ${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, + env.SHASTA_FORK_TAIKO_MONO_DIR) }} + run: cd ./packages/protocol && pnpm install + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker Build Nethermind Client + run: | + image_name="nethermindeth/nethermind" + image_tag="${GITHUB_SHA:0:8}" + full_image="${image_name}:${image_tag}" + + echo "Building Docker image: ${full_image}" + + docker buildx build . \ + --platform linux/amd64 \ + -f Dockerfile \ + -t "${full_image}" \ + --load \ + --build-arg BUILD_CONFIG=release \ + --build-arg CI=true \ + --build-arg COMMIT_HASH=${{ github.sha }} \ + --build-arg SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) + + echo "IMAGE_TAG=${full_image}" >> $GITHUB_ENV + + echo "Verifying image exists locally:" + docker images | grep "${image_name}" | grep "${image_tag}" || (echo "Error: Image not found locally" && exit 1) + + - name: Install yq + run: | + sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + sudo chmod +x /usr/local/bin/yq + yq --version + + - name: Update taiko-client docker-compose.yml with new image + working-directory: >- + ${{ env.TAIKO_MONO_MAIN_DIR }}/packages/taiko-client/internal/docker/nodes + run: | + docker_compose_file="docker-compose.yml" + if [ -f "$docker_compose_file" ]; then + echo "Current image in docker-compose.yml:" + yq eval '.services.l2_nmc.image' "$docker_compose_file" + + echo "Updating docker-compose.yml with image: ${IMAGE_TAG}" + yq eval '.services.l2_nmc.image = "'"${IMAGE_TAG}"'"' -i "$docker_compose_file" + + yq eval '.services.l2_nmc.pull_policy = "never"' -i "$docker_compose_file" + + echo "Updated image in docker-compose.yml:" + yq eval '.services.l2_nmc.image' "$docker_compose_file" + + echo "Pull policy set to:" + yq eval '.services.l2_nmc.pull_policy' "$docker_compose_file" + else + echo "Warning: docker-compose.yml not found at expected path" + exit 1 + fi + + - name: Run Tests on ${{ matrix.execution_node }} execution engine + working-directory: >- + ${{ env.TAIKO_MONO_MAIN_DIR }}/packages/taiko-client + env: + L2_NODE: ${{ matrix.execution_node }} + run: >- + SHASTA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, env.SHASTA_FORK_TAIKO_MONO_DIR) }} + PACAYA_FORK_TAIKO_MONO=${GITHUB_WORKSPACE}/${{ format('{0}/{1}', env.TAIKO_MONO_MAIN_DIR, env.PACAYA_FORK_TAIKO_MONO_DIR) }} + make test + + - name: Codecov.io + uses: codecov/codecov-action@v5 + with: + files: ${{ env.TAIKO_MONO_MAIN_DIR }}/packages/taiko-client/coverage.out + flags: taiko-client + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/code-formatting.yml b/.github/workflows/code-formatting.yml index f2029c97af7..578e0543042 100644 --- a/.github/workflows/code-formatting.yml +++ b/.github/workflows/code-formatting.yml @@ -17,3 +17,5 @@ jobs: uses: actions/setup-dotnet@v5 - name: Format run: dotnet format whitespace src/Nethermind/ --folder --verify-no-changes + - name: Check spelling + uses: streetsidesoftware/cspell-action@v7 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 43b3d18e32f..82079d5bff2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -20,20 +20,30 @@ jobs: matrix: language: ['csharp', 'actions'] steps: + - name: Free up disk space + uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1 + with: + large-packages: false + tool-cache: false + - name: Check out repository uses: actions/checkout@v6 + - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} queries: security-and-quality packs: githubsecuritylab/codeql-csharp-queries + - name: Set up .NET uses: actions/setup-dotnet@v5 + - name: Build Nethermind working-directory: src/Nethermind run: dotnet build Nethermind.slnx -c release + - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: '/language:${{ matrix.language }}' diff --git a/.github/workflows/hive-tests.yml b/.github/workflows/hive-tests.yml index 79f786cc933..d1bb92f9ec3 100644 --- a/.github/workflows/hive-tests.yml +++ b/.github/workflows/hive-tests.yml @@ -10,13 +10,12 @@ on: description: Test suite type: choice options: - - '' + - 'all' - devp2p - ethereum/consensus - ethereum/eest - ethereum/engine - ethereum/graphql - - ethereum/rpc - ethereum/rpc-compat - ethereum/sync limit: @@ -56,8 +55,10 @@ jobs: strategy: fail-fast: false matrix: - test-suite: ${{ fromJson(github.event.inputs.test-suite == '' && - '["ethereum/engine","ethereum/graphql","ethereum/rpc","ethereum/rpc-compat","ethereum/sync"]' || + test-suite: ${{ fromJson(( + github.event_name == 'push' || + (github.event_name == 'workflow_dispatch' && github.event.inputs.test-suite == 'all')) && + '["ethereum/engine","ethereum/graphql","ethereum/rpc-compat","ethereum/sync"]' || format('["{0}"]', github.event.inputs.test-suite) ) }} steps: diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml deleted file mode 100644 index d2b44cbbb77..00000000000 --- a/.github/workflows/publish-nuget.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Publish NuGet packages - -on: - release: - types: [published] - -jobs: - publish: - name: Publish Nethermind.ReferenceAssemblies - runs-on: ubuntu-latest - if: ${{ !github.event.release.prerelease }} - steps: - - name: Check out Nethermind repository - uses: actions/checkout@v6 - with: - ref: ${{ github.event.release.tag_name }} - - name: Set up .NET - uses: actions/setup-dotnet@v5 - - name: Download Nethermind reference assemblies - run: | - json=$(curl -s ${{ github.event.release.assets_url }}) - url=$(echo "$json" | jq -r '.[].browser_download_url | select(contains("ref-assemblies"))') - curl -sL $url -o refasm.zip - unzip refasm.zip -d src/Nethermind/Nethermind.ReferenceAssemblies/ref - - name: Submit package - working-directory: src/Nethermind/Nethermind.ReferenceAssemblies - run: | - dotnet pack -c release - dotnet nuget push ../artifacts/**/*.nupkg -k ${{ secrets.NUGET_API_KEY }} -s https://api.nuget.org/v3/index.json diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index 2596058e076..30637b0e1d3 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest if: ${{ !github.event.release.prerelease }} steps: - - name: Check out Nethermind repository + - name: Check out repository uses: actions/checkout@v6 with: ref: ${{ github.event.release.tag_name }} @@ -34,9 +34,9 @@ jobs: run: | version=${{ github.event.release.tag_name }} json=$(curl -sL ${{ github.event.release.assets_url }}) - arm64_url=$(echo "$json" | jq -r '.[].browser_download_url | select(contains("linux-arm64"))') + arm64_url=$(echo "$json" | jq -r '.[].browser_download_url | select(endswith("linux-arm64.zip"))') arm64_hash=$(curl -sL $arm64_url | sha256sum | awk '{print $1}') - x64_url=$(echo "$json" | jq -r '.[].browser_download_url | select(contains("linux-x64"))') + x64_url=$(echo "$json" | jq -r '.[].browser_download_url | select(endswith("linux-x64.zip"))') x64_hash=$(curl -sL $x64_url | sha256sum | awk '{print $1}') awk -i inplace -v n=1 '/url/ { if (++count == n) sub(/url.*/, "url='$arm64_url'"); } 1' debian/postinst awk -i inplace -v n=2 '/url/ { if (++count == n) sub(/url.*/, "url='$x64_url'"); } 1' debian/postinst @@ -92,7 +92,7 @@ jobs: WINGET_CREATE_GITHUB_TOKEN: ${{ secrets.WINGET_TOKEN }} run: | $releaseInfo = curl -sL ${{ github.event.release.assets_url }} | ConvertFrom-Json - $releaseUrl = $releaseInfo | Where-Object -Property name -match 'windows' | Select -ExpandProperty browser_download_url + $releaseUrl = $releaseInfo | Where-Object -Property name -like '*windows-x64.zip' | Select-Object -ExpandProperty browser_download_url curl -sL https://aka.ms/wingetcreate/latest -o wingetcreate.exe ./wingetcreate update Nethermind.Nethermind -s -v ${{ github.event.release.tag_name }} -u $releaseUrl @@ -103,13 +103,13 @@ jobs: env: FORMULA: nethermind.rb steps: - - name: Authenticate App + - name: Create GitHub app token id: gh-app uses: actions/create-github-app-token@v2 with: app-id: ${{ vars.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} - repositories: "homebrew-nethermind" + repositories: homebrew-nethermind - name: Check out homebrew-nethermind repository uses: actions/checkout@v6 @@ -120,15 +120,16 @@ jobs: - name: Update formula file run: | json=$(curl -sL ${{ github.event.release.assets_url }}) - x64_url=$(echo "$json" | jq -r '.[].browser_download_url | select(contains("macos-x64"))') - x64_hash=$(curl -sL $x64_url | shasum -a 256 | awk '{print $1}') - arm64_url=$(echo "$json" | jq -r '.[].browser_download_url | select(contains("macos-arm64"))') + arm64_url=$(echo "$json" | jq -r '.[].browser_download_url | select(endswith("macos-arm64.zip"))') arm64_hash=$(curl -sL $arm64_url | shasum -a 256 | awk '{print $1}') + x64_url=$(echo "$json" | jq -r '.[].browser_download_url | select(endswith("macos-x64.zip"))') + x64_hash=$(curl -sL $x64_url | shasum -a 256 | awk '{print $1}') sed -i "s/version .*/version \"${{ github.event.release.tag_name }}\"/" $FORMULA awk -i inplace -v n=1 '/url/ { if (++count == n) sub(/url.*/, "url \"'$x64_url'\""); } 1' $FORMULA awk -i inplace -v n=2 '/url/ { if (++count == n) sub(/url.*/, "url \"'$arm64_url'\""); } 1' $FORMULA awk -i inplace -v n=1 '/sha256/ { if (++count == n) sub(/sha256.*/, "sha256 \"'$x64_hash'\""); } 1' $FORMULA awk -i inplace -v n=2 '/sha256/ { if (++count == n) sub(/sha256.*/, "sha256 \"'$arm64_hash'\""); } 1' $FORMULA + - name: Submit package env: GH_TOKEN: ${{ steps.gh-app.outputs.token }} @@ -142,3 +143,47 @@ jobs: git commit -am "$message" git push origin $head_branch gh pr create -B main -H $head_branch -t "$message" -b "Auto-updated Homebrew formula for Nethermind v${{ github.event.release.tag_name }}" + + publish-nuget: + name: Publish Nethermind.ReferenceAssemblies + runs-on: ubuntu-latest + if: ${{ !github.event.release.prerelease }} + steps: + - name: Check out repository + uses: actions/checkout@v6 + with: + ref: ${{ github.event.release.tag_name }} + + - name: Set up .NET + uses: actions/setup-dotnet@v5 + + - name: Download Nethermind reference assemblies + id: download + run: | + json=$(curl -s ${{ github.event.release.assets_url }}) + asset=$(echo "$json" | jq -r '.[] | select(.name | contains("ref-assemblies"))') + name=$(echo "$asset" | jq -r '.name') + url=$(echo "$asset" | jq -r '.browser_download_url') + curl -sL $url -o refasm.zip + unzip refasm.zip -d src/Nethermind/Nethermind.ReferenceAssemblies/ref + echo "asset-name=$name" >> $GITHUB_OUTPUT + + - name: Submit package + working-directory: src/Nethermind/Nethermind.ReferenceAssemblies + env: + NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }} + run: | + dotnet pack -c release + dotnet nuget push ../artifacts/**/*.nupkg -k "$NUGET_API_KEY" -s https://api.nuget.org/v3/index.json + + - name: Create GitHub app token + id: gh-app + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - name: Delete asset from release + env: + GITHUB_TOKEN: ${{ steps.gh-app.outputs.token }} + run: gh release delete-asset ${{ github.event.release.tag_name }} ${{ steps.download.outputs.asset-name }} -y diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6c258785763..1bb9a671b73 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -12,7 +12,6 @@ env: PACKAGE_DIR: pkg PACKAGE_RETENTION: 7 PUB_DIR: pub - SCRIPTS_PATH: ${{ github.workspace }}/scripts/build jobs: build: @@ -44,16 +43,29 @@ jobs: - name: Build Nethermind.Runner id: build run: | - mkdir $GITHUB_WORKSPACE/$PUB_DIR - - docker build . -t nethermind-build -f $SCRIPTS_PATH/Dockerfile \ + mkdir $PUB_DIR + docker build . -t nethermind-build -f scripts/build/Dockerfile \ --build-arg COMMIT_HASH=$GITHUB_SHA \ --build-arg SOURCE_DATE_EPOCH=$(git log -1 --format=%ct) - - docker run --rm --mount type=bind,source=$GITHUB_WORKSPACE/$PUB_DIR,target=/output nethermind-build + docker run --rm --mount type=bind,source=./$PUB_DIR,target=/output nethermind-build - name: Archive packages - run: $SCRIPTS_PATH/archive.sh + run: scripts/build/archive.sh + + - name: Sign packages + env: + GPG_PASSPHRASE: ${{ secrets.PPA_GPG_PASSPHRASE }} + GPG_SECRET_KEY: ${{ secrets.PPA_GPG_SECRET_KEY }} + working-directory: ${{ env.PACKAGE_DIR }} + run: | + printf '%s' "$GPG_SECRET_KEY" | base64 --decode | gpg --import --no-tty --batch --yes + + for rid in "linux-arm64" "linux-x64" "macos-arm64" "macos-x64" "windows-x64"; do + file_name=$(basename *$rid*) + gpg --no-tty --batch --yes --pinentry-mode loopback \ + --passphrase-file <(printf '%s' "$GPG_PASSPHRASE") \ + -o "$file_name.asc" -ab "$file_name" + done - name: Upload Nethermind Linux arm64 package uses: actions/upload-artifact@v4 @@ -116,7 +128,7 @@ jobs: - name: Check out Nethermind repository uses: actions/checkout@v6 - - name: Authenticate App + - name: Create GitHub app token id: gh-app uses: actions/create-github-app-token@v2 with: @@ -132,42 +144,39 @@ jobs: env: GIT_TAG: ${{ needs.build.outputs.version }} GITHUB_TOKEN: ${{ steps.gh-app.outputs.token }} - PACKAGE_PREFIX: ${{ needs.build.outputs.package-prefix }} - PRERELEASE: ${{ needs.build.outputs.prerelease }} run: | - cp $GITHUB_WORKSPACE/$PACKAGE_DIR/**/*.zip $GITHUB_WORKSPACE/$PACKAGE_DIR - rm -rf $GITHUB_WORKSPACE/$PACKAGE_DIR/*/ - $SCRIPTS_PATH/publish-github.sh + cp $PACKAGE_DIR/**/*.zip $PACKAGE_DIR/**/*.zip.asc $PACKAGE_DIR + rm -rf $PACKAGE_DIR/*/ + echo "Publishing packages to GitHub" + prerelease=${{ needs.build.outputs.prerelease && '1' || '' }} + if gh release view "$GIT_TAG" >/dev/null 2>&1; then publish=1; else publish=0; fi - publish-downloads: - name: Publish to Downloads page - runs-on: ubuntu-latest - needs: [approval, build] - if: needs.build.outputs.prerelease == 'false' - steps: - - name: Check out Nethermind repository - uses: actions/checkout@v6 + if (( !publish )); then + echo "Drafting release $GIT_TAG" + relnotes=$(cat <<'EOF' + # Release notes - - name: Download artifacts - uses: actions/download-artifact@v4 - with: - path: ${{ github.workspace }}/${{ env.PACKAGE_DIR }} + ### [CONTENT PLACEHOLDER] - - name: Configure GPG Key - run: | - mkdir -p ~/.gnupg/ - printf "${{ secrets.GPG_SIGNING_KEY }}" | base64 --decode > ~/.gnupg/private.key - gpg --import --no-tty --batch --yes ~/.gnupg/private.key + #### Build signatures - - name: Publish packages to Downloads page - env: - DOWNLOADS_PAGE: ${{ secrets.DOWNLOADS_API_KEY }} - PACKAGE_PREFIX: ${{ needs.build.outputs.package-prefix }} - PASS: ${{ secrets.GPG_PASSWORD }} - run: | - cp $GITHUB_WORKSPACE/$PACKAGE_DIR/**/*.zip $GITHUB_WORKSPACE/$PACKAGE_DIR - rm -rf $GITHUB_WORKSPACE/$PACKAGE_DIR/*/ - $SCRIPTS_PATH/publish-downloads.sh + The packages are signed with the following OpenPGP key: `AD12 7976 5093 C675 9CD8 A400 24A7 7461 6F1E 617E` + EOF + ) + gh release create "$GIT_TAG" --target "$GITHUB_SHA" -t "v$GIT_TAG" -d ${prerelease:+-p} -F <(printf '%s' "$relnotes") + fi + + cd $PACKAGE_DIR + echo "Uploading assets" + gh release upload "$GIT_TAG" *.zip *.zip.asc --clobber + + if (( publish )); then + echo "Publishing release $GIT_TAG" + if [[ -n "$prerelease" ]]; then flag="--prerelease"; else flag="--latest"; fi + gh release edit "$GIT_TAG" --verify-tag --target "$GITHUB_SHA" -t "v$GIT_TAG" --draft=false $flag + fi + + echo "Publishing completed" publish-docker: name: Publish to Docker Hub @@ -177,7 +186,7 @@ jobs: - name: Check out Nethermind repository uses: actions/checkout@v6 - - name: Authenticate App + - name: Create GitHub app token id: gh-app uses: actions/create-github-app-token@v2 with: @@ -202,7 +211,6 @@ jobs: for suffix in "" ".chiseled"; do tag_suffix=$([[ -z "$suffix" ]] && echo "" || echo "-${suffix:1}") - docker buildx build . --platform=linux/amd64,linux/arm64 -f Dockerfile$suffix \ ${{ needs.build.outputs.prerelease == 'false' && '-t $image:latest$tag_suffix' || '' }} \ -t "$image:${{ needs.build.outputs.version }}$tag_suffix" \ diff --git a/.github/workflows/sync-supported-chains.yml b/.github/workflows/sync-supported-chains.yml index f2531992615..7517b5416bf 100644 --- a/.github/workflows/sync-supported-chains.yml +++ b/.github/workflows/sync-supported-chains.yml @@ -249,16 +249,15 @@ jobs: $extra_param elif [[ "$network" == taiko-* ]]; then + taiko_client_version="latest" if [[ "$network" == *alethia* ]]; then CONSENSUS_URL="${{ secrets.MAINNET_CONSENSUS_URL }}" EXECUTION_URL="${{ secrets.MAINNET_EXECUTION_URL }}" stripped_network="mainnet" - taiko_client_version="taiko-alethia-client-v0.43.2" elif [[ "$network" == *hoodi* ]]; then CONSENSUS_URL="${{ secrets.HOODI_CONSENSUS_URL }}" EXECUTION_URL="${{ secrets.HOODI_EXECUTION_URL }}" stripped_network="hoodi" - taiko_client_version="latest" else echo "Unknown network" exit 1 diff --git a/.gitignore b/.gitignore index be433a3fc74..0742ca13e6e 100644 --- a/.gitignore +++ b/.gitignore @@ -435,6 +435,10 @@ FodyWeavers.xsd ## macOS .DS_Store +# AI +.claude/ +.gemini/ + ## Nethermind keystore/ /.githooks diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..b3bb411a5cb --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,112 @@ +# AGENTS instructions + +This guide helps to get started with the Nethermind Ethereum execution client repository. It covers the project structure, how to build and test the code, and follow the PR workflow. + +## Repo structure + +- [src/Nethermind](./src/Nethermind/): The Nethermind codebase +- [tools](./tools/): Various servicing tools for testing, monitoring, etc. +- [scripts](./scripts/): The build scripts and stuff used by GitHub Actions workflows +- See [README.md](./README.md) for more info + +## Coding guidelines and style + +- Do follow the [CONTRIBUTING.md](./CONTRIBUTING.md) guidelines +- Do follow the [.editorconfig](./.editorconfig) rules +- Do prefer low-allocation code patterns +- Prefer the latest C# syntax and conventions +- Prefer file-scoped namespaces (for existing files, follow their style) +- Prefer pattern matching and switch expressions over the traditional control flow +- Use the `nameof` operator instead of string literals for member references +- Use `is null` and `is not null` instead of `== null` and `!= null` +- Use `?.` null-conditional operator where applicable +- Use the `ArgumentNullException.ThrowIfNull` method for null checks and other similar methods +- Use the `ObjectDisposedException.ThrowIf` method for disposal checks +- Use documentation comments for all public APIs with proper structure +- Consider performance implications in high-throughput paths +- Trust null annotations—don't add redundant null checks +- Add tests to existing test files rather than creating new ones +- Code comments must explain _why_, not _what_ +- Do not use the `#region` and `#endregion` pragmas +- Do not alter anything in the [src/bench_precompiles](./src/bench_precompiles/) and [src/tests](./src/tests/) directories + +## Project structure + +The codebase in [src/Nethermind](./src/Nethermind/) is organized into three independent solutions: + +- [Nethermind.slnx](./src/Nethermind/Nethermind.slnx): The Nethermind client codebase and tests +- [EthereumTests.slnx](./src/Nethermind/EthereumTests.slnx): The Ethereum Foundation test suite +- [Benchmarks.slnx](./src/Nethermind/Benchmarks.slnx): Performance benchmarking + +### Architecture + +- **Entry point and initialization** + - [Nethermind.Runner](./src/Nethermind/Nethermind.Runner/): The app entry point and startup orchestration + - [Nethermind.Init](./src/Nethermind/Nethermind.Init/): Initialization logic, memory management, metrics +- **General API** + - [Nethermind.Api](./src/Nethermind/Nethermind.Api/): Core API interfaces and plugin API + - [Nethermind.Config](./src/Nethermind/Nethermind.Config/): Configuration handling + - [Nethermind.Logging](./src/Nethermind/Nethermind.Logging/): Logging +- **Consensus algorithms** + - [Nethermind.Consensus.AuRa](./src/Nethermind/Nethermind.Consensus.AuRa/): Authority round (Aura) + - [Nethermind.Consensus.Clique](./src/Nethermind/Nethermind.Consensus.Clique/): Proof of Authority (PoA) + - [Nethermind.Consensus.Ethash](./src/Nethermind/Nethermind.Consensus.Ethash/): Proof of Work (PoW) + - [Nethermind.Merge.Plugin](./src/Nethermind/Nethermind.Merge.Plugin/): Proof of Stake (PoS) +- **Core blockchain** + - [Nethermind.Blockchain](./src/Nethermind/Nethermind.Blockchain/): Block processing, chain management, validators + - [Nethermind.Core](./src/Nethermind/Nethermind.Core/): Foundational types + - [Nethermind.Crypto](./src/Nethermind/Nethermind.Crypto/): Core cryptographic algorithms + - [Nethermind.Evm](./src/Nethermind/Nethermind.Evm/): EVM implementation + - [Nethermind.Evm.Precompiles](./src/Nethermind/Nethermind.Evm.Precompiles/): EVM precompiled contracts + - [Nethermind.Specs](./src/Nethermind/Nethermind.Specs/): Network specifications and hard fork rules +- **State and storage:** + - [Nethermind.Db](./src/Nethermind/Nethermind.Db/): Database abstraction layer + - [Nethermind.Db.Rocks](./src/Nethermind/Nethermind.Db.Rocks/): RocksDB implementation (primary storage backend) + - [Nethermind.State](./src/Nethermind/Nethermind.State/): World state management, accounts, contract storage + - [Nethermind.Trie](./src/Nethermind/Nethermind.Trie/): Merkle Patricia trie implementation +- **Networking:** + - [Nethermind.Network](./src/Nethermind/Nethermind.Network/): devp2p protocol implementation + - [Nethermind.Network.Discovery](./src/Nethermind/Nethermind.Network.Discovery/): Peer discovery + - [Nethermind.Network.Dns](./src/Nethermind/Nethermind.Network.Dns/): DNS-based node discovery + - [Nethermind.Network.Enr](./src/Nethermind/Nethermind.Network.Enr/): Ethereum Node Records (ENR) handling + - [Nethermind.Synchronization](./src/Nethermind/Nethermind.Synchronization/): Block synchronization strategies (fast sync, snap sync) + - [Nethermind.UPnP.Plugin](./src/Nethermind/Nethermind.UPnP.Plugin/): UPnP support +- **Transaction management:** + - [Nethermind.TxPool](./src/Nethermind/Nethermind.TxPool/): Transaction pool (mempool) management, validation, sorting +- **RPC and external interface:** + - [Nethermind.Facade](./src/Nethermind/Nethermind.Facade/): High-level API facades for external interaction + - [Nethermind.JsonRpc](./src/Nethermind/Nethermind.JsonRpc/): JSON-RPC server + - [Nethermind.Sockets](./src/Nethermind/Nethermind.Sockets/): WebSocket server +- **Monitoring** + - [Nethermind.HealthChecks](./src/Nethermind/Nethermind.HealthChecks/): Health checks + - [Nethermind.Monitoring](./src/Nethermind/Nethermind.Monitoring/): Monitoring API + - [Nethermind.Seq](./src/Nethermind/Nethermind.Seq/): Seq integration +- **Serialization:** + - [Nethermind.Serialization.Json](./src/Nethermind/Nethermind.Serialization.Json/): JSON serialization + - [Nethermind.Serialization.Rlp](./src/Nethermind/Nethermind.Serialization.Rlp/): RLP serialization + - [Nethermind.Serialization.Ssz](./src/Nethermind/Nethermind.Serialization.Ssz/): SSZ serialization +- **Third-party integration:** + - [Nethermind.Flashbots](./src/Nethermind/Nethermind.Flashbots/): Flashbots integration + - [Nethermind.Optimism](./src/Nethermind/Nethermind.Optimism/): Optimism network (OP Stack) support + - [Nethermind.Taiko](./src/Nethermind/Nethermind.Taiko/): Taiko network support +- **Tests** + - Test suites reside in Nethermind.\*.Test directories + +## Pull request guidelines + +Before creating a pull request: + +- Ensure the code compiles +- Add tests covering your changes and ensure they pass: + ```bash + dotnet test --project path/to/.csproj -c release -- --filter FullyQualifiedName~TestName + ``` +- Ensure the code is well-formatted: + ```bash + dotnet format whitespace src/Nethermind/ --folder + ``` +- Use [pull_request_template.md](.github/pull_request_template.md) + +## Prerequisites + +See [global.json](./global.json) for the required .NET SDK version. diff --git a/CLAUDE.md b/CLAUDE.md index 954ccb13926..285e0f5b36b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,255 +1 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Repository Overview - -Nethermind is an industry-leading Ethereum execution client built on .NET 9.0, designed for high-performance syncing and tip-of-chain processing. It features a modular architecture with a plugin system, supporting multiple networks including Ethereum, Gnosis, Optimism, Base, Taiko, World Chain, Linea, and Energy Web. - -**Key Characteristics:** -- Large-scale C# codebase with 100+ projects -- Target framework: .NET 9.0 (C# 13) -- License: LGPL-3.0-only -- Modular plugin-based architecture - -## Essential Build Commands - -### Prerequisites -- .NET SDK 9.0.2 or later (required by `global.json`) -- Git with submodules: `git clone --recursive https://github.com/nethermindeth/nethermind.git` - -### Build Commands -All build commands run from `src/Nethermind/`: - -```bash -# Navigate to source directory -cd src/Nethermind - -# Build main solution (~4 minutes) -dotnet build Nethermind.slnx -c Release - -# Build for debugging -dotnet build Nethermind.slnx -c Debug - -# Build Ethereum Foundation tests (when needed) -dotnet build EthereumTests.slnx -c Release - -# Build benchmarks (when needed) -dotnet build Benchmarks.slnx -c Release -``` - -### Testing Commands - -```bash -# From src/Nethermind/ directory - -# Run single test project (fast, ~15 seconds) -dotnet test Nethermind.Core.Test/Nethermind.Core.Test.csproj - -# Run all Nethermind tests -dotnet test Nethermind.slnx -c Release - -# Run Ethereum Foundation test suite -dotnet test EthereumTests.slnx -c Release - -# Run specific test class or method -dotnet test .csproj --filter "FullyQualifiedName~ClassName" -``` - -### Code Formatting - -**CRITICAL: Always run before committing. CI enforces this.** - -```bash -# Check formatting (what CI runs) -dotnet format whitespace src/Nethermind/ --folder --verify-no-changes - -# Fix formatting issues -dotnet format whitespace src/Nethermind/ --folder -``` - -### Running the Application - -```bash -# From repository root -cd src/Nethermind/Nethermind.Runner - -# Run mainnet -dotnet run -c release -- -c mainnet --data-dir path/to/data/dir - -# Debug mode -dotnet run -c debug -- -c mainnet -``` - -## Architecture Overview - -### Three Solution Structure - -The codebase is organized into three independent solutions located in `src/Nethermind/`: - -1. **Nethermind.slnx** - Main execution client with all core functionality -2. **EthereumTests.slnx** - Ethereum Foundation test suite compliance -3. **Benchmarks.slnx** - Performance benchmarking tools - -### Core Architecture Layers - -**Entry Point and Initialization:** -- `Nethermind.Runner/` - Application entry point, startup orchestration -- `Nethermind.Init/` - Initialization logic, memory management, metrics - -**API and Extension System:** -- `Nethermind.Api/` - Core API interfaces (`INethermindApi`, `IApiWithNetwork`, `IApiWithStores`) -- `Nethermind.Api/Extensions/` - Plugin system interfaces (`INethermindPlugin`, `IConsensusPlugin`) -- Plugins implement lifecycle hooks: `InitTxTypesAndRlpDecoders()`, `Init()`, `InitNetworkProtocol()`, `InitRpcModules()` - -**Consensus Layer:** -- `Nethermind.Consensus.*/` - Pluggable consensus mechanisms: - - `Nethermind.Consensus.Ethash/` - Proof of Work - - `Nethermind.Consensus.AuRa/` - Authority Round (Gnosis) - - `Nethermind.Consensus.Clique/` - Proof of Authority -- `Nethermind.Merge.Plugin/` - Proof of Stake (post-merge Ethereum) - -**Blockchain Core:** -- `Nethermind.Core/` - Fundamental types (`Block`, `BlockHeader`, `Transaction`, `TransactionReceipt`) -- `Nethermind.Blockchain/` - Block processing, chain management, validators -- `Nethermind.Evm/` - Ethereum Virtual Machine implementation -- `Nethermind.Evm.Precompiles/` - Precompiled contracts -- `Nethermind.Specs/` - Network specifications and hard fork rules - -**State and Storage:** -- `Nethermind.State/` - World state management, accounts, contract storage -- `Nethermind.Trie/` - Merkle Patricia Trie implementation -- `Nethermind.Db/` - Database abstraction layer -- `Nethermind.Db.Rocks/` - RocksDB implementation (primary storage backend) - -**Networking:** -- `Nethermind.Network/` - P2P protocol implementation (DevP2P) -- `Nethermind.Network.Discovery/` - Peer discovery -- `Nethermind.Network.Dns/` - DNS-based node discovery -- `Nethermind.Network.Enr/` - Ethereum Node Records -- `Nethermind.Synchronization/` - Block synchronization strategies (fast sync, snap sync, beam sync) - -**Transaction Management:** -- `Nethermind.TxPool/` - Transaction pool management, validation, sorting -- Custom transaction types registered via `INethermindApi.RegisterTxType()` - -**RPC and External Interface:** -- `Nethermind.JsonRpc/` - JSON-RPC API server -- `Nethermind.Facade/` - High-level API facades for external interaction - -**Serialization:** -- `Nethermind.Serialization.Rlp/` - RLP (Recursive Length Prefix) encoding -- `Nethermind.Serialization.Ssz/` - SSZ (Simple Serialize) for consensus layer -- `Nethermind.Serialization.Json/` - JSON serialization - -**Network-Specific Implementations:** -- `Nethermind.Taiko/` - Taiko L2 support -- `Nethermind.Xdc/` - XDC Network support -- `Nethermind.Flashbots/` - MEV-Boost integration - -### Plugin System Architecture - -Plugins extend Nethermind functionality through the `INethermindPlugin` interface: - -```csharp -public interface INethermindPlugin -{ - string Name { get; } - void InitTxTypesAndRlpDecoders(INethermindApi api); - Task Init(INethermindApi nethermindApi); - Task InitNetworkProtocol(); - Task InitRpcModules(); - bool MustInitialize => false; - bool Enabled { get; } -} -``` - -Consensus plugins implement `IConsensusPlugin` which extends `IBlockProducerFactory` and `IBlockProducerRunnerFactory`. - -Examples: `Nethermind.Merge.Plugin`, `Nethermind.ExternalSigner.Plugin`, `Nethermind.UPnP.Plugin` - -### Configuration System - -Configuration uses strongly-typed interfaces inheriting from `IConfig`: -- Each module has `IConfig` interface and `Config` implementation -- Examples: `ITxPoolConfig`, `IDbConfig`, `IInitConfig` -- Access via `INethermindApi.Config()` where `T : IConfig` - -## Code Style and Conventions - -**Critical Requirements:** -- All code MUST follow `.editorconfig` rules (enforced by CI) -- Use C# 13 features (latest language version) -- All files require SPDX header: - ```csharp - // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited - // SPDX-License-Identifier: LGPL-3.0-only - ``` - -**Strongly Preferred Patterns:** -- Use file-scoped namespaces (but match existing file style if present) -- Pattern matching and switch expressions over traditional control flow -- `nameof()` instead of string literals for member references -- `is null` / `is not null` instead of `== null` / `!= null` -- `?.` null-conditional operator where applicable -- Trust null annotations - don't add redundant null checks -- `ObjectDisposedException.ThrowIf` for disposal checks -- Add tests to existing test files rather than creating new ones -- XML doc comments for all public APIs with proper structure: - - Use `` tag for brief description (one sentence preferred) - - Use `` tag for each parameter with concise description - - Use `` tag to describe the return value - - Use `` tag (optional) for additional implementation details, behavior notes, or important context when needed -- Code comments explain "why", not "what" - -**Prohibited Patterns:** -- Do NOT use `#region` -- Do NOT use "Act", "Arrange", "Assert" comments in tests -- Do NOT leave tests commented out or disabled unless previously disabled -- Do NOT change `global.json`, `package.json`, `NuGet.config` unless explicitly requested - -**Performance Focus:** -- Prefer low-allocation code patterns -- Consider performance implications in high-throughput paths - -## Build Configuration - -Key settings from `Directory.Build.props`: -- `TreatWarningsAsErrors`: true (warnings block builds) -- `InvariantGlobalization`: true (consistent cross-locale behavior) -- `UseArtifactsOutput`: true (outputs to `artifacts/` directory) -- `TargetFramework`: net9.0 -- `LangVersion`: 13.0 - -## Testing Requirements - -**Before committing code:** -1. Your changes MUST compile -2. New and existing related tests MUST pass -3. Formatting check MUST pass: `dotnet format whitespace src/Nethermind/ --folder --verify-no-changes` -4. Report if unable to verify build/test success - -**When writing tests:** -- Use filters and verify test counts to ensure tests actually ran -- Copy style from nearby test files for naming and capitalization -- Ensure new code files are listed in `.csproj` if other files in the folder are listed - -## Common File Locations - -- Solution files: `src/Nethermind/*.slnx` -- Main entry point: `src/Nethermind/Nethermind.Runner/Program.cs` -- Core types: `src/Nethermind/Nethermind.Core/` -- API definitions: `src/Nethermind/Nethermind.Api/` -- Plugin interfaces: `src/Nethermind/Nethermind.Api/Extensions/` -- Global build config: `Directory.Build.props`, `global.json` -- Editor config: `.editorconfig` - -## Branch Naming Convention - -Use `kebab-case` or `snake_case`, all lowercase. Pattern: `[project/]type/[issue-]description` - -Examples: -- `feature/1234-issue-title` -- `shanghai/feature/1234-issue-title` -- `fix/1234-bug-description` -- `shanghai/refactor/title` +@./AGENTS.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b9adeabc56d..53cf3198a30 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,7 +59,7 @@ Branch names must follow the `kebab-case` or `snake_case` pattern and be all low The following notice must be included as a header in all source files if possible. ``` -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only ``` diff --git a/Directory.Packages.props b/Directory.Packages.props index afd33e24308..689a8550a51 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -8,11 +8,10 @@ - - + + - - + @@ -20,14 +19,14 @@ - - + + - + - - + + @@ -37,12 +36,13 @@ - - + + - - - + + + + @@ -57,8 +57,8 @@ - - + + @@ -69,8 +69,8 @@ - - + + @@ -79,12 +79,12 @@ - - - - - - + + + + + + diff --git a/Dockerfile b/Dockerfile index 31536b41068..8ecddbd5413 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.100-noble@sha256:c7445f141c04f1a6b454181bd098dcfa606c61ba0bd213d0a702489e5bd4cd71 AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.102-noble@sha256:25d14b400b75fa4e89d5bd4487a92a604a4e409ab65becb91821e7dc4ac7f81f AS build ARG BUILD_CONFIG=release ARG CI=true @@ -25,7 +25,7 @@ RUN arch=$([ "$TARGETARCH" = "amd64" ] && echo "x64" || echo "$TARGETARCH") && \ # A temporary symlink to support the old executable name RUN ln -sr /publish/nethermind /publish/Nethermind.Runner -FROM mcr.microsoft.com/dotnet/aspnet:10.0.0-noble@sha256:7c4246c1c384319346d45b3e24a10a21d5b6fc9b36a04790e1588148ff8055b0 +FROM mcr.microsoft.com/dotnet/aspnet:10.0.2-noble@sha256:1aacc8154bc3071349907dae26849df301188be1a2e1f4560b903fb6275e481a WORKDIR /nethermind diff --git a/Dockerfile.chiseled b/Dockerfile.chiseled index 3031ccce3bd..bf96919ae53 100644 --- a/Dockerfile.chiseled +++ b/Dockerfile.chiseled @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.100-noble@sha256:c7445f141c04f1a6b454181bd098dcfa606c61ba0bd213d0a702489e5bd4cd71 AS build +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:10.0.102-noble@sha256:25d14b400b75fa4e89d5bd4487a92a604a4e409ab65becb91821e7dc4ac7f81f AS build ARG BUILD_CONFIG=release ARG CI=true @@ -28,7 +28,7 @@ RUN cd /publish && \ mkdir logs && \ mkdir nethermind_db -FROM mcr.microsoft.com/dotnet/aspnet:10.0.0-noble-chiseled@sha256:5730fa91fab5ec91f69661b86897249ad6a01c8da6da557695e9da4c6bc83621 +FROM mcr.microsoft.com/dotnet/aspnet:10.0.2-noble-chiseled@sha256:cc6a8adc9402e9c2c84423ee1a4c58a3098511ed5399804df0659eeafb0ae0cb WORKDIR /nethermind diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 00000000000..285e0f5b36b --- /dev/null +++ b/GEMINI.md @@ -0,0 +1 @@ +@./AGENTS.md diff --git a/cspell.json b/cspell.json new file mode 100644 index 00000000000..d7998dc7512 --- /dev/null +++ b/cspell.json @@ -0,0 +1,768 @@ +{ + "version": "0.2", + "language": "en", + "files": [ + "**/*.{cs,csproj,props,targets,sln,config,json,yml,yaml,md,txt,ps1,psm1,sh}", + "**/*.{ts,tsx,js,jsx,html,css,scss,xml}", + "**/*.proto" + ], + "ignorePaths": [ + "**/.git/**", + "**/.vs/**", + "**/*.json", + "**/*.zst", + "**/bin/**", + "**/node_modules/**", + "**/obj/**", + "**/wwwroot/**", + "src/bench_precompiles", + "src/Nethermind/artifacts/**", + "src/Nethermind/BenchmarkDotNet.Artifacts/**", + "src/Nethermind/Chains/**", + "src/Nethermind/Nethermind.Runner/configs/**", + "src/Nethermind/TestResults/**", + "src/tests" + ], + "ignoreRegExpList": [ + "/0x[0-9a-fA-F]+/g", + "/\"[0-9a-fA-F]{64}\"/g", + "/https?:\\/\\/\\S+/g" + ], + "words": [ + "accesslist", + "accnt", + "adab", + "addmod", + "affinitize", + "akhunov", + "alethia", + "alexey", + "analysed", + "apikey", + "argjson", + "asmv", + "aspnet", + "aspnetcore", + "assertoor", + "autofac", + "autogen", + "auxdata", + "BALS", + "badreq", + "barebone", + "baseblock", + "basefee", + "basefeepergas", + "beaverbuild", + "behaviour", + "behaviours", + "benchmarkdotnet", + "bendian", + "beregszaszi", + "besu", + "bigrams", + "bilinearity", + "bitarray", + "bitfield", + "bitlist", + "bitlists", + "bitmask", + "bitvec", + "bitvector", + "bitvectors", + "bloatnet", + "blobbasefee", + "blobgasperblob", + "blobgasused", + "blobhash", + "blobhashes", + "bloboptions", + "blobpool", + "blobtransactions", + "blobversionedhashes", + "blockhash", + "blockhashes", + "blocknr", + "blocksconfig", + "blocksdb", + "blocktest", + "blocktree", + "blom", + "bloomconfig", + "bloomfilter", + "blsg", + "blsmapfp", + "blsmapfptog", + "blspairingcheck", + "bnadd", + "bnmul", + "bnpair", + "bootnode", + "bootnodes", + "bottlenecked", + "browsable", + "btcs", + "buildtransitive", + "bulkset", + "bursty", + "buterin", + "bylica", + "bytecodes", + "callcode", + "calldatacopy", + "calldataload", + "calldatasize", + "callf", + "callme", + "callvalue", + "cand", + "canonicality", + "castagnoli", + "chainid", + "chainspec", + "chiado", + "cipherparams", + "ciphertext", + "ckzg", + "cloneable", + "cmix", + "codecopy", + "codehash", + "codesection", + "codesize", + "coef", + "collectd", + "colour", + "commitset", + "comparand", + "concurrenc", + "configurer", + "configurers", + "conflictor's", + "containersection", + "contentfiles", + "corechart", + "cpufrequency", + "crummey", + "cryptosuite", + "cryptotests", + "datacopy", + "datagram", + "dataloadn", + "datasection", + "datasize", + "dbdir", + "dbsize", + "deadlined", + "deauthorized", + "debhelper", + "decommit", + "decompiled", + "decompiler", + "deconfigure", + "deconfigured's", + "decryptor", + "defaultable", + "delegatecall", + "delegatecode", + "demerzel", + "deque", + "deserialised", + "dests", + "devirtualize", + "devnet", + "devnets", + "devp2p", + "diagnoser", + "diagnosers", + "disappearer's", + "discontiguous", + "discport", + "discv", + "distros", + "divmod", + "dklen", + "dont", + "dontneed", + "dotmemory", + "dotnetty's", + "dottrace", + "dpapi", + "dpkg", + "dupn", + "ecies", + "ecrec", + "edgecase", + "efbbbf", + "eips", + "emojize", + "emptish", + "emptystep", + "emptystring", + "encrypter", + "encryptor", + "energi", + "energyweb", + "enginehost", + "engineport", + "enode", + "enodes", + "enrs", + "enrtree", + "eofcall", + "eofcreate", + "eofdelegatecall", + "eofstaticcall", + "eoftest", + "ephem", + "eradir", + "eraexportdir", + "erigon", + "esac", + "ethash", + "ethdisco", + "ethermine", + "ethpool", + "ethrex", + "ethstats", + "ethxx", + "evmdis", + "ewasm", + "extcall", + "extcode", + "extcodecopy", + "extcodehash", + "extcopycode", + "extdelegatecall", + "extopcodes", + "extradata", + "extstaticcall", + "fastbin", + "fastlz", + "fastmod", + "fastsync", + "feemultiplier", + "finalizer", + "finalizers", + "findnode", + "firepool", + "flashbots", + "flatdb", + "flushoptions", + "fmap", + "forkchoice", + "forkhash", + "forkid", + "fusaka", + "gasrefund", + "gbps", + "gcdump", + "geoff", + "getblobs", + "getnull", + "getpayloadv", + "getrlimit", + "gettrie", + "gopherium", + "gwat", + "halfpath", + "hardfork", + "hardforks", + "hashas", + "hashcode", + "hashdb", + "hashimoto", + "hashkey", + "hashlib", + "hashrate", + "hashtable", + "hashtables", + "hbrns", + "headp", + "healthchecks", + "hellothere", + "hexary", + "hexencodetest", + "hexroot", + "highbits", + "hiveon", + "hmac", + "holesky", + "hoodi", + "hostnames", + "hotstuff", + "hyperthreading", + "idxs", + "iface", + "ikvp", + "immediates", + "includable", + "initcode", + "initcodes", + "inited", + "initialized", + "insens", + "insize", + "installdeb", + "instart", + "internaltype", + "interp", + "invalidblockhash", + "isnull", + "iszero", + "ivalidatorregistrycontract", + "ivle", + "jemalloc", + "jimbojones", + "jitted", + "jitting", + "jmps", + "jsonrpcconfig", + "jumpdest", + "jumpdestpush", + "jumpf", + "jumpi", + "jumps", + "jumptable", + "jumpv", + "justcache", + "kademlia", + "karalabe", + "kdfparams", + "keccak", + "keccaks", + "keepalive", + "keyaddrtest", + "keyspace", + "keyper", + "keypers", + "kneth", + "kute", + "kychash", + "lanfranchi", + "langdale", + "lastmemline", + "ldarg", + "ldfld", + "lemire's", + "libc", + "libdl", + "libp", + "libsnappy", + "limbologs", + "limitmeterinitcode", + "linea", + "liskov", + "logicalcpu", + "longdate", + "lukasz", + "machdep", + "machinename", + "madv", + "maiboroda", + "mainchain", + "mallopt", + "mapg", + "marshallers", + "maskz", + "masternode", + "masternodes", + "masterodes", + "maxcandidatepeercount", + "maxcandidatepercount", + "maxfee", + "maxfeeperblobgas", + "maxfeeperdatagas", + "maxheaders", + "maxlevel", + "maxpriorityfee", + "mclbn", + "mcmc", + "mcopy", + "mellor", + "memberwise", + "memin", + "meminstart", + "meminsz", + "memmove", + "memout", + "memoutsz", + "mempoool", + "memtable", + "merkle", + "merkleization", + "merkleize", + "merkleizer", + "mgas", + "microsecs", + "midnib", + "millis", + "mingas", + "minlevel", + "mintable", + "misbehaviour", + "mklink", + "mload", + "mmap", + "modexp", + "modexpprecompile", + "morden", + "movbe", + "movzx", + "mres", + "mscorlib", + "msgrecv", + "msgsend", + "msize", + "msm", + "msms", + "msquic", + "mstore", + "mula", + "mulmod", + "mult", + "multiaddress", + "multicall", + "multidim", + "multiexp", + "multifeed", + "multiformats", + "mvid", + "mycryptowallet", + "nada", + "nameof", + "nananana", + "nanopool", + "neighbour", + "neighbours", + "netcore", + "netframework", + "netheofparse", + "nethermind's", + "nethermind", + "nethermindeth", + "nethtest", + "netstandard", + "nettrace", + "networkconfig", + "networkconfigmaxcandidatepeercount", + "networkid", + "newtonsoft", + "nito", + "nlog", + "nodedata", + "nodelay", + "nodestore", + "nodetype", + "nofile", + "nonposdao", + "nonstring", + "nops", + "nostack", + "nowal", + "npoints", + "npushes", + "nsubstitute", + "nugettest", + "numfiles", + "oand", + "offchain", + "ommer", + "ommers", + "ontake", + "opcodeswith", + "opcount", + "openethereum", + "opinfo", + "opstack", + "opstr", + "optane", + "overwriter", + "owlf", + "pacaya", + "parallelizable", + "paweł", + "pctg", + "pearce", + "pectra", + "pendingtxs", + "perfnet", + "perfolizer", + "permissioned", + "pgrep", + "physicalcpu", + "piechart", + "pinnable", + "pinnableslice", + "pkcs", + "pmsg", + "poacore", + "poaps", + "podc", + "popcnt", + "posdao", + "postinst", + "postrm", + "poststate", + "powm", + "preconf", + "preconfirmation", + "predeploy", + "prefixedf", + "prefund", + "preimage", + "preimages", + "preinstallation", + "prepopulate", + "prestate", + "prevop", + "prevrandao", + "prewarmer", + "prioritise", + "protoc", + "prysm", + "ptree", + "pushgateway", + "pwas", + "pwgen", + "pyroscope", + "pyspec", + "quickselect", + "qwords", + "randao", + "randomexists", + "rawblock", + "rblob", + "rbuilder", + "rcvbuf", + "reada", + "readahead", + "readded", + "readhead", + "readonlycolumndb", + "readoptions", + "receiveraddress", + "recents", + "recid", + "recognises", + "reconfig", + "redownloading", + "reencoding", + "refint", + "refstruct", + "regenesis", + "reitwiessner", + "reorganisation", + "reorganisations", + "reorganised", + "resettables", + "retesteth", + "retf", + "returncode", + "returndata", + "returndatacopy", + "returndataload", + "returndatasize", + "ripemd", + "rjump", + "rjumpi", + "rjumpv", + "rlim", + "rlimit", + "rlps", + "rlptest", + "rlpx", + "rlpxhost", + "rocksdb", + "ronin", + "roothash", + "rormask", + "rpcurl", + "runtimeconfig", + "rustc", + "ryzen", + "samplenewpayload", + "sankey", + "sbrk", + "scopable", + "sdiv", + "secp", + "securetrie", + "segmentations", + "selfbalance", + "selfdestruct", + "serialised", + "setcode", + "sete", + "shamir", + "shlibs", + "shouldly", + "shutterized", + "sig₁", + "sig₂", + "signextend", + "sizeinbase", + "skiplastn", + "slnx", + "sload", + "smod", + "somelabel", + "spaceneth", + "spammy", + "sparkline", + "spinlocks", + "squarify", + "ssse", + "sstfiles", + "sstore", + "sswu", + "stackoverflow", + "starthash", + "statediff", + "stateroot", + "statetest", + "staticcall", + "stddev", + "stelem", + "stfld", + "stoppables", + "storagefuzz", + "stree", + "strs", + "stylesheet", + "subcall", + "subcalls", + "subchain", + "subcompaction", + "subcompactions", + "subcontainer", + "subcontext", + "subfolders", + "substate", + "subtrace", + "subtraces", + "superchain", + "swapn", + "swende", + "synchronised", + "synclag", + "syscall", + "szalay", + "taiko", + "taskkill", + "tdxs", + "teku", + "testdata", + "testdb", + "testenvironments", + "testpassword", + "testpuppeth", + "testsimulate", + "testspec", + "testspecdir", + "threadid", + "threadlocal", + "threadpool", + "threadsafe", + "timestamper", + "titanbuilder", + "tload", + "toobig", + "trambabamba", + "treemap", + "trieanyorder", + "trieexception", + "trienode", + "triestore", + "trietest", + "trietestnextprev", + "triggerable", + "tstore", + "tukey", + "tupleception", + "txcreate", + "txns", + "txpointer", + "txpool", + "txps", + "txtest", + "typesection", + "ufixed", + "uleypool", + "ulongs", + "unalign", + "unbonded", + "uncast", + "uncompacted", + "unconfigured", + "underflowed", + "underflows", + "undiagonalize", + "unfinalized", + "unflushed", + "unimpacted", + "unkeyed", + "unlocker", + "unmarshalling", + "unmetered", + "unpad", + "unpooled", + "unreferred", + "unrequested", + "unresolve", + "unsub", + "unsubscription", + "unsynchronized", + "unvote", + "upnp", + "upto", + "upvoting", + "vbmi", + "vitalik", + "vmovups", + "vmtrace", + "vote₁", + "vote₂", + "vote₃", + "voteₙ", + "vpcbr", + "vpor", + "vptest", + "vzeroupper", + "wamp", + "warmcoinbase", + "wblob", + "winget", + "winsvega", + "wnew", + "wojciech", + "worklet", + "worklist", + "worldchain", + "worldstate", + "writebatch", + "writeoptions", + "wwwroot", + "wycheproof", + "xdai", + "xmlstarlet", + "xnpool", + "yellowpaper", + "ymmword", + "yparity", + "zcompressor", + "zdecompressor", + "zhizhu", + "zstandard", + "zstd", + "zwcm" + ], + "overrides": [ + { + "filename": "**/*.json", + "ignoreRegExpList": [ + "/\\\\u[0-9a-fA-F]{4}/g" + ] + } + ] +} diff --git a/scripts/build/Dockerfile b/scripts/build/Dockerfile index f21cb787fff..bb2f165f611 100644 --- a/scripts/build/Dockerfile +++ b/scripts/build/Dockerfile @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited # SPDX-License-Identifier: LGPL-3.0-only -FROM mcr.microsoft.com/dotnet/sdk:10.0.100-noble@sha256:c7445f141c04f1a6b454181bd098dcfa606c61ba0bd213d0a702489e5bd4cd71 +FROM mcr.microsoft.com/dotnet/sdk:10.0.102-noble@sha256:25d14b400b75fa4e89d5bd4487a92a604a4e409ab65becb91821e7dc4ac7f81f ARG COMMIT_HASH ARG SOURCE_DATE_EPOCH diff --git a/scripts/build/publish-downloads.sh b/scripts/build/publish-downloads.sh deleted file mode 100755 index 8983f6daa8d..00000000000 --- a/scripts/build/publish-downloads.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -# SPDX-License-Identifier: LGPL-3.0-only - -set -e - -export GPG_TTY=$(tty) - -echo "Publishing packages to Downloads page" - -cd $GITHUB_WORKSPACE/$PACKAGE_DIR - -for rid in "linux-arm64" "linux-x64" "macos-arm64" "macos-x64" "windows-x64"; do - file_name=$(basename *$rid*) - - echo "Signing $file_name" - - gpg --batch --detach-sign --passphrase=$PASS --pinentry-mode loopback --armor $file_name - - echo "Uploading $file_name" - - curl https://downloads.nethermind.io/files?apikey=$DOWNLOADS_PAGE \ - -X POST \ - --fail-with-body \ - -# \ - -F "files=@$PWD/$file_name" \ - -F "files=@$PWD/$file_name.asc" -done - -echo "Publishing completed" diff --git a/scripts/build/publish-github.sh b/scripts/build/publish-github.sh deleted file mode 100755 index 121a6975643..00000000000 --- a/scripts/build/publish-github.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -# SPDX-License-Identifier: LGPL-3.0-only - -set -e - -echo "Publishing packages to GitHub" - -release_id=$(curl https://api.github.com/repos/$GITHUB_REPOSITORY/releases \ - -X GET \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GITHUB_TOKEN" | jq -r '.[] | select(.tag_name == "'$GIT_TAG'") | .id') - -should_publish=true - -if [[ -z "$release_id" ]]; then - echo "Drafting release $GIT_TAG" - - body=$(printf \ - '{"tag_name": "%s", "target_commitish": "%s", "name": "v%s", "body": "# Release notes\\n\\n", "draft": true, "prerelease": %s}' \ - $GIT_TAG $GITHUB_SHA $GIT_TAG $PRERELEASE) - - release_id=$(curl https://api.github.com/repos/$GITHUB_REPOSITORY/releases \ - -X POST \ - --fail-with-body \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -d "$body" | jq -r '.id') - - should_publish=false -fi - -cd $GITHUB_WORKSPACE/$PACKAGE_DIR - -for rid in "linux-arm64" "linux-x64" "macos-arm64" "macos-x64" "windows-x64" "ref-assemblies"; do - file_name=$(basename *$rid*) - - echo "Uploading $file_name" - - curl https://uploads.github.com/repos/$GITHUB_REPOSITORY/releases/$release_id/assets?name=$file_name \ - -X POST \ - --fail-with-body \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -H "Content-Type: application/octet-stream" \ - --data-binary @"$file_name" -done - -if [[ "$should_publish" == "true" ]]; then - echo "Publishing release $GIT_TAG" - - make_latest=$([[ "$PRERELEASE" == "true" ]] && echo "false" || echo "true") - - body=$(printf \ - '{"target_commitish": "%s", "name": "v%s", "draft": false, "make_latest": "%s", "prerelease": %s}' \ - $GITHUB_SHA $GIT_TAG $make_latest $PRERELEASE) - - curl https://api.github.com/repos/$GITHUB_REPOSITORY/releases/$release_id \ - -X PATCH \ - --fail-with-body \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer $GITHUB_TOKEN" \ - -d "$body" -fi - -echo "Publishing completed" diff --git a/scripts/known-failing-hive-tests.txt b/scripts/known-failing-hive-tests.txt index 63ff5f96d73..d9464669c7c 100644 --- a/scripts/known-failing-hive-tests.txt +++ b/scripts/known-failing-hive-tests.txt @@ -1,10 +1,5 @@ # rpc-compat -eth_getStorageAt/get-storage-invalid-key (nethermind) -eth_getStorageAt/get-storage-invalid-key-too-large (nethermind) -eth_simulateV1/ethSimulate-two-blocks-with-complete-eth-sends (nethermind) -eth_simulateV1/ethSimulate-use-as-many-features-as-possible (nethermind) - # graphql 01_eth_blockNumber (nethermind) diff --git a/src/Nethermind/Chains/arena-z-mainnet.json.zst b/src/Nethermind/Chains/arena-z-mainnet.json.zst index 0d8b97f7971..fef9920bc91 100644 Binary files a/src/Nethermind/Chains/arena-z-mainnet.json.zst and b/src/Nethermind/Chains/arena-z-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/arena-z-sepolia.json.zst b/src/Nethermind/Chains/arena-z-sepolia.json.zst index b2a2cbd62a5..3a5412fe44e 100644 Binary files a/src/Nethermind/Chains/arena-z-sepolia.json.zst and b/src/Nethermind/Chains/arena-z-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/automata-mainnet.json.zst b/src/Nethermind/Chains/automata-mainnet.json.zst index 928b4ed5b16..4c2ad397e96 100644 Binary files a/src/Nethermind/Chains/automata-mainnet.json.zst and b/src/Nethermind/Chains/automata-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/base-mainnet.json.zst b/src/Nethermind/Chains/base-mainnet.json.zst index d78fc9e0a7d..aaaea720746 100644 Binary files a/src/Nethermind/Chains/base-mainnet.json.zst and b/src/Nethermind/Chains/base-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/base-sepolia.json.zst b/src/Nethermind/Chains/base-sepolia.json.zst index 2663198739d..9e2091dc43a 100644 Binary files a/src/Nethermind/Chains/base-sepolia.json.zst and b/src/Nethermind/Chains/base-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/bob-mainnet.json.zst b/src/Nethermind/Chains/bob-mainnet.json.zst index daf835b245b..6a90d30d85e 100644 Binary files a/src/Nethermind/Chains/bob-mainnet.json.zst and b/src/Nethermind/Chains/bob-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/boba-mainnet.json.zst b/src/Nethermind/Chains/boba-mainnet.json.zst index 98aead51470..59216510839 100644 Binary files a/src/Nethermind/Chains/boba-mainnet.json.zst and b/src/Nethermind/Chains/boba-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/boba-sepolia.json.zst b/src/Nethermind/Chains/boba-sepolia.json.zst index b8b8a2f5a4f..ab72b76af1a 100644 Binary files a/src/Nethermind/Chains/boba-sepolia.json.zst and b/src/Nethermind/Chains/boba-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/camp-sepolia.json.zst b/src/Nethermind/Chains/camp-sepolia.json.zst index 427d2a858f9..af0607192f9 100644 Binary files a/src/Nethermind/Chains/camp-sepolia.json.zst and b/src/Nethermind/Chains/camp-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/celo-sep-sepolia.json.zst b/src/Nethermind/Chains/celo-sep-sepolia.json.zst new file mode 100644 index 00000000000..79009164efc Binary files /dev/null and b/src/Nethermind/Chains/celo-sep-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/cyber-mainnet.json.zst b/src/Nethermind/Chains/cyber-mainnet.json.zst index 0356a2ee70a..ca178a08100 100644 Binary files a/src/Nethermind/Chains/cyber-mainnet.json.zst and b/src/Nethermind/Chains/cyber-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/cyber-sepolia.json.zst b/src/Nethermind/Chains/cyber-sepolia.json.zst index 00151e80b87..8cd41d36866 100644 Binary files a/src/Nethermind/Chains/cyber-sepolia.json.zst and b/src/Nethermind/Chains/cyber-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/dictionary b/src/Nethermind/Chains/dictionary index f2966efa857..3bb35bbf9d5 100644 Binary files a/src/Nethermind/Chains/dictionary and b/src/Nethermind/Chains/dictionary differ diff --git a/src/Nethermind/Chains/ethernity-mainnet.json.zst b/src/Nethermind/Chains/ethernity-mainnet.json.zst index 45e72e825e0..278fc5701e5 100644 Binary files a/src/Nethermind/Chains/ethernity-mainnet.json.zst and b/src/Nethermind/Chains/ethernity-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/ethernity-sepolia.json.zst b/src/Nethermind/Chains/ethernity-sepolia.json.zst index d6d0797a3cf..0a365fae323 100644 Binary files a/src/Nethermind/Chains/ethernity-sepolia.json.zst and b/src/Nethermind/Chains/ethernity-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/fraxtal-mainnet.json.zst b/src/Nethermind/Chains/fraxtal-mainnet.json.zst index ab6f7ba3855..869705de89c 100644 Binary files a/src/Nethermind/Chains/fraxtal-mainnet.json.zst and b/src/Nethermind/Chains/fraxtal-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/funki-mainnet.json.zst b/src/Nethermind/Chains/funki-mainnet.json.zst index f57bde28303..3f45d457365 100644 Binary files a/src/Nethermind/Chains/funki-mainnet.json.zst and b/src/Nethermind/Chains/funki-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/funki-sepolia.json.zst b/src/Nethermind/Chains/funki-sepolia.json.zst index 1ede536ec8d..eb82ebcedc1 100644 Binary files a/src/Nethermind/Chains/funki-sepolia.json.zst and b/src/Nethermind/Chains/funki-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/gnosis.json b/src/Nethermind/Chains/gnosis.json index 44b95dd869f..3ed45d0397e 100644 --- a/src/Nethermind/Chains/gnosis.json +++ b/src/Nethermind/Chains/gnosis.json @@ -36,6 +36,11 @@ "21735000": { "0xf8D1677c8a0c961938bf2f9aDc3F3CFDA759A9d9": "0x6080604052600436106101b35763ffffffff60e060020a60003504166305d2035b81146101b857806306fdde03146101e1578063095ea7b31461026b5780630b26cf661461028f57806318160ddd146102b257806323b872dd146102d957806330adf81f14610303578063313ce567146103185780633644e5151461034357806339509351146103585780634000aea01461037c57806340c10f19146103ad57806342966c68146103d157806354fd4d50146103e957806366188463146103fe57806369ffa08a1461042257806370a0823114610449578063715018a61461046a578063726600ce1461047f5780637d64bcb4146104a05780637ecebe00146104b5578063859ba28c146104d65780638da5cb5b146105175780638fcbaf0c1461054857806395d89b4114610586578063a457c2d71461059b578063a9059cbb146105bf578063b753a98c146105e3578063bb35783b14610607578063c6a1dedf14610631578063cd59658314610646578063d505accf1461065b578063d73dd62314610694578063dd62ed3e146106b8578063f2d5d56b146106df578063f2fde38b14610703578063ff9e884d14610724575b600080fd5b3480156101c457600080fd5b506101cd61074b565b604080519115158252519081900360200190f35b3480156101ed57600080fd5b506101f661076c565b6040805160208082528351818301528351919283929083019185019080838360005b83811015610230578181015183820152602001610218565b50505050905090810190601f16801561025d5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561027757600080fd5b506101cd600160a060020a03600435166024356107fa565b34801561029b57600080fd5b506102b0600160a060020a0360043516610810565b005b3480156102be57600080fd5b506102c761086a565b60408051918252519081900360200190f35b3480156102e557600080fd5b506101cd600160a060020a0360043581169060243516604435610870565b34801561030f57600080fd5b506102c7610a38565b34801561032457600080fd5b5061032d610a5c565b6040805160ff9092168252519081900360200190f35b34801561034f57600080fd5b506102c7610a65565b34801561036457600080fd5b506101cd600160a060020a0360043516602435610a6b565b34801561038857600080fd5b506101cd60048035600160a060020a0316906024803591604435918201910135610aac565b3480156103b957600080fd5b506101cd600160a060020a0360043516602435610bbd565b3480156103dd57600080fd5b506102b0600435610cc8565b3480156103f557600080fd5b506101f6610cd5565b34801561040a57600080fd5b506101cd600160a060020a0360043516602435610d0c565b34801561042e57600080fd5b506102b0600160a060020a0360043581169060243516610de9565b34801561045557600080fd5b506102c7600160a060020a0360043516610e0e565b34801561047657600080fd5b506102b0610e29565b34801561048b57600080fd5b506101cd600160a060020a0360043516610e40565b3480156104ac57600080fd5b506101cd610e54565b3480156104c157600080fd5b506102c7600160a060020a0360043516610e5b565b3480156104e257600080fd5b506104eb610e6d565b6040805167ffffffffffffffff9485168152928416602084015292168183015290519081900360600190f35b34801561052357600080fd5b5061052c610e78565b60408051600160a060020a039092168252519081900360200190f35b34801561055457600080fd5b506102b0600160a060020a0360043581169060243516604435606435608435151560ff60a4351660c43560e435610e87565b34801561059257600080fd5b506101f6610fc5565b3480156105a757600080fd5b506101cd600160a060020a036004351660243561101f565b3480156105cb57600080fd5b506101cd600160a060020a0360043516602435611032565b3480156105ef57600080fd5b506102b0600160a060020a0360043516602435611054565b34801561061357600080fd5b506102b0600160a060020a0360043581169060243516604435611064565b34801561063d57600080fd5b506102c7611075565b34801561065257600080fd5b5061052c611099565b34801561066757600080fd5b506102b0600160a060020a036004358116906024351660443560643560ff6084351660a43560c4356110a8565b3480156106a057600080fd5b506101cd600160a060020a0360043516602435611184565b3480156106c457600080fd5b506102c7600160a060020a036004358116906024351661120b565b3480156106eb57600080fd5b506102b0600160a060020a0360043516602435611236565b34801561070f57600080fd5b506102b0600160a060020a0360043516611241565b34801561073057600080fd5b506102c7600160a060020a0360043581169060243516611261565b60065474010000000000000000000000000000000000000000900460ff1681565b6000805460408051602060026001851615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156107f25780601f106107c7576101008083540402835291602001916107f2565b820191906000526020600020905b8154815290600101906020018083116107d557829003601f168201915b505050505081565b600061080733848461127e565b50600192915050565b600654600160a060020a0316331461082757600080fd5b610830816112c0565b151561083b57600080fd5b6007805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0392909216919091179055565b60045490565b600080600160a060020a038516151561088857600080fd5b600160a060020a038416151561089d57600080fd5b600160a060020a0385166000908152600360205260409020546108c6908463ffffffff6112c816565b600160a060020a0380871660009081526003602052604080822093909355908616815220546108fb908463ffffffff6112da16565b600160a060020a038086166000818152600360209081526040918290209490945580518781529051919392891692600080516020611d7283398151915292918290030190a3600160a060020a0385163314610a225761095a853361120b565b905060001981146109c457610975818463ffffffff6112c816565b600160a060020a038616600081815260056020908152604080832033808552908352928190208590558051948552519193600080516020611d92833981519152929081900390910190a3610a22565b600160a060020a0385166000908152600a602090815260408083203384529091529020541580610a175750600160a060020a0385166000908152600a602090815260408083203384529091529020544211155b1515610a2257600080fd5b610a2d8585856112ed565b506001949350505050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b60025460ff1681565b60085481565b336000818152600560209081526040808320600160a060020a03871684529091528120549091610807918590610aa7908663ffffffff6112da16565b61127e565b600084600160a060020a03811615801590610ad05750600160a060020a0381163014155b1515610adb57600080fd5b610ae58686611324565b1515610af057600080fd5b85600160a060020a031633600160a060020a03167fe19260aff97b920c7df27010903aeb9c8d2be5d310a2c67824cf3f15396e4c16878787604051808481526020018060200182810382528484828181526020019250808284376040519201829003965090945050505050a3610b65866112c0565b15610bb157610ba633878787878080601f01602080910402602001604051908101604052809392919081815260200183838082843750611330945050505050565b1515610bb157600080fd5b50600195945050505050565b600654600090600160a060020a03163314610bd757600080fd5b60065474010000000000000000000000000000000000000000900460ff1615610bff57600080fd5b600454610c12908363ffffffff6112da16565b600455600160a060020a038316600090815260036020526040902054610c3e908363ffffffff6112da16565b600160a060020a038416600081815260036020908152604091829020939093558051858152905191927f0f6798a560793a54c3bcfe86a93cde1e73087d944c0ea20544137d412139688592918290030190a2604080518381529051600160a060020a03851691600091600080516020611d728339815191529181900360200190a350600192915050565b610cd233826114ad565b50565b60408051808201909152600181527f3100000000000000000000000000000000000000000000000000000000000000602082015281565b336000908152600560209081526040808320600160a060020a0386168452909152812054808310610d6057336000908152600560209081526040808320600160a060020a0388168452909152812055610d95565b610d70818463ffffffff6112c816565b336000908152600560209081526040808320600160a060020a03891684529091529020555b336000818152600560209081526040808320600160a060020a038916808552908352928190205481519081529051929392600080516020611d92833981519152929181900390910190a35060019392505050565b600654600160a060020a03163314610e0057600080fd5b610e0a828261159c565b5050565b600160a060020a031660009081526003602052604090205490565b600654600160a060020a031633146101b357600080fd5b600754600160a060020a0390811691161490565b6000806000fd5b60096020526000908152604090205481565b600260056000909192565b600654600160a060020a031681565b600080861580610e975750864211155b1515610ea257600080fd5b604080517fea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb6020820152600160a060020a03808d16828401528b166060820152608081018a905260a0810189905287151560c0808301919091528251808303909101815260e0909101909152610f17906115da565b9150610f25828686866116e1565b600160a060020a038b8116911614610f3c57600080fd5b600160a060020a038a1660009081526009602052604090208054600181019091558814610f6857600080fd5b85610f74576000610f78565b6000195b905085610f86576000610f88565b865b600160a060020a03808c166000908152600a60209081526040808320938e1683529290522055610fb98a8a836118e3565b50505050505050505050565b60018054604080516020600284861615610100026000190190941693909304601f810184900484028201840190925281815292918301828280156107f25780601f106107c7576101008083540402835291602001916107f2565b600061102b8383610d0c565b9392505050565b600061103e8383611324565b151561104957600080fd5b6108073384846112ed565b61105f338383610870565b505050565b61106f838383610870565b50505050565b7fea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb81565b600754600160a060020a031690565b600080428610156110b857600080fd5b600160a060020a03808a1660008181526009602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c99281019290925281830193909352928b166060840152608083018a905260a0830182905260c08084018a90528151808503909101815260e090930190529250611149906115da565b9050611157818686866116e1565b600160a060020a038a811691161461116e57600080fd5b61117989898961127e565b505050505050505050565b336000908152600560209081526040808320600160a060020a03861684529091528120546111b8908363ffffffff6112da16565b336000818152600560209081526040808320600160a060020a038916808552908352928190208590558051948552519193600080516020611d92833981519152929081900390910190a350600192915050565b600160a060020a03918216600090815260056020908152604080832093909416825291909152205490565b61105f823383610870565b600654600160a060020a0316331461125857600080fd5b610cd281611a3e565b600a60209081526000928352604080842090915290825290205481565b6112898383836118e3565b60001981141561105f57600160a060020a038084166000908152600a60209081526040808320938616835292905290812055505050565b6000903b1190565b6000828211156112d457fe5b50900390565b818101828110156112e757fe5b92915050565b6112f682610e40565b1561105f5760408051600081526020810190915261131990849084908490611330565b151561105f57600080fd5b600061102b8383611abc565b600083600160a060020a031663a4c0ed3660e060020a028685856040516024018084600160a060020a0316600160a060020a0316815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b838110156113a8578181015183820152602001611390565b50505050905090810190601f1680156113d55780820380516001836020036101000a031916815260200191505b5060408051601f198184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909916989098178852518151919790965086955093509150819050838360005b8381101561146357818101518382015260200161144b565b50505050905090810190601f1680156114905780820380516001836020036101000a031916815260200191505b509150506000604051808303816000865af1979650505050505050565b600160a060020a0382166000908152600360205260409020548111156114d257600080fd5b600160a060020a0382166000908152600360205260409020546114fb908263ffffffff6112c816565b600160a060020a038316600090815260036020526040902055600454611527908263ffffffff6112c816565b600455604080518281529051600160a060020a038416917fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5919081900360200190a2604080518281529051600091600160a060020a03851691600080516020611d728339815191529181900360200190a35050565b80600160a060020a03811615156115b257600080fd5b600160a060020a03831615156115d0576115cb82611b8b565b61105f565b61105f8383611b97565b6000600854826040518082805190602001908083835b6020831061160f5780518252601f1990920191602091820191016115f0565b51815160209384036101000a6000190180199092169116179052604080519290940182900382207f190100000000000000000000000000000000000000000000000000000000000083830152602283019790975260428083019790975283518083039097018752606290910192839052855192945084935085019190508083835b602083106116af5780518252601f199092019160209182019101611690565b5181516020939093036101000a6000190180199091169216919091179052604051920182900390912095945050505050565b6000808460ff16601b14806116f957508460ff16601c145b1515611775576040805160e560020a62461bcd02815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202776272076616c60448201527f7565000000000000000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b7f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0831115611813576040805160e560020a62461bcd02815260206004820152602260248201527f45434453413a20696e76616c6964207369676e6174757265202773272076616c60448201527f7565000000000000000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b60408051600080825260208083018085528a905260ff8916838501526060830188905260808301879052925160019360a0808501949193601f19840193928390039091019190865af115801561186d573d6000803e3d6000fd5b5050604051601f190151915050600160a060020a03811615156118da576040805160e560020a62461bcd02815260206004820152601860248201527f45434453413a20696e76616c6964207369676e61747572650000000000000000604482015290519081900360640190fd5b95945050505050565b600160a060020a0383161515611968576040805160e560020a62461bcd028152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460448201527f7265737300000000000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b600160a060020a03821615156119ee576040805160e560020a62461bcd02815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f20616464726560448201527f7373000000000000000000000000000000000000000000000000000000000000606482015290519081900360840190fd5b600160a060020a0380841660008181526005602090815260408083209487168084529482529182902085905581518581529151600080516020611d928339815191529281900390910190a3505050565b600160a060020a0381161515611a5357600080fd5b600654604051600160a060020a038084169216907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a36006805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0392909216919091179055565b33600090815260036020526040812054821115611ad857600080fd5b600160a060020a0383161515611aed57600080fd5b33600090815260036020526040902054611b0d908363ffffffff6112c816565b3360009081526003602052604080822092909255600160a060020a03851681522054611b3f908363ffffffff6112da16565b600160a060020a038416600081815260036020908152604091829020939093558051858152905191923392600080516020611d728339815191529281900390910190a350600192915050565b3031610e0a8282611c44565b604080517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015290518391600091600160a060020a038416916370a0823191602480830192602092919082900301818787803b158015611bfc57600080fd5b505af1158015611c10573d6000803e3d6000fd5b505050506040513d6020811015611c2657600080fd5b5051905061106f600160a060020a038516848363ffffffff611cac16565b604051600160a060020a0383169082156108fc029083906000818181858888f193505050501515610e0a578082611c79611d41565b600160a060020a039091168152604051908190036020019082f080158015611ca5573d6000803e3d6000fd5b5050505050565b82600160a060020a031663a9059cbb83836040518363ffffffff1660e060020a0281526004018083600160a060020a0316600160a060020a0316815260200182815260200192505050600060405180830381600087803b158015611d0f57600080fd5b505af1158015611d23573d6000803e3d6000fd5b505050503d1561105f5760206000803e600051151561105f57600080fd5b604051602180611d51833901905600608060405260405160208060218339810160405251600160a060020a038116ff00ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925a165627a7a72305820b96bb0733a3e45fdddafa592f51114d0cf16cad047ad60b9b91ae91eb772c6940029" } + }, + "rewriteBytecodeTimestamp": { + "1766419900": { + "0x506d1f9efe24f0d47853adca907eb8d89ae03207": "0x60806040526004361061002c575f3560e01c80638da5cb5b14610037578063b61d27f61461006157610033565b3661003357005b5f5ffd5b348015610042575f5ffd5b5061004b610091565b604051610058919061030a565b60405180910390f35b61007b600480360381019061007691906103e9565b6100a9565b60405161008891906104ca565b60405180910390f35b737be579238a6a621601eae2c346cda54d68f7dfee81565b60606100b3610247565b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603610121576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161011890610544565b60405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff163b1161017a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610171906105ac565b60405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff168686866040516101a4929190610606565b5f6040518083038185875af1925050503d805f81146101de576040519150601f19603f3d011682016040523d82523d5f602084013e6101e3565b606091505b50915091508161023a575f815111156101ff5780518060208301fd5b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161023190610668565b60405180910390fd5b8092505050949350505050565b737be579238a6a621601eae2c346cda54d68f7dfee73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146102c9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102c0906106d0565b60405180910390fd5b565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6102f4826102cb565b9050919050565b610304816102ea565b82525050565b5f60208201905061031d5f8301846102fb565b92915050565b5f5ffd5b5f5ffd5b610334816102ea565b811461033e575f5ffd5b50565b5f8135905061034f8161032b565b92915050565b5f819050919050565b61036781610355565b8114610371575f5ffd5b50565b5f813590506103828161035e565b92915050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f8401126103a9576103a8610388565b5b8235905067ffffffffffffffff8111156103c6576103c561038c565b5b6020830191508360018202830111156103e2576103e1610390565b5b9250929050565b5f5f5f5f6060858703121561040157610400610323565b5b5f61040e87828801610341565b945050602061041f87828801610374565b935050604085013567ffffffffffffffff8111156104405761043f610327565b5b61044c87828801610394565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f61049c8261045a565b6104a68185610464565b93506104b6818560208601610474565b6104bf81610482565b840191505092915050565b5f6020820190508181035f8301526104e28184610492565b905092915050565b5f82825260208201905092915050565b7f7a65726f207461726765740000000000000000000000000000000000000000005f82015250565b5f61052e600b836104ea565b9150610539826104fa565b602082019050919050565b5f6020820190508181035f83015261055b81610522565b9050919050565b7f6e6f74206120636f6e74726163740000000000000000000000000000000000005f82015250565b5f610596600e836104ea565b91506105a182610562565b602082019050919050565b5f6020820190508181035f8301526105c38161058a565b9050919050565b5f81905092915050565b828183375f83830152505050565b5f6105ed83856105ca565b93506105fa8385846105d4565b82840190509392505050565b5f6106128284866105e2565b91508190509392505050565b7f63616c6c206661696c65640000000000000000000000000000000000000000005f82015250565b5f610652600b836104ea565b915061065d8261061e565b602082019050919050565b5f6020820190508181035f83015261067f81610646565b9050919050565b7f6e6f74206f776e657200000000000000000000000000000000000000000000005f82015250565b5f6106ba6009836104ea565b91506106c582610686565b602082019050919050565b5f6020820190508181035f8301526106e7816106ae565b905091905056fea2646970667358221220a8334a26f31db2a806db6c1bcc4107caa8ec5cbdc7b742cfec99b4f0cca066a364736f6c634300081e0033" + } } } } @@ -300,4 +305,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/Nethermind/Chains/hashkeychain-mainnet.json.zst b/src/Nethermind/Chains/hashkeychain-mainnet.json.zst index 93644fb7cd3..28dabe61bf0 100644 Binary files a/src/Nethermind/Chains/hashkeychain-mainnet.json.zst and b/src/Nethermind/Chains/hashkeychain-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/ink-mainnet.json.zst b/src/Nethermind/Chains/ink-mainnet.json.zst index d69b5fdd613..79711aa9665 100644 Binary files a/src/Nethermind/Chains/ink-mainnet.json.zst and b/src/Nethermind/Chains/ink-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/ink-sepolia.json.zst b/src/Nethermind/Chains/ink-sepolia.json.zst index 8b6ed84f486..7e317838c1e 100644 Binary files a/src/Nethermind/Chains/ink-sepolia.json.zst and b/src/Nethermind/Chains/ink-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/joc-mainnet.json b/src/Nethermind/Chains/joc-mainnet.json index d4d361d7184..6fe008cb3b8 100644 --- a/src/Nethermind/Chains/joc-mainnet.json +++ b/src/Nethermind/Chains/joc-mainnet.json @@ -61,9 +61,8 @@ "timestamp": "0x5bfbe6b5" }, "nodes": [ - "enode://d4c1196326527c13cb318fb062571d9ae25393cbaa06222b3e57ca6407eeac550cf0fd148250282fdcb48e64877f3451d7a8cca281d9a0364c5739462976dfb5@54.199.51.112:30303", - "enode://a0662a1fb5d0b707c527355e03a59b1b5a63ffef76a3a758b2a0696c3f9e6205361db55906b91cdaa455c879aa8eb725536414fb0046990cc9e3611f4b130ef1@13.115.231.63:30303", - "enode://fcaa8046c7a81525882c409f70de7fcd3b9eab1fb4c8361fc62bc4d97459a619bedcc274d04212bf7631be6873b8547bf87e0057a4243da5919d15d58e42ab8c@54.178.230.138:30303" + "enode://c387e2b4e5231022ef30144c41fbd883139e9b5f1f4649c3d51c1611adbfaeadfd050c1bd9ac02eec6fa4c234b49a77fb5fb54f739c06d431eabfd981edc51f2@13.56.117.179:30303", + "enode://db803c26db9dac21e58452646a785b94a466eebffd6038621f78de92ccc6141fcb297650c290487375ab32a6dbc693d5dab49dba9785450002c68944ab0435a2@54.241.98.152:30303" ], "accounts": { "0000000000000000000000000000000000000000": { diff --git a/src/Nethermind/Chains/joc-testnet.json b/src/Nethermind/Chains/joc-testnet.json index e11f1a1546d..be486397105 100644 --- a/src/Nethermind/Chains/joc-testnet.json +++ b/src/Nethermind/Chains/joc-testnet.json @@ -61,8 +61,8 @@ "timestamp": "0x5bfbe6b5" }, "nodes": [ - "enode://c801556bf3e2eb2b4dcb1643febe1e7011096997e8cb41230e5f05c737cc0a3f41a76fb73f3262a8fed9742fbb3df6078eed6733dd3c358554207ec8cacfa999@43.207.64.52:30303", - "enode://8aa6f351eff4bee5d3a6a72ca5820fac65274e9dbd63e13d060682a5228000ab960ff8c177d7cf66b0555859b7eabbc866b71625626a11856e3573bf0592bfed@3.112.196.238:30303" + "enode://f964f94067a851758a3f308831602ca05a374a8a5dcba8ec5f78cde5d31dc809fc0115a84a785bbd1c8024a46d16eee732f4b681cc36cf323e8fab933f92849a@54.248.244.225:30303", + "enode://c68340e7daac1eecc3cdfbfc7c68a80ebf91dbc7f63413dae39b75b2738e63965033cefe452f0b50d8f1d2c5df74eba9905e85c223dda9bc0040fb1c06f35dc5@13.158.174.185:30303" ], "accounts": { "0000000000000000000000000000000000000000": { diff --git a/src/Nethermind/Chains/lisk-mainnet.json.zst b/src/Nethermind/Chains/lisk-mainnet.json.zst index c0a030ccf6d..598598dfa3b 100644 Binary files a/src/Nethermind/Chains/lisk-mainnet.json.zst and b/src/Nethermind/Chains/lisk-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/lisk-sepolia.json.zst b/src/Nethermind/Chains/lisk-sepolia.json.zst index 62dc3d9c516..88a36f768fd 100644 Binary files a/src/Nethermind/Chains/lisk-sepolia.json.zst and b/src/Nethermind/Chains/lisk-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/lyra-mainnet.json.zst b/src/Nethermind/Chains/lyra-mainnet.json.zst index 0d8bdd813e1..c2f409b139a 100644 Binary files a/src/Nethermind/Chains/lyra-mainnet.json.zst and b/src/Nethermind/Chains/lyra-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/metal-mainnet.json.zst b/src/Nethermind/Chains/metal-mainnet.json.zst index b522efb98e3..c0d9dc63466 100644 Binary files a/src/Nethermind/Chains/metal-mainnet.json.zst and b/src/Nethermind/Chains/metal-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/metal-sepolia.json.zst b/src/Nethermind/Chains/metal-sepolia.json.zst index 21f6ef6bf7e..2806a8338b5 100644 Binary files a/src/Nethermind/Chains/metal-sepolia.json.zst and b/src/Nethermind/Chains/metal-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/mint-mainnet.json.zst b/src/Nethermind/Chains/mint-mainnet.json.zst index 0202568f47e..827516e59bd 100644 Binary files a/src/Nethermind/Chains/mint-mainnet.json.zst and b/src/Nethermind/Chains/mint-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/mode-mainnet.json.zst b/src/Nethermind/Chains/mode-mainnet.json.zst index 828d7576ba8..fda25cd5316 100644 Binary files a/src/Nethermind/Chains/mode-mainnet.json.zst and b/src/Nethermind/Chains/mode-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/mode-sepolia.json.zst b/src/Nethermind/Chains/mode-sepolia.json.zst index 987fd93c08c..2b237e4d741 100644 Binary files a/src/Nethermind/Chains/mode-sepolia.json.zst and b/src/Nethermind/Chains/mode-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/op-mainnet.json.zst b/src/Nethermind/Chains/op-mainnet.json.zst index 22d688630aa..6664eeb91e5 100644 Binary files a/src/Nethermind/Chains/op-mainnet.json.zst and b/src/Nethermind/Chains/op-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/op-sepolia.json.zst b/src/Nethermind/Chains/op-sepolia.json.zst index 52257de104c..9a459ba8dab 100644 Binary files a/src/Nethermind/Chains/op-sepolia.json.zst and b/src/Nethermind/Chains/op-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/orderly-mainnet.json.zst b/src/Nethermind/Chains/orderly-mainnet.json.zst index a7f7146e31e..88a56c6cc9a 100644 Binary files a/src/Nethermind/Chains/orderly-mainnet.json.zst and b/src/Nethermind/Chains/orderly-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/ozean-sepolia.json.zst b/src/Nethermind/Chains/ozean-sepolia.json.zst index c94ddb78a3e..5cdd1c777ba 100644 Binary files a/src/Nethermind/Chains/ozean-sepolia.json.zst and b/src/Nethermind/Chains/ozean-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/pivotal-sepolia.json.zst b/src/Nethermind/Chains/pivotal-sepolia.json.zst index f40661f81d5..7b0a4d2ee1f 100644 Binary files a/src/Nethermind/Chains/pivotal-sepolia.json.zst and b/src/Nethermind/Chains/pivotal-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/polynomial-mainnet.json.zst b/src/Nethermind/Chains/polynomial-mainnet.json.zst index 3d457a22b96..c6777472a00 100644 Binary files a/src/Nethermind/Chains/polynomial-mainnet.json.zst and b/src/Nethermind/Chains/polynomial-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/race-mainnet.json.zst b/src/Nethermind/Chains/race-mainnet.json.zst index b912f3ca3d1..c9d5738c277 100644 Binary files a/src/Nethermind/Chains/race-mainnet.json.zst and b/src/Nethermind/Chains/race-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/race-sepolia.json.zst b/src/Nethermind/Chains/race-sepolia.json.zst index 8c33463cde1..be45c6ea1dd 100644 Binary files a/src/Nethermind/Chains/race-sepolia.json.zst and b/src/Nethermind/Chains/race-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/redstone-mainnet.json.zst b/src/Nethermind/Chains/redstone-mainnet.json.zst index 43b0b9eba19..21bc7200f92 100644 Binary files a/src/Nethermind/Chains/redstone-mainnet.json.zst and b/src/Nethermind/Chains/redstone-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/settlus-mainnet-mainnet.json.zst b/src/Nethermind/Chains/settlus-mainnet-mainnet.json.zst index 5f1cf4f95b4..d9cc94883db 100644 Binary files a/src/Nethermind/Chains/settlus-mainnet-mainnet.json.zst and b/src/Nethermind/Chains/settlus-mainnet-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/settlus-sepolia-sepolia.json.zst b/src/Nethermind/Chains/settlus-sepolia-sepolia.json.zst index b9a8fcd5ff5..91748ecb4ed 100644 Binary files a/src/Nethermind/Chains/settlus-sepolia-sepolia.json.zst and b/src/Nethermind/Chains/settlus-sepolia-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/shape-mainnet.json.zst b/src/Nethermind/Chains/shape-mainnet.json.zst index 26f59cf00c4..94111ce00e0 100644 Binary files a/src/Nethermind/Chains/shape-mainnet.json.zst and b/src/Nethermind/Chains/shape-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/shape-sepolia.json.zst b/src/Nethermind/Chains/shape-sepolia.json.zst index 3eed325e085..9cbb6aed88d 100644 Binary files a/src/Nethermind/Chains/shape-sepolia.json.zst and b/src/Nethermind/Chains/shape-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/soneium-mainnet.json.zst b/src/Nethermind/Chains/soneium-mainnet.json.zst index 66a633b6d86..33c9ba2e85e 100644 Binary files a/src/Nethermind/Chains/soneium-mainnet.json.zst and b/src/Nethermind/Chains/soneium-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/soneium-minato-sepolia.json.zst b/src/Nethermind/Chains/soneium-minato-sepolia.json.zst index 11023bceb62..13e2e36ea52 100644 Binary files a/src/Nethermind/Chains/soneium-minato-sepolia.json.zst and b/src/Nethermind/Chains/soneium-minato-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/sseed-mainnet.json.zst b/src/Nethermind/Chains/sseed-mainnet.json.zst index af814d735c6..be8682b57c7 100644 Binary files a/src/Nethermind/Chains/sseed-mainnet.json.zst and b/src/Nethermind/Chains/sseed-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/swan-mainnet.json.zst b/src/Nethermind/Chains/swan-mainnet.json.zst index ab7de38ec30..8c1d0552d6a 100644 Binary files a/src/Nethermind/Chains/swan-mainnet.json.zst and b/src/Nethermind/Chains/swan-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/swell-mainnet.json.zst b/src/Nethermind/Chains/swell-mainnet.json.zst index 8d16059d8bb..b0e270c4f8a 100644 Binary files a/src/Nethermind/Chains/swell-mainnet.json.zst and b/src/Nethermind/Chains/swell-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/tbn-mainnet.json.zst b/src/Nethermind/Chains/tbn-mainnet.json.zst index c4d8389eef1..c112ed114a9 100644 Binary files a/src/Nethermind/Chains/tbn-mainnet.json.zst and b/src/Nethermind/Chains/tbn-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/tbn-sepolia.json.zst b/src/Nethermind/Chains/tbn-sepolia.json.zst index e72c288814d..1076922f75e 100644 Binary files a/src/Nethermind/Chains/tbn-sepolia.json.zst and b/src/Nethermind/Chains/tbn-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/unichain-mainnet.json.zst b/src/Nethermind/Chains/unichain-mainnet.json.zst index 91c2603740f..0c645ad0e4d 100644 Binary files a/src/Nethermind/Chains/unichain-mainnet.json.zst and b/src/Nethermind/Chains/unichain-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/unichain-sepolia.json.zst b/src/Nethermind/Chains/unichain-sepolia.json.zst index bfd9eda1df9..af355eb5bba 100644 Binary files a/src/Nethermind/Chains/unichain-sepolia.json.zst and b/src/Nethermind/Chains/unichain-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/worldchain-mainnet.json.zst b/src/Nethermind/Chains/worldchain-mainnet.json.zst index ed824530df6..2c243b391bb 100644 Binary files a/src/Nethermind/Chains/worldchain-mainnet.json.zst and b/src/Nethermind/Chains/worldchain-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/worldchain-sepolia.json.zst b/src/Nethermind/Chains/worldchain-sepolia.json.zst index 1b05551443c..830f302c5d8 100644 Binary files a/src/Nethermind/Chains/worldchain-sepolia.json.zst and b/src/Nethermind/Chains/worldchain-sepolia.json.zst differ diff --git a/src/Nethermind/Chains/xdc.json b/src/Nethermind/Chains/xdc.json index d96bdac61bc..45908962ee4 100644 --- a/src/Nethermind/Chains/xdc.json +++ b/src/Nethermind/Chains/xdc.json @@ -45,22 +45,24 @@ "MinePeriod": 2 }, { - "MaxMasternodes": 108, - "SwitchRound": 460000, - "CertThreshold": 0.667, + "MaxMasternodes": 108, + "SwitchRound": 460000, + "CertThreshold": 0.667, "TimeoutSyncThreshold": 2, - "TimeoutPeriod": 20, - "MinePeriod": 2 + "TimeoutPeriod": 20, + "MinePeriod": 2 }, { - "MaxMasternodes": 108, - "SwitchRound": 3200000, - "CertThreshold": 0.667, + "MaxMasternodes": 108, + "SwitchRound": 3200000, + "CertThreshold": 0.667, "TimeoutSyncThreshold": 3, - "TimeoutPeriod": 10, - "MinePeriod": 2 + "TimeoutPeriod": 10, + "MinePeriod": 2 } - ] + ], + "masternodeVotingContract": "0x0000000000000000000000000000000000000088", + "blockSignerContract": "0x0000000000000000000000000000000000000089" } } }, @@ -71,7 +73,8 @@ "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", "eip155Block": 3, "eip158Block": 3, - "byzantiumBlock": 4 + "byzantiumBlock": 4, + "depositContractAddress": "0x0000000000000000000000000000000000000088" }, "genesis": { "nonce": "0x0", diff --git a/src/Nethermind/Chains/xterio-eth-mainnet.json.zst b/src/Nethermind/Chains/xterio-eth-mainnet.json.zst index 065d24db8a9..6e8bb10444c 100644 Binary files a/src/Nethermind/Chains/xterio-eth-mainnet.json.zst and b/src/Nethermind/Chains/xterio-eth-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/zora-mainnet.json.zst b/src/Nethermind/Chains/zora-mainnet.json.zst index 79ac0dc64bb..1d93b533b0b 100644 Binary files a/src/Nethermind/Chains/zora-mainnet.json.zst and b/src/Nethermind/Chains/zora-mainnet.json.zst differ diff --git a/src/Nethermind/Chains/zora-sepolia.json.zst b/src/Nethermind/Chains/zora-sepolia.json.zst index d63f5073380..763641aa82e 100644 Binary files a/src/Nethermind/Chains/zora-sepolia.json.zst and b/src/Nethermind/Chains/zora-sepolia.json.zst differ diff --git a/src/Nethermind/Directory.Build.props b/src/Nethermind/Directory.Build.props index fa845ca3670..b46ea4a88f4 100644 --- a/src/Nethermind/Directory.Build.props +++ b/src/Nethermind/Directory.Build.props @@ -5,7 +5,7 @@ $(SOURCE_DATE_EPOCH) $([System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds()) - 1.36.0 + 1.37.0 unstable diff --git a/src/Nethermind/Directory.Build.targets b/src/Nethermind/Directory.Build.targets new file mode 100644 index 00000000000..4c920adb3cb --- /dev/null +++ b/src/Nethermind/Directory.Build.targets @@ -0,0 +1,28 @@ + + + + + <_SharedRuntimesDir>$(ArtifactsPath)\runtimes\$(Configuration) + + + + + + <_RuntimeFilesToShare Include="$(OutputPath)runtimes\**\*" /> + + + + + + + + + + + diff --git a/src/Nethermind/Ethereum.Difficulty.Test/DifficultyOlimpicTests.cs b/src/Nethermind/Ethereum.Difficulty.Test/DifficultyOlympicTests.cs similarity index 67% rename from src/Nethermind/Ethereum.Difficulty.Test/DifficultyOlimpicTests.cs rename to src/Nethermind/Ethereum.Difficulty.Test/DifficultyOlympicTests.cs index 478be5ef5b3..12073260d84 100644 --- a/src/Nethermind/Ethereum.Difficulty.Test/DifficultyOlimpicTests.cs +++ b/src/Nethermind/Ethereum.Difficulty.Test/DifficultyOlympicTests.cs @@ -7,15 +7,15 @@ namespace Ethereum.Difficulty.Test { [Parallelizable(ParallelScope.All)] - public class DifficultyOlimpicTests : TestsBase + public class DifficultyOlympicTests : TestsBase { - public static IEnumerable LoadOlimpicTests() + public static IEnumerable LoadOlympicTests() { - return LoadHex("difficultyOlimpic.json"); + return LoadHex("difficultyOlympic.json"); } // ToDo: fix loader - // [TestCaseSource(nameof(LoadOlimpicTests))] + // [TestCaseSource(nameof(LoadOlympicTests))] // public void Test(DifficultyTests test) // { // RunTest(test, new SingleReleaseSpecProvider(Olympic.Instance, 0)); diff --git a/src/Nethermind/Ethereum.HexPrefix.Test/HexPrefixTests.cs b/src/Nethermind/Ethereum.HexPrefix.Test/HexPrefixTests.cs index ceb9e13da4d..6cf38a0765e 100644 --- a/src/Nethermind/Ethereum.HexPrefix.Test/HexPrefixTests.cs +++ b/src/Nethermind/Ethereum.HexPrefix.Test/HexPrefixTests.cs @@ -16,7 +16,7 @@ namespace Ethereum.HexPrefix.Test public class HexPrefixTests { // ReSharper disable once MemberCanBePrivate.Global - // used as a test case source, hasbe public + // used as a test case source, has to be public public static IEnumerable LoadTests() { return TestLoader.LoadFromFile, HexPrefixTest>( diff --git a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3651warmcoinbaseTests.cs b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3651warmcoinbaseTests.cs index 98f363a2d60..d65b7fc7e71 100644 --- a/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3651warmcoinbaseTests.cs +++ b/src/Nethermind/Ethereum.Legacy.Blockchain.Test/EIP3651warmcoinbaseTests.cs @@ -9,7 +9,7 @@ namespace Ethereum.Blockchain.Legacy.Test; [TestFixture] [Parallelizable(ParallelScope.All)] -public class EIP3651warmcoinbaseTests : GeneralStateTestBase +public class EIP3651WarmCoinbaseTests : GeneralStateTestBase { [TestCaseSource(nameof(LoadTests))] public void Test(GeneralStateTest test) diff --git a/src/Nethermind/Ethereum.Rlp.Test/RlpTests.cs b/src/Nethermind/Ethereum.Rlp.Test/RlpTests.cs index eb03c9b8c4a..29adbae0102 100644 --- a/src/Nethermind/Ethereum.Rlp.Test/RlpTests.cs +++ b/src/Nethermind/Ethereum.Rlp.Test/RlpTests.cs @@ -107,8 +107,8 @@ private static IEnumerable LoadTests(string testFileName) [Test] public void TestEmpty() { - Assert.That(Nethermind.Serialization.Rlp.Rlp.Encode(new byte[0]), Is.EqualTo(Nethermind.Serialization.Rlp.Rlp.OfEmptyByteArray)); - Assert.That(Nethermind.Serialization.Rlp.Rlp.Encode(new Nethermind.Serialization.Rlp.Rlp[0]), Is.EqualTo(Nethermind.Serialization.Rlp.Rlp.OfEmptySequence)); + Assert.That(Nethermind.Serialization.Rlp.Rlp.Encode(Array.Empty()), Is.EqualTo(Nethermind.Serialization.Rlp.Rlp.OfEmptyByteArray)); + Assert.That(Nethermind.Serialization.Rlp.Rlp.Encode(Array.Empty()), Is.EqualTo(Nethermind.Serialization.Rlp.Rlp.OfEmptySequence)); } [Test] diff --git a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs index b230affbee3..99c23d87f1f 100644 --- a/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/BlockchainTestBase.cs @@ -44,7 +44,7 @@ public abstract class BlockchainTestBase private static readonly ILogger _logger; private static readonly ILogManager _logManager = new TestLogManager(LogLevel.Warn); private static DifficultyCalculatorWrapper DifficultyCalculator { get; } - private const int _genesisProcessingTimeoutMs = 5000; + private const int _genesisProcessingTimeoutMs = 30000; static BlockchainTestBase() { diff --git a/src/Nethermind/Ethereum.Test.Base/GeneralStateTest.cs b/src/Nethermind/Ethereum.Test.Base/GeneralStateTest.cs index 800ff17e843..da8142a513f 100644 --- a/src/Nethermind/Ethereum.Test.Base/GeneralStateTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/GeneralStateTest.cs @@ -13,6 +13,12 @@ namespace Ethereum.Test.Base { public class GeneralStateTest : EthereumTest { + /// + /// When true, uses legacy coinbase behavior (create before tx) for backward compatibility + /// with old test expectations that were computed with buggy coinbase timing. + /// + public bool IsLegacy { get; set; } + public IReleaseSpec? Fork { get; set; } public string? ForkName { get; set; } public Address? CurrentCoinbase { get; set; } @@ -31,8 +37,6 @@ public class GeneralStateTest : EthereumTest public Hash256? CurrentBeaconRoot { get; set; } public Hash256? CurrentWithdrawalsRoot { get; set; } public ulong? CurrentExcessBlobGas { get; set; } - public UInt256? ParentBlobGasUsed { get; set; } - public UInt256? ParentExcessBlobGas { get; set; } public Hash256? RequestsHash { get; set; } diff --git a/src/Nethermind/Ethereum.Test.Base/GeneralStateTestEnvJson.cs b/src/Nethermind/Ethereum.Test.Base/GeneralStateTestEnvJson.cs index a82748257d9..384b5465b19 100644 --- a/src/Nethermind/Ethereum.Test.Base/GeneralStateTestEnvJson.cs +++ b/src/Nethermind/Ethereum.Test.Base/GeneralStateTestEnvJson.cs @@ -20,7 +20,5 @@ public class GeneralStateTestEnvJson public Hash256? CurrentBeaconRoot { get; set; } public Hash256? CurrentWithdrawalsRoot { get; set; } public ulong? CurrentExcessBlobGas { get; set; } - public UInt256? ParentBlobGasUsed { get; set; } - public UInt256? ParentExcessBlobGas { get; set; } } } diff --git a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs index 39dbc151790..dcf87b0aa5c 100644 --- a/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs +++ b/src/Nethermind/Ethereum.Test.Base/GeneralTestBase.cs @@ -7,6 +7,7 @@ using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Core.ExecutionRequest; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Core.Test.Modules; @@ -21,6 +22,8 @@ using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.Specs.Test; +using Nethermind.State.Proofs; +using Nethermind.Trie; using NUnit.Framework; using System; using System.Collections.Generic; @@ -61,6 +64,7 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer) { _logger.Info($"Running {test.Name} at {DateTime.UtcNow:HH:mm:ss.ffffff}"); Assert.That(test.LoadFailure, Is.Null, "test data loading failure"); + Assert.That(test.Transaction, Is.Not.Null, "there is no transaction in the test"); EofValidator.Logger = _logger; @@ -87,9 +91,29 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer) IMainProcessingContext mainBlockProcessingContext = container.Resolve(); IWorldState stateProvider = mainBlockProcessingContext.WorldState; using IDisposable _ = stateProvider.BeginScope(null); + IBlockValidator blockValidator = container.Resolve(); ITransactionProcessor transactionProcessor = mainBlockProcessingContext.TransactionProcessor; - InitializeTestState(test.Pre, test.CurrentCoinbase, stateProvider, specProvider); + InitializeTestState(test.Pre, stateProvider, specProvider); + + // Legacy tests expect coinbase to be created BEFORE transaction execution + // (old buggy behavior that was baked into expected state roots). + // Modern tests correctly create coinbase only after successful tx. + if (test.IsLegacy && test.CurrentCoinbase is not null) + { + stateProvider.CreateAccountIfNotExists(test.CurrentCoinbase, UInt256.Zero); + stateProvider.Commit(specProvider.GetSpec((ForkActivation)1)); + stateProvider.RecalculateStateRoot(); + } + + if (test.Transaction.ChainId is null) + { + test.Transaction.ChainId = test.ChainId; + } + + IReleaseSpec? spec = specProvider.GetSpec((ForkActivation)test.CurrentNumber); + Transaction[] transactions = [test.Transaction]; + Withdrawal[]? withdrawals = spec.WithdrawalsEnabled ? [] : null; BlockHeader header = new( test.PreviousHash, @@ -105,47 +129,29 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer) StateRoot = test.PostHash, IsPostMerge = test.CurrentRandom is not null, MixHash = test.CurrentRandom, - WithdrawalsRoot = test.CurrentWithdrawalsRoot, + WithdrawalsRoot = test.CurrentWithdrawalsRoot ?? (spec.WithdrawalsEnabled ? PatriciaTree.EmptyTreeHash : null), ParentBeaconBlockRoot = test.CurrentBeaconRoot, ExcessBlobGas = test.CurrentExcessBlobGas ?? (test.Fork is Cancun ? 0ul : null), BlobGasUsed = BlobGasCalculator.CalculateBlobGas(test.Transaction), - RequestsHash = test.RequestsHash + RequestsHash = test.RequestsHash ?? (spec.RequestsEnabled ? ExecutionRequestExtensions.EmptyRequestsHash : null), + TxRoot = TxTrie.CalculateRoot(transactions), + ReceiptsRoot = test.PostReceiptsRoot, }; + header.Hash = header.CalculateHash(); + Block block = new(header, new BlockBody(transactions, [], withdrawals)); Stopwatch stopwatch = Stopwatch.StartNew(); - IReleaseSpec? spec = specProvider.GetSpec((ForkActivation)test.CurrentNumber); - - if (test.Transaction.ChainId is null) - test.Transaction.ChainId = test.ChainId; - if (test.ParentBlobGasUsed is not null && test.ParentExcessBlobGas is not null) - { - BlockHeader parent = new( - parentHash: Keccak.Zero, - unclesHash: Keccak.OfAnEmptySequenceRlp, - beneficiary: test.CurrentCoinbase, - difficulty: test.CurrentDifficulty, - number: test.CurrentNumber - 1, - gasLimit: test.CurrentGasLimit, - timestamp: test.CurrentTimestamp, - extraData: [] - ) - { - BlobGasUsed = (ulong)test.ParentBlobGasUsed, - ExcessBlobGas = (ulong)test.ParentExcessBlobGas, - }; - header.ExcessBlobGas = BlobGasCalculator.CalculateExcessBlobGas(parent, spec); - } - ValidationResult txIsValid = new TxValidator(test.ChainId).IsWellFormed(test.Transaction, spec); TransactionResult? txResult = null; - if (txIsValid) + + if (blockValidator.ValidateOrphanedBlock(block, out string blockValidationError)) { txResult = transactionProcessor.Execute(test.Transaction, new BlockExecutionContext(header, spec), txTracer); } else { - _logger.Info($"Skipping invalid tx with error: {txIsValid.Error}"); + _logger.Info($"Skipping invalid tx with error: {blockValidationError}"); } stopwatch.Stop(); @@ -154,11 +160,29 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer) { stateProvider.Commit(specProvider.GetSpec((ForkActivation)1)); stateProvider.CommitTree(1); + + // '@winsvega added a 0-wei reward to the miner, so we had to add that into the state test execution phase. He needed it for retesteth.' + // This must only happen after successful transaction execution, not when tx fails validation. + // For legacy tests, coinbase was already created before tx execution. + if (!test.IsLegacy) + { + stateProvider.CreateAccountIfNotExists(test.CurrentCoinbase, UInt256.Zero); + } + stateProvider.Commit(specProvider.GetSpec((ForkActivation)1)); stateProvider.RecalculateStateRoot(); } else { - stateProvider.Reset(); + // For legacy tests with failed tx, we need to recalculate root since coinbase was created + if (test.IsLegacy) + { + stateProvider.CommitTree(0); + stateProvider.RecalculateStateRoot(); + } + else + { + stateProvider.Reset(); + } } List differences = RunAssertions(test, stateProvider); @@ -176,7 +200,7 @@ protected EthereumTestResult RunTest(GeneralStateTest test, ITxTracer txTracer) return testResult; } - public static void InitializeTestState(Dictionary preState, Address coinbase, IWorldState stateProvider, ISpecProvider specProvider) + public static void InitializeTestState(Dictionary preState, IWorldState stateProvider, ISpecProvider specProvider) { foreach (KeyValuePair accountState in preState) { @@ -194,13 +218,6 @@ public static void InitializeTestState(Dictionary preStat stateProvider.Commit(specProvider.GenesisSpec); stateProvider.CommitTree(0); stateProvider.Reset(); - - if (!stateProvider.AccountExists(coinbase)) - { - stateProvider.CreateAccount(coinbase, 0); - stateProvider.Commit(specProvider.GetSpec((ForkActivation)1)); - stateProvider.RecalculateStateRoot(); - } } private List RunAssertions(GeneralStateTest test, IWorldState stateProvider) diff --git a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs index 72e19a59013..10e799741ab 100644 --- a/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs +++ b/src/Nethermind/Ethereum.Test.Base/JsonToEthereumTest.cs @@ -156,7 +156,12 @@ public static Transaction Convert(PostStateJson postStateJson, TransactionJson t transaction.AccessList = null; if (transactionJson.MaxFeePerGas is not null) + { transaction.Type = TxType.EIP1559; + // For EIP-1559+ transactions, GasPrice is aliased to MaxPriorityFeePerGas. + // Use maxPriorityFeePerGas from JSON, falling back to maxFeePerGas per go-ethereum behavior. + transaction.GasPrice = transactionJson.MaxPriorityFeePerGas ?? transactionJson.MaxFeePerGas.Value; + } if (transaction.BlobVersionedHashes?.Length > 0) transaction.Type = TxType.Blob; @@ -273,8 +278,6 @@ public static IEnumerable Convert(string name, string category CurrentBeaconRoot = testJson.Env.CurrentBeaconRoot, CurrentWithdrawalsRoot = testJson.Env.CurrentWithdrawalsRoot, CurrentExcessBlobGas = testJson.Env.CurrentExcessBlobGas, - ParentBlobGasUsed = testJson.Env.ParentBlobGasUsed, - ParentExcessBlobGas = testJson.Env.ParentExcessBlobGas, PostReceiptsRoot = stateJson.Logs, PostHash = stateJson.Hash, Pre = testJson.Pre.ToDictionary(p => p.Key, p => p.Value), diff --git a/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestsStrategy.cs b/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestsStrategy.cs index 1e9a135021c..5c91a45ea1c 100644 --- a/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestsStrategy.cs +++ b/src/Nethermind/Ethereum.Test.Base/LoadBlockchainTestsStrategy.cs @@ -24,13 +24,13 @@ public IEnumerable Load(string testsDirectoryName, string wildcard testDirs = new[] { testsDirectoryName }; } - List testJsons = new(); + List tests = new(); foreach (string testDir in testDirs) { - testJsons.AddRange(LoadTestsFromDirectory(testDir, wildcard)); + tests.AddRange(LoadTestsFromDirectory(testDir, wildcard)); } - return testJsons; + return tests; } private string GetBlockchainTestsDirectory() diff --git a/src/Nethermind/Ethereum.Test.Base/LoadEipTestsStrategy.cs b/src/Nethermind/Ethereum.Test.Base/LoadEipTestsStrategy.cs index 9cd12eed740..135cb457097 100644 --- a/src/Nethermind/Ethereum.Test.Base/LoadEipTestsStrategy.cs +++ b/src/Nethermind/Ethereum.Test.Base/LoadEipTestsStrategy.cs @@ -23,13 +23,13 @@ public IEnumerable Load(string testsDirectoryName, string wildcard testDirs = new[] { testsDirectoryName }; } - List testJsons = new(); + List tests = new(); foreach (string testDir in testDirs) { - testJsons.AddRange(LoadTestsFromDirectory(testDir, wildcard)); + tests.AddRange(LoadTestsFromDirectory(testDir, wildcard)); } - return testJsons; + return tests; } private string GetGeneralStateTestsDirectory() diff --git a/src/Nethermind/Ethereum.Test.Base/LoadEofTestsStrategy.cs b/src/Nethermind/Ethereum.Test.Base/LoadEofTestsStrategy.cs index a26ebf22a00..ea99491f854 100644 --- a/src/Nethermind/Ethereum.Test.Base/LoadEofTestsStrategy.cs +++ b/src/Nethermind/Ethereum.Test.Base/LoadEofTestsStrategy.cs @@ -23,13 +23,13 @@ public IEnumerable Load(string testsDirectoryName, string wildcard testDirs = new[] { testsDirectoryName }; } - List testJsons = new(); + List tests = new(); foreach (string testDir in testDirs) { - testJsons.AddRange(LoadTestsFromDirectory(testDir, wildcard)); + tests.AddRange(LoadTestsFromDirectory(testDir, wildcard)); } - return testJsons; + return tests; } private string GetGeneralStateTestsDirectory() diff --git a/src/Nethermind/Ethereum.Test.Base/LoadGeneralStateTestsStrategy.cs b/src/Nethermind/Ethereum.Test.Base/LoadGeneralStateTestsStrategy.cs index 19334b8606f..564f4a0ea75 100644 --- a/src/Nethermind/Ethereum.Test.Base/LoadGeneralStateTestsStrategy.cs +++ b/src/Nethermind/Ethereum.Test.Base/LoadGeneralStateTestsStrategy.cs @@ -23,13 +23,13 @@ public IEnumerable Load(string testsDirectoryName, string wildcard testDirs = new[] { testsDirectoryName }; } - List testJsons = new(); + List tests = new(); foreach (string testDir in testDirs) { - testJsons.AddRange(LoadTestsFromDirectory(testDir, wildcard)); + tests.AddRange(LoadTestsFromDirectory(testDir, wildcard)); } - return testJsons; + return tests; } private string GetGeneralStateTestsDirectory() diff --git a/src/Nethermind/Ethereum.Test.Base/LoadLegacyBlockchainTestsStrategy.cs b/src/Nethermind/Ethereum.Test.Base/LoadLegacyBlockchainTestsStrategy.cs index 63dd6d9ca0f..ad5d8a85b6a 100644 --- a/src/Nethermind/Ethereum.Test.Base/LoadLegacyBlockchainTestsStrategy.cs +++ b/src/Nethermind/Ethereum.Test.Base/LoadLegacyBlockchainTestsStrategy.cs @@ -24,13 +24,13 @@ public IEnumerable Load(string testsDirectoryName, string wildcard testDirs = new[] { testsDirectoryName }; } - List testJsons = new(); + List tests = new(); foreach (string testDir in testDirs) { - testJsons.AddRange(LoadTestsFromDirectory(testDir, wildcard)); + tests.AddRange(LoadTestsFromDirectory(testDir, wildcard)); } - return testJsons; + return tests; } private static string GetLegacyBlockchainTestsDirectory() diff --git a/src/Nethermind/Ethereum.Test.Base/LoadLegacyGeneralStateTestsStrategy.cs b/src/Nethermind/Ethereum.Test.Base/LoadLegacyGeneralStateTestsStrategy.cs index 60f964c0d9b..1c3a2309ad2 100644 --- a/src/Nethermind/Ethereum.Test.Base/LoadLegacyGeneralStateTestsStrategy.cs +++ b/src/Nethermind/Ethereum.Test.Base/LoadLegacyGeneralStateTestsStrategy.cs @@ -24,13 +24,13 @@ public IEnumerable Load(string testsDirectoryName, string wildcard testDirs = new[] { testsDirectoryName }; } - List testJsons = new(); + List tests = new(); foreach (string testDir in testDirs) { - testJsons.AddRange(LoadTestsFromDirectory(testDir, wildcard)); + tests.AddRange(LoadTestsFromDirectory(testDir, wildcard)); } - return testJsons; + return tests; } private static string GetLegacyGeneralStateTestsDirectory() @@ -50,9 +50,14 @@ private IEnumerable LoadTestsFromDirectory(string testDir, string { FileTestsSource fileTestsSource = new(testFile, wildcard); var tests = fileTestsSource.LoadTests(TestType.State); - foreach (EthereumTest blockchainTest in tests) + foreach (EthereumTest ethereumTest in tests) { - blockchainTest.Category = testDir; + ethereumTest.Category = testDir; + // Mark legacy tests to use old coinbase behavior for backward compatibility + if (ethereumTest is GeneralStateTest generalStateTest) + { + generalStateTest.IsLegacy = true; + } } testsByName.AddRange(tests); diff --git a/src/Nethermind/Ethereum.Transaction.Test/TransactionTests.cs b/src/Nethermind/Ethereum.Transaction.Test/TransactionTests.cs index 5995dfe0203..062eb09c296 100644 --- a/src/Nethermind/Ethereum.Transaction.Test/TransactionTests.cs +++ b/src/Nethermind/Ethereum.Transaction.Test/TransactionTests.cs @@ -31,11 +31,11 @@ private static IEnumerable LoadTests(string testSet) { Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory); IEnumerable testDirs = Directory.EnumerateDirectories(".", "tt" + testSet); - Dictionary> testJsons = + Dictionary> testJsonMap = new(); foreach (string testDir in testDirs) { - testJsons[testDir] = new Dictionary(); + testJsonMap[testDir] = new Dictionary(); IEnumerable testFiles = Directory.EnumerateFiles(testDir).ToList(); foreach (string testFile in testFiles) { @@ -43,13 +43,13 @@ private static IEnumerable LoadTests(string testSet) Dictionary testsInFile = JsonSerializer.Deserialize>(json); foreach (KeyValuePair namedTest in testsInFile) { - testJsons[testDir].Add(namedTest.Key, namedTest.Value); + testJsonMap[testDir].Add(namedTest.Key, namedTest.Value); } } } List tests = new(); - foreach (KeyValuePair> byDir in testJsons) + foreach (KeyValuePair> byDir in testJsonMap) { foreach (KeyValuePair byName in byDir.Value) { diff --git a/src/Nethermind/Ethereum.Trie.Test/Permutations.cs b/src/Nethermind/Ethereum.Trie.Test/Permutations.cs index 61aa35185af..ce54da79625 100644 --- a/src/Nethermind/Ethereum.Trie.Test/Permutations.cs +++ b/src/Nethermind/Ethereum.Trie.Test/Permutations.cs @@ -14,7 +14,7 @@ namespace Ethereum.Trie.Test public static class Permutations { /// - /// Heap's algorithm to find all pmermutations. Non recursive, more efficient. + /// Heap's algorithm to find all permutations. Non recursive, more efficient. /// /// Items to permute in each possible ways /// diff --git a/src/Nethermind/Ethereum.Trie.Test/StorageTrieTests.cs b/src/Nethermind/Ethereum.Trie.Test/StorageTrieTests.cs index 65a5fc9a126..445fc2575e8 100644 --- a/src/Nethermind/Ethereum.Trie.Test/StorageTrieTests.cs +++ b/src/Nethermind/Ethereum.Trie.Test/StorageTrieTests.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using Nethermind.Core.Crypto; using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; @@ -25,7 +26,7 @@ public void Storage_trie_set_reset_with_empty() StorageTree tree = CreateStorageTrie(); Hash256 rootBefore = tree.RootHash; tree.Set(1, new byte[] { 1 }); - tree.Set(1, new byte[] { }); + tree.Set(1, Array.Empty()); tree.UpdateRootHash(); Hash256 rootAfter = tree.RootHash; Assert.That(rootAfter, Is.EqualTo(rootBefore)); diff --git a/src/Nethermind/Ethereum.Trie.Test/TrieTests.cs b/src/Nethermind/Ethereum.Trie.Test/TrieTests.cs index 637c77717f0..9185ec807b4 100644 --- a/src/Nethermind/Ethereum.Trie.Test/TrieTests.cs +++ b/src/Nethermind/Ethereum.Trie.Test/TrieTests.cs @@ -311,7 +311,7 @@ public void Quick_empty() public void Delete_on_empty() { PatriciaTree patriciaTree = new PatriciaTree(_db, Keccak.EmptyTreeHash, true, NullLogManager.Instance); - patriciaTree.Set(Keccak.Compute("1").Bytes, new byte[0]); + patriciaTree.Set(Keccak.Compute("1").Bytes, Array.Empty()); patriciaTree.Commit(); Assert.That(patriciaTree.RootHash, Is.EqualTo(PatriciaTree.EmptyTreeHash)); } @@ -323,7 +323,7 @@ public void Delete_missing_resolved_on_branch() patriciaTree.Set(Keccak.Compute("1123").Bytes, new byte[] { 1 }); patriciaTree.Set(Keccak.Compute("1124").Bytes, new byte[] { 2 }); Hash256 rootBefore = patriciaTree.RootHash; - patriciaTree.Set(Keccak.Compute("1125").Bytes, new byte[0]); + patriciaTree.Set(Keccak.Compute("1125").Bytes, Array.Empty()); Assert.That(patriciaTree.RootHash, Is.EqualTo(rootBefore)); } @@ -335,7 +335,7 @@ public void Delete_missing_resolved_on_extension() patriciaTree.Set(new Nibble[] { 1, 2, 3, 4, 5 }.ToPackedByteArray(), new byte[] { 2 }); patriciaTree.UpdateRootHash(); Hash256 rootBefore = patriciaTree.RootHash; - patriciaTree.Set(new Nibble[] { 1, 2, 3 }.ToPackedByteArray(), new byte[] { }); + patriciaTree.Set(new Nibble[] { 1, 2, 3 }.ToPackedByteArray(), Array.Empty()); patriciaTree.UpdateRootHash(); Assert.That(patriciaTree.RootHash, Is.EqualTo(rootBefore)); } @@ -348,7 +348,7 @@ public void Delete_missing_resolved_on_leaf() patriciaTree.Set(Keccak.Compute("1234501").Bytes, new byte[] { 2 }); patriciaTree.UpdateRootHash(); Hash256 rootBefore = patriciaTree.RootHash; - patriciaTree.Set(Keccak.Compute("1234502").Bytes, new byte[0]); + patriciaTree.Set(Keccak.Compute("1234502").Bytes, Array.Empty()); patriciaTree.UpdateRootHash(); Assert.That(patriciaTree.RootHash, Is.EqualTo(rootBefore)); } diff --git a/src/Nethermind/Nethermind.Abi.Test/AbiTests.cs b/src/Nethermind/Nethermind.Abi.Test/AbiTests.cs index 67ab6646d1b..1b95b5e1d91 100644 --- a/src/Nethermind/Nethermind.Abi.Test/AbiTests.cs +++ b/src/Nethermind/Nethermind.Abi.Test/AbiTests.cs @@ -348,6 +348,14 @@ public void Test_uint_exception(int length) Assert.Throws(() => _ = new AbiUInt(length)); } + [TestCase("uint64[abc]")] + [TestCase("bytes32[xyz]")] + [TestCase("address[!@#]")] + public void Test_invalid_array_syntax_exception(string type) + { + Assert.Throws(() => System.Text.Json.JsonSerializer.Deserialize($"\"{type}\"")); + } + [TestCase(AbiEncodingStyle.IncludeSignature)] [TestCase(AbiEncodingStyle.IncludeSignature | AbiEncodingStyle.Packed)] [TestCase(AbiEncodingStyle.Packed)] @@ -597,6 +605,49 @@ private class UserOperationAbi public byte[] Signature { get; set; } } + [TestCase(AbiEncodingStyle.IncludeSignature)] + [TestCase(AbiEncodingStyle.IncludeSignature | AbiEncodingStyle.Packed)] + [TestCase(AbiEncodingStyle.Packed)] + [TestCase(AbiEncodingStyle.None)] + public void Dynamic_array_of_fixed_array_of_uint64(AbiEncodingStyle encodingStyle) + { + AbiType type = new AbiArray(new AbiFixedLengthArray(new AbiUInt(64), 3)); + ulong[] element = [100UL, 200UL, 300UL]; + ulong[][] data = [element, [400UL, 500UL, 600UL]]; + AbiSignature signature = new("abc", type); + byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, [data]); + object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); + Assert.That(arguments[0], Is.EqualTo(data)); + } + + [TestCase(AbiEncodingStyle.IncludeSignature)] + [TestCase(AbiEncodingStyle.IncludeSignature | AbiEncodingStyle.Packed)] + [TestCase(AbiEncodingStyle.Packed)] + [TestCase(AbiEncodingStyle.None)] + public void Dynamic_array_of_fixed_array_of_uint64_single_element(AbiEncodingStyle encodingStyle) + { + AbiType type = new AbiArray(new AbiFixedLengthArray(new AbiUInt(64), 3)); + ulong[][] data = [[1000000UL, 7UL, 3600UL]]; + AbiSignature signature = new("abc", type); + byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, [data]); + object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); + Assert.That(arguments[0], Is.EqualTo(data)); + } + + [TestCase(AbiEncodingStyle.IncludeSignature)] + [TestCase(AbiEncodingStyle.IncludeSignature | AbiEncodingStyle.Packed)] + [TestCase(AbiEncodingStyle.Packed)] + [TestCase(AbiEncodingStyle.None)] + public void Dynamic_array_of_fixed_array_of_uint64_empty(AbiEncodingStyle encodingStyle) + { + AbiType type = new AbiArray(new AbiFixedLengthArray(new AbiUInt(64), 3)); + ulong[][] data = []; + AbiSignature signature = new("abc", type); + byte[] encoded = _abiEncoder.Encode(encodingStyle, signature, [data]); + object[] arguments = _abiEncoder.Decode(encodingStyle, signature, encoded); + Assert.That(arguments[0], Is.EqualTo(data)); + } + /// /// http://solidity.readthedocs.io/en/develop/abi-spec.html /// diff --git a/src/Nethermind/Nethermind.Abi.Test/Json/AbiParameterConverterTests.cs b/src/Nethermind/Nethermind.Abi.Test/Json/AbiParameterConverterTests.cs index fbc22718809..b95d067d2b7 100644 --- a/src/Nethermind/Nethermind.Abi.Test/Json/AbiParameterConverterTests.cs +++ b/src/Nethermind/Nethermind.Abi.Test/Json/AbiParameterConverterTests.cs @@ -46,6 +46,8 @@ object[] GetTestDataWithException(string type, Exception exception, object[] com yield return new TestCaseData(GetTestData("string", AbiType.String)); yield return new TestCaseData(GetTestData("int[]", new AbiArray(AbiType.Int256))); yield return new TestCaseData(GetTestData("string[5]", new AbiFixedLengthArray(AbiType.String, 5))); + yield return new TestCaseData(GetTestData("uint64[3]", new AbiFixedLengthArray(new AbiUInt(64), 3))); + yield return new TestCaseData(GetTestData("uint64[3][]", new AbiArray(new AbiFixedLengthArray(new AbiUInt(64), 3)))); yield return new TestCaseData(GetTestData("tuple", new AbiTuple([]))); yield return new TestCaseData(GetTestData("tuple", diff --git a/src/Nethermind/Nethermind.Abi/AbiParameterConverter.cs b/src/Nethermind/Nethermind.Abi/AbiParameterConverter.cs index 5f09bf4857f..9764f122202 100644 --- a/src/Nethermind/Nethermind.Abi/AbiParameterConverter.cs +++ b/src/Nethermind/Nethermind.Abi/AbiParameterConverter.cs @@ -93,6 +93,26 @@ private static string GetName(JsonElement token) => private AbiType GetParameterType(string type, JsonElement? components) { + if (type.Contains('[')) + { + int lastBracket = type.LastIndexOf('['); + string innerType = type[..lastBracket]; + string bracketPart = type[lastBracket..]; + + // Recursively parse the inner type + AbiType elementType = GetParameterType(innerType, components); + + // Parse array suffix: [] for dynamic, [N] for fixed + if (bracketPart == "[]") + return new AbiArray(elementType); + + if (!bracketPart.StartsWith('[') || !bracketPart.EndsWith(']')) + throw new ArgumentException($"Invalid contract ABI json. Unknown array type {type}."); + string sizeStr = bracketPart[1..^1]; + return int.TryParse(sizeStr, out int size) ? new AbiFixedLengthArray(elementType, size) + : throw new ArgumentException($"Invalid contract ABI json. Unknown array type {type}."); + } + var match = AbiParameterConverterStatics.TypeExpression.Match(type); if (match.Success) { diff --git a/src/Nethermind/Nethermind.Abi/AbiType.Sequence.cs b/src/Nethermind/Nethermind.Abi/AbiType.Sequence.cs index 3f9ae167b10..701134ffd8f 100644 --- a/src/Nethermind/Nethermind.Abi/AbiType.Sequence.cs +++ b/src/Nethermind/Nethermind.Abi/AbiType.Sequence.cs @@ -75,7 +75,7 @@ internal static byte[][] EncodeSequence(int length, IEnumerable types, return encodedParts; } - internal static (object[], int) DecodeSequence(int length, IEnumerable types, byte[] data, bool packed, int startPosition) + public static (object[], int) DecodeSequence(int length, IEnumerable types, byte[] data, bool packed, int startPosition) { (Array array, int position) = DecodeSequence(typeof(object), length, types, data, packed, startPosition); return ((object[])array, position); diff --git a/src/Nethermind/Nethermind.Abi/AbiType.cs b/src/Nethermind/Nethermind.Abi/AbiType.cs index 11b73b2a13b..08562463273 100644 --- a/src/Nethermind/Nethermind.Abi/AbiType.cs +++ b/src/Nethermind/Nethermind.Abi/AbiType.cs @@ -91,17 +91,37 @@ public class AbiTypeConverter : JsonConverter static AbiType ParseAbiType(string type) { - bool isArray = false; - if (type.EndsWith("[]")) + if (type == "tuple" || type.StartsWith("tuple[")) { - isArray = true; - type = type[..^2]; + return new AbiTuple(); } - if (type == "tuple") + // Check for array suffix: [N] for fixed-size or [] for dynamic + int lastBracket = type.LastIndexOf('['); + if (lastBracket >= 0 && type.EndsWith(']')) { - return new AbiTuple(); + string bracketContent = type[(lastBracket + 1)..^1]; + string elementTypeStr = type[..lastBracket]; + + switch (bracketContent.Length) + { + case > 0 when int.TryParse(bracketContent, out int length): + { + // Fixed-size array: type[N] + AbiType elementType = ParseAbiType(elementTypeStr); + return new AbiFixedLengthArray(elementType, length); + } + case 0: + { + // Dynamic array: type[] + AbiType elementType = ParseAbiType(elementTypeStr); + return new AbiArray(elementType); + } + default: + throw new ArgumentException($"Invalid array syntax in ABI type '{type}'.", nameof(type)); + } } + if (type.StartsWith('(') && type.EndsWith(')')) { string[] types = type[1..^1].Split(','); @@ -113,10 +133,7 @@ static AbiType ParseAbiType(string type) return ParseTuple(types); } - AbiType value = GetType(type); - - return isArray ? new AbiArray(value) : value; - + return GetType(type); } static AbiType ParseTuple(string[] types) diff --git a/src/Nethermind/Nethermind.Abi/AbiUInt.cs b/src/Nethermind/Nethermind.Abi/AbiUInt.cs index 56dcdf62c4c..38dd9f4d4ee 100644 --- a/src/Nethermind/Nethermind.Abi/AbiUInt.cs +++ b/src/Nethermind/Nethermind.Abi/AbiUInt.cs @@ -24,7 +24,7 @@ public class AbiUInt : AbiType public static new readonly AbiUInt UInt96 = new(96); public static new readonly AbiUInt UInt256 = new(256); - private static readonly byte[][] PrealocatedBytes = + private static readonly byte[][] PreallocatedBytes = Enumerable.Range(0, 256).Select(x => new[] { (byte)x }).ToArray(); public AbiUInt(int length) @@ -109,7 +109,7 @@ public override byte[] Encode(object? arg, bool packed) } else if (arg is byte byteInput) { - bytes = PrealocatedBytes[byteInput]; + bytes = PreallocatedBytes[byteInput]; } else if (arg is JsonElement element && element.ValueKind == JsonValueKind.Number) { diff --git a/src/Nethermind/Nethermind.AuRa.Test/AuRaPluginTests.cs b/src/Nethermind/Nethermind.AuRa.Test/AuRaPluginTests.cs index c5d7b358c46..1959871bfbf 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/AuRaPluginTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/AuRaPluginTests.cs @@ -27,7 +27,7 @@ namespace Nethermind.AuRa.Test public class AuRaPluginTests { [Test] - public void Init_when_not_AuRa_doesnt_trow() + public void Init_when_not_AuRa_does_not_throw() { ChainSpec chainSpec = new(); AuRaPlugin auRaPlugin = new(chainSpec); diff --git a/src/Nethermind/Nethermind.AuRa.Test/AuraBlockProcessorTests.cs b/src/Nethermind/Nethermind.AuRa.Test/AuraBlockProcessorTests.cs index 9b1cd703442..710fd0c6d5d 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/AuraBlockProcessorTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/AuraBlockProcessorTests.cs @@ -21,17 +21,13 @@ using Nethermind.Core.Specs; using Nethermind.Core.Test; using Nethermind.Core.Test.Builders; -using Nethermind.Db; using Nethermind.Evm; using Nethermind.Evm.State; -using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; using Nethermind.Int256; using Nethermind.Logging; using Nethermind.Specs; using Nethermind.Specs.Forks; -using Nethermind.State; -using Nethermind.Trie.Pruning; using Nethermind.TxPool; using NSubstitute; using NUnit.Framework; @@ -95,17 +91,24 @@ public void For_normal_processing_it_should_not_fail_with_gas_remaining_rules() } [Test] - public void Should_rewrite_contracts() + public void Should_rewrite_contracts([Values] bool isPostMerge) { - static BlockHeader Process(BranchProcessor auRaBlockProcessor, BlockHeader parent) + static BlockHeader Process(BranchProcessor auRaBlockProcessor, BlockHeader parent, IBlockTree blockTree, bool isPostMerge) { - BlockHeader header = Build.A.BlockHeader.WithAuthor(TestItem.AddressD).WithParent(parent).TestObject; + BlockHeader header = Build.A.BlockHeader + .WithAuthor(TestItem.AddressD) + .WithParent(parent) + .WithTimestamp(parent.Timestamp + 12) + .WithTotalDifficulty(0).TestObject; + header.IsPostMerge = isPostMerge; Block block = Build.A.Block.WithHeader(header).TestObject; - return auRaBlockProcessor.Process( + BlockHeader res = auRaBlockProcessor.Process( parent, new List { block }, ProcessingOptions.None, NullBlockTracer.Instance)[0].Header; + blockTree.Insert(res); + return res; } Dictionary> contractOverrides = new() @@ -128,8 +131,15 @@ static BlockHeader Process(BranchProcessor auRaBlockProcessor, BlockHeader paren }, }; - (BranchProcessor processor, IWorldState stateProvider) = - CreateProcessor(contractRewriter: new ContractRewriter(contractOverrides)); + (ulong, Address, byte[])[] contractOverridesTimestamp = [ + (1000024, TestItem.AddressC, Bytes.FromHexString("0x123")), + (1000024, TestItem.AddressD, Bytes.FromHexString("0x321")), + (1000036, TestItem.AddressC, Bytes.FromHexString("0x456")), + (1000036, TestItem.AddressD, Bytes.FromHexString("0x654")) + ]; + + (BranchProcessor processor, IWorldState stateProvider, IBlockTree blockTree) = + CreateProcessor(contractRewriter: new ContractRewriter(contractOverrides, contractOverridesTimestamp)); Hash256 stateRoot; @@ -137,6 +147,8 @@ static BlockHeader Process(BranchProcessor auRaBlockProcessor, BlockHeader paren { stateProvider.CreateAccount(TestItem.AddressA, UInt256.One); stateProvider.CreateAccount(TestItem.AddressB, UInt256.One); + stateProvider.CreateAccount(TestItem.AddressC, UInt256.One); + stateProvider.CreateAccount(TestItem.AddressD, UInt256.One); stateProvider.Commit(London.Instance); stateProvider.CommitTree(0); stateProvider.RecalculateStateRoot(); @@ -144,37 +156,44 @@ static BlockHeader Process(BranchProcessor auRaBlockProcessor, BlockHeader paren } BlockHeader currentBlock = Build.A.BlockHeader.WithNumber(0).WithStateRoot(stateRoot).TestObject; - currentBlock = Process(processor, currentBlock); + currentBlock = Process(processor, currentBlock, blockTree, isPostMerge); using (stateProvider.BeginScope(currentBlock)) { stateProvider.GetCode(TestItem.AddressA).Should().BeEquivalentTo(Array.Empty()); stateProvider.GetCode(TestItem.AddressB).Should().BeEquivalentTo(Array.Empty()); + stateProvider.GetCode(TestItem.AddressC).Should().BeEquivalentTo(Array.Empty()); + stateProvider.GetCode(TestItem.AddressD).Should().BeEquivalentTo(Array.Empty()); } - currentBlock = Process(processor, currentBlock); + currentBlock = Process(processor, currentBlock, blockTree, isPostMerge); using (stateProvider.BeginScope(currentBlock)) { stateProvider.GetCode(TestItem.AddressA).Should().BeEquivalentTo(Bytes.FromHexString("0x123")); stateProvider.GetCode(TestItem.AddressB).Should().BeEquivalentTo(Bytes.FromHexString("0x321")); + stateProvider.GetCode(TestItem.AddressC).Should().BeEquivalentTo(Bytes.FromHexString("0x123")); + stateProvider.GetCode(TestItem.AddressD).Should().BeEquivalentTo(Bytes.FromHexString("0x321")); } - currentBlock = Process(processor, currentBlock); + currentBlock = Process(processor, currentBlock, blockTree, isPostMerge); using (stateProvider.BeginScope(currentBlock)) { stateProvider.GetCode(TestItem.AddressA).Should().BeEquivalentTo(Bytes.FromHexString("0x456")); stateProvider.GetCode(TestItem.AddressB).Should().BeEquivalentTo(Bytes.FromHexString("0x654")); + stateProvider.GetCode(TestItem.AddressC).Should().BeEquivalentTo(Bytes.FromHexString("0x456")); + stateProvider.GetCode(TestItem.AddressD).Should().BeEquivalentTo(Bytes.FromHexString("0x654")); } } - private (BranchProcessor Processor, IWorldState StateProvider) CreateProcessor(ITxFilter? txFilter = null, ContractRewriter? contractRewriter = null) + private (BranchProcessor Processor, IWorldState StateProvider, IBlockTree blockTree) CreateProcessor(ITxFilter? txFilter = null, ContractRewriter? contractRewriter = null) { IWorldState stateProvider = TestWorldStateFactory.CreateForTest(); + IBlockTree blockTree = Build.A.BlockTree(GnosisSpecProvider.Instance).TestObject; ITransactionProcessor transactionProcessor = Substitute.For(); - AuRaBlockProcessor processor = new AuRaBlockProcessor( - HoodiSpecProvider.Instance, + AuRaBlockProcessor processor = new( + GnosisSpecProvider.Instance, TestBlockValidator.AlwaysValid, NoBlockRewards.Instance, new BlockProcessor.BlockValidationTransactionsExecutor(new ExecuteTransactionProcessorAdapter(transactionProcessor), stateProvider), @@ -182,22 +201,22 @@ static BlockHeader Process(BranchProcessor auRaBlockProcessor, BlockHeader paren NullReceiptStorage.Instance, new BeaconBlockRootHandler(transactionProcessor, stateProvider), LimboLogs.Instance, - Substitute.For(), + blockTree, new WithdrawalProcessor(stateProvider, LimboLogs.Instance), new ExecutionRequestsProcessor(transactionProcessor), auRaValidator: null, txFilter, contractRewriter: contractRewriter); - BranchProcessor branchProcessor = new BranchProcessor( + BranchProcessor branchProcessor = new( processor, - HoodiSpecProvider.Instance, + GnosisSpecProvider.Instance, stateProvider, new BeaconBlockRootHandler(transactionProcessor, stateProvider), Substitute.For(), LimboLogs.Instance); - return (branchProcessor, stateProvider); + return (branchProcessor, stateProvider, blockTree); } } } diff --git a/src/Nethermind/Nethermind.AuRa.Test/ChainSpecLoaderTest.cs b/src/Nethermind/Nethermind.AuRa.Test/ChainSpecLoaderTest.cs index fa61a1c7971..b39857c9c0f 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/ChainSpecLoaderTest.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/ChainSpecLoaderTest.cs @@ -7,7 +7,6 @@ using Nethermind.Consensus.AuRa.Config; using Nethermind.Core; using Nethermind.Core.Extensions; -using Nethermind.Core.Test; using Nethermind.Logging; using Nethermind.Serialization.Json; using Nethermind.Specs; @@ -76,7 +75,6 @@ public void Can_load_chiado() [Test] public void Can_load_posdao_with_rewriteBytecode() { - // TODO: modexp 2565 string path = Path.Combine(TestContext.CurrentContext.WorkDirectory, "Specs/posdao.json"); ChainSpec chainSpec = LoadChainSpec(path); IDictionary> expected = new Dictionary> @@ -90,8 +88,31 @@ public void Can_load_posdao_with_rewriteBytecode() } }; - var auraParams = chainSpec.EngineChainSpecParametersProvider.GetChainSpecParameters(); - + AuRaChainSpecEngineParameters auraParams = chainSpec.EngineChainSpecParametersProvider.GetChainSpecParameters(); auraParams.RewriteBytecode.Should().BeEquivalentTo(expected); + + // posdao.json uses old modexp pricing format (divisor: 20) without modexp2565 transition + // Therefore Eip2565Transition should be null + chainSpec.Parameters.Eip2565Transition.Should().BeNull(); + } + + [Test] + public void Can_load_gnosis_with_rewriteBytecodeGnosis() + { + string path = Path.Combine(TestContext.CurrentContext.WorkDirectory, "../../../../", "Chains/gnosis.json"); + ChainSpec chainSpec = LoadChainSpec(path); + IDictionary> expected = new Dictionary> + { + { + GnosisSpecProvider.BalancerTimestamp, new Dictionary() + { + {new Address("0x506d1f9efe24f0d47853adca907eb8d89ae03207"), Bytes.FromHexString("0x60806040526004361061002c575f3560e01c80638da5cb5b14610037578063b61d27f61461006157610033565b3661003357005b5f5ffd5b348015610042575f5ffd5b5061004b610091565b604051610058919061030a565b60405180910390f35b61007b600480360381019061007691906103e9565b6100a9565b60405161008891906104ca565b60405180910390f35b737be579238a6a621601eae2c346cda54d68f7dfee81565b60606100b3610247565b5f73ffffffffffffffffffffffffffffffffffffffff168573ffffffffffffffffffffffffffffffffffffffff1603610121576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161011890610544565b60405180910390fd5b5f8573ffffffffffffffffffffffffffffffffffffffff163b1161017a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610171906105ac565b60405180910390fd5b5f5f8673ffffffffffffffffffffffffffffffffffffffff168686866040516101a4929190610606565b5f6040518083038185875af1925050503d805f81146101de576040519150601f19603f3d011682016040523d82523d5f602084013e6101e3565b606091505b50915091508161023a575f815111156101ff5780518060208301fd5b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161023190610668565b60405180910390fd5b8092505050949350505050565b737be579238a6a621601eae2c346cda54d68f7dfee73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146102c9576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102c0906106d0565b60405180910390fd5b565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6102f4826102cb565b9050919050565b610304816102ea565b82525050565b5f60208201905061031d5f8301846102fb565b92915050565b5f5ffd5b5f5ffd5b610334816102ea565b811461033e575f5ffd5b50565b5f8135905061034f8161032b565b92915050565b5f819050919050565b61036781610355565b8114610371575f5ffd5b50565b5f813590506103828161035e565b92915050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f8401126103a9576103a8610388565b5b8235905067ffffffffffffffff8111156103c6576103c561038c565b5b6020830191508360018202830111156103e2576103e1610390565b5b9250929050565b5f5f5f5f6060858703121561040157610400610323565b5b5f61040e87828801610341565b945050602061041f87828801610374565b935050604085013567ffffffffffffffff8111156104405761043f610327565b5b61044c87828801610394565b925092505092959194509250565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f61049c8261045a565b6104a68185610464565b93506104b6818560208601610474565b6104bf81610482565b840191505092915050565b5f6020820190508181035f8301526104e28184610492565b905092915050565b5f82825260208201905092915050565b7f7a65726f207461726765740000000000000000000000000000000000000000005f82015250565b5f61052e600b836104ea565b9150610539826104fa565b602082019050919050565b5f6020820190508181035f83015261055b81610522565b9050919050565b7f6e6f74206120636f6e74726163740000000000000000000000000000000000005f82015250565b5f610596600e836104ea565b91506105a182610562565b602082019050919050565b5f6020820190508181035f8301526105c38161058a565b9050919050565b5f81905092915050565b828183375f83830152505050565b5f6105ed83856105ca565b93506105fa8385846105d4565b82840190509392505050565b5f6106128284866105e2565b91508190509392505050565b7f63616c6c206661696c65640000000000000000000000000000000000000000005f82015250565b5f610652600b836104ea565b915061065d8261061e565b602082019050919050565b5f6020820190508181035f83015261067f81610646565b9050919050565b7f6e6f74206f776e657200000000000000000000000000000000000000000000005f82015250565b5f6106ba6009836104ea565b91506106c582610686565b602082019050919050565b5f6020820190508181035f8301526106e7816106ae565b905091905056fea2646970667358221220a8334a26f31db2a806db6c1bcc4107caa8ec5cbdc7b742cfec99b4f0cca066a364736f6c634300081e0033")}, + } + } + }; + + AuRaChainSpecEngineParameters auraParams = chainSpec.EngineChainSpecParametersProvider.GetChainSpecParameters(); + + auraParams.RewriteBytecodeTimestamp.Should().BeEquivalentTo(expected); } } diff --git a/src/Nethermind/Nethermind.AuRa.Test/Contract/ContractDataStoreTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Contract/ContractDataStoreTests.cs index f88647fade1..a9669604116 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Contract/ContractDataStoreTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Contract/ContractDataStoreTests.cs @@ -5,7 +5,6 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using FluentAssertions; using Nethermind.Abi; using Nethermind.Blockchain; @@ -91,7 +90,7 @@ public void returns_data_from_getAll_on_non_consecutive_receipts_with_incrementa } [Test] - public async Task returns_data_from_receipts_on_non_consecutive_with_not_incremental_changes() + public void returns_data_from_receipts_on_non_consecutive_with_not_incremental_changes() { TestCase
testCase = BuildTestCase
(); testCase.DataContract.IncrementalChanges.Returns(false); @@ -109,9 +108,10 @@ public async Task returns_data_from_receipts_on_non_consecutive_with_not_increme testCase.ContractDataStore.GetItemsFromContractAtBlock(blockHeader); testCase.BlockTree.NewHeadBlock += Raise.EventWith(new BlockEventArgs(secondBlock)); - await Task.Delay(10); // delay for refresh from contract as its async - - testCase.ContractDataStore.GetItemsFromContractAtBlock(secondBlock.Header).Should().BeEquivalentTo(expected.Cast()); + Assert.That( + () => testCase.ContractDataStore.GetItemsFromContractAtBlock(secondBlock.Header), + Is.EquivalentTo(expected.Cast()).After(200, 20) + ); } [Test] @@ -127,7 +127,7 @@ public void returns_data_from_getAll_on_non_consecutive_with_not_incremental_cha } [Test] - public async Task returns_data_from_receipts_on_consecutive_with_not_incremental_changes() + public void returns_data_from_receipts_on_consecutive_with_not_incremental_changes() { TestCase
testCase = BuildTestCase
(); testCase.DataContract.IncrementalChanges.Returns(false); @@ -145,13 +145,14 @@ public async Task returns_data_from_receipts_on_consecutive_with_not_incremental testCase.ContractDataStore.GetItemsFromContractAtBlock(blockHeader); testCase.BlockTree.NewHeadBlock += Raise.EventWith(new BlockEventArgs(secondBlock)); - await Task.Delay(10); // delay for refresh from contract as its async - - testCase.ContractDataStore.GetItemsFromContractAtBlock(secondBlock.Header).Should().BeEquivalentTo(expected.Cast()); + Assert.That( + () => testCase.ContractDataStore.GetItemsFromContractAtBlock(secondBlock.Header), + Is.EquivalentTo(expected.Cast()).After(200, 20) + ); } [Test] - public async Task returns_data_from_receipts_on_consecutive_with_incremental_changes() + public void returns_data_from_receipts_on_consecutive_with_incremental_changes() { TestCase
testCase = BuildTestCase
(); BlockHeader blockHeader = Build.A.BlockHeader.WithNumber(1).WithHash(TestItem.KeccakA).TestObject; @@ -167,16 +168,14 @@ public async Task returns_data_from_receipts_on_consecutive_with_incremental_cha testCase.ContractDataStore.GetItemsFromContractAtBlock(blockHeader); testCase.BlockTree.NewHeadBlock += Raise.EventWith(new BlockEventArgs(secondBlock)); - await Task.Delay(50); // delay for refresh from contract as its async - Assert.That( () => testCase.ContractDataStore.GetItemsFromContractAtBlock(secondBlock.Header).ToList(), - Is.EquivalentTo(new ArrayList() { TestItem.AddressA, TestItem.AddressB }).After(1000, 100) + Is.EquivalentTo(new ArrayList() { TestItem.AddressA, TestItem.AddressB }).After(200, 20) ); } [Test] - public async Task returns_unmodified_data_from_empty_receipts_on_consecutive_with_incremental_changes() + public void returns_unmodified_data_from_empty_receipts_on_consecutive_with_incremental_changes() { TestCase
testCase = BuildTestCase
(); BlockHeader blockHeader = Build.A.BlockHeader.WithNumber(1).WithHash(TestItem.KeccakA).TestObject; @@ -192,13 +191,14 @@ public async Task returns_unmodified_data_from_empty_receipts_on_consecutive_wit testCase.ContractDataStore.GetItemsFromContractAtBlock(blockHeader); testCase.BlockTree.NewHeadBlock += Raise.EventWith(new BlockEventArgs(secondBlock)); - await Task.Delay(10); // delay for refresh from contract as its async - - testCase.ContractDataStore.GetItemsFromContractAtBlock(secondBlock.Header).Should().BeEquivalentTo(TestItem.AddressA, TestItem.AddressC); + Assert.That( + () => testCase.ContractDataStore.GetItemsFromContractAtBlock(secondBlock.Header), + Is.EquivalentTo(new[] { TestItem.AddressA, TestItem.AddressC }).After(200, 20) + ); } [Test] - public async Task returns_data_from_receipts_on_consecutive_with_incremental_changes_with_identity() + public void returns_data_from_receipts_on_consecutive_with_incremental_changes_with_identity() { TestCase testCase = BuildTestCase( TxPriorityContract.DistinctDestinationMethodComparer.Instance, @@ -227,11 +227,9 @@ public async Task returns_data_from_receipts_on_consecutive_with_incremental_cha testCase.ContractDataStore.GetItemsFromContractAtBlock(blockHeader); testCase.BlockTree.NewHeadBlock += Raise.EventWith(new BlockEventArgs(secondBlock)); - await Task.Delay(10); // delay for refresh from contract as its async - Assert.That( () => testCase.ContractDataStore.GetItemsFromContractAtBlock(secondBlock.Header).Count(), - Is.EqualTo(3).After(1000, 100) + Is.EqualTo(3).After(200, 20) ); testCase.ContractDataStore.GetItemsFromContractAtBlock(secondBlock.Header).Should().BeEquivalentTo(new[] diff --git a/src/Nethermind/Nethermind.AuRa.Test/Contract/ContractDataStoreWithLocalDataTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Contract/ContractDataStoreWithLocalDataTests.cs index d9ec5662e3b..9d1f68f7092 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Contract/ContractDataStoreWithLocalDataTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Contract/ContractDataStoreWithLocalDataTests.cs @@ -55,7 +55,7 @@ public void reloads_data_from_local_on_changed() } [Test] - public void doesnt_reload_data_from_local_when_changed_not_fired() + public void does_not_reload_data_from_local_when_changed_not_fired() { ILocalDataSource> localDataSource = Substitute.For>>(); Address[] expected = { TestItem.AddressA }; @@ -86,7 +86,7 @@ public void combines_contract_and_local_data_correctly() Assert.That( () => testCase.ContractDataStore.GetItemsFromContractAtBlock(secondBlock.Header), - Is.EquivalentTo(expected.Cast()).After(1000, 100) + Is.EquivalentTo(expected.Cast()).After(200, 20) ); localDataSource.Data.Returns(new[] { TestItem.AddressC, TestItem.AddressD }); @@ -95,7 +95,7 @@ public void combines_contract_and_local_data_correctly() Assert.That( () => testCase.ContractDataStore.GetItemsFromContractAtBlock(secondBlock.Header), - Is.EquivalentTo(expected.Cast()).After(1000, 100) + Is.EquivalentTo(expected.Cast()).After(200, 20) ); } diff --git a/src/Nethermind/Nethermind.AuRa.Test/Transactions/TxCertifierFilterTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Transactions/TxCertifierFilterTests.cs index a2e4c6ae271..ed9cd9362e6 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Transactions/TxCertifierFilterTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Transactions/TxCertifierFilterTests.cs @@ -120,14 +120,14 @@ public async Task registry_contract_returns_correct_address() } [Test] - public async Task registry_contract_returns_not_found_when_key_doesnt_exist() + public async Task registry_contract_returns_not_found_when_key_does_not_exist() { using TestTxPermissionsBlockchain chain = await TestContractBlockchain.ForTest(); chain.RegisterContract.TryGetAddress(chain.BlockTree.Head.Header, "not existing key", out Address _).Should().BeFalse(); } [Test] - public async Task registry_contract_returns_not_found_when_contract_doesnt_exist() + public async Task registry_contract_returns_not_found_when_contract_does_not_exist() { using TestTxPermissionsBlockchain chain = await TestContractBlockchain.ForTest(); RegisterContract contract = new(AbiEncoder.Instance, Address.FromNumber(1000), chain.ReadOnlyTxProcessingEnvFactory.Create()); diff --git a/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs index 53b92c06e85..e5659fadcd4 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Validators/ContractBasedValidatorTests.cs @@ -111,7 +111,7 @@ public void throws_ArgumentNullException_on_empty_validatorStore() } [Test] - public void throws_ArgumentNullException_on_empty_validSealearStrategy() + public void throws_ArgumentNullException_on_empty_validSealerStrategy() { Action act = () => new ContractBasedValidator(_validatorContract, _blockTree, _receiptsStorage, _validatorStore, null, _blockFinalizationManager, default, _logManager, 1); act.Should().Throw(); diff --git a/src/Nethermind/Nethermind.AuRa.Test/Validators/MultiValidatorTests.cs b/src/Nethermind/Nethermind.AuRa.Test/Validators/MultiValidatorTests.cs index 45a7bda8c9a..b3e7b235a9c 100644 --- a/src/Nethermind/Nethermind.AuRa.Test/Validators/MultiValidatorTests.cs +++ b/src/Nethermind/Nethermind.AuRa.Test/Validators/MultiValidatorTests.cs @@ -140,7 +140,7 @@ long GetFinalizedIndex(int j) } [Test] - public void doesnt_call_inner_validators_before_start_block() + public void does_not_call_inner_validators_before_start_block() { // Arrange _validator.Validators.Remove(0); diff --git a/src/Nethermind/Nethermind.Benchmark/Core/ByteArrayToHexBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Core/ByteArrayToHexBenchmarks.cs index aa4ab3250b8..d52cba7a728 100644 --- a/src/Nethermind/Nethermind.Benchmark/Core/ByteArrayToHexBenchmarks.cs +++ b/src/Nethermind/Nethermind.Benchmark/Core/ByteArrayToHexBenchmarks.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using BenchmarkDotNet.Attributes; -using HexMate; using Nethermind.Core.Extensions; namespace Nethermind.Benchmarks.Core @@ -28,11 +27,5 @@ public string SafeLookup() { return Bytes.ByteArrayToHexViaLookup32Safe(array, false); } - - [Benchmark(Baseline = true)] - public string HexMateA() - { - return Convert.ToHexString(array, HexFormattingOptions.Lowercase); - } } } diff --git a/src/Nethermind/Nethermind.Benchmark/Core/Keccak256Benchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Core/Keccak256Benchmarks.cs index 85d737f14a6..7e0c156295b 100644 --- a/src/Nethermind/Nethermind.Benchmark/Core/Keccak256Benchmarks.cs +++ b/src/Nethermind/Nethermind.Benchmark/Core/Keccak256Benchmarks.cs @@ -17,7 +17,7 @@ public class Keccak256Benchmarks private byte[][] _scenarios = { - new byte[]{}, + Array.Empty(), new byte[]{1}, new byte[100000], TestItem.AddressA.Bytes diff --git a/src/Nethermind/Nethermind.Benchmark/Core/Keccak512Benchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Core/Keccak512Benchmarks.cs index fd511255d29..0b4b149501c 100644 --- a/src/Nethermind/Nethermind.Benchmark/Core/Keccak512Benchmarks.cs +++ b/src/Nethermind/Nethermind.Benchmark/Core/Keccak512Benchmarks.cs @@ -17,7 +17,7 @@ public class Keccak512Benchmarks private byte[][] _scenarios = { - new byte[]{}, + Array.Empty(), new byte[]{1}, new byte[100000], TestItem.AddressA.Bytes diff --git a/src/Nethermind/Nethermind.Benchmark/Core/LruCacheKeccakBytesBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Core/LruCacheKeccakBytesBenchmarks.cs index f9e00b8dc29..ab3861cf03f 100644 --- a/src/Nethermind/Nethermind.Benchmark/Core/LruCacheKeccakBytesBenchmarks.cs +++ b/src/Nethermind/Nethermind.Benchmark/Core/LruCacheKeccakBytesBenchmarks.cs @@ -28,7 +28,7 @@ public void InitKeccaks() public Hash256[] Keys { get; set; } = new Hash256[64]; - public byte[] Value { get; set; } = new byte[0]; + public byte[] Value { get; set; } = Array.Empty(); [Benchmark] public LruCache WithItems() diff --git a/src/Nethermind/Nethermind.Benchmark/Core/RecoverSignaturesBenchmark.cs b/src/Nethermind/Nethermind.Benchmark/Core/RecoverSignaturesBenchmark.cs index 2ec1cd60b18..6e9ef8dfeca 100644 --- a/src/Nethermind/Nethermind.Benchmark/Core/RecoverSignaturesBenchmark.cs +++ b/src/Nethermind/Nethermind.Benchmark/Core/RecoverSignaturesBenchmark.cs @@ -162,13 +162,13 @@ void ResetSigs(Block block) } [Benchmark] - public void Recover100TxSignatureswith100AuthoritySignatures() + public void Recover100TxSignaturesWith100AuthoritySignatures() { _sut.RecoverData(_block100TxWith100AuthSigs); } [Benchmark] - public void Recover100TxSignatureswith10AuthoritySignatures() + public void Recover100TxSignaturesWith10AuthoritySignatures() { _sut.RecoverData(_block100TxWith10AuthSigs); } diff --git a/src/Nethermind/Nethermind.Benchmark/Evm/Blake2Benchmark.cs b/src/Nethermind/Nethermind.Benchmark/Evm/Blake2Benchmark.cs index 2bbfe76480c..55ce475eb35 100644 --- a/src/Nethermind/Nethermind.Benchmark/Evm/Blake2Benchmark.cs +++ b/src/Nethermind/Nethermind.Benchmark/Evm/Blake2Benchmark.cs @@ -20,7 +20,7 @@ public void Setup() { if (!Bytes.AreEqual(Current(), Improved())) { - throw new InvalidBenchmarkDeclarationException("blakes"); + throw new InvalidBenchmarkDeclarationException("blake2 mismatch"); } } diff --git a/src/Nethermind/Nethermind.Benchmark/Evm/MemoryCostBenchmark.cs b/src/Nethermind/Nethermind.Benchmark/Evm/MemoryCostBenchmark.cs index 54092dc5652..be9e86212db 100644 --- a/src/Nethermind/Nethermind.Benchmark/Evm/MemoryCostBenchmark.cs +++ b/src/Nethermind/Nethermind.Benchmark/Evm/MemoryCostBenchmark.cs @@ -35,7 +35,7 @@ public void Setup() public long Current() { UInt256 dest = _location; - return _current.CalculateMemoryCost(in dest, _length); + return _current.CalculateMemoryCost(in dest, _length, out _); } } } diff --git a/src/Nethermind/Nethermind.Benchmark/Nethermind.Benchmark.csproj b/src/Nethermind/Nethermind.Benchmark/Nethermind.Benchmark.csproj index 47d883bbd2e..4fe68806389 100644 --- a/src/Nethermind/Nethermind.Benchmark/Nethermind.Benchmark.csproj +++ b/src/Nethermind/Nethermind.Benchmark/Nethermind.Benchmark.csproj @@ -6,7 +6,6 @@ - diff --git a/src/Nethermind/Nethermind.Benchmark/Store/PatriciaTreeBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Store/PatriciaTreeBenchmarks.cs index 0155d6a36c0..01abd043137 100644 --- a/src/Nethermind/Nethermind.Benchmark/Store/PatriciaTreeBenchmarks.cs +++ b/src/Nethermind/Nethermind.Benchmark/Store/PatriciaTreeBenchmarks.cs @@ -138,7 +138,7 @@ public class PatriciaTreeBenchmarks Hash256 rootHash = tree.RootHash; tree.Commit(); }), - ("extenson_create_new_extension", tree => + ("extension_create_new_extension", tree => { tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb00000000"), _account0); tree.Set(new Hash256("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeb11111111"), _account1); diff --git a/src/Nethermind/Nethermind.Benchmark/Store/WorldStateBenchmarks.cs b/src/Nethermind/Nethermind.Benchmark/Store/WorldStateBenchmarks.cs index 8a37c31c0c3..e9378a6c9a8 100644 --- a/src/Nethermind/Nethermind.Benchmark/Store/WorldStateBenchmarks.cs +++ b/src/Nethermind/Nethermind.Benchmark/Store/WorldStateBenchmarks.cs @@ -6,6 +6,7 @@ using Autofac; using BenchmarkDotNet.Attributes; using DotNetty.Common.Utilities; +using Nethermind.Consensus.Processing; using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; @@ -44,7 +45,7 @@ public void Setup() .AddModule(new TestNethermindModule()) .Build(); - IWorldState worldState = _globalWorldState = _container.Resolve().GlobalWorldState; + IWorldState worldState = _globalWorldState = _container.Resolve().WorldState; using var _ = worldState.BeginScope(IWorldState.PreGenesis); Random rand = new Random(0); diff --git a/src/Nethermind/Nethermind.Blockchain.Test.Runner/BlockchainTestsBugHunter.cs b/src/Nethermind/Nethermind.Blockchain.Test.Runner/BlockchainTestsBugHunter.cs index eba2ada07de..73a57efc392 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test.Runner/BlockchainTestsBugHunter.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test.Runner/BlockchainTestsBugHunter.cs @@ -14,12 +14,12 @@ namespace Nethermind.Blockchain.Test.Runner public class BlockchainTestsBugHunter : BlockchainTestBase, IBlockchainTestRunner { private ITestSourceLoader _testsSource; - private ConsoleColor _defaultColour; + private ConsoleColor _defaultColor; public BlockchainTestsBugHunter(ITestSourceLoader testsSource) { _testsSource = testsSource ?? throw new ArgumentNullException(nameof(testsSource)); - _defaultColour = Console.ForegroundColor; + _defaultColor = Console.ForegroundColor; } public async Task> RunTestsAsync() @@ -67,14 +67,14 @@ private void WriteRed(string text) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(text); - Console.ForegroundColor = _defaultColour; + Console.ForegroundColor = _defaultColor; } private void WriteGreen(string text) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine(text); - Console.ForegroundColor = _defaultColour; + Console.ForegroundColor = _defaultColor; } } } diff --git a/src/Nethermind/Nethermind.Blockchain.Test.Runner/StateTestsBugHunter.cs b/src/Nethermind/Nethermind.Blockchain.Test.Runner/StateTestsBugHunter.cs index dc13573519c..6b34eab9585 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test.Runner/StateTestsBugHunter.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test.Runner/StateTestsBugHunter.cs @@ -14,12 +14,12 @@ namespace Nethermind.Blockchain.Test.Runner public class StateTestsBugHunter : GeneralStateTestBase, IStateTestRunner { private ITestSourceLoader _testsSource; - private ConsoleColor _defaultColour; + private ConsoleColor _defaultColor; public StateTestsBugHunter(ITestSourceLoader testsSource) { _testsSource = testsSource ?? throw new ArgumentNullException(nameof(testsSource)); - _defaultColour = Console.ForegroundColor; + _defaultColor = Console.ForegroundColor; } public IEnumerable RunTests() @@ -68,14 +68,14 @@ private void WriteRed(string text) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(text); - Console.ForegroundColor = _defaultColour; + Console.ForegroundColor = _defaultColor; } private void WriteGreen(string text) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine(text); - Console.ForegroundColor = _defaultColour; + Console.ForegroundColor = _defaultColor; } } } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockAccessListTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockAccessListTests.cs index 9ad09d35432..b87c3781781 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockAccessListTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockAccessListTests.cs @@ -269,11 +269,16 @@ public async Task Can_construct_BAL() { using BasicTestBlockchain testBlockchain = await BasicTestBlockchain.Create(BuildContainer()); - IWorldState worldState = testBlockchain.WorldStateManager.GlobalWorldState; - using IDisposable _ = worldState.BeginScope(IWorldState.PreGenesis); - InitWorldState(worldState); + // Get the main world state which should be a TracedAccessWorldState after DI fix + IWorldState mainWorldState = testBlockchain.MainWorldState; + TracedAccessWorldState? tracedWorldState = mainWorldState as TracedAccessWorldState; + Assert.That(tracedWorldState, Is.Not.Null, "Main world state should be TracedAccessWorldState"); - (worldState as TracedAccessWorldState)!.BlockAccessList = new(); + // Begin scope and initialize state + using IDisposable _ = mainWorldState.BeginScope(IWorldState.PreGenesis); + InitWorldState(mainWorldState); + + tracedWorldState!.BlockAccessList = new(); const long gasUsed = 167340; const long gasUsedBeforeFinal = 92100; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs index 8042d314406..3d327872c84 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockTreeTests.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System; @@ -254,10 +254,10 @@ public void Cleans_invalid_blocks_before_starting() Assert.That(tree2.BestKnownNumber, Is.EqualTo(0L), "best known"); Assert.That(tree2.Head?.Number, Is.EqualTo(0), "head"); Assert.That(tree2.BestSuggestedHeader!.Number, Is.EqualTo(0L), "suggested"); - Assert.That(blockStore.Get(block2.Number, block2.Hash!), Is.Null, "block 1"); + Assert.That(blockStore.Get(block1.Number, block1.Hash!), Is.Null, "block 1"); Assert.That(blockStore.Get(block2.Number, block2.Hash!), Is.Null, "block 2"); Assert.That(blockStore.Get(block3.Number, block3.Hash!), Is.Null, "block 3"); - Assert.That(blockInfosDb.Get(2), Is.Null, "level 1"); + Assert.That(blockInfosDb.Get(1), Is.Null, "level 1"); Assert.That(blockInfosDb.Get(2), Is.Null, "level 2"); Assert.That(blockInfosDb.Get(3), Is.Null, "level 3"); } @@ -1050,10 +1050,6 @@ public void When_deleting_invalid_block_deletes_its_descendants_even_if_not_firs Assert.That(blockInfosDb.Get(2), Is.Not.Null, "level 2"); Assert.That(blockInfosDb.Get(3), Is.Not.Null, "level 3"); - Assert.That(blockInfosDb.Get(1), Is.Not.Null, "level 1b"); - Assert.That(blockInfosDb.Get(2), Is.Not.Null, "level 2b"); - Assert.That(blockInfosDb.Get(3), Is.Not.Null, "level 3b"); - repository.LoadLevel(1)!.BlockInfos.Length.Should().Be(1); repository.LoadLevel(2)!.BlockInfos.Length.Should().Be(1); repository.LoadLevel(3)!.BlockInfos.Length.Should().Be(1); @@ -1146,7 +1142,7 @@ public void When_lowestInsertedHeaderWasNotPersisted_useBinarySearchToLoadLowest SyncConfig syncConfig = new() { FastSync = true, - PivotNumber = beginIndex.ToString(), + PivotNumber = beginIndex, }; BlockTreeBuilder builder = Build.A @@ -1177,7 +1173,7 @@ public void When_lowestInsertedHeaderWasPersisted_doNot_useBinarySearchToLoadLow SyncConfig syncConfig = new() { FastSync = true, - PivotNumber = "105", + PivotNumber = 105, }; BlockTreeBuilder builder = Build.A @@ -1218,7 +1214,7 @@ public void Does_not_load_bestKnownNumber_before_syncPivot(long syncPivot, long SyncConfig syncConfig = new() { FastSync = true, - PivotNumber = $"{syncPivot}" + PivotNumber = syncPivot }; MemDb blockInfosDb = new MemDb(); @@ -1275,7 +1271,7 @@ public void Loads_best_known_correctly_on_inserts(long beginIndex, long inserted SyncConfig syncConfig = new() { - PivotNumber = beginIndex.ToString(), + PivotNumber = beginIndex, FastSync = true, }; @@ -1312,7 +1308,7 @@ public void Loads_best_head_up_to_best_persisted_state() SyncConfig syncConfig = new() { - PivotNumber = "0", + PivotNumber = 0, FastSync = true, }; @@ -1366,7 +1362,7 @@ public void Loads_best_known_correctly_on_inserts_followed_by_suggests(long pivo { SyncConfig syncConfig = new() { - PivotNumber = pivotNumber.ToString(), + PivotNumber = pivotNumber, }; BlockTreeBuilder builder = Build.A.BlockTree() .WithoutSettingHead @@ -1402,7 +1398,7 @@ public void Loads_best_known_correctly_when_head_before_pivot() int head = 10; SyncConfig syncConfig = new() { - PivotNumber = pivotNumber.ToString() + PivotNumber = pivotNumber }; BlockTreeBuilder treeBuilder = Build.A.BlockTree().OfChainLength(head + 1); @@ -1423,7 +1419,7 @@ public void Cannot_insert_genesis() SyncConfig syncConfig = new() { - PivotNumber = pivotNumber.ToString(), + PivotNumber = pivotNumber, }; BlockTree tree = Build.A.BlockTree() @@ -1444,7 +1440,7 @@ public void Should_set_zero_total_difficulty() SyncConfig syncConfig = new() { - PivotNumber = pivotNumber.ToString(), + PivotNumber = pivotNumber, }; CustomSpecProvider specProvider = new(((ForkActivation)0, London.Instance)); @@ -1472,7 +1468,7 @@ public void Inserts_blooms() SyncConfig syncConfig = new() { - PivotNumber = pivotNumber.ToString(), + PivotNumber = pivotNumber, }; IBloomStorage bloomStorage = Substitute.For(); @@ -1503,7 +1499,7 @@ public void Block_loading_is_lazy() { SyncConfig syncConfig = new() { - PivotNumber = 0L.ToString(), + PivotNumber = 0L, }; BlockTreeBuilder builder = Build.A.BlockTree() @@ -2037,7 +2033,7 @@ public void Load_SyncPivot_FromConfig() SyncConfig syncConfig = new SyncConfig() { FastSync = true, - PivotNumber = "999", + PivotNumber = 999, PivotHash = Hash256.Zero.ToString(), }; BlockTree blockTree = Build.A.BlockTree().WithSyncConfig(syncConfig).TestObject; @@ -2050,7 +2046,7 @@ public void Load_SyncPivot_FromDb() SyncConfig syncConfig = new SyncConfig() { FastSync = true, - PivotNumber = "999", + PivotNumber = 999, PivotHash = Hash256.Zero.ToString(), }; IDb metadataDb = new MemDb(); @@ -2069,7 +2065,7 @@ public void On_UpdateMainBranch_UpdateSyncPivot_ToLowestPersistedHeader() SyncConfig syncConfig = new() { FastSync = true, - PivotNumber = pivotNumber.ToString(), + PivotNumber = pivotNumber, PivotHash = TestItem.KeccakA.ToString(), }; @@ -2112,7 +2108,7 @@ public void On_ForkChoiceUpdated_UpdateSyncPivot_ToFinalizedHeader_BeforePersist SyncConfig syncConfig = new() { FastSync = true, - PivotNumber = pivotNumber.ToString(), + PivotNumber = pivotNumber, PivotHash = TestItem.KeccakA.ToString(), }; @@ -2160,7 +2156,7 @@ public void On_UpdateMainBranch_UpdateSyncPivot_ToHeaderUnderReorgDepth() SyncConfig syncConfig = new() { FastSync = true, - PivotNumber = pivotNumber.ToString(), + PivotNumber = pivotNumber, PivotHash = TestItem.KeccakA.ToString(), }; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs index 17d9d5a0bc2..2b1e6c244b5 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockchainProcessorTests.cs @@ -623,7 +623,7 @@ public void Can_change_branch_on_invalid_block() } [Test(Description = "Covering scenario when we have an invalid block followed by its descendants." + - "All the descandant blocks should get discarded and an alternative branch should get selected." + + "All the descendant blocks should get discarded and an alternative branch should get selected." + "BRANCH A | BLOCK 2 | INVALID | DISCARD" + "BRANCH A | BLOCK 3 | VALID | DISCARD" + "BRANCH A | BLOCK 4 | VALID | DISCARD" + diff --git a/src/Nethermind/Nethermind.Blockchain.Test/BlockhashCacheTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/BlockhashCacheTests.cs index e07dde4e3e8..17d1363095a 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/BlockhashCacheTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/BlockhashCacheTests.cs @@ -89,7 +89,7 @@ public void GetHash_handles_max_depth_of_256() } [Test] - public void GetHash_doesnt_go_beyond_depth_256() + public void GetHash_does_not_go_beyond_depth_256() { (BlockTree tree, BlockhashCache cache) = BuildTest(300); @@ -311,15 +311,14 @@ public async Task Prefetch_reuses_parent_data(int chainDepth) Assert.Multiple(() => { int compareLength = headHashes.Length - 1; - Assert.That(prevHashes.AsSpan(0, compareLength) - .SequenceEqual(headHashes.AsSpan(1, compareLength))); - Assert.That(headHashes[0], Is.EqualTo(head.Hash)); + Assert.That(prevHashes.AsSpan(0, compareLength).SequenceEqual(headHashes.AsSpan(1, compareLength))); + Assert.That(headHashes[0], Is.EqualTo(head.ParentHash)); } ); } [Test] - public async Task Doesnt_cache_cancelled_searches() + public async Task DoesNot_cache_cancelled_searches() { SlowHeaderStore headerStore = new(new HeaderStore(new MemDb(), new MemDb())); (BlockTree tree, BlockhashCache cache) = BuildTest(260, headerStore); @@ -330,6 +329,45 @@ public async Task Doesnt_cache_cancelled_searches() cache.GetStats().Should().Be(new BlockhashCache.Stats(0, 0, 0)); } + [Test] + public void DoesNot_cache_null_hashes() + { + (BlockTree tree, BlockhashCache cache) = BuildTest(100); + BlockHeader head = tree.FindHeader(99, BlockTreeLookupOptions.None)!; + BlockHeader headMinus4 = tree.FindHeader(95, BlockTreeLookupOptions.None)!; + BlockHeader headerOnHeadMinus4 = Build.A.BlockHeader.WithParent(headMinus4).WithHash(null!).TestObject; + BlockHeader headerOnHead = Build.A.BlockHeader.WithParent(head).WithHash(null!).TestObject; + Hash256? hashOnHeadMinus8 = cache.GetHash(headerOnHeadMinus4, 4); + cache.GetHash(headerOnHead, 5).Should().Be(headMinus4.Hash!); + hashOnHeadMinus8.Should().Be(cache.GetHash(headerOnHead, 8)!); + } + + [Test] + public async Task Prefetch_with_null_hash_does_not_cache([Values] bool prefetchParent) + { + (BlockTree tree, BlockhashCache cache) = BuildTest(10); + + BlockHeader parent = tree.FindHeader(9, BlockTreeLookupOptions.None)!; + if (prefetchParent) + { + await cache.Prefetch(parent); + } + + int cacheCountBefore = cache.GetStats().FlatCache; + + BlockHeader production = Build.A.BlockHeader.WithParent(parent).WithNumber(10).TestObject; + production.Hash = null; + Hash256[] hashes = (await cache.Prefetch(production))!; + + hashes.Should().NotBeNull(); + hashes[0].Should().Be(parent.Hash!); + + if (prefetchParent) + { + cache.GetStats().FlatCache.Should().Be(cacheCountBefore); + } + } + private static (BlockTree, BlockhashCache) BuildTest(int chainLength, IHeaderStore? headerStore = null) { Block genesis = Build.A.Block.Genesis.TestObject; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs index 525a99c5353..46c3d9da71c 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Blocks/BlockStoreTests.cs @@ -37,7 +37,7 @@ public void Test_can_insert_get_and_remove_blocks(bool cached) } [Test] - public void Test_insert_would_pass_in_writeflag() + public void Test_insert_would_pass_in_write_flag() { TestMemDb db = new(); BlockStore store = new(db); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs index 628ff7f9c60..17af0b2ad38 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Consensus/NullSignerTests.cs @@ -19,7 +19,7 @@ public void Test() { NullSigner signer = NullSigner.Instance; signer.Address.Should().Be(Address.Zero); - signer.CanSign.Should().BeTrue(); + signer.CanSign.Should().BeFalse(); } [Test, MaxTime(Timeout.MaxTestTime)] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs index cbedcef98a5..4fe896388c7 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/AddressFilterTests.cs @@ -98,10 +98,7 @@ public void Accepts_any_address_when_set_is_empty_by_ref() [Test] public void Accepts_only_addresses_in_a_set() { - HashSet addresses = new() - { - TestItem.AddressA, TestItem.AddressC - }; + HashSet addresses = [TestItem.AddressA, TestItem.AddressC]; AddressFilter filter = new AddressFilter(addresses); filter.Accepts(TestItem.AddressA).Should().BeTrue(); @@ -112,10 +109,7 @@ public void Accepts_only_addresses_in_a_set() [Test] public void Accepts_only_addresses_in_a_set_by_ref() { - HashSet addresses = new() - { - TestItem.AddressA, TestItem.AddressC - }; + HashSet addresses = [TestItem.AddressA, TestItem.AddressC]; AddressFilter filter = new AddressFilter(addresses); AddressStructRef addressARef = TestItem.AddressA.ToStructRef(); @@ -212,7 +206,7 @@ public void Matches_any_bloom_when_set_is_empty_by_ref() [Test] public void Matches_any_bloom_when_set_is_forced_null() { - AddressFilter filter = new AddressFilter(addresses: null!); + AddressFilter filter = new AddressFilter([]); filter.Matches(BloomFromAddress(TestItem.AddressA)).Should().BeTrue(); filter.Matches(BloomFromAddress(TestItem.AddressB)).Should().BeTrue(); @@ -222,7 +216,7 @@ public void Matches_any_bloom_when_set_is_forced_null() [Test] public void Matches_any_bloom_when_set_is_forced_null_by_ref() { - AddressFilter filter = new AddressFilter(addresses: null!); + AddressFilter filter = new AddressFilter([]); BloomStructRef bloomARef = BloomFromAddress(TestItem.AddressA).ToStructRef(); BloomStructRef bloomBRef = BloomFromAddress(TestItem.AddressB).ToStructRef(); @@ -235,10 +229,7 @@ public void Matches_any_bloom_when_set_is_forced_null_by_ref() [Test] public void Matches_any_bloom_using_addresses_set() { - HashSet addresses = new() - { - TestItem.AddressA, TestItem.AddressC - }; + HashSet addresses = [TestItem.AddressA, TestItem.AddressC]; AddressFilter filter = new AddressFilter(addresses); filter.Matches(BloomFromAddress(TestItem.AddressA)).Should().BeTrue(); @@ -249,10 +240,7 @@ public void Matches_any_bloom_using_addresses_set() [Test] public void Matches_any_bloom_using_addresses_set_by_ref() { - HashSet addresses = new() - { - TestItem.AddressA, TestItem.AddressC - }; + HashSet addresses = [TestItem.AddressA, TestItem.AddressC]; AddressFilter filter = new AddressFilter(addresses); BloomStructRef bloomARef = BloomFromAddress(TestItem.AddressA).ToStructRef(); @@ -266,7 +254,7 @@ public void Matches_any_bloom_using_addresses_set_by_ref() private static Core.Bloom BloomFromAddress(Address address) { LogEntry entry = new LogEntry(address, [], []); - Core.Bloom bloom = new Core.Bloom(new[] { entry }); + Core.Bloom bloom = new Core.Bloom([entry]); return bloom; } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterManagerTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterManagerTests.cs index 36194e57e04..0b863d6112b 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterManagerTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterManagerTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using FluentAssertions; using Nethermind.Blockchain.Filters; using Nethermind.Blockchain.Test.Builders; @@ -11,6 +12,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Test.Builders; +using Nethermind.Core.Timers; using Nethermind.Facade.Filters; using Nethermind.Logging; using Nethermind.TxPool; @@ -21,7 +23,7 @@ namespace Nethermind.Blockchain.Test.Filters; public class FilterManagerTests { - private IFilterStore _filterStore = null!; + private FilterStore _filterStore = null!; private IBranchProcessor _branchProcessor = null!; private IMainProcessingContext _mainProcessingContext = null!; private ITxPool _txPool = null!; @@ -34,7 +36,7 @@ public class FilterManagerTests public void Setup() { _currentFilterId = 0; - _filterStore = Substitute.For(); + _filterStore = new FilterStore(new TimerFactory(), 20, 10); _branchProcessor = Substitute.For(); _mainProcessingContext = Substitute.For(); _mainProcessingContext.BranchProcessor.Returns(_branchProcessor); @@ -49,11 +51,11 @@ public void TearDown() } [Test, MaxTime(Timeout.MaxTestTime)] - public void removing_filter_removes_data() + public async Task removing_filter_removes_data() { LogsShouldNotBeEmpty(static _ => { }, static _ => { }); _filterManager.GetLogs(0).Should().NotBeEmpty(); - _filterStore.FilterRemoved += Raise.EventWith(new FilterEventArgs(0)); + await Task.Delay(60); _filterManager.GetLogs(0).Should().BeEmpty(); } @@ -324,8 +326,8 @@ private void Assert(IEnumerable> filterBuilders, BlockFilter blockFilter = new(_currentFilterId++); filters.Add(blockFilter); - _filterStore.GetFilters().Returns(filters.OfType().ToArray()); - _filterStore.GetFilters().Returns(filters.OfType().ToArray()); + _filterStore.SaveFilters(filters.OfType()); + _filterStore.SaveFilters(filters.OfType()); _filterManager = new FilterManager(_filterStore, _mainProcessingContext, _txPool, _logManager); _branchProcessor.BlockProcessed += Raise.EventWith(_branchProcessor, new BlockProcessedEventArgs(block, [])); @@ -367,3 +369,15 @@ private static TxReceipt BuildReceipt(Action builder) return builderInstance.TestObject; } } + +file static class FilterExtensions +{ + public static void SaveFilters(this FilterStore store, IEnumerable filters) + where T : FilterBase + { + foreach (T filter in filters) + { + store.SaveFilter(filter); + } + } +} diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs index f7fdea314f1..78cfc3f70b6 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/FilterStoreTests.cs @@ -11,6 +11,7 @@ using Nethermind.Blockchain.Filters.Topics; using Nethermind.Blockchain.Find; using Nethermind.Core; +using Nethermind.Core.Crypto; using Nethermind.Core.Test.Builders; using Nethermind.Core.Timers; using NSubstitute; @@ -75,7 +76,7 @@ public void Remove_filter_removes_and_notifies() store.FilterRemoved += (s, e) => hasNotified = true; store.RemoveFilter(0); - Assert.That(hasNotified, Is.True, "notied"); + Assert.That(hasNotified, Is.True, "notified"); Assert.That(store.FilterExists(0), Is.False, "exists"); } @@ -102,14 +103,14 @@ public static IEnumerable CorrectlyCreatesAddressFilterTestCases get { yield return new TestCaseData(null, AddressFilter.AnyAddress); - yield return new TestCaseData(TestItem.AddressA.ToString(), new AddressFilter(TestItem.AddressA)); - yield return new TestCaseData(new[] { TestItem.AddressA.ToString(), TestItem.AddressB.ToString() }, - new AddressFilter(new HashSet() { TestItem.AddressA, TestItem.AddressB })); + yield return new TestCaseData(new HashSet { new(TestItem.AddressA) }, new AddressFilter(TestItem.AddressA)); + yield return new TestCaseData(new HashSet { new(TestItem.AddressA), new(TestItem.AddressB) }, + new AddressFilter([TestItem.AddressA, TestItem.AddressB])); } } [TestCaseSource(nameof(CorrectlyCreatesAddressFilterTestCases))] - public void Correctly_creates_address_filter(object address, AddressFilter expected) + public void Correctly_creates_address_filter(HashSet address, AddressFilter expected) { BlockParameter from = new(100); BlockParameter to = new(BlockParameterType.Latest); @@ -124,22 +125,25 @@ public static IEnumerable CorrectlyCreatesTopicsFilterTestCases { yield return new TestCaseData(null, SequenceTopicsFilter.AnyTopic); - yield return new TestCaseData(new[] { TestItem.KeccakA.ToString() }, + yield return new TestCaseData(new[] { new[] { TestItem.KeccakA } }, new SequenceTopicsFilter(new SpecificTopic(TestItem.KeccakA))); - yield return new TestCaseData(new[] { TestItem.KeccakA.ToString(), TestItem.KeccakB.ToString() }, + yield return new TestCaseData(new[] { new[] { TestItem.KeccakA }, new[] { TestItem.KeccakB } }, new SequenceTopicsFilter(new SpecificTopic(TestItem.KeccakA), new SpecificTopic(TestItem.KeccakB))); - yield return new TestCaseData(new[] { null, TestItem.KeccakB.ToString() }, + yield return new TestCaseData(new[] { null, new[] { TestItem.KeccakB } }, new SequenceTopicsFilter(AnyTopic.Instance, new SpecificTopic(TestItem.KeccakB))); - yield return new TestCaseData(new object[] { new[] { TestItem.KeccakA.ToString(), TestItem.KeccakB.ToString(), TestItem.KeccakC.ToString() }, TestItem.KeccakD.ToString() }, - new SequenceTopicsFilter(new OrExpression(new SpecificTopic(TestItem.KeccakA), new SpecificTopic(TestItem.KeccakB), new SpecificTopic(TestItem.KeccakC)), new SpecificTopic(TestItem.KeccakD))); + yield return new TestCaseData( + new[] { new[] { TestItem.KeccakA, TestItem.KeccakB, TestItem.KeccakC }, new[] { TestItem.KeccakD } }, + new SequenceTopicsFilter( + new OrExpression(new SpecificTopic(TestItem.KeccakA), new SpecificTopic(TestItem.KeccakB), + new SpecificTopic(TestItem.KeccakC)), new SpecificTopic(TestItem.KeccakD))); } } [TestCaseSource(nameof(CorrectlyCreatesTopicsFilterTestCases))] - public void Correctly_creates_topics_filter(IEnumerable topics, TopicsFilter expected) + public void Correctly_creates_topics_filter(Hash256[]?[]? topics, TopicsFilter expected) { BlockParameter from = new(100); BlockParameter to = new(BlockParameterType.Latest); @@ -166,6 +170,7 @@ public async Task CleanUps_filters() Assert.That(() => store.FilterExists(1), Is.False.After(30, 5), "filter 1 doesn't exist"); Assert.That(() => store.FilterExists(2), Is.False.After(30, 5), "filter 2 doesn't exist"); Assert.That(() => store.FilterExists(3), Is.False.After(30, 5), "filter 3 doesn't exist"); + store.RefreshFilter(0); Assert.That(() => removedFilterIds, Is.EquivalentTo([1, 2, 3]).After(30, 5)); } } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs index 27771f99a91..9d497daa7dd 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Filters/LogFilterTests.cs @@ -121,7 +121,7 @@ public void complex_filter_matches_bloom() } [Test, MaxTime(Timeout.MaxTestTime)] - public void address_filter_doesnt_match_bloom() + public void address_filter_does_not_match_bloom() { LogFilter filter = FilterBuilder.New(ref _filterCounter) .WithAddress(TestItem.AddressA) @@ -133,7 +133,7 @@ public void address_filter_doesnt_match_bloom() } [Test, MaxTime(Timeout.MaxTestTime)] - public void addresses_filter_doesnt_match_bloom() + public void addresses_filter_does_not_match_bloom() { LogFilter filter = FilterBuilder.New(ref _filterCounter) .WithAddresses(TestItem.AddressA, TestItem.AddressB, TestItem.AddressC) @@ -145,7 +145,7 @@ public void addresses_filter_doesnt_match_bloom() } [Test, MaxTime(Timeout.MaxTestTime)] - public void specific_topics_filter_doesnt_match_bloom() + public void specific_topics_filter_does_not_match_bloom() { LogFilter filter = FilterBuilder.New(ref _filterCounter) .WithTopicExpressions(TestTopicExpressions.Specific(TestItem.KeccakA), TestTopicExpressions.Specific(TestItem.KeccakC)) @@ -157,7 +157,7 @@ public void specific_topics_filter_doesnt_match_bloom() } [Test, MaxTime(Timeout.MaxTestTime)] - public void multiple_specific_topics_filter_doesnt_match_bloom() + public void multiple_specific_topics_filter_does_not_match_bloom() { LogFilter filter = FilterBuilder.New(ref _filterCounter) .WithTopicExpressions(TestTopicExpressions.Specific(TestItem.KeccakA), TestTopicExpressions.Specific(TestItem.KeccakB)) @@ -169,7 +169,7 @@ public void multiple_specific_topics_filter_doesnt_match_bloom() } [Test, MaxTime(Timeout.MaxTestTime)] - public void or_topics_filter_doesnt_match_bloom() + public void or_topics_filter_does_not_match_bloom() { LogFilter filter = FilterBuilder.New(ref _filterCounter) .WithTopicExpressions(TestTopicExpressions.Or(TestTopicExpressions.Specific(TestItem.KeccakB), TestTopicExpressions.Specific(TestItem.KeccakA))) @@ -181,7 +181,7 @@ public void or_topics_filter_doesnt_match_bloom() } [Test, MaxTime(Timeout.MaxTestTime)] - public void complex_topics_filter_doesnt_match_bloom() + public void complex_topics_filter_does_not_match_bloom() { LogFilter filter = FilterBuilder.New(ref _filterCounter) .WithTopicExpressions(TestTopicExpressions.Specific(TestItem.KeccakA), TestTopicExpressions.Or(TestTopicExpressions.Specific(TestItem.KeccakB), TestTopicExpressions.Specific(TestItem.KeccakA))) @@ -193,7 +193,7 @@ public void complex_topics_filter_doesnt_match_bloom() } [Test, MaxTime(Timeout.MaxTestTime)] - public void complex_filter_doesnt_match_bloom() + public void complex_filter_does_not_match_bloom() { LogFilter filter = FilterBuilder.New(ref _filterCounter) .WithTopicExpressions(TestTopicExpressions.Specific(TestItem.KeccakA), TestTopicExpressions.Or(TestTopicExpressions.Specific(TestItem.KeccakB), TestTopicExpressions.Specific(TestItem.KeccakC))) diff --git a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs index a20c7457677..7c5aae0c8b2 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/CopyTreeVisitorTests.cs @@ -93,18 +93,19 @@ private IPruningContext CopyDb(IPruningContext pruningContext, CancellationToken // Use TestWorldStateFactory.CreateForTest() with the custom DbProvider (IWorldState worldState, IStateReader stateReader) = TestWorldStateFactory.CreateForTestWithStateReader(dbProvider, logManager); + BlockHeader? baseBlock = Build.A.BlockHeader.WithStateRoot(trie.RootHash).TestObject; if (_keyScheme == INodeStorage.KeyScheme.Hash) { NodeStorage nodeStorage = new NodeStorage(pruningContext, _keyScheme); using CopyTreeVisitor copyTreeVisitor = new(nodeStorage, writeFlags, logManager, cancellationToken); - stateReader.RunTreeVisitor(copyTreeVisitor, trie.RootHash, visitingOptions); + stateReader.RunTreeVisitor(copyTreeVisitor, baseBlock, visitingOptions); copyTreeVisitor.Finish(); } else { NodeStorage nodeStorage = new NodeStorage(pruningContext, _keyScheme); using CopyTreeVisitor copyTreeVisitor = new(nodeStorage, writeFlags, logManager, cancellationToken); - stateReader.RunTreeVisitor(copyTreeVisitor, trie.RootHash, visitingOptions); + stateReader.RunTreeVisitor(copyTreeVisitor, baseBlock, visitingOptions); copyTreeVisitor.Finish(); } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs index 5b1cec3e995..d28cf1ab7be 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/FullPruning/FullPruningDiskTest.cs @@ -103,7 +103,7 @@ public static async Task Create(IPruningConfig? pruningCo PruningTestBlockchain chain = new() { PruningConfig = pruningConfig ?? new PruningConfig(), - TestTimout = testTimeoutMs, + TestTimeout = testTimeoutMs, }; await chain.Build(); return chain; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/GenesisBuilderTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/GenesisBuilderTests.cs index 75d56ac9c52..cc0607a1d8e 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/GenesisBuilderTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/GenesisBuilderTests.cs @@ -22,13 +22,13 @@ namespace Nethermind.Blockchain.Test; public class GenesisBuilderTests { [Test, MaxTime(Timeout.MaxTestTime)] - public void Can_load_genesis_with_emtpy_accounts_and_storage() + public void Can_load_genesis_with_empty_accounts_and_storage() { AssertBlockHash("0x61b2253366eab37849d21ac066b96c9de133b8c58a9a38652deae1dd7ec22e7b", "Specs/empty_accounts_and_storages.json"); } [Test, MaxTime(Timeout.MaxTestTime)] - public void Can_load_genesis_with_emtpy_accounts_and_code() + public void Can_load_genesis_with_empty_accounts_and_code() { AssertBlockHash("0xfa3da895e1c2a4d2673f60dd885b867d60fb6d823abaf1e5276a899d7e2feca5", "Specs/empty_accounts_and_codes.json"); } diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.IsProducingBlocks.cs b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.IsProducingBlocks.cs index 1470ec99471..4a5bce87837 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.IsProducingBlocks.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Producers/BlockProducerBaseTests.IsProducingBlocks.cs @@ -25,6 +25,7 @@ using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.Evm.State; +using Nethermind.State; using NSubstitute; using NUnit.Framework; @@ -40,7 +41,7 @@ public async Task DevBlockProducer_IsProducingBlocks_returns_expected_results() DevBlockProducer blockProducer = new( Substitute.For(), testRpc.BlockchainProcessor, - testRpc.WorldStateManager.GlobalWorldState, + testRpc.MainWorldState, testRpc.BlockTree, testRpc.Timestamper, testRpc.SpecProvider, @@ -59,7 +60,7 @@ public async Task TestBlockProducer_IsProducingBlocks_returns_expected_results() TestBlockProducer blockProducer = new( Substitute.For(), testRpc.BlockchainProcessor, - testRpc.WorldStateManager.GlobalWorldState, + testRpc.MainWorldState, Substitute.For(), testRpc.BlockTree, testRpc.Timestamper, @@ -81,7 +82,7 @@ public async Task MinedBlockProducer_IsProducingBlocks_returns_expected_results( testRpc.BlockchainProcessor, Substitute.For(), testRpc.BlockTree, - testRpc.WorldStateManager.GlobalWorldState, + testRpc.MainWorldState, Substitute.For(), testRpc.Timestamper, testRpc.SpecProvider, @@ -123,7 +124,7 @@ public async Task CliqueBlockProducer_IsProducingBlocks_returns_expected_results CliqueBlockProducer blockProducer = new( Substitute.For(), testRpc.BlockchainProcessor, - testRpc.WorldStateManager.GlobalWorldState, + testRpc.MainWorldState, testRpc.Timestamper, Substitute.For(), Substitute.For(), @@ -152,7 +153,7 @@ public async Task DevBlockProducer_OnGlobalWorldState_IsProducingAndSuggestingBl DevBlockProducer blockProducer = new( Substitute.For(), testRpc.BlockchainProcessor, - testRpc.WorldStateManager.GlobalWorldState, + new WorldState(testRpc.WorldStateManager.GlobalWorldState, testRpc.LogManager), testRpc.BlockTree, testRpc.Timestamper, testRpc.SpecProvider, diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs index fcf32a5ad08..31dc8039fcd 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/PersistentReceiptStorageTests.cs @@ -88,14 +88,14 @@ public void Returns_null_for_missing_tx() } [Test, MaxTime(Timeout.MaxTestTime)] - public void ReceiptsIterator_doesnt_throw_on_empty_span() + public void ReceiptsIterator_does_not_throw_on_empty_span() { _storage.TryGetReceiptsIterator(1, Keccak.Zero, out ReceiptsIterator iterator); iterator.TryGetNext(out _).Should().BeFalse(); } [Test, MaxTime(Timeout.MaxTestTime)] - public void ReceiptsIterator_doesnt_throw_on_null() + public void ReceiptsIterator_does_not_throw_on_null() { _receiptsDb.GetColumnDb(ReceiptsColumns.Blocks).Set(Keccak.Zero, null!); _storage.TryGetReceiptsIterator(1, Keccak.Zero, out ReceiptsIterator iterator); @@ -332,7 +332,7 @@ public void When_TxLookupLimitIs_NegativeOne_DoNotIndexTxHash() [TestCase(1L, false)] [TestCase(10L, false)] [TestCase(11L, true)] - public void Should_only_prune_index_tx_hashes_if_blockNumber_is_bigger_than_lookupLimit(long blockNumber, bool WillPruneOldIndicies) + public void Should_only_prune_index_tx_hashes_if_blockNumber_is_bigger_than_lookupLimit(long blockNumber, bool willPruneOldIndices) { _receiptConfig.TxLookupLimit = 10; CreateStorage(); @@ -340,7 +340,7 @@ public void Should_only_prune_index_tx_hashes_if_blockNumber_is_bigger_than_look Raise.EventWith(new BlockReplacementEventArgs(Build.A.Block.WithNumber(blockNumber).TestObject)); Assert.That(() => _blockTree.ReceivedCalls() .Where(static call => call.GetMethodInfo().Name.EndsWith(nameof(_blockTree.FindBlock))), - WillPruneOldIndicies ? Is.Not.Empty.After(100, 10) : Is.Empty.After(100, 10)); + willPruneOldIndices ? Is.Not.Empty.After(100, 10) : Is.Empty.After(100, 10)); } [Test] diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs index 0e939e90943..ad7d30e5256 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Receipts/ReceiptsIteratorTests.cs @@ -25,12 +25,12 @@ public void SmokeTestWithRecovery() .Block .WithTransactions(3, MainnetSpecProvider.Instance) .TestObject; - TxReceipt[] receipts = new[] - { + TxReceipt[] receipts = + [ Build.A.Receipt.WithAllFieldsFilled.WithSender(TestItem.AddressA).TestObject, Build.A.Receipt.WithAllFieldsFilled.WithSender(TestItem.AddressB).TestObject, - Build.A.Receipt.WithAllFieldsFilled.WithSender(TestItem.AddressC).TestObject, - }; + Build.A.Receipt.WithAllFieldsFilled.WithSender(TestItem.AddressC).TestObject + ]; ReceiptsIterator iterator = CreateIterator(receipts, block); @@ -84,11 +84,11 @@ public void SmokeTest() .WithTransactions(3, MainnetSpecProvider.Instance) .TestObject; TxReceipt[] receipts = - { + [ Build.A.Receipt.WithAllFieldsFilled.WithSender(TestItem.AddressA).TestObject, Build.A.Receipt.WithAllFieldsFilled.WithSender(TestItem.AddressB).TestObject, - Build.A.Receipt.WithAllFieldsFilled.WithSender(TestItem.AddressC).TestObject, - }; + Build.A.Receipt.WithAllFieldsFilled.WithSender(TestItem.AddressC).TestObject + ]; ReceiptsIterator iterator = CreateIterator(receipts, block); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs index 03fa6387221..b8acad1530f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/ReorgTests.cs @@ -91,11 +91,11 @@ public void Setup() transactionComparerProvider.GetDefaultComparer()); BlockhashCache blockhashCache = new(blockTreeBuilder.HeaderStore, LimboLogs.Instance); BlockhashProvider blockhashProvider = new(blockhashCache, stateProvider, LimboLogs.Instance); - VirtualMachine virtualMachine = new( + EthereumVirtualMachine virtualMachine = new( blockhashProvider, specProvider, LimboLogs.Instance); - TransactionProcessor transactionProcessor = new( + EthereumTransactionProcessor transactionProcessor = new( BlobBaseFeeCalculator.Instance, specProvider, stateProvider, diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs index 5d782d07ce8..56f236010aa 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorEip7702Tests.cs @@ -43,8 +43,8 @@ public void Setup() _stateProvider = TestWorldStateFactory.CreateForTest(); _worldStateCloser = _stateProvider.BeginScope(IWorldState.PreGenesis); EthereumCodeInfoRepository codeInfoRepository = new(_stateProvider); - VirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); - _transactionProcessor = new TransactionProcessor(BlobBaseFeeCalculator.Instance, _specProvider, _stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); + EthereumVirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); + _transactionProcessor = new EthereumTransactionProcessor(BlobBaseFeeCalculator.Instance, _specProvider, _stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); _ethereumEcdsa = new EthereumEcdsa(_specProvider.ChainId); } @@ -457,7 +457,7 @@ public void Execute_FirstTxHasAuthorizedCodeThatIncrementsAndSecondDoesNot_Stora PrivateKey signer = TestItem.PrivateKeyB; Address codeSource = TestItem.AddressC; _stateProvider.CreateAccount(sender.Address, 1.Ether()); - //Increment 1 everytime it's called + // Increment by 1 every time it's called byte[] code = Prepare.EvmCode .Op(Instruction.PUSH0) .Op(Instruction.SLOAD) @@ -578,7 +578,7 @@ public void Execute_DelegatedCodeUsesEXTOPCODES_ReturnsExpectedValue(byte[] code public static IEnumerable EXTCODEHASHAccountSetup() { - yield return new object[] { static (IWorldState state, Address accountt) => + yield return new object[] { static (IWorldState state, Address account) => { //Account does not exists }, @@ -783,7 +783,7 @@ public static IEnumerable AccountAccessGasCases() }; } [TestCaseSource(nameof(AccountAccessGasCases))] - public void Execute_DiffentAccountAccessOpcodes_ChargesCorrectAccountAccessGas(byte[] code, long expectedGas, bool isDelegated, long gasLimit, bool shouldRunOutOfGas) + public void Execute_DifferentAccountAccessOpcodes_ChargesCorrectAccountAccessGas(byte[] code, long expectedGas, bool isDelegated, long gasLimit, bool shouldRunOutOfGas) { PrivateKey signer = TestItem.PrivateKeyA; PrivateKey sender = TestItem.PrivateKeyB; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs index 0aaeb4f66c6..c6dfd09004f 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionProcessorTests.cs @@ -12,6 +12,7 @@ using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Int256; +using Nethermind.Evm; using Nethermind.Evm.Tracing; using Nethermind.Blockchain.Tracing.GethStyle; using Nethermind.Blockchain.Tracing.ParityStyle; @@ -63,8 +64,8 @@ public void Setup() _baseBlock = Build.A.BlockHeader.WithStateRoot(_stateProvider.StateRoot).TestObject; EthereumCodeInfoRepository codeInfoRepository = new(_stateProvider); - VirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); - _transactionProcessor = new TransactionProcessor(BlobBaseFeeCalculator.Instance, _specProvider, _stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); + EthereumVirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); + _transactionProcessor = new EthereumTransactionProcessor(BlobBaseFeeCalculator.Instance, _specProvider, _stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); _ethereumEcdsa = new EthereumEcdsa(_specProvider.ChainId); } @@ -287,14 +288,14 @@ public void Can_estimate_with_value(bool systemUser) Block block = Build.A.Block.WithParent(_baseBlock).WithTransactions(tx).WithGasLimit(gasLimit).TestObject; EstimateGasTracer tracer = new(); - Action action = () => _transactionProcessor.CallAndRestore(tx, new BlockExecutionContext(block.Header, _specProvider.GetSpec(block.Header)), tracer); + TransactionResult result = _transactionProcessor.CallAndRestore(tx, new BlockExecutionContext(block.Header, _specProvider.GetSpec(block.Header)), tracer); + if (!systemUser) { - action.Should().Throw(); + result.Should().Be(TransactionResult.InsufficientSenderBalance); } else { - action.Should().NotThrow(); tracer.GasSpent.Should().Be(21000); } } @@ -317,7 +318,7 @@ public long Should_not_estimate_tx_with_high_value(UInt256 txValue) long estimate = estimator.Estimate(tx, block.Header, tracer, out string? err, 0); - if (txValue > AccountBalance) + if (txValue + (UInt256)gasLimit > AccountBalance) { Assert.That(err, Is.Not.Null); // Should have error Assert.That(err, Is.EqualTo("Transaction execution fails")); @@ -334,20 +335,21 @@ public static IEnumerable EstimateWithHighTxValueTestCases { get { + UInt256 gasLimit = 100000; yield return new TestCaseData((UInt256)1) { TestName = "Sanity check", ExpectedResult = GasCostOf.Transaction }; - yield return new TestCaseData(AccountBalance - 1) + yield return new TestCaseData(AccountBalance - 1 - gasLimit) { TestName = "Less than account balance", ExpectedResult = GasCostOf.Transaction }; - yield return new TestCaseData(AccountBalance - GasCostOf.Transaction) + yield return new TestCaseData(AccountBalance - GasCostOf.Transaction - gasLimit) { TestName = "Account balance - tx cost", ExpectedResult = GasCostOf.Transaction }; - yield return new TestCaseData(AccountBalance - GasCostOf.Transaction + 1) + yield return new TestCaseData(AccountBalance - GasCostOf.Transaction - gasLimit + 1) { TestName = "More than (account balance - tx cost)", ExpectedResult = GasCostOf.Transaction }; yield return new TestCaseData(AccountBalance) - { TestName = "Exactly account balance", ExpectedResult = GasCostOf.Transaction }; + { TestName = "Exactly account balance", ExpectedResult = 0L }; yield return new TestCaseData(AccountBalance + 1) { TestName = "More than account balance", ExpectedResult = 0L }; - yield return new TestCaseData(UInt256.MaxValue) + yield return new TestCaseData(UInt256.MaxValue - gasLimit) { TestName = "Max value possible", ExpectedResult = 0L }; } } @@ -404,7 +406,7 @@ public void Can_estimate_with_refund() Transaction tx = Build.A.Transaction.SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA, _isEip155Enabled).WithCode(initByteCode).WithGasLimit(gasLimit).TestObject; Block block = Build.A.Block.WithNumber(MainnetSpecProvider.MuirGlacierBlockNumber).WithTransactions(tx).WithGasLimit(2 * gasLimit).TestObject; - IntrinsicGas intrinsicGas = IntrinsicGasCalculator.Calculate(tx, MuirGlacier.Instance); + EthereumIntrinsicGas intrinsicGas = IntrinsicGasCalculator.Calculate(tx, MuirGlacier.Instance); GethLikeTxMemoryTracer gethTracer = new(tx, GethTraceOptions.Default); var blkCtx = new BlockExecutionContext(block.Header, _specProvider.GetSpec(block.Header)); @@ -447,7 +449,7 @@ public void Can_estimate_with_destroy_refund_and_below_intrinsic_pre_berlin() Block block = Build.A.Block.WithNumber(MainnetSpecProvider.MuirGlacierBlockNumber).WithTransactions(tx).WithGasLimit(2 * gasLimit).TestObject; IReleaseSpec releaseSpec = MuirGlacier.Instance; - IntrinsicGas intrinsicGas = IntrinsicGasCalculator.Calculate(tx, releaseSpec); + EthereumIntrinsicGas intrinsicGas = IntrinsicGasCalculator.Calculate(tx, releaseSpec); var blkCtx = new BlockExecutionContext(block.Header, releaseSpec); _transactionProcessor.Execute(initTx, blkCtx, NullTxTracer.Instance); @@ -515,7 +517,7 @@ public void Can_estimate_with_stipend() Block block = Build.A.Block.WithNumber(MainnetSpecProvider.MuirGlacierBlockNumber).WithTransactions(tx).WithGasLimit(2 * gasLimit).TestObject; IReleaseSpec releaseSpec = MuirGlacier.Instance; - IntrinsicGas intrinsicGas = IntrinsicGasCalculator.Calculate(tx, releaseSpec); + EthereumIntrinsicGas intrinsicGas = IntrinsicGasCalculator.Calculate(tx, releaseSpec); GethLikeTxMemoryTracer gethTracer = new(tx, GethTraceOptions.Default); var blkCtx = new BlockExecutionContext(block.Header, releaseSpec); @@ -559,7 +561,7 @@ public void Can_estimate_with_stipend_and_refund() Block block = Build.A.Block.WithNumber(MainnetSpecProvider.MuirGlacierBlockNumber).WithTransactions(tx).WithGasLimit(2 * gasLimit).TestObject; IReleaseSpec releaseSpec = _specProvider.GetSpec(block.Header); - IntrinsicGas intrinsicGas = IntrinsicGasCalculator.Calculate(tx, releaseSpec); + EthereumIntrinsicGas intrinsicGas = IntrinsicGasCalculator.Calculate(tx, releaseSpec); GethLikeTxMemoryTracer gethTracer = new(tx, GethTraceOptions.Default); var blkCtx = new BlockExecutionContext(block.Header, releaseSpec); @@ -601,7 +603,7 @@ public void Can_estimate_with_single_call() Block block = Build.A.Block.WithNumber(MainnetSpecProvider.MuirGlacierBlockNumber).WithTransactions(tx).WithGasLimit(2 * gasLimit).TestObject; IReleaseSpec releaseSpec = _specProvider.GetSpec(block.Header); - IntrinsicGas intrinsicGas = IntrinsicGasCalculator.Calculate(tx, releaseSpec); + EthereumIntrinsicGas intrinsicGas = IntrinsicGasCalculator.Calculate(tx, releaseSpec); var blkCtx = new BlockExecutionContext(block.Header, releaseSpec); _transactionProcessor.Execute(initTx, blkCtx, NullTxTracer.Instance); diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs index 357f86fe67c..2866322ce40 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionSelectorTests.cs @@ -25,7 +25,6 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Test; using Nethermind.Crypto; -using Nethermind.State; namespace Nethermind.Blockchain.Test { @@ -164,6 +163,7 @@ public static IEnumerable EnoughShardBlobTransactionsSelectedTestCases }); maxTransactionsSelected.Transactions[1].BlobVersionedHashes = new byte[maxTransactionsSelected.ReleaseSpec.MaxBlobCount - 1][]; + maxTransactionsSelected.Transactions[1].NetworkWrapper = new ShardBlobNetworkWrapper(new byte[5][], new byte[5][], new byte[5][], ProofVersion.V0); maxTransactionsSelected.ExpectedSelectedTransactions.AddRange( maxTransactionsSelected.Transactions.OrderBy(static t => t.Nonce).Take(2)); yield return new TestCaseData(maxTransactionsSelected).SetName("Enough transactions selected"); @@ -173,19 +173,17 @@ public static IEnumerable EnoughShardBlobTransactionsSelectedTestCases enoughTransactionsSelected.ReleaseSpec = Cancun.Instance; enoughTransactionsSelected.BaseFee = 1; + ulong maxBlobCount = enoughTransactionsSelected.ReleaseSpec.MaxBlobCount; Transaction[] expectedSelectedTransactions = enoughTransactionsSelected.Transactions.OrderBy(static t => t.Nonce).ToArray(); expectedSelectedTransactions[0].Type = TxType.Blob; - expectedSelectedTransactions[0].BlobVersionedHashes = - new byte[enoughTransactionsSelected.ReleaseSpec.MaxBlobCount][]; + expectedSelectedTransactions[0].BlobVersionedHashes = new byte[maxBlobCount][]; + expectedSelectedTransactions[0].NetworkWrapper = new ShardBlobNetworkWrapper(new byte[maxBlobCount][], new byte[maxBlobCount][], new byte[maxBlobCount][], ProofVersion.V0); expectedSelectedTransactions[0].MaxFeePerBlobGas = 1; - expectedSelectedTransactions[0].NetworkWrapper = - new ShardBlobNetworkWrapper(new byte[1][], new byte[1][], new byte[1][], ProofVersion.V0); expectedSelectedTransactions[1].Type = TxType.Blob; expectedSelectedTransactions[1].BlobVersionedHashes = new byte[1][]; + expectedSelectedTransactions[1].NetworkWrapper = new ShardBlobNetworkWrapper(new byte[1][], new byte[1][], new byte[1][], ProofVersion.V0); expectedSelectedTransactions[1].MaxFeePerBlobGas = 1; - expectedSelectedTransactions[1].NetworkWrapper = - new ShardBlobNetworkWrapper(new byte[1][], new byte[1][], new byte[1][], ProofVersion.V0); enoughTransactionsSelected.ExpectedSelectedTransactions.AddRange( expectedSelectedTransactions.Where(static (_, index) => index != 1)); yield return new TestCaseData(enoughTransactionsSelected).SetName( diff --git a/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs index 8e36c8c6f49..092cd49508c 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/TransactionsExecutorTests.cs @@ -364,7 +364,7 @@ public void BlockProductionTransactionsExecutor_calculates_block_size_using_prop } } - public class WorldStateStab() : WorldState(Substitute.For(), Substitute.For(), LimboLogs.Instance), IWorldState + public class WorldStateStab() : WorldState(Substitute.For(), LimboLogs.Instance), IWorldState { // we cannot mock ref methods // ref readonly UInt256 IWorldState.GetBalance(Address address) => ref UInt256.MaxValue; diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs index 9cb84ee9ce4..8dcb6a44720 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/TxValidatorTests.cs @@ -851,6 +851,20 @@ static TransactionBuilder MakeTestObject(int blobCount = 1) => Buil TestName = "Proofs count does not match hashes count", ExpectedResult = false }; + yield return new TestCaseData(Cancun.Instance, MakeTestObject() + .With(static tx => tx.NetworkWrapper = ((ShardBlobNetworkWrapper)tx.NetworkWrapper!) with { Blobs = [], Commitments = [], Proofs = [] }) + .SignedAndResolved().TestObject) + { + TestName = "Blobs count does not match network wrapper items counts, empty blobs", + ExpectedResult = false + }; + yield return new TestCaseData(Cancun.Instance, MakeTestObject(2) + .With(static tx => tx.BlobVersionedHashes = [.. tx.BlobVersionedHashes!.Take(1)]) + .SignedAndResolved().TestObject) + { + TestName = "Blobs count does not match network wrapper items counts, less hashes", + ExpectedResult = false + }; yield return new TestCaseData(Cancun.Instance, MakeTestObject() .With(static tx => ((ShardBlobNetworkWrapper)tx.NetworkWrapper!).Commitments[0][1] ^= 0xFF) .SignedAndResolved().TestObject) diff --git a/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs b/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs index cd97053ed70..06996065534 100644 --- a/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs +++ b/src/Nethermind/Nethermind.Blockchain.Test/Validators/UnclesValidatorTests.cs @@ -14,7 +14,7 @@ namespace Nethermind.Blockchain.Test.Validators; public class UnclesValidatorTests { - private Block _grandgrandparent; + private Block _greatGrandparent; private Block _grandparent; private Block _parent; private Block _block; @@ -27,9 +27,9 @@ public class UnclesValidatorTests public void Setup() { _blockTree = Build.A.BlockTree().OfChainLength(1).TestObject; - _grandgrandparent = _blockTree.FindBlock(0, BlockTreeLookupOptions.None)!; - _grandparent = Build.A.Block.WithParent(_grandgrandparent).TestObject; - _duplicateUncle = Build.A.Block.WithParent(_grandgrandparent).TestObject; + _greatGrandparent = _blockTree.FindBlock(0, BlockTreeLookupOptions.None)!; + _grandparent = Build.A.Block.WithParent(_greatGrandparent).TestObject; + _duplicateUncle = Build.A.Block.WithParent(_greatGrandparent).TestObject; _parent = Build.A.Block.WithParent(_grandparent).WithUncles(_duplicateUncle).TestObject; _block = Build.A.Block.WithParent(_parent).TestObject; @@ -142,7 +142,7 @@ public void Grandpas_brother_is_fine() { BlockHeader[] uncles = GetValidUncles(1); uncles[0].Number = _grandparent.Number; - uncles[0].ParentHash = _grandgrandparent.Hash; + uncles[0].ParentHash = _greatGrandparent.Hash; SetupHeaderValidator(uncles); UnclesValidator unclesValidator = new(_blockTree, _headerValidator, LimboLogs.Instance); diff --git a/src/Nethermind/Nethermind.Blockchain/BlockTree.Initializer.cs b/src/Nethermind/Nethermind.Blockchain/BlockTree.Initializer.cs index 5db48531903..7425bb3b789 100644 --- a/src/Nethermind/Nethermind.Blockchain/BlockTree.Initializer.cs +++ b/src/Nethermind/Nethermind.Blockchain/BlockTree.Initializer.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System; @@ -7,7 +7,6 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Db; -using Nethermind.Serialization.Json; using Nethermind.Serialization.Rlp; namespace Nethermind.Blockchain; @@ -111,7 +110,7 @@ private bool HeaderExists(long blockNumber, bool findBeacon = false) foreach (BlockInfo blockInfo in level.BlockInfos) { - BlockHeader? header = FindHeader(blockInfo.BlockHash, BlockTreeLookupOptions.None); + BlockHeader? header = FindHeader(blockInfo.BlockHash, BlockTreeLookupOptions.TotalDifficultyNotNeeded | BlockTreeLookupOptions.DoNotCreateLevelIfMissing); if (header is not null) { if (findBeacon && blockInfo.IsBeaconHeader) @@ -139,7 +138,7 @@ private bool BodyExists(long blockNumber, bool findBeacon = false) foreach (BlockInfo blockInfo in level.BlockInfos) { - Block? block = FindBlock(blockInfo.BlockHash, BlockTreeLookupOptions.None); + Block? block = FindBlock(blockInfo.BlockHash, BlockTreeLookupOptions.TotalDifficultyNotNeeded | BlockTreeLookupOptions.DoNotCreateLevelIfMissing); if (block is not null) { if (findBeacon && blockInfo.IsBeaconBody) @@ -373,7 +372,7 @@ private void LoadSyncPivot() byte[]? pivotFromDb = _metadataDb.Get(MetadataDbKeys.UpdatedPivotData); if (pivotFromDb is null) { - _syncPivot = (LongConverter.FromString(_syncConfig.PivotNumber), _syncConfig.PivotHash is null ? null : new Hash256(Bytes.FromHexString(_syncConfig.PivotHash))); + _syncPivot = (_syncConfig.PivotNumber, _syncConfig.PivotHash is null ? null : new Hash256(Bytes.FromHexString(_syncConfig.PivotHash))); return; } @@ -383,12 +382,12 @@ private void LoadSyncPivot() if (updatedPivotBlockHash.IsZero) { - _syncPivot = (LongConverter.FromString(_syncConfig.PivotNumber), _syncConfig.PivotHash is null ? null : new Hash256(Bytes.FromHexString(_syncConfig.PivotHash))); + _syncPivot = (_syncConfig.PivotNumber, _syncConfig.PivotHash is null ? null : new Hash256(Bytes.FromHexString(_syncConfig.PivotHash))); return; } SyncPivot = (updatedPivotBlockNumber, updatedPivotBlockHash); - _syncConfig.MaxAttemptsToUpdatePivot = 0; // Disable pivot updator + _syncConfig.MaxAttemptsToUpdatePivot = 0; // Disable pivot updater if (Logger.IsInfo) Logger.Info($"Pivot block has been set based on data from db. Pivot block number: {updatedPivotBlockNumber}, hash: {updatedPivotBlockHash}"); } diff --git a/src/Nethermind/Nethermind.Blockchain/BlockTree.cs b/src/Nethermind/Nethermind.Blockchain/BlockTree.cs index e5979aad27e..aa65922ee9e 100644 --- a/src/Nethermind/Nethermind.Blockchain/BlockTree.cs +++ b/src/Nethermind/Nethermind.Blockchain/BlockTree.cs @@ -50,7 +50,7 @@ public partial class BlockTree : IBlockTree new(128, 128, "invalid blocks"); protected readonly ILogger Logger; - private readonly ISpecProvider _specProvider; + protected readonly ISpecProvider SpecProvider; private readonly IBloomStorage _bloomStorage; private readonly ISyncConfig _syncConfig; private readonly IChainLevelInfoRepository _chainLevelInfoRepository; @@ -95,9 +95,9 @@ public BlockHeader? LowestInsertedBeaconHeader public long BestKnownBeaconNumber { get; private set; } - public ulong NetworkId => _specProvider.NetworkId; + public ulong NetworkId => SpecProvider.NetworkId; - public ulong ChainId => _specProvider.ChainId; + public ulong ChainId => SpecProvider.ChainId; private int _canAcceptNewBlocksCounter; public bool CanAcceptNewBlocks => _canAcceptNewBlocksCounter == 0; @@ -129,7 +129,7 @@ public BlockTree( _metadataDb = metadataDb ?? throw new ArgumentNullException(nameof(metadataDb)); _badBlockStore = badBlockStore ?? throw new ArgumentNullException(nameof(badBlockStore)); _balStore = balStore ?? throw new ArgumentNullException(nameof(balStore)); - _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); + SpecProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); _bloomStorage = bloomStorage ?? throw new ArgumentNullException(nameof(bloomStorage)); _syncConfig = syncConfig ?? throw new ArgumentNullException(nameof(syncConfig)); _chainLevelInfoRepository = chainLevelInfoRepository ?? @@ -788,7 +788,7 @@ as it does not require the step of resolving number -> hash */ for (int i = 0; i < level.BlockInfos.Length; i++) { BlockInfo current = level.BlockInfos[i]; - if (level.BlockInfos[i].TotalDifficulty >= bestDifficultySoFar) + if (current.TotalDifficulty >= bestDifficultySoFar) { bestDifficultySoFar = current.TotalDifficulty; bestHash = current.BlockHash; @@ -1078,7 +1078,7 @@ private void TryUpdateSyncPivot() if (bestPersisted < newPivotHeader.Number) { - if (Logger.IsTrace) Logger.Trace("Best persisted is lower than sync pivot. Using best persisted stata as pivot."); + if (Logger.IsTrace) Logger.Trace("Best persisted is lower than sync pivot. Using best persisted state as pivot."); newPivotHeader = FindHeader(bestPersisted.Value, BlockTreeLookupOptions.RequireCanonical); } if (newPivotHeader is null) return; @@ -1153,7 +1153,7 @@ public void UpdateBeaconMainChain(BlockInfo[]? blockInfos, long clearBeaconMainC public bool IsBetterThanHead(BlockHeader? header) => header is not null // null is never better && ((header.IsGenesis && Genesis is null) // is genesis - || header.TotalDifficulty >= _specProvider.TerminalTotalDifficulty // is post-merge block, we follow engine API + || header.TotalDifficulty >= SpecProvider.TerminalTotalDifficulty // is post-merge block, we follow engine API || header.TotalDifficulty > (Head?.TotalDifficulty ?? 0) // pre-merge rules || (header.TotalDifficulty == Head?.TotalDifficulty // when in doubt on difficulty && ((Head?.Number ?? 0L).CompareTo(header.Number) > 0 // pick longer chain @@ -1231,15 +1231,15 @@ protected virtual bool HeadImprovementRequirementsSatisfied(BlockHeader header) // before merge TD requirements are satisfied only if TD > block head bool preMergeImprovementRequirementSatisfied = header.TotalDifficulty > (Head?.TotalDifficulty ?? 0) && (header.TotalDifficulty < - _specProvider.TerminalTotalDifficulty - || _specProvider.TerminalTotalDifficulty is null); + SpecProvider.TerminalTotalDifficulty + || SpecProvider.TerminalTotalDifficulty is null); // after the merge, we will accept only the blocks with Difficulty = 0. However, during the transition process // we can have terminal PoW blocks with Difficulty > 0. That is why we accept everything greater or equal // than current head and header.TD >= TTD. - bool postMergeImprovementRequirementSatisfied = _specProvider.TerminalTotalDifficulty is not null && + bool postMergeImprovementRequirementSatisfied = SpecProvider.TerminalTotalDifficulty is not null && header.TotalDifficulty >= - _specProvider.TerminalTotalDifficulty; + SpecProvider.TerminalTotalDifficulty; return preMergeImprovementRequirementSatisfied || postMergeImprovementRequirementSatisfied; } @@ -1247,11 +1247,11 @@ private bool BestSuggestedImprovementRequirementsSatisfied(BlockHeader header) { if (BestSuggestedHeader is null) return true; - bool reachedTtd = header.IsPostTTD(_specProvider); + bool reachedTtd = header.IsPostTTD(SpecProvider); bool isPostMerge = header.IsPoS(); bool tdImproved = header.TotalDifficulty > (BestSuggestedBody?.TotalDifficulty ?? 0); bool preMergeImprovementRequirementSatisfied = tdImproved && !reachedTtd; - bool terminalBlockRequirementSatisfied = tdImproved && reachedTtd && header.IsTerminalBlock(_specProvider) && !Head.IsPoS(); + bool terminalBlockRequirementSatisfied = tdImproved && reachedTtd && header.IsTerminalBlock(SpecProvider) && !Head.IsPoS(); bool postMergeImprovementRequirementSatisfied = reachedTtd && (BestSuggestedBody?.Number ?? 0) <= header.Number && isPostMerge; return preMergeImprovementRequirementSatisfied || terminalBlockRequirementSatisfied || postMergeImprovementRequirementSatisfied; @@ -1519,7 +1519,7 @@ private bool IsTotalDifficultyAlwaysZero() { // In some Ethereum tests and possible testnets difficulty of all blocks might be zero // We also checking TTD is zero to ensure that block after genesis have zero difficulty - return Genesis?.Difficulty == 0 && _specProvider.TerminalTotalDifficulty == 0; + return Genesis?.Difficulty == 0 && SpecProvider.TerminalTotalDifficulty == 0; } private void SetTotalDifficultyFromBlockInfo(BlockHeader header, BlockInfo blockInfo) diff --git a/src/Nethermind/Nethermind.Blockchain/BlockhashCache.cs b/src/Nethermind/Nethermind.Blockchain/BlockhashCache.cs index 91e10d97cc2..2147e771fd9 100644 --- a/src/Nethermind/Nethermind.Blockchain/BlockhashCache.cs +++ b/src/Nethermind/Nethermind.Blockchain/BlockhashCache.cs @@ -31,7 +31,7 @@ public class BlockhashCache(IHeaderFinder headerFinder, ILogManager logManager) depth == 0 ? headBlock.Hash : depth == 1 ? headBlock.ParentHash : depth > MaxDepth ? null - : _flatCache.TryGet(headBlock.ParentHash!, out Hash256[] array) ? array[depth - 1] + : _flatCache.TryGet(headBlock.ParentHash!, out Hash256[] array) ? array[depth - 2] : Load(headBlock, depth, out _)?.Hash; private CacheNode? Load(BlockHeader blockHeader, int depth, out Hash256[]? hashes, CancellationToken cancellationToken = default) @@ -58,8 +58,7 @@ public class BlockhashCache(IHeaderFinder headerFinder, ILogManager logManager) } currentNode = new CacheNode(currentHeader); - needToAdd = true; - needToAddAny = true; + needToAddAny |= needToAdd = currentHeader.Hash is not null; } } @@ -101,14 +100,19 @@ public class BlockhashCache(IHeaderFinder headerFinder, ILogManager logManager) } } - if (blocks.Count == FlatCacheLength(blockHeader)) + int ancestorCount = blocks.Count - 1; + if (ancestorCount == FlatCacheLength(blockHeader)) { - hashes = new Hash256[blocks.Count]; - for (int i = 0; i < blocks.Count; i++) + hashes = new Hash256[ancestorCount]; + for (int i = 1; i < blocks.Count; i++) { - hashes[i] = blocks[i].Node.Hash; + hashes[i - 1] = blocks[i].Node.Hash; + } + + if (blockHeader.Hash is not null) + { + _flatCache.Set(blockHeader.Hash, hashes); } - _flatCache.Set(blockHeader.Hash, hashes); } int index = depth - skipped; @@ -118,7 +122,7 @@ public class BlockhashCache(IHeaderFinder headerFinder, ILogManager logManager) : null; } - private static int FlatCacheLength(BlockHeader blockHeader) => (int)(Math.Min(MaxDepth, blockHeader.Number) + 1); + private static int FlatCacheLength(BlockHeader blockHeader) => (int)Math.Min(MaxDepth, blockHeader.Number); public Task Prefetch(BlockHeader blockHeader, CancellationToken cancellationToken = default) { @@ -129,15 +133,20 @@ public class BlockhashCache(IHeaderFinder headerFinder, ILogManager logManager) { if (!cancellationToken.IsCancellationRequested) { - if (!_flatCache.TryGet(blockHeader.Hash, out hashes)) + bool emptyHash = blockHeader.Hash is null; + + if (emptyHash || !_flatCache.TryGet(blockHeader.Hash, out hashes)) { if (_flatCache.TryGet(blockHeader.ParentHash, out Hash256[] parentHashes)) { int length = FlatCacheLength(blockHeader); hashes = new Hash256[length]; - hashes[0] = blockHeader.Hash; - Array.Copy(parentHashes, 0, hashes, 1, Math.Min(length - 1, MaxDepth)); - _flatCache.Set(blockHeader.Hash, hashes); + hashes[0] = blockHeader.ParentHash; + Array.Copy(parentHashes, 0, hashes, 1, length - 1); + if (!emptyHash) + { + _flatCache.Set(blockHeader.Hash, hashes); + } } else { diff --git a/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs b/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs index 513df956658..ea638750cea 100644 --- a/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs +++ b/src/Nethermind/Nethermind.Blockchain/BlockhashProvider.cs @@ -28,24 +28,34 @@ public class BlockhashProvider( public Hash256? GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec spec) { - if (spec.IsBlockHashInStateAvailable) + if (number < 0) { - return _blockhashStore.GetBlockHashFromState(currentBlock, number, spec); + return ReturnOutOfBounds(currentBlock, number); } - long current = currentBlock.Number; - long depth = current - number; - if (number >= current || number < 0 || depth > MaxDepth) + if (spec.IsBlockHashInStateAvailable) { - if (_logger.IsTrace) _logger.Trace($"BLOCKHASH opcode returning null for {currentBlock.Number} -> {number}"); - return null; + return _blockhashStore.GetBlockHashFromState(currentBlock, number, spec); } + long depth = currentBlock.Number - number; Hash256[]? hashes = _hashes; - return hashes is not null - ? hashes[depth] - : blockhashCache.GetHash(currentBlock, (int)depth) - ?? throw new InvalidDataException("Hash cannot be found when executing BLOCKHASH operation"); + + return depth switch + { + <= 0 or > MaxDepth => ReturnOutOfBounds(currentBlock, number), + 1 => currentBlock.ParentHash, + _ => hashes is not null + ? hashes[depth - 1] + : blockhashCache.GetHash(currentBlock, (int)depth) + ?? throw new InvalidDataException("Hash cannot be found when executing BLOCKHASH operation") + }; + } + + private Hash256? ReturnOutOfBounds(BlockHeader currentBlock, long number) + { + if (_logger.IsTrace) _logger.Trace($"BLOCKHASH opcode returning null for {currentBlock.Number} -> {number}"); + return null; } public async Task Prefetch(BlockHeader currentBlock, CancellationToken token) diff --git a/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs b/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs index 9da126d68fa..59b11b6e67c 100644 --- a/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs +++ b/src/Nethermind/Nethermind.Blockchain/CachedCodeInfoRepository.cs @@ -68,10 +68,6 @@ private class CachedPrecompile( IPrecompile precompile, ConcurrentDictionary> cache) : IPrecompile { - public static Address Address => Address.Zero; - - public static string Name => ""; - public long BaseGasCost(IReleaseSpec releaseSpec) => precompile.BaseGasCost(releaseSpec); public long DataGasCost(ReadOnlyMemory inputData, IReleaseSpec releaseSpec) => precompile.DataGasCost(inputData, releaseSpec); diff --git a/src/Nethermind/Nethermind.Blockchain/Data/JSTracers/prestateTracer_legacy.js b/src/Nethermind/Nethermind.Blockchain/Data/JSTracers/prestateTracer_legacy.js index 3d293fd14dd..24a92d6ab9e 100644 --- a/src/Nethermind/Nethermind.Blockchain/Data/JSTracers/prestateTracer_legacy.js +++ b/src/Nethermind/Nethermind.Blockchain/Data/JSTracers/prestateTracer_legacy.js @@ -67,7 +67,7 @@ // Decrement the caller's nonce, and remove empty create targets this.prestate[toHex(ctx.from)].nonce--; if (ctx.type == 'CREATE') { - // We can blibdly delete the contract prestate, as any existing state would + // We can blindly delete the contract prestate, as any existing state would // have caused the transaction to be rejected as invalid in the first place. delete this.prestate[toHex(ctx.to)]; } diff --git a/src/Nethermind/Nethermind.Blockchain/Find/BlockParameter.cs b/src/Nethermind/Nethermind.Blockchain/Find/BlockParameter.cs index 7d5f4735021..884febc8cc8 100644 --- a/src/Nethermind/Nethermind.Blockchain/Find/BlockParameter.cs +++ b/src/Nethermind/Nethermind.Blockchain/Find/BlockParameter.cs @@ -8,9 +8,9 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using System.Text.Json.Serialization; - +using System.Text; using System.Text.Json; +using System.Text.Json.Serialization; using Nethermind.Core.Crypto; using Nethermind.Serialization.Json; @@ -45,6 +45,7 @@ public BlockParameter(BlockParameterType type) public BlockParameter(long number) { + RequireCanonical = true; Type = BlockParameterType.BlockNumber; BlockNumber = number; } @@ -136,11 +137,14 @@ public override void Write(Utf8JsonWriter writer, BlockParameter value, JsonSeri { return reader.TokenType switch { - JsonTokenType.String when (reader.HasValueSequence ? reader.ValueSequence.Length : reader.ValueSpan.Length) > 66 => JsonSerializer.Deserialize(reader.GetString()!, options), + JsonTokenType.String => !reader.HasValueSequence ? + reader.ValueSpan.Length <= 66 ? + ReadStringFormat(reader.ValueSpan) : + ReadStringComplex(ref reader, options) : + ReadStringFormatValueSequence(ref reader, options), JsonTokenType.StartObject => ReadObjectFormat(ref reader, typeToConvert, options), JsonTokenType.Null => BlockParameter.Latest, JsonTokenType.Number when !EthereumJsonSerializer.StrictHexFormat => new BlockParameter(reader.GetInt64()), - JsonTokenType.String => ReadStringFormat(ref reader), _ => throw new FormatException("unknown block parameter type") }; } @@ -181,28 +185,62 @@ private BlockParameter ReadObjectFormat(ref Utf8JsonReader reader, Type typeToCo }; } - private BlockParameter ReadStringFormat(ref Utf8JsonReader reader) + private static BlockParameter ReadStringFormat(ReadOnlySpan span) { - // Check for known string values first (fast path) - BlockParameter? knownValue = reader switch + int length = span.Length; + // Creates a jmp table based on length + switch (length) { - _ when reader.ValueTextEquals(ReadOnlySpan.Empty) || reader.ValueTextEquals("latest"u8) => BlockParameter.Latest, - _ when reader.ValueTextEquals("earliest"u8) => BlockParameter.Earliest, - _ when reader.ValueTextEquals("pending"u8) => BlockParameter.Pending, - _ when reader.ValueTextEquals("finalized"u8) => BlockParameter.Finalized, - _ when reader.ValueTextEquals("safe"u8) => BlockParameter.Safe, - _ => null - }; + case 0: + // Empty string => latest + return BlockParameter.Latest; + case 4: + if (Ascii.EqualsIgnoreCase(span, "safe"u8)) + return BlockParameter.Safe; + break; + case 6: + if (Ascii.EqualsIgnoreCase(span, "latest"u8)) + return BlockParameter.Latest; + break; + case 7: + if (Ascii.EqualsIgnoreCase(span, "pending"u8)) + return BlockParameter.Pending; + break; + case 8: + if (Ascii.EqualsIgnoreCase(span, "earliest"u8)) + return BlockParameter.Earliest; + break; + case 9: + if (Ascii.EqualsIgnoreCase(span, "finalized"u8)) + return BlockParameter.Finalized; + break; + } + + // Unknown tag or 0x quantity etc + return ReadStringFormatOther(span); + } - if (knownValue is not null) + [SkipLocalsInit] + private static BlockParameter ReadStringFormatValueSequence(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + if (reader.ValueSequence.Length > 66) { - return knownValue; + return ReadStringComplex(ref reader, options); } Span span = stackalloc byte[66]; int hexLength = reader.CopyString(span); span = span[..hexLength]; + return ReadStringFormat(span); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static BlockParameter ReadStringComplex(ref Utf8JsonReader reader, JsonSerializerOptions options) + => JsonSerializer.Deserialize(reader.GetString()!, options)!; + + private static BlockParameter ReadStringFormatOther(ReadOnlySpan span) + { // Try hex format if (span.Length >= 2 && span.StartsWith("0x"u8)) { @@ -226,19 +264,12 @@ _ when reader.ValueTextEquals("safe"u8) => BlockParameter.Safe, return new BlockParameter(decimalValue); } - // Try case-insensitive string match - BlockParameter? result = TryMatchCaseInsensitive(span); - if (result is not null) - { - return result; - } - ThrowInvalidFormatting(); return null; } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static long ParseHexNumber(Span span) + private static long ParseHexNumber(ReadOnlySpan span) { int oddMod = span.Length % 2; int length = (span.Length >> 1) + oddMod; @@ -254,35 +285,9 @@ private static long ParseHexNumber(Span span) }; } - [DoesNotReturn] - [StackTraceHidden] + [DoesNotReturn, StackTraceHidden] private static void ThrowInvalidFormatting() - { - throw new FormatException("unknown block parameter type"); - } - - private static BlockParameter? TryMatchCaseInsensitive(Span span) - { - if (span.Length > 10) return null; // Longest keyword is "finalized" (9 chars) - - Span lower = stackalloc byte[span.Length]; - for (int i = 0; i < span.Length; i++) - { - byte ch = span[i]; - lower[i] = (ch >= 'A' && ch <= 'Z') ? (byte)(ch + 32) : ch; - } - - return lower switch - { - _ when lower.SequenceEqual("latest"u8) => BlockParameter.Latest, - _ when lower.SequenceEqual("earliest"u8) => BlockParameter.Earliest, - _ when lower.SequenceEqual("pending"u8) => BlockParameter.Pending, - _ when lower.SequenceEqual("finalized"u8) => BlockParameter.Finalized, - _ when lower.SequenceEqual("safe"u8) => BlockParameter.Safe, - _ => null - }; - } - + => throw new FormatException("unknown block parameter type"); public static BlockParameter GetBlockParameter(string? value) { diff --git a/src/Nethermind/Nethermind.Blockchain/FullPruning/CompositePruningTrigger.cs b/src/Nethermind/Nethermind.Blockchain/FullPruning/CompositePruningTrigger.cs index 49f20bb7e2e..8ae718f97ee 100644 --- a/src/Nethermind/Nethermind.Blockchain/FullPruning/CompositePruningTrigger.cs +++ b/src/Nethermind/Nethermind.Blockchain/FullPruning/CompositePruningTrigger.cs @@ -2,20 +2,24 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; namespace Nethermind.Blockchain.FullPruning; /// /// Allows to have multiple s. /// -public class CompositePruningTrigger : IPruningTrigger +public class CompositePruningTrigger : IPruningTrigger, IDisposable { + private readonly List _triggers = []; + /// - /// Adds new to the be watched."/> + /// Adds new to be watched. /// /// trigger to be watched public void Add(IPruningTrigger trigger) { + _triggers.Add(trigger); trigger.Prune += OnPrune; } @@ -24,6 +28,19 @@ private void OnPrune(object? sender, PruningTriggerEventArgs e) Prune?.Invoke(sender, e); } - /// + /// public event EventHandler? Prune; + + public void Dispose() + { + foreach (IPruningTrigger trigger in _triggers) + { + trigger.Prune -= OnPrune; + if (trigger is IDisposable d) + { + d.Dispose(); + } + } + _triggers.Clear(); + } } diff --git a/src/Nethermind/Nethermind.Blockchain/FullPruning/CopyTreeVisitor.cs b/src/Nethermind/Nethermind.Blockchain/FullPruning/CopyTreeVisitor.cs index c2afa4cf718..82cb1e9b06b 100644 --- a/src/Nethermind/Nethermind.Blockchain/FullPruning/CopyTreeVisitor.cs +++ b/src/Nethermind/Nethermind.Blockchain/FullPruning/CopyTreeVisitor.cs @@ -24,12 +24,12 @@ public class CopyTreeVisitor : ICopyTreeVisitor, ITreeVisitor true; @@ -68,36 +69,33 @@ public void VisitMissingNode(in TContext ctx, in ValueHash256 nodeHash) throw new TrieException($"Trie {nodeHash} missing"); } - public void VisitBranch(in TContext ctx, TrieNode node) => PersistNode(ctx.Storage, ctx.Path, node); + public void VisitBranch(in TContext ctx, TrieNode node) + { + PersistNode(ctx.Storage, ctx.Path, node, isLeaf: false); + } - public void VisitExtension(in TContext ctx, TrieNode node) => PersistNode(ctx.Storage, ctx.Path, node); + public void VisitExtension(in TContext ctx, TrieNode node) + { + PersistNode(ctx.Storage, ctx.Path, node, isLeaf: false); + } - public void VisitLeaf(in TContext ctx, TrieNode node) => PersistNode(ctx.Storage, ctx.Path, node); + public void VisitLeaf(in TContext ctx, TrieNode node) + { + PersistNode(ctx.Storage, ctx.Path, node, isLeaf: true); + } public void VisitAccount(in TContext ctx, TrieNode node, in AccountStruct account) { } - private void PersistNode(Hash256 storage, in TreePath path, TrieNode node) + private void PersistNode(Hash256? storage, in TreePath path, TrieNode node, bool isLeaf) { if (node.Keccak is not null) { // simple copy of nodes RLP _concurrentWriteBatcher.Set(storage, path, node.Keccak, node.FullRlp.Span, _writeFlags); - Interlocked.Increment(ref _persistedNodes); - - // log message every 1 mln nodes - if (_persistedNodes % Million == 0) - { - LogProgress("In Progress"); - } + _progressTracker.OnNodeVisited(path, isStorage: storage is not null, isLeaf); } } - private void LogProgress(string state) - { - if (_logger.IsInfo) - _logger.Info($"Full Pruning {state}: {_stopwatch.Elapsed} {_persistedNodes / (double)Million:N} mln nodes mirrored."); - } - public void Dispose() { if (_logger.IsWarn && !_finished) @@ -109,7 +107,9 @@ public void Dispose() public void Finish() { _finished = true; - LogProgress("Finished"); + _progressTracker.Finish(); + if (_logger.IsInfo) + _logger.Info($"Full Pruning Finished: {_stopwatch.Elapsed} {_progressTracker.NodeCount / (double)Million:N} mln nodes mirrored."); _concurrentWriteBatcher.Dispose(); } } diff --git a/src/Nethermind/Nethermind.Blockchain/FullPruning/FullPruner.cs b/src/Nethermind/Nethermind.Blockchain/FullPruning/FullPruner.cs index 01ee4f9cde9..89152d3de30 100644 --- a/src/Nethermind/Nethermind.Blockchain/FullPruning/FullPruner.cs +++ b/src/Nethermind/Nethermind.Blockchain/FullPruning/FullPruner.cs @@ -180,7 +180,7 @@ await WaitForMainChainChange((e) => } if (_logger.IsInfo) _logger.Info($"Full Pruning Ready to start: pruning garbage before state {stateToCopy} with root {header.StateRoot}"); - await CopyTrie(pruningContext, header.StateRoot!, cancellationToken); + await CopyTrie(pruningContext, header, cancellationToken); } private bool CanStartNewPruning() => _fullPruningDb.CanStartPruning; @@ -220,7 +220,7 @@ private void HandlePruningFinished(object? sender, PruningEventArgs e) } } - private Task CopyTrie(IPruningContext pruning, Hash256 stateRoot, CancellationToken cancellationToken) + private Task CopyTrie(IPruningContext pruning, BlockHeader? baseBlock, CancellationToken cancellationToken) { INodeStorage.KeyScheme originalKeyScheme = _nodeStorage.Scheme; ICopyTreeVisitor visitor = null; @@ -265,8 +265,8 @@ private Task CopyTrie(IPruningContext pruning, Hash256 stateRoot, CancellationTo if (_logger.IsInfo) _logger.Info($"Full pruning started with MaxDegreeOfParallelism: {visitingOptions.MaxDegreeOfParallelism} and FullScanMemoryBudget: {visitingOptions.FullScanMemoryBudget}"); visitor = targetNodeStorage.Scheme == INodeStorage.KeyScheme.Hash - ? CopyTree(stateRoot, targetNodeStorage, writeFlags, visitingOptions, cancellationToken) - : CopyTree(stateRoot, targetNodeStorage, writeFlags, visitingOptions, cancellationToken); + ? CopyTree(baseBlock, targetNodeStorage, writeFlags, visitingOptions, cancellationToken) + : CopyTree(baseBlock, targetNodeStorage, writeFlags, visitingOptions, cancellationToken); if (!cancellationToken.IsCancellationRequested) { @@ -296,7 +296,7 @@ private Task CopyTrie(IPruningContext pruning, Hash256 stateRoot, CancellationTo } private ICopyTreeVisitor CopyTree( - Hash256 stateRoot, + BlockHeader? baseBlock, INodeStorage targetNodeStorage, WriteFlags writeFlags, VisitingOptions visitingOptions, @@ -304,7 +304,7 @@ CancellationToken cancellationToken ) where TContext : struct, ITreePathContextWithStorage, INodeContext { CopyTreeVisitor copyTreeVisitor = new(targetNodeStorage, writeFlags, _logManager, cancellationToken); - _stateReader.RunTreeVisitor(copyTreeVisitor, stateRoot, visitingOptions); + _stateReader.RunTreeVisitor(copyTreeVisitor, baseBlock, visitingOptions); return copyTreeVisitor; } diff --git a/src/Nethermind/Nethermind.Blockchain/Metrics.cs b/src/Nethermind/Nethermind.Blockchain/Metrics.cs index 91864c136fb..648eac7402c 100644 --- a/src/Nethermind/Nethermind.Blockchain/Metrics.cs +++ b/src/Nethermind/Nethermind.Blockchain/Metrics.cs @@ -104,6 +104,6 @@ public static class Metrics [DetailedMetric] [ExponentialPowerHistogramMetric(Start = 100, Factor = 1.25, Count = 50)] - [Description("Histogram of block prorcessing time")] + [Description("Histogram of block processing time")] public static IMetricObserver BlockProcessingTimeMicros { get; set; } = new NoopMetricObserver(); } diff --git a/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs b/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs index 5f2cbd74005..2db303f9dd8 100644 --- a/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs +++ b/src/Nethermind/Nethermind.Blockchain/Receipts/PersistentReceiptStorage.cs @@ -76,7 +76,7 @@ private void BlockTreeOnBlockAddedToMain(object? sender, BlockReplacementEventAr EnsureCanonical(e.Block); NewCanonicalReceipts?.Invoke(this, e); - // Dont block main loop + // Don't block the main loop Task.Run(() => { Block newMain = e.Block; @@ -223,7 +223,12 @@ public bool TryGetReceiptsIterator(long blockNumber, Hash256 blockHash, out Rece return true; } - var result = CanGetReceiptsByHash(blockNumber); + if (!CanGetReceiptsByHash(blockNumber)) + { + iterator = new ReceiptsIterator(); + return false; + } + Span receiptsData = GetReceiptData(blockNumber, blockHash); Func recoveryContextFactory = () => null; @@ -245,8 +250,8 @@ public bool TryGetReceiptsIterator(long blockNumber, Hash256 blockHash, out Rece IReceiptRefDecoder refDecoder = _storageDecoder.GetRefDecoder(receiptsData); - iterator = result ? new ReceiptsIterator(receiptsData, _receiptsDb, recoveryContextFactory, refDecoder) : new ReceiptsIterator(); - return result; + iterator = new ReceiptsIterator(receiptsData, _receiptsDb, recoveryContextFactory, refDecoder); + return true; } public void Insert(Block block, TxReceipt[]? txReceipts, bool ensureCanonical = true, WriteFlags writeFlags = WriteFlags.None, long? lastBlockNumber = null) diff --git a/src/Nethermind/Nethermind.Blockchain/ReorgDepthFinalizedStateProvider.cs b/src/Nethermind/Nethermind.Blockchain/ReorgDepthFinalizedStateProvider.cs index abe22d32a00..fda29ae0910 100644 --- a/src/Nethermind/Nethermind.Blockchain/ReorgDepthFinalizedStateProvider.cs +++ b/src/Nethermind/Nethermind.Blockchain/ReorgDepthFinalizedStateProvider.cs @@ -4,12 +4,14 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Trie.Pruning; +using System; namespace Nethermind.Blockchain; public class ReorgDepthFinalizedStateProvider(IBlockTree blockTree) : IFinalizedStateProvider { - public long FinalizedBlockNumber => blockTree.BestKnownNumber - Reorganization.MaxDepth; + public long FinalizedBlockNumber => Math.Max(0, blockTree.BestKnownNumber - Reorganization.MaxDepth); + public Hash256? GetFinalizedStateRootAt(long blockNumber) { if (FinalizedBlockNumber < blockNumber) return null; diff --git a/src/Nethermind/Nethermind.Blockchain/Spec/ChainHeadSpecProvider.cs b/src/Nethermind/Nethermind.Blockchain/Spec/ChainHeadSpecProvider.cs index b9ddc9f9ae6..7530d72461b 100644 --- a/src/Nethermind/Nethermind.Blockchain/Spec/ChainHeadSpecProvider.cs +++ b/src/Nethermind/Nethermind.Blockchain/Spec/ChainHeadSpecProvider.cs @@ -31,7 +31,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public IReleaseSpec GenesisSpec => _specProvider.GenesisSpec; - IReleaseSpec ISpecProvider.GetSpecInternal(ForkActivation forkActivation) => _specProvider.GetSpec(forkActivation); + public IReleaseSpec GetSpec(ForkActivation forkActivation) => _specProvider.GetSpec(forkActivation); public long? DaoBlockNumber => _specProvider.DaoBlockNumber; diff --git a/src/Nethermind/Nethermind.Blockchain/SpecificBlockReadOnlyStateProvider.cs b/src/Nethermind/Nethermind.Blockchain/SpecificBlockReadOnlyStateProvider.cs index 9fe0a072ecf..68ae8474ee4 100644 --- a/src/Nethermind/Nethermind.Blockchain/SpecificBlockReadOnlyStateProvider.cs +++ b/src/Nethermind/Nethermind.Blockchain/SpecificBlockReadOnlyStateProvider.cs @@ -31,18 +31,8 @@ public class SpecificBlockReadOnlyStateProvider(IStateReader stateReader, BlockH public byte[]? GetCode(in ValueHash256 codeHash) => _stateReader.GetCode(in codeHash); - public void Accept(ITreeVisitor visitor, Hash256 stateRoot, VisitingOptions? visitingOptions) where TCtx : struct, INodeContext - { - _stateReader.RunTreeVisitor(visitor, stateRoot, visitingOptions); - } - public bool AccountExists(Address address) => _stateReader.TryGetAccount(BaseBlock, address, out _); - [SkipLocalsInit] - public bool IsEmptyAccount(Address address) => TryGetAccount(address, out AccountStruct account) && account.IsEmpty; - - public bool HasStateForBlock(BlockHeader? header) => _stateReader.HasStateForBlock(header); - [SkipLocalsInit] public bool IsDeadAccount(Address address) => !TryGetAccount(address, out AccountStruct account) || account.IsEmpty; } diff --git a/src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncConfig.cs b/src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncConfig.cs index 45871f73fa1..acd70b687a4 100644 --- a/src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncConfig.cs +++ b/src/Nethermind/Nethermind.Blockchain/Synchronization/ISyncConfig.cs @@ -1,13 +1,10 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System; using Nethermind.Config; -using Nethermind.Core.Crypto; -using Nethermind.Core.Extensions; using Nethermind.Db; using Nethermind.Int256; -using Nethermind.Serialization.Json; namespace Nethermind.Blockchain.Synchronization; @@ -48,14 +45,11 @@ public interface ISyncConfig : IConfig string PivotTotalDifficulty { get; } [ConfigItem(Description = "The number of the pivot block for the Fast sync mode.", DefaultValue = "0")] - string PivotNumber { get; set; } + long PivotNumber { get; set; } [ConfigItem(Description = "The hash of the pivot block for the Fast sync mode.", DefaultValue = "null")] string? PivotHash { get; set; } - [ConfigItem(DisabledForCli = true, HiddenFromDocs = true, DefaultValue = "0")] - private long PivotNumberParsed => LongConverter.FromString(PivotNumber); - [ConfigItem(DisabledForCli = true, HiddenFromDocs = true, DefaultValue = "0")] UInt256 PivotTotalDifficultyParsed => UInt256.Parse(PivotTotalDifficulty ?? "0"); @@ -74,7 +68,7 @@ public interface ISyncConfig : IConfig public long AncientBodiesBarrier { get; set; } [ConfigItem(DisabledForCli = true, HiddenFromDocs = true, DefaultValue = "1")] - public long AncientBodiesBarrierCalc => Math.Max(1, Math.Min(PivotNumberParsed, AncientBodiesBarrier)); + public long AncientBodiesBarrierCalc => Math.Max(1, Math.Min(PivotNumber, AncientBodiesBarrier)); [ConfigItem(Description = $$""" The earliest receipt downloaded with fast sync when `{{nameof(DownloadReceiptsInFastSync)}}` is set to `true`. The actual value is determined as follows: @@ -88,7 +82,7 @@ public interface ISyncConfig : IConfig public long AncientReceiptsBarrier { get; set; } [ConfigItem(DisabledForCli = true, HiddenFromDocs = true, DefaultValue = "1")] - public long AncientReceiptsBarrierCalc => Math.Max(1, Math.Min(PivotNumberParsed, Math.Max(AncientBodiesBarrier, AncientReceiptsBarrier))); + public long AncientReceiptsBarrierCalc => Math.Max(1, Math.Min(PivotNumber, Math.Max(AncientBodiesBarrier, AncientReceiptsBarrier))); [ConfigItem(Description = "Whether to use the Snap sync mode.", DefaultValue = "false")] public bool SnapSync { get; set; } diff --git a/src/Nethermind/Nethermind.Blockchain/Synchronization/SyncConfig.cs b/src/Nethermind/Nethermind.Blockchain/Synchronization/SyncConfig.cs index 22c59297319..3060c83855a 100644 --- a/src/Nethermind/Nethermind.Blockchain/Synchronization/SyncConfig.cs +++ b/src/Nethermind/Nethermind.Blockchain/Synchronization/SyncConfig.cs @@ -1,5 +1,6 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only + using Nethermind.Config; using Nethermind.Core.Extensions; using Nethermind.Db; @@ -35,10 +36,10 @@ public bool SynchronizationEnabled public long AncientBodiesBarrier { get; set; } public long AncientReceiptsBarrier { get; set; } public string PivotTotalDifficulty { get; set; } - private string _pivotNumber = "0"; - public string PivotNumber + private long _pivotNumber = 0; + public long PivotNumber { - get => FastSync || SnapSync ? _pivotNumber : "0"; + get => FastSync || SnapSync ? _pivotNumber : 0; set => _pivotNumber = value; } diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/EstimateGasTracer.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/EstimateGasTracer.cs index 2a3b74b2d2a..1d6e7187eaa 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/EstimateGasTracer.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/EstimateGasTracer.cs @@ -40,6 +40,8 @@ public EstimateGasTracer() public bool OutOfGas { get; private set; } + public bool TopLevelRevert { get; private set; } + public override void MarkAsSuccess(Address recipient, GasConsumed gasSpent, byte[] output, LogEntry[] logs, Hash256? stateRoot = null) { @@ -108,6 +110,7 @@ public override void ReportAction(long gas, UInt256 value, Address from, Address if (_currentNestingLevel == -1) { OutOfGas = false; + TopLevelRevert = false; IntrinsicGasAt = gas; } @@ -146,7 +149,12 @@ public void ReportActionError(EvmExceptionType exceptionType, long gasLeft) public override void ReportOperationError(EvmExceptionType error) { - OutOfGas |= error == EvmExceptionType.OutOfGas || error == EvmExceptionType.Revert; + OutOfGas |= error == EvmExceptionType.OutOfGas; + + if (error == EvmExceptionType.Revert && _currentNestingLevel == 0) + { + TopLevelRevert = true; + } } private void UpdateAdditionalGas(long? gasLeft = null) diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/GasEstimator.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/GasEstimator.cs index 777682391ac..8f245f699f2 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/GasEstimator.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/GasEstimator.cs @@ -5,6 +5,7 @@ using System.Threading; using Nethermind.Config; using Nethermind.Core; +using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Evm; using Nethermind.Evm.TransactionProcessing; @@ -134,6 +135,10 @@ private long BinarySearchEstimate( private static string GetError(EstimateGasTracer gasTracer, string defaultError = "Transaction execution fails") => gasTracer switch { + { TopLevelRevert: true } => gasTracer.Error ?? + (gasTracer.ReturnValue?.Length > 0 ? + $"execution reverted: {gasTracer.ReturnValue.ToHexString(true)}" + : "execution reverted"), { OutOfGas: true } => "Gas estimation failed due to out of gas", { StatusCode: StatusCode.Failure } => gasTracer.Error ?? "Transaction execution fails", _ => defaultError @@ -149,6 +154,8 @@ private bool TryExecutableTransaction(Transaction transaction, BlockHeader block transactionProcessor.SetBlockExecutionContext(new BlockExecutionContext(block, specProvider.GetSpec(block))); TransactionResult result = transactionProcessor.CallAndRestore(txClone, gasTracer.WithCancellation(token)); - return result.TransactionExecuted && gasTracer.StatusCode == StatusCode.Success && !gasTracer.OutOfGas; + // Transaction succeeds if it executed, has success status, no OutOfGas, and no top-level revert + return result.TransactionExecuted && gasTracer.StatusCode == StatusCode.Success && + !gasTracer.OutOfGas && !gasTracer.TopLevelRevert; } } diff --git a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/GethLikeNativeTracerFactory.cs b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/GethLikeNativeTracerFactory.cs index b75fe892ce8..1b84577e64e 100644 --- a/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/GethLikeNativeTracerFactory.cs +++ b/src/Nethermind/Nethermind.Blockchain/Tracing/GethStyle/Custom/Native/GethLikeNativeTracerFactory.cs @@ -31,7 +31,7 @@ private static void RegisterNativeTracers() RegisterTracer(NativeCallTracer.CallTracer, static (options, _, transaction, _) => new NativeCallTracer(transaction, options)); } - private static void RegisterTracer(string tracerName, GethLikeNativeTracerFactoryDelegate tracerDelegate) + public static void RegisterTracer(string tracerName, GethLikeNativeTracerFactoryDelegate tracerDelegate) { _tracers.Add(tracerName, tracerDelegate); } diff --git a/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs b/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs index b242c70e9c9..e21da4ac482 100644 --- a/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs +++ b/src/Nethermind/Nethermind.Clique.Test/CliqueBlockProducerTests.cs @@ -105,7 +105,7 @@ public On CreateNode(PrivateKey privateKey, bool withGenesisAlreadyProcessed = f SepoliaSpecProvider testnetSpecProvider = SepoliaSpecProvider.Instance; IReleaseSpec finalSpec = testnetSpecProvider.GetFinalSpec(); - IWorldState stateProvider = container.Resolve().GlobalWorldState; + IWorldState stateProvider = container.Resolve().WorldState; using (stateProvider.BeginScope(IWorldState.PreGenesis)) { stateProvider.CreateAccount(TestItem.PrivateKeyD.Address, 100.Ether()); @@ -280,7 +280,7 @@ public On ProcessBadGenesis() public On ProcessGenesis(PrivateKey nodeKey) { - using var _ = _containers[nodeKey].Resolve().GlobalWorldState.BeginScope(IWorldState.PreGenesis); + using var _ = _containers[nodeKey].Resolve().WorldState.BeginScope(IWorldState.PreGenesis); if (_logger.IsInfo) _logger.Info($"SUGGESTING GENESIS ON {nodeKey.Address}"); _blockTrees[nodeKey].SuggestBlock(_genesis).Should().Be(AddBlockResult.Added); _blockEvents[nodeKey].WaitOne(_timeout); @@ -289,7 +289,7 @@ public On ProcessGenesis(PrivateKey nodeKey) public On ProcessGenesis3Validators(PrivateKey nodeKey) { - using var _ = _containers[nodeKey].Resolve().GlobalWorldState.BeginScope(IWorldState.PreGenesis); + using var _ = _containers[nodeKey].Resolve().WorldState.BeginScope(IWorldState.PreGenesis); _blockTrees[nodeKey].SuggestBlock(_genesis3Validators); _blockEvents[nodeKey].WaitOne(_timeout); return this; diff --git a/src/Nethermind/Nethermind.Config.Test/ConfigProvider_FindIncorrectSettings_Tests.cs b/src/Nethermind/Nethermind.Config.Test/ConfigProvider_FindIncorrectSettings_Tests.cs index e06da50a042..87ccec88703 100644 --- a/src/Nethermind/Nethermind.Config.Test/ConfigProvider_FindIncorrectSettings_Tests.cs +++ b/src/Nethermind/Nethermind.Config.Test/ConfigProvider_FindIncorrectSettings_Tests.cs @@ -61,7 +61,7 @@ public void NoCategorySettings() { "NETHERMIND_CLI_SWITCH_LOCAL", "http://localhost:80" }, { "NETHERMIND_CONFIG", "test2.json" }, { "NETHERMIND_XYZ", "xyz" }, // not existing, should get error - { "QWER", "qwerty" } // not Nethermind setting, no error + { "QWERTY", "qwerty" } // not Nethermind setting, no error }); EnvConfigSource? envSource = new(_env); diff --git a/src/Nethermind/Nethermind.Config/ConfigItemAttribute.cs b/src/Nethermind/Nethermind.Config/ConfigItemAttribute.cs index e37ece79cad..21038431e3c 100644 --- a/src/Nethermind/Nethermind.Config/ConfigItemAttribute.cs +++ b/src/Nethermind/Nethermind.Config/ConfigItemAttribute.cs @@ -3,20 +3,22 @@ using System; -namespace Nethermind.Config +namespace Nethermind.Config; + +[AttributeUsage(AttributeTargets.Property)] +public class ConfigItemAttribute : Attribute { - public class ConfigItemAttribute : Attribute - { - public string Description { get; set; } + public string Description { get; set; } + + public string DefaultValue { get; set; } - public string DefaultValue { get; set; } + public bool HiddenFromDocs { get; set; } - public bool HiddenFromDocs { get; set; } + public bool DisabledForCli { get; set; } - public bool DisabledForCli { get; set; } + public string EnvironmentVariable { get; set; } - public string EnvironmentVariable { get; set; } + public bool IsPortOption { get; set; } - public bool IsPortOption { get; set; } - } + public string CliOptionAlias { get; set; } } diff --git a/src/Nethermind/Nethermind.Config/ExitCodes.cs b/src/Nethermind/Nethermind.Config/ExitCodes.cs index 50e7f86b0f4..f20c402193f 100644 --- a/src/Nethermind/Nethermind.Config/ExitCodes.cs +++ b/src/Nethermind/Nethermind.Config/ExitCodes.cs @@ -20,6 +20,7 @@ public static class ExitCodes public const int ForbiddenOptionValue = 107; public const int MissingChainspecEipConfiguration = 108; public const int DbCorruption = 109; + public const int MissingPrecompile = 110; // Posix exit code // https://tldp.org/LDP/abs/html/exitcodes.html diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockProcessor.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockProcessor.cs index c4de02bb510..b710603c74f 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaBlockProcessor.cs @@ -80,11 +80,7 @@ public AuRaBlockProcessor(ISpecProvider specProvider, protected override TxReceipt[] ProcessBlock(Block block, IBlockTracer blockTracer, ProcessingOptions options, IReleaseSpec spec, CancellationToken token) { ValidateAuRa(block); - bool wereChanges = _contractRewriter?.RewriteContracts(block.Number, _stateProvider, spec) ?? false; - if (wereChanges) - { - _stateProvider.Commit(spec, commitRoots: true); - } + RewriteContracts(block, spec); AuRaValidator.OnBlockProcessingStart(block, options); TxReceipt[] receipts = base.ProcessBlock(block, blockTracer, options, spec, token); AuRaValidator.OnBlockProcessingEnd(block, receipts, options); @@ -92,9 +88,25 @@ protected override TxReceipt[] ProcessBlock(Block block, IBlockTracer blockTrace return receipts; } + private void RewriteContracts(Block block, IReleaseSpec spec) + { + bool wereChanges = _contractRewriter?.RewriteContracts(block.Number, _stateProvider, spec) ?? false; + BlockHeader? parent = _blockTree.FindParentHeader(block.Header, BlockTreeLookupOptions.None); + if (parent is not null) + { + wereChanges |= _contractRewriter?.RewriteContracts(block.Timestamp, parent.Timestamp, _stateProvider, spec) ?? false; + } + + if (wereChanges) + { + _stateProvider.Commit(spec, commitRoots: true); + } + } + // After PoS switch we need to revert to standard block processing, ignoring AuRa customizations protected TxReceipt[] PostMergeProcessBlock(Block block, IBlockTracer blockTracer, ProcessingOptions options, IReleaseSpec spec, CancellationToken token) { + RewriteContracts(block, spec); return base.ProcessBlock(block, blockTracer, options, spec, token); } diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaPlugin.cs b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaPlugin.cs index 58b878a4478..d7f682349a3 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/AuRaPlugin.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/AuRaPlugin.cs @@ -135,7 +135,7 @@ protected override void Load(ContainerBuilder builder) } /// - /// Some validation component that is active in rpc and validation but not in block produccer. + /// Some validation component that is active in RPC and validation but not in block producer. /// /// /// @@ -152,7 +152,8 @@ protected override void Load(ContainerBuilder builder) ITxFilter txFilter = txAuRaFilterBuilders.CreateAuRaTxFilter(new ServiceTxFilter()); IDictionary> rewriteBytecode = parameters.RewriteBytecode; - ContractRewriter? contractRewriter = rewriteBytecode?.Count > 0 ? new ContractRewriter(rewriteBytecode) : null; + (ulong, Address, byte[])[] rewriteBytecodeTimestamp = [.. parameters.RewriteBytecodeTimestampParsed]; + ContractRewriter? contractRewriter = rewriteBytecode?.Count > 0 || rewriteBytecodeTimestamp?.Length > 0 ? new(rewriteBytecode, rewriteBytecodeTimestamp) : null; AuRaContractGasLimitOverride? gasLimitOverride = gasLimitOverrideFactory.GetGasLimitCalculator(); diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/Config/AuRaChainSpecEngineParameters.cs b/src/Nethermind/Nethermind.Consensus.AuRa/Config/AuRaChainSpecEngineParameters.cs index c1beed8e1e8..705f81b1063 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/Config/AuRaChainSpecEngineParameters.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/Config/AuRaChainSpecEngineParameters.cs @@ -9,6 +9,7 @@ using System.Text.Json.Serialization; using Nethermind.Consensus.AuRa.Validators; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Int256; using Nethermind.Specs; using Nethermind.Specs.ChainSpecStyle; @@ -54,6 +55,21 @@ public class AuRaChainSpecEngineParameters : IChainSpecEngineParameters public long PosdaoTransition { get; set; } = TransitionDisabled; public IDictionary> RewriteBytecode { get; set; } = new Dictionary>(); + public IDictionary> RewriteBytecodeTimestamp { get; set; } = new Dictionary>(); + + public IEnumerable<(ulong, Address, byte[])> RewriteBytecodeTimestampParsed + { + get + { + foreach (KeyValuePair> timestampOverrides in RewriteBytecodeTimestamp) + { + foreach (KeyValuePair addressOverride in timestampOverrides.Value) + { + yield return (timestampOverrides.Key, addressOverride.Key, addressOverride.Value); + } + } + } + } public Address WithdrawalContractAddress { get; set; } @@ -70,6 +86,11 @@ public void ApplyToReleaseSpec(ReleaseSpec spec, long startBlock, ulong? startTi spec.MaximumUncleCount = (int)(startBlock >= (MaximumUncleCountTransition ?? long.MaxValue) ? MaximumUncleCount ?? 2 : 2); } + public void AddTransitions(SortedSet blockNumbers, SortedSet timestamps) + { + timestamps.AddRange(RewriteBytecodeTimestamp.Keys); + } + static AuRaParameters.Validator LoadValidator(AuRaValidatorJson validatorJson, int level = 0) { AuRaParameters.ValidatorType validatorType = validatorJson.GetValidatorType(); diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/ContractRewriter.cs b/src/Nethermind/Nethermind.Consensus.AuRa/ContractRewriter.cs index 02498c9c75e..a0d55dae611 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/ContractRewriter.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/ContractRewriter.cs @@ -8,26 +8,51 @@ namespace Nethermind.Consensus.AuRa; -public class ContractRewriter +public class ContractRewriter( + IDictionary> contractOverrides, + (ulong, Address, byte[])[] contractOverridesTimestamp) { - private readonly IDictionary> _contractOverrides; + private readonly IDictionary> _contractOverrides = contractOverrides; + private readonly (ulong, Address, byte[])[] _contractOverridesTimestamp = contractOverridesTimestamp; - public ContractRewriter(IDictionary> contractOverrides) + public bool RewriteContracts(long blockNumber, IWorldState stateProvider, IReleaseSpec spec) { - _contractOverrides = contractOverrides; + bool result = false; + if (_contractOverrides.TryGetValue(blockNumber, out IDictionary overrides)) + { + result = InsertOverwriteCode(overrides, stateProvider, spec); + } + return result; } - public bool RewriteContracts(long blockNumber, IWorldState stateProvider, IReleaseSpec spec) + public bool RewriteContracts(ulong timestamp, ulong parentTimestamp, IWorldState stateProvider, IReleaseSpec spec) { bool result = false; - if (_contractOverrides.TryGetValue(blockNumber, out IDictionary overrides)) + foreach ((ulong Timestamp, Address Address, byte[] Code) codeOverride in _contractOverridesTimestamp) { - foreach (KeyValuePair contractOverride in overrides) + if (timestamp >= codeOverride.Timestamp && parentTimestamp < codeOverride.Timestamp) { - stateProvider.InsertCode(contractOverride.Key, contractOverride.Value, spec); + InsertOverwriteCode(codeOverride.Address, codeOverride.Code, stateProvider, spec); result = true; } } return result; } + + private static bool InsertOverwriteCode(IDictionary overrides, IWorldState stateProvider, IReleaseSpec spec) + { + bool result = false; + foreach (KeyValuePair contractOverride in overrides) + { + InsertOverwriteCode(contractOverride.Key, contractOverride.Value, stateProvider, spec); + result = true; + } + return result; + } + + private static void InsertOverwriteCode(Address address, byte[] code, IWorldState stateProvider, IReleaseSpec spec) + { + stateProvider.CreateAccountIfNotExists(address, 0, 0); + stateProvider.InsertCode(address, code, spec); + } } diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/StartBlockProducerAuRa.cs b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/StartBlockProducerAuRa.cs index a92127f90d7..caa2c2fd893 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/StartBlockProducerAuRa.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/InitializationSteps/StartBlockProducerAuRa.cs @@ -26,7 +26,6 @@ using Nethermind.Consensus.Validators; using Nethermind.Consensus.Withdrawals; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Crypto; using Nethermind.Evm.State; @@ -158,7 +157,8 @@ private BlockProcessor CreateBlockProcessor(ITransactionProcessor txProcessor, I } IDictionary> rewriteBytecode = _parameters.RewriteBytecode; - ContractRewriter? contractRewriter = rewriteBytecode?.Count > 0 ? new ContractRewriter(rewriteBytecode) : null; + (ulong, Address, byte[])[] rewriteBytecodeTimestamp = [.. _parameters.RewriteBytecodeTimestampParsed]; + ContractRewriter? contractRewriter = rewriteBytecode?.Count > 0 || rewriteBytecodeTimestamp?.Length > 0 ? new(rewriteBytecode, rewriteBytecodeTimestamp) : null; var transactionExecutor = new BlockProcessor.BlockProductionTransactionsExecutor( new BuildUpTransactionProcessorAdapter(txProcessor), @@ -259,15 +259,16 @@ BlockProducerEnv Create() { ReadOnlyBlockTree readOnlyBlockTree = blockTree.AsReadOnly(); - IWorldState worldState = worldStateManager.CreateResettableWorldState(); + IWorldStateScopeProvider worldStateScopeProvider = worldStateManager.CreateResettableWorldState(); ILifetimeScope innerLifetime = lifetimeScope.BeginLifetimeScope((builder) => builder - .AddSingleton(worldState) + .AddSingleton(worldStateScopeProvider) .AddSingleton(BlockchainProcessor.Options.NoReceipts) .AddSingleton(CreateBlockProcessor) .AddDecorator()); lifetimeScope.Disposer.AddInstanceForAsyncDisposal(innerLifetime); IBlockchainProcessor chainProcessor = innerLifetime.Resolve(); + IWorldState worldState = innerLifetime.Resolve(); return new BlockProducerEnv(readOnlyBlockTree, chainProcessor, worldState, CreateTxSourceForProducer()); } diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/Rewards/AuRaRewardCalculator.cs b/src/Nethermind/Nethermind.Consensus.AuRa/Rewards/AuRaRewardCalculator.cs index 83dc02b25b2..6925261f4fa 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/Rewards/AuRaRewardCalculator.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/Rewards/AuRaRewardCalculator.cs @@ -68,7 +68,7 @@ public BlockReward[] CalculateRewards(Block block) private static BlockReward[] CalculateRewardsWithContract(Block block, IRewardContract contract) { - (Address[] beneficieries, ushort[] kinds) GetBeneficiaries() + (Address[] beneficiaries, ushort[] kinds) GetBeneficiaries() { var length = block.Uncles.Length + 1; diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/CompareTxByPriorityBase.cs b/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/CompareTxByPriorityBase.cs index 84cda756fc6..058a25af08d 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/CompareTxByPriorityBase.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/CompareTxByPriorityBase.cs @@ -58,8 +58,8 @@ public int Compare(Transaction x, Transaction y) // we already have nonce ordered by previous code, we don't deal with it here // first order by whitelisted - int whitelistedComparision = IsWhiteListed(y).CompareTo(IsWhiteListed(x)); - if (whitelistedComparision != 0) return whitelistedComparision; + int whitelistedComparison = IsWhiteListed(y).CompareTo(IsWhiteListed(x)); + if (whitelistedComparison != 0) return whitelistedComparison; // then order by priority descending return GetPriority(y).CompareTo(GetPriority(x)); diff --git a/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/RandomContractTxSource.cs b/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/RandomContractTxSource.cs index af820085092..c4dc44efa37 100644 --- a/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/RandomContractTxSource.cs +++ b/src/Nethermind/Nethermind.Consensus.AuRa/Transactions/RandomContractTxSource.cs @@ -38,7 +38,7 @@ public RandomContractTxSource( IList contracts, IEciesCipher eciesCipher, ISigner signer, - IProtectedPrivateKey previousCryptoKey, // this is for backwards-compability when upgrading validator node + IProtectedPrivateKey previousCryptoKey, // this is for backwards-compatibility when upgrading validator node ICryptoRandom cryptoRandom, ILogManager logManager) { diff --git a/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs b/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs index b71813d8461..6c1bf551641 100644 --- a/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs +++ b/src/Nethermind/Nethermind.Consensus.Clique/BlockHeaderExtensions.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using Nethermind.Blockchain; using Nethermind.Core; namespace Nethermind.Consensus.Clique @@ -17,7 +18,7 @@ internal static Address[] ExtractSigners(BlockHeader blockHeader) { if (blockHeader.ExtraData is null) { - throw new Exception(string.Empty); + throw new BlockchainException("Block header ExtraData cannot be null when extracting signers"); } Span signersData = blockHeader.ExtraData.AsSpan(Clique.ExtraVanityLength, (blockHeader.ExtraData.Length - Clique.ExtraSealLength)); diff --git a/src/Nethermind/Nethermind.Consensus.Clique/CliqueSealer.cs b/src/Nethermind/Nethermind.Consensus.Clique/CliqueSealer.cs index f7f8ffed645..c2553622c2b 100644 --- a/src/Nethermind/Nethermind.Consensus.Clique/CliqueSealer.cs +++ b/src/Nethermind/Nethermind.Consensus.Clique/CliqueSealer.cs @@ -101,15 +101,6 @@ public bool CanSeal(long blockNumber, Hash256 parentHash) return false; } - if (_snapshotManager.HasSignedRecently(snapshot, blockNumber, _signer.Address)) - { - if (_snapshotManager.HasSignedRecently(snapshot, blockNumber, _signer.Address)) - { - if (_logger.IsTrace) _logger.Trace("Signed recently"); - return false; - } - } - // If we're amongst the recent signers, wait for the next block if (_snapshotManager.HasSignedRecently(snapshot, blockNumber, _signer.Address)) { diff --git a/src/Nethermind/Nethermind.Consensus.Clique/SnapshotManager.cs b/src/Nethermind/Nethermind.Consensus.Clique/SnapshotManager.cs index e782eac5931..9a63d1949f9 100644 --- a/src/Nethermind/Nethermind.Consensus.Clique/SnapshotManager.cs +++ b/src/Nethermind/Nethermind.Consensus.Clique/SnapshotManager.cs @@ -53,7 +53,8 @@ public Address GetBlockSealer(BlockHeader header) { if (header.Author is not null) return header.Author; if (header.Number == 0) return Address.Zero; - if (_signatures.Get(header.Hash) is not null) return _signatures.Get(header.Hash); + Address? cached = _signatures.Get(header.Hash); + if (cached is not null) return cached; int extraSeal = 65; diff --git a/src/Nethermind/Nethermind.Consensus.Test/BlockExtenstionsTests.cs b/src/Nethermind/Nethermind.Consensus.Test/BlockExtenstionsTests.cs index 5c0cc4f82b4..a37dbe23e03 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/BlockExtenstionsTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/BlockExtenstionsTests.cs @@ -10,7 +10,7 @@ namespace Nethermind.Consensus.Test { - public class BlockExtenstionsTests + public class BlockExtensionsTests { [Test] public void Is_by_nethermind_node() diff --git a/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs b/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs index a4180ffc934..a838bce57d6 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/Scheduler/BackgroundTaskSchedulerTests.cs @@ -35,7 +35,7 @@ public async Task Test_task_will_execute() TaskCompletionSource tcs = new TaskCompletionSource(); await using BackgroundTaskScheduler scheduler = new BackgroundTaskScheduler(_branchProcessor, _chainHeadInfo, 1, 65536, LimboLogs.Instance); - scheduler.ScheduleTask(1, (_, token) => + scheduler.TryScheduleTask(1, (_, token) => { tcs.SetResult(1); return Task.CompletedTask; @@ -52,13 +52,13 @@ public async Task Test_task_will_execute_concurrently_when_configured_so() int counter = 0; SemaphoreSlim waitSignal = new SemaphoreSlim(0); - scheduler.ScheduleTask(1, async (_, token) => + scheduler.TryScheduleTask(1, async (_, token) => { Interlocked.Increment(ref counter); await waitSignal.WaitAsync(token); Interlocked.Decrement(ref counter); }); - scheduler.ScheduleTask(1, async (_, token) => + scheduler.TryScheduleTask(1, async (_, token) => { Interlocked.Increment(ref counter); await waitSignal.WaitAsync(token); @@ -77,7 +77,7 @@ public async Task Test_task_will_cancel_on_block_processing() bool wasCancelled = false; ManualResetEvent waitSignal = new ManualResetEvent(false); - scheduler.ScheduleTask(1, async (_, token) => + scheduler.TryScheduleTask(1, async (_, token) => { waitSignal.Set(); try @@ -107,7 +107,7 @@ public async Task Test_task_that_is_scheduled_during_block_processing_will_conti int executionCount = 0; for (int i = 0; i < 5; i++) { - scheduler.ScheduleTask(1, (_, _) => + scheduler.TryScheduleTask(1, (_, token) => { executionCount++; return Task.CompletedTask; @@ -129,7 +129,7 @@ public async Task Test_task_that_is_scheduled_during_block_processing_but_deadli bool wasCancelled = false; ManualResetEvent waitSignal = new ManualResetEvent(false); - scheduler.ScheduleTask(1, (_, token) => + scheduler.TryScheduleTask(1, (_, token) => { wasCancelled = token.IsCancellationRequested; waitSignal.Set(); diff --git a/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs b/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs index 882e58a7a4d..4761d0e9ee8 100644 --- a/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs +++ b/src/Nethermind/Nethermind.Consensus.Test/TargetAdjustedGasLimitCalculatorTests.cs @@ -49,7 +49,7 @@ public void Is_calculating_correct_gasLimit(long currentGasLimit, long targetGas } [Test] - public void Doesnt_go_below_minimum() + public void DoesNot_go_below_minimum() { int londonBlock = 5; long gasLimit = 5000; diff --git a/src/Nethermind/Nethermind.Consensus/IBlockProducerFactory.cs b/src/Nethermind/Nethermind.Consensus/IBlockProducerFactory.cs index f89cef995db..15ac802c7a3 100644 --- a/src/Nethermind/Nethermind.Consensus/IBlockProducerFactory.cs +++ b/src/Nethermind/Nethermind.Consensus/IBlockProducerFactory.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Nethermind.Consensus.Transactions; - namespace Nethermind.Consensus; public interface IBlockProducerFactory diff --git a/src/Nethermind/Nethermind.Consensus/IMiningConfig.cs b/src/Nethermind/Nethermind.Consensus/IMiningConfig.cs index 1b9734c51e6..3bd63ff5145 100644 --- a/src/Nethermind/Nethermind.Consensus/IMiningConfig.cs +++ b/src/Nethermind/Nethermind.Consensus/IMiningConfig.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Config; -using Nethermind.Int256; namespace Nethermind.Consensus; diff --git a/src/Nethermind/Nethermind.Consensus/MiningConfig.cs b/src/Nethermind/Nethermind.Consensus/MiningConfig.cs index aa7772f5958..2e7b5177ef2 100644 --- a/src/Nethermind/Nethermind.Consensus/MiningConfig.cs +++ b/src/Nethermind/Nethermind.Consensus/MiningConfig.cs @@ -1,9 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Nethermind.Config; -using Nethermind.Int256; - namespace Nethermind.Consensus; public class MiningConfig : IMiningConfig diff --git a/src/Nethermind/Nethermind.Consensus/NullSigner.cs b/src/Nethermind/Nethermind.Consensus/NullSigner.cs index 8f5e70ba4da..2210d911435 100644 --- a/src/Nethermind/Nethermind.Consensus/NullSigner.cs +++ b/src/Nethermind/Nethermind.Consensus/NullSigner.cs @@ -18,7 +18,7 @@ public class NullSigner : ISigner, ISignerStore public Signature Sign(in ValueHash256 message) { return new(new byte[65]); } - public bool CanSign { get; } = true; // TODO: why true? + public bool CanSign { get; } = false; public PrivateKey? Key { get; } = null; diff --git a/src/Nethermind/Nethermind.Consensus/Processing/AutoReadOnlyTxProcessingEnvFactory.cs b/src/Nethermind/Nethermind.Consensus/Processing/AutoReadOnlyTxProcessingEnvFactory.cs index ce6a7f705b0..50582c606ca 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/AutoReadOnlyTxProcessingEnvFactory.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/AutoReadOnlyTxProcessingEnvFactory.cs @@ -5,7 +5,6 @@ using Autofac; using Nethermind.Blockchain; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Evm.State; using Nethermind.Evm.TransactionProcessing; using Nethermind.State; @@ -16,24 +15,11 @@ public class AutoReadOnlyTxProcessingEnvFactory(ILifetimeScope parentLifetime, I { public IReadOnlyTxProcessorSource Create() { - IWorldState worldState = worldStateManager.CreateResettableWorldState(); + IWorldStateScopeProvider worldState = worldStateManager.CreateResettableWorldState(); ILifetimeScope childScope = parentLifetime.BeginLifetimeScope((builder) => { builder - .AddSingleton(worldState) - .AddSingleton(); - }); - - return childScope.Resolve(); - } - - public IReadOnlyTxProcessorSource CreateForWarmingUp(IWorldState worldStateToWarmUp) - { - IWorldState worldState = worldStateManager.CreateWorldStateForWarmingUp(worldStateToWarmUp); - ILifetimeScope childScope = parentLifetime.BeginLifetimeScope((builder) => - { - builder - .AddSingleton(worldState) + .AddSingleton(worldState) .AddSingleton(); }); diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs index 5305d5ccdbe..5fc465c2ea6 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockCachePreWarmer.cs @@ -25,30 +25,30 @@ namespace Nethermind.Consensus.Processing; public sealed class BlockCachePreWarmer( - IReadOnlyTxProcessingEnvFactory envFactory, - IWorldState worldStateToWarmup, + PrewarmerEnvFactory envFactory, int concurrency, - ILogManager logManager, - PreBlockCaches? preBlockCaches = null + NodeStorageCache nodeStorageCache, + PreBlockCaches preBlockCaches, + ILogManager logManager ) : IBlockCachePreWarmer { private int _concurrencyLevel = (concurrency == 0 ? Math.Min(Environment.ProcessorCount - 1, 16) : concurrency); - private readonly ObjectPool _envPool = new DefaultObjectPool(new ReadOnlyTxProcessingEnvPooledObjectPolicy(envFactory, worldStateToWarmup), Environment.ProcessorCount * 2); + private readonly ObjectPool _envPool = new DefaultObjectPool(new ReadOnlyTxProcessingEnvPooledObjectPolicy(envFactory, preBlockCaches), Environment.ProcessorCount * 2); private readonly ILogger _logger = logManager.GetClassLogger(); private BlockStateSource? _currentBlockState = null; public BlockCachePreWarmer( - IReadOnlyTxProcessingEnvFactory envFactory, - IWorldState worldStateToWarmup, + PrewarmerEnvFactory envFactory, IBlocksConfig blocksConfig, - ILogManager logManager, - PreBlockCaches? preBlockCaches = null + NodeStorageCache nodeStorageCache, + PreBlockCaches preBlockCaches, + ILogManager logManager ) : this( envFactory, - worldStateToWarmup, blocksConfig.PreWarmStateConcurrency, - logManager, - preBlockCaches) + nodeStorageCache, + preBlockCaches, + logManager) { } @@ -58,6 +58,8 @@ public Task PreWarmCaches(Block suggestedBlock, BlockHeader? parent, IReleaseSpe { _currentBlockState = new(this, suggestedBlock, parent, spec); CacheType result = preBlockCaches.ClearCaches(); + result |= nodeStorageCache.ClearCaches() ? CacheType.Rlp : CacheType.None; + nodeStorageCache.Enabled = true; if (result != default) { if (_logger.IsWarn) _logger.Warn($"Caches {result} are not empty. Clearing them."); @@ -82,8 +84,10 @@ public CacheType ClearCaches() { if (_logger.IsDebug) _logger.Debug("Clearing caches"); CacheType cachesCleared = preBlockCaches?.ClearCaches() ?? default; - if (_logger.IsDebug) _logger.Debug($"Cleared caches: {cachesCleared}"); + nodeStorageCache.Enabled = false; + cachesCleared |= nodeStorageCache.ClearCaches() ? CacheType.Rlp : CacheType.None; + if (_logger.IsDebug) _logger.Debug($"Cleared caches: {cachesCleared}"); return cachesCleared; } @@ -373,9 +377,9 @@ public void Dispose() private static void DisposeThreadState(AddressWarmingState state) => state.Dispose(); } - private class ReadOnlyTxProcessingEnvPooledObjectPolicy(IReadOnlyTxProcessingEnvFactory envFactory, IWorldState worldStateToWarmUp) : IPooledObjectPolicy + private class ReadOnlyTxProcessingEnvPooledObjectPolicy(PrewarmerEnvFactory envFactory, PreBlockCaches preBlockCaches) : IPooledObjectPolicy { - public IReadOnlyTxProcessorSource Create() => envFactory.CreateForWarmingUp(worldStateToWarmUp); + public IReadOnlyTxProcessorSource Create() => envFactory.Create(preBlockCaches); public bool Return(IReadOnlyTxProcessorSource obj) => true; } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionsExecutor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionsExecutor.cs index fdddfd350b3..6c548cf0972 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionsExecutor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockProcessor.BlockProductionTransactionsExecutor.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using Nethermind.Blockchain.Tracing; diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs index 0469e62fc9f..816517ff416 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BlockchainProcessor.cs @@ -143,6 +143,8 @@ public async ValueTask Enqueue(Block block, ProcessingOptions processingOptions) if (!_recoveryComplete) { Interlocked.Increment(ref _queueCount); + BlockAdded?.Invoke(this, new BlockEventArgs(block)); + _lastProcessedBlock = DateTime.UtcNow; try { @@ -424,6 +426,7 @@ private void FireProcessingQueueEmpty() public event EventHandler? ProcessingQueueEmpty; public event EventHandler? BlockRemoved; + public event EventHandler? BlockAdded; public bool IsEmpty => Volatile.Read(ref _queueCount) == 0; public int Count => Volatile.Read(ref _queueCount); diff --git a/src/Nethermind/Nethermind.Consensus/Processing/BranchProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/BranchProcessor.cs index 763baa9855c..719adc62367 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/BranchProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/BranchProcessor.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Nethermind.Blockchain; using Nethermind.Blockchain.BeaconBlockRoot; using Nethermind.Core; using Nethermind.Core.Extensions; @@ -59,10 +58,10 @@ public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlo { if (baseBlock is null && suggestedBlock.IsGenesis) { - // Super special ultra mega - I dont wanna deal with this right now - special case where genesis is handled - // specially from outside where the state are added from `GenesisLoader` but not part of the block processor - // but it still pass through the blocktree suggest to blockchain processor event chain - // Meaning dont set state when handling genesis. + // Super special ultra mega – I don't want to deal with this right now – special case where genesis is handled + // externally, where the state is added via `GenesisLoader` but not processed by the block processor + // even though it still passes through the block tree suggest to blockchain processor event chain. + // Meaning don't set state when handling genesis. } else { @@ -71,7 +70,8 @@ public Block[] Process(BlockHeader? baseBlock, IReadOnlyList suggestedBlo } else { - worldStateCloser = stateProvider.BeginScope(baseBlock); + BlockHeader? scopeBaseBlock = baseBlock ?? (suggestedBlock.IsGenesis ? suggestedBlock.Header : null); + worldStateCloser = stateProvider.BeginScope(scopeBaseBlock); } CancellationTokenSource? backgroundCancellation = new(); diff --git a/src/Nethermind/Nethermind.Consensus/Processing/CensorshipDetector/CensorshipDetector.cs b/src/Nethermind/Nethermind.Consensus/Processing/CensorshipDetector/CensorshipDetector.cs index cc56f546338..b225e7e5cb7 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/CensorshipDetector/CensorshipDetector.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/CensorshipDetector/CensorshipDetector.cs @@ -4,7 +4,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Nethermind.Blockchain; using Nethermind.Core; diff --git a/src/Nethermind/Nethermind.Consensus/Processing/GenesisLoader.cs b/src/Nethermind/Nethermind.Consensus/Processing/GenesisLoader.cs index d6b89a2eda1..50d14deb954 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/GenesisLoader.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/GenesisLoader.cs @@ -84,7 +84,7 @@ private void ValidateGenesisHash(Hash256? expectedGenesisHash, BlockHeader genes { if (expectedGenesisHash is not null && genesis.Hash != expectedGenesisHash) { - if (_logger.IsTrace) _logger.Trace(stateReader.DumpState(genesis.StateRoot!)); + if (_logger.IsTrace) _logger.Trace(stateReader.DumpState(genesis)); if (_logger.IsWarn) _logger.Warn(genesis.ToString(BlockHeader.Format.Full)); if (_logger.IsError) _logger.Error($"Unexpected genesis hash, expected {expectedGenesisHash}, but was {genesis.Hash}"); } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessingQueue.cs b/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessingQueue.cs index 7ca72e70d41..3312d286e23 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessingQueue.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessingQueue.cs @@ -26,6 +26,7 @@ public interface IBlockProcessingQueue /// event EventHandler ProcessingQueueEmpty; + event EventHandler BlockAdded; event EventHandler BlockRemoved; /// diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessor.cs index 718d7e3e8fe..2adaabe6f4b 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/IBlockProcessor.cs @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Threading; using Nethermind.Blockchain.Tracing; using Nethermind.Core; diff --git a/src/Nethermind/Nethermind.Consensus/Processing/IReadOnlyTxProcessingEnvFactory.cs b/src/Nethermind/Nethermind.Consensus/Processing/IReadOnlyTxProcessingEnvFactory.cs index baeb04fb694..2d46d4d7004 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/IReadOnlyTxProcessingEnvFactory.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/IReadOnlyTxProcessingEnvFactory.cs @@ -2,12 +2,10 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Blockchain; -using Nethermind.Evm.State; namespace Nethermind.Consensus.Processing; public interface IReadOnlyTxProcessingEnvFactory { public IReadOnlyTxProcessorSource Create(); - public IReadOnlyTxProcessorSource CreateForWarmingUp(IWorldState worldState); } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/NullBlockProcessor.cs b/src/Nethermind/Nethermind.Consensus/Processing/NullBlockProcessor.cs index 51b998dcae4..819aabd99c2 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/NullBlockProcessor.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/NullBlockProcessor.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Evm.Tracing; diff --git a/src/Nethermind/Nethermind.Consensus/Processing/PrewarmerEnvFactory.cs b/src/Nethermind/Nethermind.Consensus/Processing/PrewarmerEnvFactory.cs new file mode 100644 index 00000000000..17b3d97d0a8 --- /dev/null +++ b/src/Nethermind/Nethermind.Consensus/Processing/PrewarmerEnvFactory.cs @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Autofac; +using Nethermind.Blockchain; +using Nethermind.Core; +using Nethermind.Evm.State; +using Nethermind.State; + +namespace Nethermind.Consensus.Processing; + +public class PrewarmerEnvFactory(IWorldStateManager worldStateManager, ILifetimeScope parentLifetime) +{ + public IReadOnlyTxProcessorSource Create(PreBlockCaches preBlockCaches) + { + var worldState = new PrewarmerScopeProvider( + worldStateManager.CreateResettableWorldState(), + preBlockCaches, + populatePreBlockCache: true + ); + + ILifetimeScope childScope = parentLifetime.BeginLifetimeScope((builder) => + { + builder + .AddSingleton(worldState) + .AddSingleton(); + }); + + return childScope.Resolve(); + } +} diff --git a/src/Nethermind/Nethermind.Consensus/Processing/PrewarmerTxAdapter.cs b/src/Nethermind/Nethermind.Consensus/Processing/PrewarmerTxAdapter.cs index f9b0774fce8..c5c1efaf2eb 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/PrewarmerTxAdapter.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/PrewarmerTxAdapter.cs @@ -14,7 +14,7 @@ public class PrewarmerTxAdapter(ITransactionProcessorAdapter baseAdapter, BlockC { public TransactionResult Execute(Transaction transaction, ITxTracer txTracer) { - if (worldState is IPreBlockCaches preBlockCaches && preBlockCaches.IsWarmWorldState) + if (worldState.ScopeProvider is IPreBlockCaches { IsWarmWorldState: true }) { preWarmer.OnBeforeTxExecution(transaction); } diff --git a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs index 52756254fd6..f2246ed3e73 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/ProcessingStats.cs @@ -292,7 +292,7 @@ private void GenerateReport(BlockData data) double bps = chunkMicroseconds == 0 ? -1 : chunkBlocks / chunkMicroseconds * 1_000_000.0; double chunkMs = (chunkMicroseconds == 0 ? -1 : chunkMicroseconds / 1000.0); double runMs = (data.RunMicroseconds == 0 ? -1 : data.RunMicroseconds / 1000.0); - string blockGas = Evm.Metrics.BlockMinGasPrice != float.MaxValue ? $"⛽ Gas gwei: {Evm.Metrics.BlockMinGasPrice:N2} .. {whiteText}{Math.Max(Evm.Metrics.BlockMinGasPrice, Evm.Metrics.BlockEstMedianGasPrice):N2}{resetColor} ({Evm.Metrics.BlockAveGasPrice:N2}) .. {Evm.Metrics.BlockMaxGasPrice:N2}" : ""; + string blockGas = Evm.Metrics.BlockMinGasPrice != float.MaxValue ? $"⛽ Gas gwei: {Evm.Metrics.BlockMinGasPrice:N3} .. {whiteText}{Math.Max(Evm.Metrics.BlockMinGasPrice, Evm.Metrics.BlockEstMedianGasPrice):N3}{resetColor} ({Evm.Metrics.BlockAveGasPrice:N3}) .. {Evm.Metrics.BlockMaxGasPrice:N3}" : ""; string mgasColor = whiteText; NewProcessingStatistics?.Invoke(this, new BlockStatistics() diff --git a/src/Nethermind/Nethermind.Consensus/Processing/ShareableTxProcessingSource.cs b/src/Nethermind/Nethermind.Consensus/Processing/ShareableTxProcessingSource.cs index b0ea3a22b5d..dbdcda33322 100644 --- a/src/Nethermind/Nethermind.Consensus/Processing/ShareableTxProcessingSource.cs +++ b/src/Nethermind/Nethermind.Consensus/Processing/ShareableTxProcessingSource.cs @@ -4,10 +4,8 @@ using Microsoft.Extensions.ObjectPool; using Nethermind.Blockchain; using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Evm.State; using Nethermind.Evm.TransactionProcessing; -using Nethermind.State; namespace Nethermind.Consensus.Processing; diff --git a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerEnvFactory.cs b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerEnvFactory.cs index 2f7b3f960db..5fa8e99d39a 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerEnvFactory.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/BlockProducerEnvFactory.cs @@ -31,7 +31,7 @@ protected virtual ContainerBuilder ConfigureBuilder(ContainerBuilder builder) => public IBlockProducerEnv Create() { - IWorldState worldState = new TracedAccessWorldState(worldStateManager.CreateResettableWorldState()); + IWorldStateScopeProvider worldState = new TracedAccessWorldStateScopeProvider(worldStateManager.CreateResettableWorldState()); ILifetimeScope lifetimeScope = rootLifetime.BeginLifetimeScope(builder => ConfigureBuilder(builder) .AddScoped(worldState)); diff --git a/src/Nethermind/Nethermind.Consensus/Producers/TxPoolTxSource.cs b/src/Nethermind/Nethermind.Consensus/Producers/TxPoolTxSource.cs index f664421c82d..3faeb58aef4 100644 --- a/src/Nethermind/Nethermind.Consensus/Producers/TxPoolTxSource.cs +++ b/src/Nethermind/Nethermind.Consensus/Producers/TxPoolTxSource.cs @@ -105,20 +105,31 @@ public IEnumerable GetTransactions(BlockHeader parent, long gasLimi bool ResolveBlob(Transaction blobTx, out Transaction fullBlobTx) { - if (TryGetFullBlobTx(blobTx, out fullBlobTx)) + if (!TryGetFullBlobTx(blobTx, out fullBlobTx)) { - ProofVersion? proofVersion = (fullBlobTx.NetworkWrapper as ShardBlobNetworkWrapper)?.Version; - if (spec.BlobProofVersion != proofVersion) - { - if (_logger.IsTrace) _logger.Trace($"Declining {blobTx.ToShortString()}, {spec.BlobProofVersion} is wanted, but tx's proof version is {proofVersion}."); - return false; - } + if (_logger.IsTrace) _logger.Trace($"Declining {blobTx.ToShortString()}, failed to get full version of this blob tx from TxPool."); + return false; + } - return true; + if (fullBlobTx.NetworkWrapper is not ShardBlobNetworkWrapper wrapper) + { + if (_logger.IsTrace) _logger.Trace($"Declining {blobTx.ToShortString()}, missing blob data."); + return false; } - if (_logger.IsTrace) _logger.Trace($"Declining {blobTx.ToShortString()}, failed to get full version of this blob tx from TxPool."); - return false; + if (spec.BlobProofVersion != wrapper.Version) + { + if (_logger.IsTrace) _logger.Trace($"Declining {blobTx.ToShortString()}, {spec.BlobProofVersion} is wanted, but tx's proof version is {wrapper.Version}."); + return false; + } + + if (wrapper.Blobs.Length != blobTx.BlobVersionedHashes.Length) + { + if (_logger.IsTrace) _logger.Trace($"Declining {blobTx.ToShortString()}, incorrect blob count."); + return false; + } + + return true; } } diff --git a/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs b/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs index d6d77481318..c138491985f 100644 --- a/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs +++ b/src/Nethermind/Nethermind.Consensus/Scheduler/BackgroundTaskScheduler.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Channels; @@ -18,15 +16,15 @@ namespace Nethermind.Consensus.Scheduler; /// -/// Provide a way to orchestrate tasks to run in background at a lower priority. +/// Provides a way to orchestrate tasks to run in the background at a lower priority. /// - Task will be run in a lower priority thread, but there is a concurrency limit. -/// - Task closure will have CancellationToken which will be cancelled if block processing happens while the task is running. -/// - Task have a default timeout, which is counted from the time it is queued. If timed out because too many other background -/// task before it for example, the cancellation token passed to it will be cancelled. -/// - Task will still run when block processing is happening and its timed out this is so that it can handle its cancellation. -/// - Task will not run if block processing is happening and it still have some time left. -/// It is up to the task to determine what happen if cancelled, maybe it will reschedule for later, or resume later, but -/// preferably, stop execution immediately. Don't hang BTW. Other background task need to cancel too. +/// - Task closure will have the CancellationToken which will be canceled if block processing happens while the task is running. +/// - The Task has a default timeout, which is counted from the time it is queued. If timed out because too many other background +/// tasks before it, for example, the cancellation token passed to it will be canceled. +/// - Task will still run when block processing is happening, and it's timed out this is so that it can handle its cancellation. +/// - Task will not run if block processing is happening, and it still has some time left. +/// It is up to the task to determine what happens if canceled, maybe it will reschedule for later or resume later, but +/// preferably, stop execution immediately. Don't hang BTW. Other background tasks need to be canceled too. /// - A failure at this level is considered unexpected and loud. Exception should be handled at handler level. /// public class BackgroundTaskScheduler : IBackgroundTaskScheduler, IAsyncDisposable @@ -35,7 +33,6 @@ public class BackgroundTaskScheduler : IBackgroundTaskScheduler, IAsyncDisposabl private readonly CancellationTokenSource _mainCancellationTokenSource; private readonly Channel _taskQueue; - private readonly Lock _queueLock = new(); private readonly BelowNormalPriorityTaskScheduler _scheduler; private readonly ManualResetEventSlim _restartQueueSignal; private readonly Task[] _tasksExecutors; @@ -57,8 +54,14 @@ public BackgroundTaskScheduler(IBranchProcessor branchProcessor, IChainHeadInfoP _blockProcessorCancellationTokenSource = new CancellationTokenSource(); // In priority order, so if we reach an activity with time left, - // we know rest still have time left - _taskQueue = Channel.CreateUnboundedPrioritized(); + // we know the rest still have time left + _taskQueue = Channel.CreateUnboundedPrioritized( + new UnboundedPrioritizedChannelOptions + { + SingleReader = concurrency == 1, + SingleWriter = false, + AllowSynchronousContinuations = false + }); _logger = logManager.GetClassLogger(); _branchProcessor = branchProcessor; _headInfo = headInfo; @@ -81,25 +84,25 @@ public BackgroundTaskScheduler(IBranchProcessor branchProcessor, IChainHeadInfoP private void BranchProcessorOnBranchesProcessing(object? sender, BlocksProcessingEventArgs e) { - // If we are syncing we don't block background task processing + // If we are syncing, we don't block background task processing // as there are potentially no gaps between blocks if (!_headInfo.IsSyncing) { - // Reset background queue processing signal, causing it to wait + // Reset the background queue processing signal, causing it to wait _restartQueueSignal.Reset(); - // On block processing, we cancel the block process cts, causing current task to get cancelled. + // On block processing, we cancel the block process cts, causing the current task to get canceled. _blockProcessorCancellationTokenSource.Cancel(); } } private void BranchProcessorOnBranchProcessed(object? sender, BlockProcessedEventArgs e) { - // Once block is processed, we replace the cancellation token with a fresh uncancelled one + // Once the block is processed, we replace the cancellation token with a fresh uncanceled one using CancellationTokenSource oldTokenSource = Interlocked.Exchange( ref _blockProcessorCancellationTokenSource, new CancellationTokenSource()); - // We also set queue signal causing it to continue processing task. + // We also set a queue signal causing it to continue processing the task. _restartQueueSignal.Set(); } @@ -119,18 +122,18 @@ private async Task StartChannel() Interlocked.Decrement(ref _queueCount); if (token.IsCancellationRequested) { - // In case of task that is suppose to run when a block is being processed, if there is some time left - // from its deadline, we re-queue it. We do this in case there are some task in the queue that already - // reached deadline during block processing in which case, it will need to execute in order to handle + // In case of a task supposed to run when a block is being processed, if there is some time left + // from its deadline, we re-queue it. We do this in case there is some task in the queue that already + // reached deadline during block processing, in which case, it will need to execute to handle // its cancellation. if (DateTimeOffset.UtcNow < activity.Deadline) { Interlocked.Increment(ref _queueCount); await _taskQueue.Writer.WriteAsync(activity); UpdateQueueCount(); - // Requeued, throttle to prevent infinite loop. - // The tasks are in priority order, so we know next is same deadline or longer - // And we want to exit inner loop to refresh CancellationToken + // Re-queued, throttle to prevent infinite loop. + // The tasks are in priority order, so we know next is the same deadline or longer, + // And we want to exit the inner loop to refresh CancellationToken goto Throttle; } } @@ -158,51 +161,33 @@ private async Task StartChannel() } } - public void ScheduleTask(TReq request, Func fulfillFunc, TimeSpan? timeout = null) + public bool TryScheduleTask(TReq request, Func fulfillFunc, TimeSpan? timeout = null) { - timeout ??= DefaultTimeout; - DateTimeOffset deadline = DateTimeOffset.UtcNow + timeout.Value; - IActivity activity = new Activity { - Deadline = deadline, + Deadline = DateTimeOffset.UtcNow + (timeout ?? DefaultTimeout), Request = request, FulfillFunc = fulfillFunc, }; Evm.Metrics.IncrementTotalBackgroundTasksQueued(); - bool success = false; - lock (_queueLock) + if (Interlocked.Increment(ref _queueCount) <= _capacity) { - if (_queueCount + 1 < _capacity) + if (_taskQueue.Writer.TryWrite(activity)) { - success = _taskQueue.Writer.TryWrite(activity); - if (success) - { - Interlocked.Increment(ref _queueCount); - } + UpdateQueueCount(); + return true; } } - if (success) - { - UpdateQueueCount(); - } - else - { - request.TryDispose(); - // This should never happen unless something goes very wrong. - UnableToWriteToTaskQueue(); - } - - [StackTraceHidden, DoesNotReturn] - static void UnableToWriteToTaskQueue() - => throw new InvalidOperationException("Unable to write to background task queue."); + if (_logger.IsWarn) _logger.Warn($"Background task queue is full (Count: {_queueCount}, Capacity: {_capacity}), dropping task."); + Interlocked.Decrement(ref _queueCount); + request.TryDispose(); + return false; } - private void UpdateQueueCount() - => Evm.Metrics.NumberOfBackgroundTasksScheduled = Volatile.Read(ref _queueCount); + private void UpdateQueueCount() => Evm.Metrics.NumberOfBackgroundTasksScheduled = Volatile.Read(ref _queueCount); public async ValueTask DisposeAsync() { @@ -220,21 +205,11 @@ public async ValueTask DisposeAsync() private readonly struct Activity : IActivity { - private static CancellationToken CancelledToken { get; } = CreateCancelledToken(); - - private static CancellationToken CreateCancelledToken() - { - CancellationTokenSource cts = new(); - cts.Cancel(); - return cts.Token; - } - public DateTimeOffset Deadline { get; init; } public TReq Request { get; init; } public Func FulfillFunc { get; init; } - public int CompareTo(IActivity? other) - => Deadline.CompareTo(other.Deadline); + public int CompareTo(IActivity? other) => Deadline.CompareTo(other?.Deadline ?? DateTimeOffset.MaxValue); public async Task Do(CancellationToken cancellationToken) { @@ -245,7 +220,7 @@ public async Task Do(CancellationToken cancellationToken) if (timeToComplete <= TimeSpan.Zero) { // Cancel immediately. Got no time left. - token = CancelledToken; + token = CancellationTokenExtensions.AlreadyCancelledToken; } else { diff --git a/src/Nethermind/Nethermind.Consensus/Scheduler/IBackgroundTaskScheduler.cs b/src/Nethermind/Nethermind.Consensus/Scheduler/IBackgroundTaskScheduler.cs index b585a58f9bc..b43ec458331 100644 --- a/src/Nethermind/Nethermind.Consensus/Scheduler/IBackgroundTaskScheduler.cs +++ b/src/Nethermind/Nethermind.Consensus/Scheduler/IBackgroundTaskScheduler.cs @@ -9,5 +9,5 @@ namespace Nethermind.Consensus.Scheduler; public interface IBackgroundTaskScheduler { - void ScheduleTask(TReq request, Func fulfillFunc, TimeSpan? timeout = null); + bool TryScheduleTask(TReq request, Func fulfillFunc, TimeSpan? timeout = null); } diff --git a/src/Nethermind/Nethermind.Consensus/Signer.cs b/src/Nethermind/Nethermind.Consensus/Signer.cs index 922fab54f15..66a70c68ad5 100644 --- a/src/Nethermind/Nethermind.Consensus/Signer.cs +++ b/src/Nethermind/Nethermind.Consensus/Signer.cs @@ -40,7 +40,7 @@ public Signer(ulong chainId, IProtectedPrivateKey key, ILogManager logManager) public Signature Sign(in ValueHash256 message) { if (!CanSign) throw new InvalidOperationException("Cannot sign without provided key."); - byte[] rs = SpanSecP256k1.SignCompact(message.Bytes, _key!.KeyBytes, out int v); + byte[] rs = SecP256k1.SignCompact(message.Bytes, _key!.KeyBytes, out int v); return new Signature(rs, v); } diff --git a/src/Nethermind/Nethermind.Consensus/Stateless/StatelessBlockProcessingEnv.cs b/src/Nethermind/Nethermind.Consensus/Stateless/StatelessBlockProcessingEnv.cs index 551e5c665ff..055a46dec12 100644 --- a/src/Nethermind/Nethermind.Consensus/Stateless/StatelessBlockProcessingEnv.cs +++ b/src/Nethermind/Nethermind.Consensus/Stateless/StatelessBlockProcessingEnv.cs @@ -1,31 +1,22 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Generic; -using System.Linq; using Nethermind.Blockchain; using Nethermind.Blockchain.BeaconBlockRoot; using Nethermind.Blockchain.Blocks; -using Nethermind.Blockchain.Find; -using Nethermind.Blockchain.Headers; using Nethermind.Blockchain.Receipts; using Nethermind.Consensus.ExecutionRequests; using Nethermind.Consensus.Processing; using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Validators; using Nethermind.Consensus.Withdrawals; -using Nethermind.Core; -using Nethermind.Core.Crypto; using Nethermind.Core.Specs; -using Nethermind.Db; using Nethermind.Evm; using Nethermind.Evm.State; using Nethermind.Evm.TransactionProcessing; using Nethermind.Logging; using Nethermind.State; using Nethermind.Trie; -using Nethermind.Trie.Pruning; namespace Nethermind.Consensus.Stateless; @@ -45,8 +36,8 @@ public IBlockProcessor BlockProcessor public IWorldState WorldState { get => _worldState ??= new WorldState( - new RawTrieStore(witness.NodeStorage), - witness.CodeDb, logManager); + new TrieStoreScopeProvider(new RawTrieStore(witness.NodeStorage), + witness.CodeDb, logManager), logManager); } private IBlockProcessor GetProcessor() @@ -81,7 +72,7 @@ private IBlockProcessor GetProcessor() private ITransactionProcessor CreateTransactionProcessor(IWorldState state, IBlockhashCache blockhashCache) { BlockhashProvider blockhashProvider = new(blockhashCache, state, logManager); - VirtualMachine vm = new(blockhashProvider, specProvider, logManager); - return new TransactionProcessor(BlobBaseFeeCalculator.Instance, specProvider, state, vm, new EthereumCodeInfoRepository(state), logManager); + EthereumVirtualMachine vm = new(blockhashProvider, specProvider, logManager); + return new EthereumTransactionProcessor(BlobBaseFeeCalculator.Instance, specProvider, state, vm, new EthereumCodeInfoRepository(state), logManager); } } diff --git a/src/Nethermind/Nethermind.Consensus/Stateless/Witness.cs b/src/Nethermind/Nethermind.Consensus/Stateless/Witness.cs index e6a139d4cb9..30ccd459154 100644 --- a/src/Nethermind/Nethermind.Consensus/Stateless/Witness.cs +++ b/src/Nethermind/Nethermind.Consensus/Stateless/Witness.cs @@ -7,10 +7,8 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Db; -using Nethermind.Logging; using Nethermind.Serialization.Rlp; using Nethermind.Trie; -using Nethermind.Trie.Pruning; namespace Nethermind.Consensus.Stateless; diff --git a/src/Nethermind/Nethermind.Consensus/Tracing/BlockProcessorFacade.cs b/src/Nethermind/Nethermind.Consensus/Tracing/BlockProcessorFacade.cs index 224f2ae63b7..fabb1b7d57c 100644 --- a/src/Nethermind/Nethermind.Consensus/Tracing/BlockProcessorFacade.cs +++ b/src/Nethermind/Nethermind.Consensus/Tracing/BlockProcessorFacade.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Threading; -using Nethermind.Blockchain; using Nethermind.Consensus.Processing; using Nethermind.Core; using Nethermind.Core.Specs; @@ -27,14 +26,8 @@ CompositeBlockPreprocessorStep preprocessorStep preprocessorStep.RecoverData(block); IReleaseSpec spec = specProvider.GetSpec(block.Header); - try - { - (Block? processedBlock, TxReceipt[] _) = blockProcessor.ProcessOne(block, options, tracer, spec, token); - return processedBlock; - } - catch (InvalidBlockException) - { - return null; - } + + (Block? processedBlock, TxReceipt[] _) = blockProcessor.ProcessOne(block, options, tracer, spec, token); + return processedBlock; } } diff --git a/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs b/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs index 3a1bb9d7be6..83833ef5631 100644 --- a/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs +++ b/src/Nethermind/Nethermind.Consensus/Tracing/GethStyleTracer.cs @@ -174,7 +174,7 @@ public IEnumerable TraceBadBlockToFile(Hash256 blockHash, GethTraceOptio // Previously, when the processing options is not `TraceTransaction`, the base block is the parent of the block // which is set by the `BranchProcessor`, which mean the state override probably does not take affect. - // However, when it is `TraceTransactioon`, it apply `ForceSameBlock` to `BlockchainProcessor` which will send the same + // However, when it is `TraceTransaction`, it applies `ForceSameBlock` to `BlockchainProcessor`, which will send the same // block as the baseBlock, which is important as the stateroot of the baseblock is modified in `BuildAndOverride`. // // Wild stuff! diff --git a/src/Nethermind/Nethermind.Consensus/Tracing/ITracer.cs b/src/Nethermind/Nethermind.Consensus/Tracing/ITracer.cs index 95478961c51..898c20c4496 100644 --- a/src/Nethermind/Nethermind.Consensus/Tracing/ITracer.cs +++ b/src/Nethermind/Nethermind.Consensus/Tracing/ITracer.cs @@ -27,6 +27,6 @@ public interface ITracer /// Trace to act on block processing events. void Execute(Block block, IBlockTracer tracer); - void Accept(ITreeVisitor visitor, Hash256 stateRoot) where TCtx : struct, INodeContext; + void Accept(ITreeVisitor visitor, BlockHeader? baseBlock) where TCtx : struct, INodeContext; } } diff --git a/src/Nethermind/Nethermind.Consensus/Tracing/Tracer.cs b/src/Nethermind/Nethermind.Consensus/Tracing/Tracer.cs index 92e60ae2bdf..680e1495789 100644 --- a/src/Nethermind/Nethermind.Consensus/Tracing/Tracer.cs +++ b/src/Nethermind/Nethermind.Consensus/Tracing/Tracer.cs @@ -33,12 +33,12 @@ We also want to make it read only so the state is not modified persistently in a public void Execute(Block block, IBlockTracer tracer) => Process(block, tracer, executeProcessor, executeOptions); - public void Accept(ITreeVisitor visitor, Hash256 stateRoot) where TCtx : struct, INodeContext + public void Accept(ITreeVisitor visitor, BlockHeader? baseBlock) where TCtx : struct, INodeContext { ArgumentNullException.ThrowIfNull(visitor); - ArgumentNullException.ThrowIfNull(stateRoot); + ArgumentNullException.ThrowIfNull(baseBlock); - stateReader.RunTreeVisitor(visitor, stateRoot); + stateReader.RunTreeVisitor(visitor, baseBlock); } } } diff --git a/src/Nethermind/Nethermind.Consensus/Transactions/BaseFeeTxFilter.cs b/src/Nethermind/Nethermind.Consensus/Transactions/BaseFeeTxFilter.cs index 8e1ba000575..614ca9b63a3 100644 --- a/src/Nethermind/Nethermind.Consensus/Transactions/BaseFeeTxFilter.cs +++ b/src/Nethermind/Nethermind.Consensus/Transactions/BaseFeeTxFilter.cs @@ -13,11 +13,15 @@ public class BaseFeeTxFilter : ITxFilter { public AcceptTxResult IsAllowed(Transaction tx, BlockHeader parentHeader, IReleaseSpec spec) { - UInt256 baseFee = BaseFeeCalculator.Calculate(parentHeader, spec); bool isEip1559Enabled = spec.IsEip1559Enabled; - bool skipCheck = tx.IsServiceTransaction || !isEip1559Enabled; - bool allowed = skipCheck || tx.MaxFeePerGas >= baseFee; + if (!isEip1559Enabled || tx.IsServiceTransaction) + { + return AcceptTxResult.Accepted; + } + + UInt256 baseFee = BaseFeeCalculator.Calculate(parentHeader, spec); + bool allowed = tx.MaxFeePerGas >= baseFee; return allowed ? AcceptTxResult.Accepted : AcceptTxResult.FeeTooLow.WithMessage( diff --git a/src/Nethermind/Nethermind.Consensus/TxFilterBuilders.cs b/src/Nethermind/Nethermind.Consensus/TxFilterBuilders.cs index 39bd8807bf6..294b4f667d5 100644 --- a/src/Nethermind/Nethermind.Consensus/TxFilterBuilders.cs +++ b/src/Nethermind/Nethermind.Consensus/TxFilterBuilders.cs @@ -3,7 +3,6 @@ using Nethermind.Config; using Nethermind.Consensus.Transactions; -using Nethermind.Core.Specs; namespace Nethermind.Consensus { diff --git a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs index fdca5665fbb..adf89564515 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/BlockValidator.cs @@ -132,13 +132,22 @@ private bool ValidateBlockSize(Block block, IReleaseSpec spec, ref string? error if (spec.BlockLevelAccessListsEnabled) { - if (block.BlockAccessList is null || block.BlockAccessListHash is null) + // Genesis blocks don't have a BlockAccessList (only the hash of an empty list) + bool isGenesis = block.IsGenesis; + if (!isGenesis && (block.BlockAccessList is null || block.BlockAccessListHash is null)) { if (_logger.IsDebug) _logger.Debug($"{Invalid(block)} Block-level access list was missing or empty"); errorMessage = BlockErrorMessages.InvalidBlockLevelAccessList; return false; } + if (isGenesis && block.BlockAccessListHash is null) + { + if (_logger.IsDebug) _logger.Debug($"{Invalid(block)} Genesis block missing BlockAccessListHash"); + errorMessage = BlockErrorMessages.InvalidBlockLevelAccessList; + return false; + } + // try // { // block.DecodedBlockAccessList = Rlp.Decode(block.BlockAccessList); diff --git a/src/Nethermind/Nethermind.Consensus/Validators/HeaderValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/HeaderValidator.cs index 1f3d352dc10..3236ae5fa5d 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/HeaderValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/HeaderValidator.cs @@ -29,7 +29,7 @@ public class HeaderValidator( protected readonly ISpecProvider _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); private readonly long? _daoBlockNumber = specProvider.DaoBlockNumber; protected readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - private readonly IBlockTree _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); + protected readonly IBlockTree _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); public static bool ValidateHash(BlockHeader header, out Hash256 actualHash) { diff --git a/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs b/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs index f1bb22feeea..14467894203 100644 --- a/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs +++ b/src/Nethermind/Nethermind.Consensus/Validators/TxValidator.cs @@ -124,7 +124,7 @@ private IntrinsicGasTxValidator() { } public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec releaseSpec) { // This is unnecessarily calculated twice - at validation and execution times. - IntrinsicGas intrinsicGas = IntrinsicGasCalculator.Calculate(transaction, releaseSpec); + EthereumIntrinsicGas intrinsicGas = IntrinsicGasCalculator.Calculate(transaction, releaseSpec); return transaction.GasLimit < intrinsicGas.MinimalGas ? TxErrorMessages.IntrinsicGasTooLow : ValidationResult.Success; @@ -281,15 +281,15 @@ public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec relea return transaction switch { { NetworkWrapper: null } => ValidationResult.Success, - { Type: TxType.Blob, NetworkWrapper: ShardBlobNetworkWrapper wrapper } => ValidateBlobs(transaction, wrapper, releaseSpec), + { Type: TxType.Blob, NetworkWrapper: ShardBlobNetworkWrapper wrapper } => ValidateBlobs(transaction, wrapper), { Type: TxType.Blob } or { NetworkWrapper: not null } => TxErrorMessages.InvalidTransactionForm, }; - static ValidationResult ValidateBlobs(Transaction transaction, ShardBlobNetworkWrapper wrapper, IReleaseSpec _) + static ValidationResult ValidateBlobs(Transaction transaction, ShardBlobNetworkWrapper wrapper) { IBlobProofsVerifier proofsManager = IBlobProofsManager.For(wrapper.Version); - return !proofsManager.ValidateLengths(wrapper) ? TxErrorMessages.InvalidBlobDataSize : + return (transaction.BlobVersionedHashes?.Length ?? 0) != wrapper.Blobs.Length || !proofsManager.ValidateLengths(wrapper) ? TxErrorMessages.InvalidBlobDataSize : transaction.BlobVersionedHashes is null || !proofsManager.ValidateHashes(wrapper, transaction.BlobVersionedHashes) ? TxErrorMessages.InvalidBlobHashes : !proofsManager.ValidateProofs(wrapper) ? TxErrorMessages.InvalidBlobProofs : ValidationResult.Success; diff --git a/src/Nethermind/Nethermind.Core.Test/AddressTests.cs b/src/Nethermind/Nethermind.Core.Test/AddressTests.cs index bb029f46be5..1eee046ad3c 100644 --- a/src/Nethermind/Nethermind.Core.Test/AddressTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/AddressTests.cs @@ -183,6 +183,8 @@ public bool Is_PointEvaluationPrecompile_properly_activated(IReleaseSpec spec) = [TestCase(Address.SystemUserHex, false)] [TestCase("2" + Address.SystemUserHex, false)] [TestCase("2" + Address.SystemUserHex, true)] + [TestCase("0x00" + Address.SystemUserHex, true)] + [TestCase("0x00fffffffffffffffffffffffffffffffffffffffe", true)] public void Parse_variable_length(string addressHex, bool allowOverflow) { var result = Address.TryParseVariableLength(addressHex, out Address? address, allowOverflow); diff --git a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs index de732591010..b1c2ca7a937 100644 --- a/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs +++ b/src/Nethermind/Nethermind.Core.Test/Blockchain/TestBlockchain.cs @@ -47,7 +47,7 @@ namespace Nethermind.Core.Test.Blockchain; public class TestBlockchain : IDisposable { public const int DefaultTimeout = 10000; - protected long TestTimout { get; init; } = DefaultTimeout; + protected long TestTimeout { get; init; } = DefaultTimeout; public IStateReader StateReader => _fromContainer.StateReader; public IEthereumEcdsa EthereumEcdsa => _fromContainer.EthereumEcdsa; public INonceManager NonceManager => _fromContainer.NonceManager; @@ -57,6 +57,7 @@ public class TestBlockchain : IDisposable public ITxPool TxPool => _fromContainer.TxPool; public IForkInfo ForkInfo => _fromContainer.ForkInfo; public IWorldStateManager WorldStateManager => _fromContainer.WorldStateManager; + public IWorldState MainWorldState => MainProcessingContext.WorldState; public IReadOnlyTxProcessingEnvFactory ReadOnlyTxProcessingEnvFactory => _fromContainer.ReadOnlyTxProcessingEnvFactory; public IShareableTxProcessorSource ShareableTxProcessorSource => _fromContainer.ShareableTxProcessorSource; public IBranchProcessor BranchProcessor => _fromContainer.MainProcessingContext.BranchProcessor; @@ -323,7 +324,7 @@ public Block Build() // Eip2935 if (specProvider.GenesisSpec.IsBlockHashInStateAvailable) { - state.CreateAccount(specProvider.GenesisSpec.Eip2935ContractAddress, 1); + state.CreateAccount(specProvider.GenesisSpec.Eip2935ContractAddress!, 1); } state.CreateAccount(TestItem.AddressA, testConfiguration.AccountInitialValue); @@ -393,7 +394,7 @@ public Block Build() protected virtual AutoCancelTokenSource CreateCancellationSource() { - return AutoCancelTokenSource.ThatCancelAfter(Debugger.IsAttached ? TimeSpan.FromMilliseconds(-1) : TimeSpan.FromMilliseconds(TestTimout)); + return AutoCancelTokenSource.ThatCancelAfter(Debugger.IsAttached ? TimeSpan.FromMilliseconds(-1) : TimeSpan.FromMilliseconds(TestTimeout)); } protected virtual async Task AddBlocksOnStart() diff --git a/src/Nethermind/Nethermind.Core.Test/BloomTests.cs b/src/Nethermind/Nethermind.Core.Test/BloomTests.cs index 231b6353f7b..18b11fbfa93 100644 --- a/src/Nethermind/Nethermind.Core.Test/BloomTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/BloomTests.cs @@ -38,7 +38,7 @@ public void matches_previously_added_item(int count, int topicMax) [TestCase(10, 1)] [TestCase(10, 10)] [TestCase(100, 1)] - public void doesnt_match_not_added_item(int count, int topicMax) + public void does_not_match_not_added_item(int count, int topicMax) { MatchingTest(() => GetLogEntries(count, topicMax), addedEntries => GetLogEntries(count, topicMax, @@ -46,7 +46,7 @@ public void doesnt_match_not_added_item(int count, int topicMax) } [Test] - public void empty_doesnt_match_any_item() + public void empty_does_not_match_any_item() { MatchingTest(Array.Empty, static addedEntries => GetLogEntries(100, 10), false); } diff --git a/src/Nethermind/Nethermind.Core.Test/BytesTests.cs b/src/Nethermind/Nethermind.Core.Test/BytesTests.cs index b4ddca6e783..f3371d834c1 100644 --- a/src/Nethermind/Nethermind.Core.Test/BytesTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/BytesTests.cs @@ -463,7 +463,7 @@ public void Xor(byte[] first, byte[] second, byte[] expected) } [Test] - public void NullableComparision() + public void NullableComparison() { Bytes.NullableEqualityComparer.Equals(null, null).Should().BeTrue(); } diff --git a/src/Nethermind/Nethermind.Core.Test/Container/ContainerBuilderExtensions.cs b/src/Nethermind/Nethermind.Core.Test/Container/ContainerBuilderExtensions.cs index e49a3b60427..7c47d05007b 100644 --- a/src/Nethermind/Nethermind.Core.Test/Container/ContainerBuilderExtensions.cs +++ b/src/Nethermind/Nethermind.Core.Test/Container/ContainerBuilderExtensions.cs @@ -5,6 +5,7 @@ using Autofac; using Autofac.Core; using Nethermind.Blockchain; +using Nethermind.Core.Specs; using Nethermind.Evm.State; using Nethermind.Specs.ChainSpecStyle; @@ -92,4 +93,13 @@ public static ContainerBuilder WithGenesisPostProcessor(this ContainerBuilder bu }); }); } + + public static ContainerBuilder WithGenesisPostProcessor(this ContainerBuilder builder, + Action postProcessor) + { + return builder.AddScoped((worldState, specProvider) => new FunctionalGenesisPostProcessor((block) => + { + postProcessor(block, worldState, specProvider); + })); + } } diff --git a/src/Nethermind/Nethermind.Core.Test/Crypto/SignatureTests.cs b/src/Nethermind/Nethermind.Core.Test/Crypto/SignatureTests.cs index d810fd16248..939457a1f4d 100644 --- a/src/Nethermind/Nethermind.Core.Test/Crypto/SignatureTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Crypto/SignatureTests.cs @@ -41,7 +41,7 @@ public void can_recover_from_message() var signatureObject = new Signature(signatureSlice, recoveryId); var keccak = Keccak.Compute(Bytes.Concat(messageType, data)); Span publicKey = stackalloc byte[65]; - bool result = SpanSecP256k1.RecoverKeyFromCompact(publicKey, keccak.Bytes, signatureObject.Bytes.ToArray(), signatureObject.RecoveryId, false); + bool result = SecP256k1.RecoverKeyFromCompact(publicKey, keccak.Bytes, signatureObject.Bytes, signatureObject.RecoveryId, false); result.Should().BeTrue(); } } diff --git a/src/Nethermind/Nethermind.Core.Test/Db/TestMemDbProvider.cs b/src/Nethermind/Nethermind.Core.Test/Db/TestMemDbProvider.cs index b13c69f5aff..c0fa78e4d9b 100644 --- a/src/Nethermind/Nethermind.Core.Test/Db/TestMemDbProvider.cs +++ b/src/Nethermind/Nethermind.Core.Test/Db/TestMemDbProvider.cs @@ -10,6 +10,7 @@ using Nethermind.Blockchain.Synchronization; using Nethermind.Db; using Nethermind.Init.Modules; +using Nethermind.Monitoring; namespace Nethermind.Core.Test.Db { @@ -23,7 +24,11 @@ public static Task InitAsync() public static IDbProvider Init() { return new ContainerBuilder() - .AddModule(new DbModule(new InitConfig() { DiagnosticMode = DiagnosticMode.MemDb }, new ReceiptConfig(), new SyncConfig())) + .AddModule(new DbModule( + new InitConfig() { DiagnosticMode = DiagnosticMode.MemDb }, + new ReceiptConfig(), + new SyncConfig() + )) .AddSingleton() .Build() .Resolve(); diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/CompactReceiptStorageDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/CompactReceiptStorageDecoderTests.cs index 6c63361f2b8..3b12e39b849 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/CompactReceiptStorageDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/CompactReceiptStorageDecoderTests.cs @@ -254,7 +254,7 @@ private void AssertStorageReceipt(TxReceipt txReceipt, TxReceipt? deserialized) Assert.That(deserialized?.StatusCode, Is.EqualTo(txReceipt.StatusCode), "status"); } - private void AssertStorageLegaxyReceipt(TxReceipt txReceipt, TxReceipt deserialized) + private void AssertStorageLegacyReceipt(TxReceipt txReceipt, TxReceipt deserialized) { Assert.That(deserialized.TxType, Is.EqualTo(txReceipt.TxType), "tx type"); Assert.That(deserialized.BlockHash, Is.EqualTo(txReceipt.BlockHash), "block hash"); diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/HeaderDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/HeaderDecoderTests.cs index c60d4e76442..e4942c21b16 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/HeaderDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/HeaderDecoderTests.cs @@ -192,7 +192,7 @@ public void Can_encode_decode_with_ValidatorExitRoot_equals_to_null() } [Test] - public void Can_encode_decode_with_missing_excess_blob_gass() + public void Can_encode_decode_with_missing_excess_blob_gas() { BlockHeader header = Build.A.BlockHeader .WithHash(new Hash256("0x3d8b9cc98eee58243461bd5a83663384b50293cd1e459a6841cb005296305590")) diff --git a/src/Nethermind/Nethermind.Core.Test/Encoding/ShardBlobTxDecoderTests.cs b/src/Nethermind/Nethermind.Core.Test/Encoding/ShardBlobTxDecoderTests.cs index 61ceea3d2bf..e5099188283 100644 --- a/src/Nethermind/Nethermind.Core.Test/Encoding/ShardBlobTxDecoderTests.cs +++ b/src/Nethermind/Nethermind.Core.Test/Encoding/ShardBlobTxDecoderTests.cs @@ -43,6 +43,17 @@ public void Roundtrip_ExecutionPayloadForm_for_shard_blobs((Transaction Tx, stri decoded.Should().BeEquivalentTo(testCase.Tx, testCase.Description); } + [Test] + public void TestDecodeTamperedBlob() + { + var bytes = Bytes.FromHexString( + "b8aa03f8a7018001808252089400000000000000000000000000000000000000000180c001f841a00100000000000000000000000000000000000000000000000000000000000000a0010000000000000000000000000000000000000000000000000000000000000080a00fb9ad625df88e2fea9e088b69a31497f0d9b767067db8c03fd2453d7092e7bfa0086f2930db968d992d0fb06ddc903ca5522ba38bedc0530eb28b61082897efa1"); + var stream = new RlpStream(bytes); + + var tryDecode = () => _txDecoder.Decode(stream); + tryDecode.Should().Throw(); + } + [TestCaseSource(nameof(TestCaseSource))] public void Roundtrip_ValueDecoderContext_ExecutionPayloadForm_for_shard_blobs((Transaction Tx, string Description) testCase) { @@ -59,6 +70,46 @@ public void Roundtrip_ValueDecoderContext_ExecutionPayloadForm_for_shard_blobs(( decoded.Should().BeEquivalentTo(testCase.Tx, testCase.Description); } + private static IEnumerable TamperedTestCaseSource() + { + yield return Build.A.Transaction + .WithShardBlobTxTypeAndFields(2, false) + .WithChainId(TestBlockchainIds.ChainId) + .SignedAndResolved() + .TestObject; + yield return Build.A.Transaction + .WithShardBlobTxTypeAndFields(2, false) + .WithChainId(TestBlockchainIds.ChainId) + .WithNonce(0) + .SignedAndResolved() + .TestObject; + } + + [TestCaseSource(nameof(TamperedTestCaseSource))] + public void Tampered_Roundtrip_ExecutionPayloadForm_for_shard_blobs(Transaction tx) + { + var stream = new RlpStream(_txDecoder.GetLength(tx, RlpBehaviors.None)); + _txDecoder.Encode(stream, tx); + // Tamper with sequence length + { + var itemsLength = 0; + foreach (var array in tx.BlobVersionedHashes!) + { + itemsLength += Rlp.LengthOf(array); + } + + // Position where it starts encoding `BlobVersionedHashes` + stream.Position = 37; + // Accepts `itemsLength - 10` all the way to `itemsLength - 1` + stream.StartSequence(itemsLength - 1); + } + stream.Position = 0; + + // Decoding should fail + var tryDecode = () => _txDecoder.Decode(stream); + tryDecode.Should().Throw(); + } + [TestCaseSource(nameof(ShardBlobTxTests))] public void NetworkWrapper_is_decoded_correctly(string rlp, Hash256 signedHash, RlpBehaviors rlpBehaviors) { diff --git a/src/Nethermind/Nethermind.Core.Test/FixedBlockChainHeadSpecProvider.cs b/src/Nethermind/Nethermind.Core.Test/FixedBlockChainHeadSpecProvider.cs index 30b0ca5f7e6..49b43aaf566 100644 --- a/src/Nethermind/Nethermind.Core.Test/FixedBlockChainHeadSpecProvider.cs +++ b/src/Nethermind/Nethermind.Core.Test/FixedBlockChainHeadSpecProvider.cs @@ -23,7 +23,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public IReleaseSpec GenesisSpec => specProvider.GenesisSpec; - IReleaseSpec ISpecProvider.GetSpecInternal(ForkActivation forkActivation) => specProvider.GetSpec(forkActivation); + public IReleaseSpec GetSpec(ForkActivation forkActivation) => specProvider.GetSpec(forkActivation); public long? DaoBlockNumber => specProvider.DaoBlockNumber; diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/LocalChannelFactory.cs b/src/Nethermind/Nethermind.Core.Test/Modules/LocalChannelFactory.cs index b25817faa58..f931caedcc0 100644 --- a/src/Nethermind/Nethermind.Core.Test/Modules/LocalChannelFactory.cs +++ b/src/Nethermind/Nethermind.Core.Test/Modules/LocalChannelFactory.cs @@ -90,7 +90,7 @@ protected override void DoBind(EndPoint localAddress) } } - // Needed because the default local address did not compare the id and because it need to be convertiable to + // Needed because the default local address did not compare the ID and because it needs to be convertible to // IPEndpoint private class NethermindLocalAddress(string id, IPEndPoint ipEndPoint) : LocalAddress(id), IIPEndpointSource { diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/TestBlockProcessingModule.cs b/src/Nethermind/Nethermind.Core.Test/Modules/TestBlockProcessingModule.cs index 06098ae038c..ada4a68da99 100644 --- a/src/Nethermind/Nethermind.Core.Test/Modules/TestBlockProcessingModule.cs +++ b/src/Nethermind/Nethermind.Core.Test/Modules/TestBlockProcessingModule.cs @@ -20,7 +20,7 @@ protected override void Load(ContainerBuilder builder) { builder .AddSingleton() - // NOTE: The ordering of block preprocessor is not guarenteed + // NOTE: The ordering of block preprocessors is not guaranteed .AddComposite() .AddSingleton() .AddSingleton() diff --git a/src/Nethermind/Nethermind.Core.Test/Modules/TestEnvironmentModule.cs b/src/Nethermind/Nethermind.Core.Test/Modules/TestEnvironmentModule.cs index b9cd9fca88a..12d5c54a532 100644 --- a/src/Nethermind/Nethermind.Core.Test/Modules/TestEnvironmentModule.cs +++ b/src/Nethermind/Nethermind.Core.Test/Modules/TestEnvironmentModule.cs @@ -42,7 +42,7 @@ protected override void Load(ContainerBuilder builder) builder .AddSingleton(new TestLogManager(LogLevel.Error)) // Limbologs actually have IsTrace set to true, so actually slow. .AddSingleton((_) => new MemDbFactory()) - // These two dont use db provider + // These two don't use the DB provider .AddKeyedSingleton(DbNames.PeersDb, (_) => new MemDb()) .AddKeyedSingleton(DbNames.DiscoveryNodes, (_) => new MemDb()) .AddSingleton(networkConfig => new LocalChannelFactory(networkGroup ?? nameof(TestEnvironmentModule), networkConfig)) diff --git a/src/Nethermind/Nethermind.Core.Test/RunImmediatelyScheduler.cs b/src/Nethermind/Nethermind.Core.Test/RunImmediatelyScheduler.cs index 1056e8dbd39..cb81087435d 100644 --- a/src/Nethermind/Nethermind.Core.Test/RunImmediatelyScheduler.cs +++ b/src/Nethermind/Nethermind.Core.Test/RunImmediatelyScheduler.cs @@ -16,8 +16,9 @@ private RunImmediatelyScheduler() { } - public void ScheduleTask(TReq request, Func fulfillFunc, TimeSpan? timeout = null) + public bool TryScheduleTask(TReq request, Func fulfillFunc, TimeSpan? timeout = null) { fulfillFunc(request, CancellationToken.None); + return true; } } diff --git a/src/Nethermind/Nethermind.Core.Test/TestMemColumnDb.cs b/src/Nethermind/Nethermind.Core.Test/TestMemColumnDb.cs index 529df1fdfc1..630f69bd37f 100644 --- a/src/Nethermind/Nethermind.Core.Test/TestMemColumnDb.cs +++ b/src/Nethermind/Nethermind.Core.Test/TestMemColumnDb.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; using Nethermind.Db; @@ -30,6 +31,12 @@ public IColumnsWriteBatch StartWriteBatch() { return new InMemoryColumnWriteBatch(this); } + + public IColumnDbSnapshot CreateSnapshot() + { + throw new NotSupportedException("Snapshot not implemented"); + } + public void Dispose() { } public void Flush(bool onlyWal = false) { } } diff --git a/src/Nethermind/Nethermind.Core.Test/TestRawTrieStore.cs b/src/Nethermind/Nethermind.Core.Test/TestRawTrieStore.cs index a31eb627a2d..67e44e3e28f 100644 --- a/src/Nethermind/Nethermind.Core.Test/TestRawTrieStore.cs +++ b/src/Nethermind/Nethermind.Core.Test/TestRawTrieStore.cs @@ -44,7 +44,7 @@ public event EventHandler? ReorgBoundaryReached remove => throw new Exception("Unsupported operation"); } - public IReadOnlyKeyValueStore TrieNodeRlpStore => throw new Exception("Unsupported operatioon"); + public IReadOnlyKeyValueStore TrieNodeRlpStore => throw new Exception("Unsupported operation"); private Lock _scopeLock = new Lock(); private Lock _pruneLock = new Lock(); diff --git a/src/Nethermind/Nethermind.Core.Test/TestWorldStateFactory.cs b/src/Nethermind/Nethermind.Core.Test/TestWorldStateFactory.cs index b6295ac8bec..e3d2ffc6d15 100644 --- a/src/Nethermind/Nethermind.Core.Test/TestWorldStateFactory.cs +++ b/src/Nethermind/Nethermind.Core.Test/TestWorldStateFactory.cs @@ -14,15 +14,12 @@ namespace Nethermind.Core.Test; public static class TestWorldStateFactory { - public static IWorldState CreateForTest() - { - return CreateForTest(TestMemDbProvider.Init(), LimboLogs.Instance); - } - - public static IWorldState CreateForTest(IDbProvider dbProvider, ILogManager logManager) + public static IWorldState CreateForTest(IDbProvider? dbProvider = null, ILogManager? logManager = null) { PruningConfig pruningConfig = new PruningConfig(); TestFinalizedStateProvider finalizedStateProvider = new TestFinalizedStateProvider(pruningConfig.PruningBoundary); + dbProvider ??= TestMemDbProvider.Init(); + logManager ??= LimboLogs.Instance; TrieStore trieStore = new TrieStore( new NodeStorage(dbProvider.StateDb), No.Pruning, @@ -31,7 +28,7 @@ public static IWorldState CreateForTest(IDbProvider dbProvider, ILogManager logM pruningConfig, LimboLogs.Instance); finalizedStateProvider.TrieStore = trieStore; - return new WorldState(trieStore, dbProvider.CodeDb, logManager); + return new WorldState(new TrieStoreScopeProvider(trieStore, dbProvider.CodeDb, logManager), logManager); } public static (IWorldState, IStateReader) CreateForTestWithStateReader(IDbProvider? dbProvider = null, ILogManager? logManager = null) @@ -49,7 +46,7 @@ public static (IWorldState, IStateReader) CreateForTestWithStateReader(IDbProvid pruningConfig, LimboLogs.Instance); finalizedStateProvider.TrieStore = trieStore; - return (new WorldState(trieStore, dbProvider.CodeDb, logManager), new StateReader(trieStore, dbProvider.CodeDb, logManager)); + return (new WorldState(new TrieStoreScopeProvider(trieStore, dbProvider.CodeDb, logManager), logManager), new StateReader(trieStore, dbProvider.CodeDb, logManager)); } public static WorldStateManager CreateWorldStateManagerForTest(IDbProvider dbProvider, ILogManager logManager) @@ -64,7 +61,7 @@ public static WorldStateManager CreateWorldStateManagerForTest(IDbProvider dbPro pruningConfig, LimboLogs.Instance); finalizedStateProvider.TrieStore = trieStore; - var worldState = new WorldState(trieStore, dbProvider.CodeDb, logManager); + var worldState = new TrieStoreScopeProvider(trieStore, dbProvider.CodeDb, logManager); return new WorldStateManager(worldState, trieStore, dbProvider, logManager); } diff --git a/src/Nethermind/Nethermind.Core/Account.cs b/src/Nethermind/Nethermind.Core/Account.cs index ab7b335ff02..d0166e228f2 100644 --- a/src/Nethermind/Nethermind.Core/Account.cs +++ b/src/Nethermind/Nethermind.Core/Account.cs @@ -127,6 +127,9 @@ public Account WithChangedCodeHash(Hash256 newCodeHash) } public AccountStruct ToStruct() => new(Nonce, Balance, StorageRoot, CodeHash); + + public override string ToString() => + $"[Account|N:{Nonce}|B:{Balance}|S:{StorageRoot}|C:{CodeHash}]"; } public readonly struct AccountStruct : IEquatable diff --git a/src/Nethermind/Nethermind.Core/AccountStateProviderExtensions.cs b/src/Nethermind/Nethermind.Core/AccountStateProviderExtensions.cs deleted file mode 100644 index 23ba0f6de61..00000000000 --- a/src/Nethermind/Nethermind.Core/AccountStateProviderExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System.Runtime.CompilerServices; - -namespace Nethermind.Core -{ - [SkipLocalsInit] - public static class AccountStateProviderExtensions - { - public static bool HasCode(this IAccountStateProvider stateProvider, Address address) => - stateProvider.TryGetAccount(address, out AccountStruct account) && account.HasCode; - } -} diff --git a/src/Nethermind/Nethermind.Core/Address.cs b/src/Nethermind/Nethermind.Core/Address.cs index d20e96be38d..0bad6dd8360 100644 --- a/src/Nethermind/Nethermind.Core/Address.cs +++ b/src/Nethermind/Nethermind.Core/Address.cs @@ -109,7 +109,7 @@ public static bool TryParseVariableLength(string? value, out Address? address, b { if (allowOverflow) { - span = span[(value.Length - size)..]; + span = span[(span.Length - size)..]; } else { diff --git a/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs b/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs index 21c8ce26d02..9ea0853c183 100644 --- a/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs +++ b/src/Nethermind/Nethermind.Core/Authentication/JwtAuthentication.cs @@ -2,10 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; @@ -16,22 +18,31 @@ namespace Nethermind.Core.Authentication; public sealed partial class JwtAuthentication : IRpcAuthentication { + private const string JwtMessagePrefix = "Bearer "; + private const int JwtTokenTtl = 60; + private const int JwtSecretLength = 64; + + private static readonly Task True = Task.FromResult(true); private static readonly Task False = Task.FromResult(false); + private readonly JsonWebTokenHandler _handler = new(); private readonly SecurityKey _securityKey; private readonly ILogger _logger; private readonly ITimestamper _timestamper; - private const string JwtMessagePrefix = "Bearer "; - private const int JwtTokenTtl = 60; - private const int JwtSecretLength = 64; + private readonly LifetimeValidator _lifetimeValidator; + + // Single entry cache: last successfully validated token + private TokenCacheEntry? _lastToken; private JwtAuthentication(byte[] secret, ITimestamper timestamper, ILogger logger) { ArgumentNullException.ThrowIfNull(secret); + ArgumentNullException.ThrowIfNull(timestamper); _securityKey = new SymmetricSecurityKey(secret); _logger = logger; - _timestamper = timestamper ?? throw new ArgumentNullException(nameof(timestamper)); + _timestamper = timestamper; + _lifetimeValidator = LifetimeValidator; } public static JwtAuthentication FromSecret(string secret, ITimestamper timestamper, ILogger logger) @@ -118,6 +129,14 @@ public Task Authenticate(string? token) return False; } + // fast path - reuse last successful validation for the same token + // we keep it very cheap: one time read, one cache read, one string compare + long nowUnixSeconds = _timestamper.UtcNow.ToUnixTimeSeconds(); + if (TryLastValidationFromCache(token, nowUnixSeconds)) + { + return True; + } + return AuthenticateCore(token); [MethodImpl(MethodImplOptions.NoInlining)] @@ -138,7 +157,7 @@ private async Task AuthenticateCore(string token) ValidateLifetime = true, ValidateAudience = false, ValidateIssuer = false, - LifetimeValidator = LifetimeValidator + LifetimeValidator = _lifetimeValidator }; ReadOnlyMemory tokenSlice = token.AsMemory(JwtMessagePrefix.Length); @@ -152,8 +171,12 @@ private async Task AuthenticateCore(string token) } DateTime now = _timestamper.UtcNow; - if (Math.Abs(jwtToken.IssuedAt.ToUnixTimeSeconds() - now.ToUnixTimeSeconds()) <= JwtTokenTtl) + long issuedAtUnix = jwtToken.IssuedAt.ToUnixTimeSeconds(); + if (Math.Abs(issuedAtUnix - now.ToUnixTimeSeconds()) <= JwtTokenTtl) { + // full validation succeeded and TTL check passed - cache as last valid token + CacheLastToken(token, issuedAtUnix); + if (_logger.IsTrace) Trace(jwtToken, now, tokenSlice); return true; } @@ -210,6 +233,44 @@ private bool LifetimeValidator( return _timestamper.UnixTime.SecondsLong < expires.Value.ToUnixTimeSeconds(); } + private void CacheLastToken(string token, long issuedAtUnixSeconds) + { + TokenCacheEntry entry = new(token, issuedAtUnixSeconds); + // last writer wins, atomic swap + Interlocked.Exchange(ref _lastToken, entry); + } + + private bool TryLastValidationFromCache(string token, long nowUnixSeconds) + { + // Read the last validated token entry atomically + // this is a single entry cache because tokens tend to be reused + // for a handful of sequential requests before a fresh token is issued + TokenCacheEntry? entry = Volatile.Read(ref _lastToken); + if (entry is null) + return false; + + // Only allow cache hit if the exact same token string is being reused + // different tokens bypass the cache and undergo full validation + if (!string.Equals(entry.Token, token, StringComparison.Ordinal)) + return false; + + // Token reuse is only allowed within the original JWT lifetime + // We never extend token validity beyond what the issuer intended + // - IssuedAtUnixSeconds ensures we don't accept a token older than TTL + if (Math.Abs(entry.IssuedAtUnixSeconds - nowUnixSeconds) > JwtTokenTtl) + { + // Token lifetime exceeded - drop the cached entry and force a fresh validation + Interlocked.CompareExchange(ref _lastToken, null, entry); + return false; + } + + // Same token, within TTL, recently validated: + // Accept as valid without rerunning JWT parsing and crypto checks + return true; + } + [GeneratedRegex("^(0x)?[0-9a-fA-F]{64}$")] private static partial Regex SecretRegex(); + + private record TokenCacheEntry(string Token, long IssuedAtUnixSeconds); } diff --git a/src/Nethermind/Nethermind.Core/BaseFeeCalculator.cs b/src/Nethermind/Nethermind.Core/BaseFeeCalculator.cs index 42b25af8cb7..6987630b47f 100644 --- a/src/Nethermind/Nethermind.Core/BaseFeeCalculator.cs +++ b/src/Nethermind/Nethermind.Core/BaseFeeCalculator.cs @@ -22,8 +22,6 @@ public UInt256 Calculate(BlockHeader parent, IEip1559Spec specFor1559) if (specFor1559.IsEip1559Enabled) { UInt256 parentBaseFee = parent.BaseFeePerGas; - long gasDelta; - UInt256 feeDelta; bool isForkBlockNumber = specFor1559.Eip1559TransitionBlock == parent.Number + 1; long parentGasTarget = parent.GasLimit / specFor1559.ElasticityMultiplier; if (isForkBlockNumber) @@ -33,18 +31,22 @@ public UInt256 Calculate(BlockHeader parent, IEip1559Spec specFor1559) { expectedBaseFee = parent.BaseFeePerGas; } + else if (parentGasTarget == 0 || specFor1559.BaseFeeMaxChangeDenominator.IsZero) + { + expectedBaseFee = parentBaseFee; + } else if (parent.GasUsed > parentGasTarget) { - gasDelta = parent.GasUsed - parentGasTarget; - feeDelta = UInt256.Max( + long gasDelta = parent.GasUsed - parentGasTarget; + UInt256 feeDelta = UInt256.Max( parentBaseFee * (UInt256)gasDelta / (UInt256)parentGasTarget / specFor1559.BaseFeeMaxChangeDenominator, UInt256.One); expectedBaseFee = parentBaseFee + feeDelta; } else { - gasDelta = parentGasTarget - parent.GasUsed; - feeDelta = parentBaseFee * (UInt256)gasDelta / (UInt256)parentGasTarget / specFor1559.BaseFeeMaxChangeDenominator; + long gasDelta = parentGasTarget - parent.GasUsed; + UInt256 feeDelta = parentBaseFee * (UInt256)gasDelta / (UInt256)parentGasTarget / specFor1559.BaseFeeMaxChangeDenominator; expectedBaseFee = UInt256.Max(parentBaseFee - feeDelta, 0); } diff --git a/src/Nethermind/Nethermind.Core/Bloom.cs b/src/Nethermind/Nethermind.Core/Bloom.cs index e3f2749e7cc..baeb3e75e16 100644 --- a/src/Nethermind/Nethermind.Core/Bloom.cs +++ b/src/Nethermind/Nethermind.Core/Bloom.cs @@ -54,6 +54,7 @@ public Bloom(ReadOnlySpan bytes) } public Span Bytes => _bloomData.AsSpan(); + public ReadOnlySpan ReadOnlyBytes => _bloomData.AsReadOnlySpan(); private Span ULongs => _bloomData.AsULongs(); public void Set(ReadOnlySpan sequence, Bloom? masterBloom = null) @@ -79,7 +80,7 @@ public void Set(ReadOnlySpan sequence, Bloom? masterBloom = null) public bool Matches(ReadOnlySpan sequence) => Matches(GetExtract(sequence)); - public override string ToString() => Bytes.ToHexString(); + public override string ToString() => ReadOnlyBytes.ToHexString(); public static bool operator !=(Bloom? a, Bloom? b) { @@ -101,7 +102,7 @@ public bool Equals(Bloom? other) if (other is null) return false; if (ReferenceEquals(this, other)) return true; - return Nethermind.Core.Extensions.Bytes.AreEqual(Bytes, other.Bytes); + return Nethermind.Core.Extensions.Bytes.AreEqual(ReadOnlyBytes, other.ReadOnlyBytes); } public override bool Equals(object? obj) @@ -112,7 +113,7 @@ public override bool Equals(object? obj) return Equals((Bloom)obj); } - public override int GetHashCode() => Bytes.FastHash(); + public override int GetHashCode() => ReadOnlyBytes.FastHash(); public void Add(LogEntry[] logEntries) { @@ -251,7 +252,7 @@ public readonly struct BloomExtract(ulong indexes) public Bloom Clone() { Bloom clone = new(); - Bytes.CopyTo(clone.Bytes); + ReadOnlyBytes.CopyTo(clone.Bytes); return clone; } @@ -259,6 +260,7 @@ public Bloom Clone() public struct BloomData { private byte _element0; + public ReadOnlySpan AsReadOnlySpan() => MemoryMarshal.CreateReadOnlySpan(ref _element0, ByteLength); public Span AsSpan() => MemoryMarshal.CreateSpan(ref _element0, ByteLength); public Span AsULongs() => MemoryMarshal.CreateSpan(ref Unsafe.As(ref _element0), ByteLength / sizeof(ulong)); } diff --git a/src/Nethermind/Nethermind.Core/BloomConverter.cs b/src/Nethermind/Nethermind.Core/BloomConverter.cs index c2b59492a3a..f58df98a53a 100644 --- a/src/Nethermind/Nethermind.Core/BloomConverter.cs +++ b/src/Nethermind/Nethermind.Core/BloomConverter.cs @@ -24,6 +24,6 @@ public override void Write( Bloom bloom, JsonSerializerOptions options) { - ByteArrayConverter.Convert(writer, bloom.Bytes, skipLeadingZeros: false); + ByteArrayConverter.Convert(writer, bloom.ReadOnlyBytes, skipLeadingZeros: false); } } diff --git a/src/Nethermind/Nethermind.Core/Caching/LruCache.cs b/src/Nethermind/Nethermind.Core/Caching/LruCache.cs index 727f21d6827..bc87e4e83ba 100644 --- a/src/Nethermind/Nethermind.Core/Caching/LruCache.cs +++ b/src/Nethermind/Nethermind.Core/Caching/LruCache.cs @@ -53,10 +53,7 @@ public TValue Get(TKey key) return value; } -#pragma warning disable 8603 - // fixed C# 9 - return default; -#pragma warning restore 8603 + return default!; } public bool TryGet(TKey key, out TValue value) @@ -70,10 +67,7 @@ public bool TryGet(TKey key, out TValue value) return true; } -#pragma warning disable 8601 - // fixed C# 9 - value = default; -#pragma warning restore 8601 + value = default!; return false; } diff --git a/src/Nethermind/Nethermind.Core/Caching/StaticPool.cs b/src/Nethermind/Nethermind.Core/Caching/StaticPool.cs new file mode 100644 index 00000000000..520010fb55a --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Caching/StaticPool.cs @@ -0,0 +1,91 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Concurrent; +using System.Threading; +using Nethermind.Core.Resettables; + +namespace Nethermind.Core.Caching; + +/// +/// High performance static pool for reference types that support reset semantics. +/// +/// +/// The pooled type. Must be a reference type that implements and +/// has a public parameterless constructor. +/// +public static class StaticPool where T : class, IResettable, new() +{ + /// + /// Hard cap for the total number of items that can be stored in the shared pool. + /// Prevents unbounded growth under bursty workloads while still allowing reuse. + /// + private const int MaxPooledCount = 4096; + + /// + /// Global pool shared between threads. + /// + private static readonly ConcurrentQueue _pool = []; + + /// + /// Manual count of items in the queue. + /// We maintain this separately because ConcurrentQueue.Count + /// is an O(n) traversal — it walks the internal segment chain. + /// Keeping our own count avoids that cost and keeps the hot path O(1). + /// + private static int _poolCount; + + /// + /// Rents an instance of from the pool. + /// + /// + /// The method first attempts to dequeue an existing instance from the shared pool. + /// If the pool is empty, a new instance is created using the parameterless constructor. + /// + /// + /// A reusable instance of . The returned instance is not guaranteed + /// to be zeroed or reset beyond the guarantees provided by and + /// the constructor. Callers should treat it as a freshly created instance. + /// + public static T Rent() + { + // Try to pop from the global pool — this is only hit when a thread + // has exhausted its own fast slot or is cross-thread renting. + if (Volatile.Read(ref _poolCount) > 0 && _pool.TryDequeue(out T? item)) + { + // We track count manually with Interlocked ops instead of using queue.Count. + Interlocked.Decrement(ref _poolCount); + return item; + } + + // Nothing available, allocate new instance + return new(); + } + + /// + /// Returns an instance of to the pool for reuse. + /// + /// + /// The instance is reset via before being enqueued. + /// If adding the instance would exceed , the instance is + /// discarded and not pooled. + /// + /// + /// The instance to return to the pool. Must not be . + /// After returning, the caller must not use the instance again. + /// + public static void Return(T item) + { + // We use Interlocked.Increment to reserve a slot up front. + // This guarantees a bounded queue length without relying on slow Count(). + if (Interlocked.Increment(ref _poolCount) > MaxPooledCount) + { + // Roll back reservation if we'd exceed the cap. + Interlocked.Decrement(ref _poolCount); + return; + } + + item.Reset(); + _pool.Enqueue(item); + } +} diff --git a/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs b/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs index cc8e1349d68..178027c5d29 100644 --- a/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs +++ b/src/Nethermind/Nethermind.Core/Collections/ArrayPoolList.cs @@ -243,7 +243,7 @@ public void Dispose() ArrayPoolListCore.Dispose(_arrayPool, ref _array, ref _count, ref _capacity, ref _disposed); #if DEBUG - GC.SuppressFinalize(this); + GC.SuppressFinalize(this); #endif } diff --git a/src/Nethermind/Nethermind.Core/Collections/LinkedHashSet.cs b/src/Nethermind/Nethermind.Core/Collections/LinkedHashSet.cs index 85930124744..047d96412e1 100644 --- a/src/Nethermind/Nethermind.Core/Collections/LinkedHashSet.cs +++ b/src/Nethermind/Nethermind.Core/Collections/LinkedHashSet.cs @@ -72,7 +72,7 @@ public void IntersectWith(IEnumerable? other) T[] ts = new T[Count]; CopyTo(ts, 0); HashSet set = other.ToHashSet(); - foreach (T t in this.ToArray()) + foreach (T t in ts) { if (!set.Contains(t)) { diff --git a/src/Nethermind/Nethermind.Core/Collections/StackList.cs b/src/Nethermind/Nethermind.Core/Collections/StackList.cs index 2170d94de1b..1537fc69d04 100644 --- a/src/Nethermind/Nethermind.Core/Collections/StackList.cs +++ b/src/Nethermind/Nethermind.Core/Collections/StackList.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; +using Nethermind.Core.Caching; +using Nethermind.Core.Resettables; namespace Nethermind.Core.Collections { - public sealed class StackList : List + public sealed class StackList : List, IResettable, IReturnable where T : struct, IComparable { public T Peek() => this[^1]; @@ -47,10 +49,7 @@ public bool TryPop(out T item) } } - public void Push(T item) - { - Add(item); - } + public void Push(T item) => Add(item); public bool TryGetSearchedItem(T activation, out T item) { @@ -79,5 +78,21 @@ public bool TryGetSearchedItem(T activation, out T item) return result; } + + internal static StackList Rent() + => StaticPool>.Rent(); + + public void Return() => Return(this); + public void Reset() => Clear(); + + private static void Return(StackList value) + { + const int MaxPooledCapacity = 128; + + if (value.Capacity > MaxPooledCapacity) + return; + + StaticPool>.Return(value); + } } } diff --git a/src/Nethermind/Nethermind.Core/Container/OrderedComponentsContainerBuilderExtensions.cs b/src/Nethermind/Nethermind.Core/Container/OrderedComponentsContainerBuilderExtensions.cs index be286cedbe8..e4f64bfb89c 100644 --- a/src/Nethermind/Nethermind.Core/Container/OrderedComponentsContainerBuilderExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Container/OrderedComponentsContainerBuilderExtensions.cs @@ -36,7 +36,7 @@ public static ContainerBuilder AddFirst(this ContainerBuilder builder, Func(this ContainerBuilder builder) { - string registeredMarker = $"Registerd OrderedComponents For {typeof(T).Name}"; + string registeredMarker = $"Registered OrderedComponents For {typeof(T).Name}"; if (!builder.Properties.TryAdd(registeredMarker, null)) { return builder; diff --git a/src/Nethermind/Nethermind.Core/ContainerBuilderExtensions.cs b/src/Nethermind/Nethermind.Core/ContainerBuilderExtensions.cs index 9f80fe93011..224300328fa 100644 --- a/src/Nethermind/Nethermind.Core/ContainerBuilderExtensions.cs +++ b/src/Nethermind/Nethermind.Core/ContainerBuilderExtensions.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System; @@ -32,13 +32,17 @@ public static ContainerBuilder AddSingleton(this ContainerBuilder builder) wh return builder; } - public static ContainerBuilder AddSingleton(this ContainerBuilder builder, T instance) where T : class + public static ContainerBuilder AddSingleton(this ContainerBuilder builder, T instance, bool takeOwnership = false) where T : class { - builder.RegisterInstance(instance) + IRegistrationBuilder registrationBuilder = builder.RegisterInstance(instance) .As() - .ExternallyOwned() .SingleInstance(); + if (!takeOwnership) + { + registrationBuilder.ExternallyOwned(); + } + return builder; } @@ -370,8 +374,17 @@ public static ContainerBuilder Intercept(this ContainerBuilder builder, Actio }); } + public static ContainerBuilder Intercept(this ContainerBuilder builder, Action interceptor) where T : class + { + return builder.AddDecorator((ctx, service) => + { + interceptor(service, ctx); + return service; + }); + } + /// - /// A convenient way of creating a service whose member can be configured indipendent of other instance of the same + /// A convenient way of creating a service whose members can be configured independent of other instances of the same /// type (assuming the type is of lifetime scope). This is useful for same type with multiple configuration /// or a graph of multiple same type. The T is expected to be of a main container of sort that contains the /// main service of interest. diff --git a/src/Nethermind/Nethermind.Core/Crypto/KeccakCache.cs b/src/Nethermind/Nethermind.Core/Crypto/KeccakCache.cs index 8574ebd169f..675bc8756ec 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/KeccakCache.cs +++ b/src/Nethermind/Nethermind.Core/Crypto/KeccakCache.cs @@ -7,6 +7,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using System.Runtime.Intrinsics.X86; using System.Threading; using Nethermind.Core.Extensions; @@ -15,7 +16,7 @@ namespace Nethermind.Core.Crypto; /// /// This is a minimalistic one-way set associative cache for Keccak values. /// -/// It allocates only 12MB of memory to store 128k of entries. +/// It allocates only 16MB of memory to store 128k of entries. /// No misaligned reads. Everything is aligned to both cache lines as well as to boundaries so no torn reads. /// Requires a single CAS to lock and to unlock. /// On a lock failure, it just moves on with execution. @@ -34,7 +35,7 @@ public static unsafe class KeccakCache private const int InputLengthOfKeccak = ValueHash256.MemorySize; private const int InputLengthOfAddress = Address.Size; - + private const int CacheLineSizeBytes = 64; private static readonly Entry* Memory; static KeccakCache() @@ -69,8 +70,14 @@ public static void ComputeTo(ReadOnlySpan input, out ValueHash256 keccak25 Debug.Assert(index < Count); ref Entry e = ref Unsafe.Add(ref Unsafe.AsRef(Memory), index); + if (Sse.IsSupported) + { + // This would be a GC hole if was managed memory, but it's native. + // Regardless, prefetch is non-faulting so it's safe. + Sse.PrefetchNonTemporal((byte*)Unsafe.AsPointer(ref e) + CacheLineSizeBytes); + } - // Half the hash his encoded in the bucket so we only need half of it and can use other half for length. + // Half the hash is encoded in the bucket so we only need half of it and can use other half for length. // This allows to create a combined value that represents a part of the hash, the input's length and the lock marker. uint combined = (HashMask & (uint)hashCode) | (uint)input.Length; @@ -174,10 +181,9 @@ public static void ComputeTo(ReadOnlySpan input, out ValueHash256 keccak25 private struct Entry { /// - /// The size will make it 1.5 CPU cache entry or 0.75 which may result in some collisions. - /// Still, it's better to save these 32 bytes per entry and have a bigger cache. + /// The size will make it 2 CPU cache entries. /// - public const int Size = 96; + public const int Size = 128; private const int PayloadStart = sizeof(uint); private const int ValueStart = Size - ValueHash256.MemorySize; diff --git a/src/Nethermind/Nethermind.Core/Crypto/KeccakHash.cs b/src/Nethermind/Nethermind.Core/Crypto/KeccakHash.cs index 972f96a92ca..35673cec481 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/KeccakHash.cs +++ b/src/Nethermind/Nethermind.Core/Crypto/KeccakHash.cs @@ -422,7 +422,7 @@ public static void ComputeHash(ReadOnlySpan input, Span output) private static unsafe void XorVectors(Span state, ReadOnlySpan input) { ref byte stateRef = ref MemoryMarshal.GetReference(state); - if (Vector512.IsSupported && input.Length >= Vector512.Count) + if (Vector512.IsHardwareAccelerated && input.Length >= Vector512.Count) { // Convert to uint for the mod else the Jit does a more complicated signed mod // whereas as uint it just does an And @@ -441,7 +441,7 @@ private static unsafe void XorVectors(Span state, ReadOnlySpan input stateRef = ref Unsafe.Add(ref stateRef, vectorLength); } - if (Vector256.IsSupported && input.Length >= Vector256.Count) + if (Vector256.IsHardwareAccelerated && input.Length >= Vector256.Count) { // Convert to uint for the mod else the Jit does a more complicated signed mod // whereas as uint it just does an And @@ -460,7 +460,7 @@ private static unsafe void XorVectors(Span state, ReadOnlySpan input stateRef = ref Unsafe.Add(ref stateRef, vectorLength); } - if (Vector128.IsSupported && input.Length >= Vector128.Count) + if (Vector128.IsHardwareAccelerated && input.Length >= Vector128.Count) { int vectorLength = input.Length - (int)((uint)input.Length % (uint)Vector128.Count); ref byte inputRef = ref MemoryMarshal.GetReference(input); diff --git a/src/Nethermind/Nethermind.Core/Crypto/PublicKey.cs b/src/Nethermind/Nethermind.Core/Crypto/PublicKey.cs index c76225ffdc8..d6bcbc9cb64 100644 --- a/src/Nethermind/Nethermind.Core/Crypto/PublicKey.cs +++ b/src/Nethermind/Nethermind.Core/Crypto/PublicKey.cs @@ -10,7 +10,7 @@ namespace Nethermind.Core.Crypto { - [JsonConverter(typeof(PublicKeyConverter))] + [JsonConverter(typeof(PublicKeyHashedConverter))] public class PublicKey : IEquatable { public const int PrefixedLengthInBytes = 65; @@ -76,7 +76,7 @@ public Hash256 Hash public static Address ComputeAddress(ReadOnlySpan publicKeyBytes) { - Span hash = ValueKeccak.Compute(publicKeyBytes).BytesAsSpan; + Span hash = KeccakCache.Compute(publicKeyBytes).BytesAsSpan; return new Address(hash[12..].ToArray()); } diff --git a/src/Nethermind/Nethermind.Core/Extensions/ByteArrayExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/ByteArrayExtensions.cs index 94c1c12f86a..362afa9809e 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/ByteArrayExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/ByteArrayExtensions.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System; @@ -45,6 +45,7 @@ public static byte[] Slice(this byte[] bytes, int startIndex, int length) public static byte[] SliceWithZeroPaddingEmptyOnError(this byte[] bytes, int startIndex, int length) { int copiedFragmentLength = Math.Min(bytes.Length - startIndex, length); + if (copiedFragmentLength <= 0) { return []; @@ -59,21 +60,44 @@ public static byte[] SliceWithZeroPaddingEmptyOnError(this byte[] bytes, int sta public static ReadOnlySpan SliceWithZeroPaddingEmptyOnError(this ReadOnlySpan bytes, int startIndex, int length) { int copiedFragmentLength = Math.Min(bytes.Length - startIndex, length); + if (copiedFragmentLength <= 0) { return default; } + return SafeSliceWithZeroPadding(bytes, startIndex, length, copiedFragmentLength); + } + + public static ReadOnlySpan SliceWithZeroPaddingEmptyOnError(this ReadOnlySpan bytes, uint startIndex, uint length) + { + if (bytes.Length < startIndex) + { + return default; + } + + long copiedFragmentLength = Math.Min((uint)bytes.Length - startIndex, length); + + if (bytes.Length < startIndex + copiedFragmentLength) + { + return default; + } + + return SafeSliceWithZeroPadding(bytes, (int)startIndex, (int)length, (int)copiedFragmentLength); + } + + private static ReadOnlySpan SafeSliceWithZeroPadding(ReadOnlySpan bytes, int startIndex, int length, int copiedFragmentLength) + { ReadOnlySpan sliced = bytes.Slice(startIndex, copiedFragmentLength); + if (copiedFragmentLength < length) { byte[] extended = new byte[length]; sliced.CopyTo(extended); - sliced = extended; + return extended; } return sliced; } - } } diff --git a/src/Nethermind/Nethermind.Core/Extensions/Bytes.Vector.cs b/src/Nethermind/Nethermind.Core/Extensions/Bytes.Vector.cs index 4527b52a94f..573d658ef22 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/Bytes.Vector.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/Bytes.Vector.cs @@ -51,7 +51,7 @@ public static void Or(this Span thisSpan, ReadOnlySpan valueSpan) ref byte thisRef = ref MemoryMarshal.GetReference(thisSpan); ref byte valueRef = ref MemoryMarshal.GetReference(valueSpan); - if (Vector512.IsSupported && thisSpan.Length >= Vector512.Count) + if (Vector512.IsHardwareAccelerated && thisSpan.Length >= Vector512.Count) { for (int i = 0; i < thisSpan.Length - Vector512.Count; i += Vector512.Count) { @@ -66,7 +66,7 @@ public static void Or(this Span thisSpan, ReadOnlySpan valueSpan) Vector512 b2 = Vector512.LoadUnsafe(ref Unsafe.Add(ref valueRef, offset)); Vector512.BitwiseOr(b1, b2).StoreUnsafe(ref Unsafe.Add(ref thisRef, offset)); } - else if (Vector256.IsSupported && thisSpan.Length >= Vector256.Count) + else if (Vector256.IsHardwareAccelerated && thisSpan.Length >= Vector256.Count) { for (int i = 0; i < thisSpan.Length - Vector256.Count; i += Vector256.Count) { @@ -81,7 +81,7 @@ public static void Or(this Span thisSpan, ReadOnlySpan valueSpan) Vector256 b2 = Vector256.LoadUnsafe(ref Unsafe.Add(ref valueRef, offset)); Vector256.BitwiseOr(b1, b2).StoreUnsafe(ref Unsafe.Add(ref thisRef, offset)); } - else if (Vector128.IsSupported && thisSpan.Length >= Vector128.Count) + else if (Vector128.IsHardwareAccelerated && thisSpan.Length >= Vector128.Count) { for (int i = 0; i < thisSpan.Length - Vector128.Count; i += Vector128.Count) { @@ -118,7 +118,7 @@ public static void Xor(this Span thisSpan, ReadOnlySpan valueSpan) int i = 0; // We can't do the fold back technique for xor so need to fall though each size - if (Vector512.IsSupported) + if (Vector512.IsHardwareAccelerated) { for (; i <= thisSpan.Length - Vector512.Count; i += Vector512.Count) { @@ -131,7 +131,7 @@ public static void Xor(this Span thisSpan, ReadOnlySpan valueSpan) if (i == thisSpan.Length) return; } - if (Vector256.IsSupported) + if (Vector256.IsHardwareAccelerated) { for (; i <= thisSpan.Length - Vector256.Count; i += Vector256.Count) { @@ -144,7 +144,7 @@ public static void Xor(this Span thisSpan, ReadOnlySpan valueSpan) if (i == thisSpan.Length) return; } - if (Vector128.IsSupported) + if (Vector128.IsHardwareAccelerated) { for (; i <= thisSpan.Length - Vector128.Count; i += Vector128.Count) { @@ -192,7 +192,7 @@ public static uint CountBits(this Span thisSpan) public static int CountLeadingZeroBits(this in Vector256 v) { - if (Vector256.IsSupported) + if (Vector256.IsHardwareAccelerated) { var cmp = Vector256.Equals(v, Vector256.Zero); uint nonZeroMask = ~cmp.ExtractMostSignificantBits(); diff --git a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs index e6a5f23545b..8e395ecb720 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/Bytes.cs @@ -722,7 +722,7 @@ public static void OutputBytesToByteHex(this ReadOnlySpan bytes, Span(ref output) = (byte)(val >> 8); @@ -779,7 +779,7 @@ public static void OutputBytesToCharHex(ref byte input, int length, ref char cha { skip++; // Odd number of hex chars, handle the first - // seperately so loop can work in pairs + // separately so loop can work in pairs uint val = Unsafe.Add(ref Lookup32[0], input); charsRef = (char)(val >> 16); @@ -1135,7 +1135,7 @@ public static void FromHexString(ReadOnlySpan hexString, Span result if (actualLength != result.Length) { - throw new ArgumentException($"Incorrect result lenght, expected {actualLength}", nameof(result)); + throw new ArgumentException($"Incorrect result length, expected {actualLength}", nameof(result)); } FromHexString(chars, result, oddMod); diff --git a/src/Nethermind/Nethermind.Core/Extensions/DictionaryExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/DictionaryExtensions.cs new file mode 100644 index 00000000000..6168e96121f --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Extensions/DictionaryExtensions.cs @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using Nethermind.Core.Resettables; + +namespace Nethermind.Core.Extensions; + +public static class DictionaryExtensions +{ + /// + /// Returns all values in the dictionary to their pool by calling on each value, + /// then clears the dictionary. + /// + /// The type of the keys in the dictionary. + /// The type of the values in the dictionary, which must implement . + /// The dictionary whose values will be returned and cleared. + /// + /// Use this method when you need to both return pooled objects and clear the dictionary in one operation. + /// + public static void ResetAndClear(this IDictionary dictionary) + where TValue : class, IReturnable + { + foreach (TValue value in dictionary.Values) + { + value.Return(); + } + dictionary.Clear(); + } +} diff --git a/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs index 14ac04fe0fb..5118ea293bb 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/SpanExtensions.cs @@ -132,7 +132,7 @@ private static string ToHexStringWithEip55Checksum(ReadOnlySpan bytes, boo if (odd) { // Odd number of hex chars, handle the first - // seperately so loop can work in pairs + // separately so loop can work in pairs uint val = lookup32[bytes[0]]; char char2 = (char)(val >> 16); chars[0] = char.IsLetter(char2) && hashHex[1] > '7' @@ -197,83 +197,205 @@ public static ArrayPoolListRef ToPooledListRef(this in ReadOnlySpan spa return newList; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static int FastHash(this Span input) - => FastHash((ReadOnlySpan)input); - + /// + /// Computes a very fast, non-cryptographic 32-bit hash of the supplied bytes. + /// + /// The input bytes to hash. + /// + /// A 32-bit hash value for . Returns 0 when is empty. + /// Note that the value is returned as a signed (the underlying 32-bit pattern may appear negative). + /// + /// + /// + /// This routine is optimized for throughput and low overhead on modern CPUs. It is based on CRC32C (Castagnoli) + /// via and related overloads, and will use hardware acceleration + /// when the runtime and processor support it. + /// + /// + /// The hash is intended for in-memory data structures (for example, hash tables, caches, and quick bucketing). + /// It is not suitable for cryptographic purposes, integrity verification, or security-sensitive scenarios. + /// In particular, it is not collision resistant and should not be used as a MAC, signature, or authentication token. + /// + /// + /// The returned value is an implementation detail. It is seeded with an instance-random value and may be + /// platform and runtime dependent, so do not persist it or rely on it being stable across processes or versions. + /// + /// [SkipLocalsInit] public static int FastHash(this ReadOnlySpan input) { - // Very fast hardware accelerated non-cryptographic hash function - var length = input.Length; - if (length == 0) return 0; - - ref var b = ref MemoryMarshal.GetReference(input); - uint hash = s_instanceRandom + (uint)length; - if (length < sizeof(long)) + // Fast hardware-accelerated, non-cryptographic hash. + // Core idea: CRC32C is extremely cheap on CPUs with SSE4.2/ARM CRC, + // and gives good diffusion for hashing. We then optionally add extra + // mixing to reduce "CRC linearity" artifacts. + + int len = input.Length; + + // Contract choice: empty input hashes to 0. + // (Also avoids doing any ref work on an empty span.) + if (len == 0) return 0; + // Using ref + Unsafe.ReadUnaligned lets the JIT hoist bounds checks + // and keep the hot loop tight. + ref byte start = ref MemoryMarshal.GetReference(input); + + // Seed with an instance-random value so attackers cannot trivially + // engineer lots of same-bucket keys. Mixing in length makes "same prefix, + // different length" less correlated (CRC alone can be length-sensitive). + uint seed = s_instanceRandom + (uint)len; + + // Small: 1-7 bytes. + // Using the tail routine here avoids building a synthetic + // 64-bit value with shifts/byte-permute. + if (len < 8) { - goto Short; + uint small = CrcTailOrdered(seed, ref start, len); + // FinalMix breaks some remaining linearity and improves avalanche for tiny inputs. + return (int)FinalMix(small); } - // Start with instance random, length and first ulong as seed - hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned(ref b)); - - // Calculate misalignment and move by it if needed. - // If no misalignment, advance by the size of ulong - uint misaligned = (uint)length & 7; - if (misaligned != 0) + // Medium: 8-31 bytes. + // A single CRC lane is usually fine here - overhead dominates, + // and latency hiding is less important. + if (len < 32) { - // Align by moving by the misaligned count - b = ref Unsafe.Add(ref b, misaligned); - length -= (int)misaligned; + uint h = seed; + ref byte p = ref start; + + // Process as many full 64-bit words as possible. + // "& ~7" is a cheap round-down-to-multiple-of-8 (no division/mod). + int full = len & ~7; + int tail = len - full; + + // Streaming CRC over 8-byte chunks. + // ReadUnaligned keeps us safe for arbitrary input alignment. + for (int i = 0; i < full; i += 8) + { + h = BitOperations.Crc32C(h, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 8); + } + + // Hash remaining 1-7 bytes in strict order (no over-read). + if (tail != 0) + h = CrcTailOrdered(h, ref p, tail); + + // Final mixing for better bit diffusion than raw CRC, + // especially for shorter payloads. + return (int)FinalMix(h); } - else + + // Large: 32+ bytes. + // Use multiple independent CRC accumulators ("lanes") to hide crc32 + // latency and increase ILP. CRC32C instructions have decent throughput + // but non-trivial latency; 4 lanes keeps the CPU busy. + uint h0 = seed; + uint h1 = seed ^ 0x9E3779B9u; // golden-ratio-ish constants to separate lanes + uint h2 = seed ^ 0x85EBCA6Bu; // constants borrowed from common finalizers (good bit dispersion) + uint h3 = seed ^ 0xC2B2AE35u; + + ref byte q = ref start; + + // Consume all full 64-bit words first. Tail (1-7 bytes) is handled later. + int aligned = len & ~7; + int remaining = aligned; + + // 64-byte unroll: + // - amortizes loop branch/compare overhead + // - feeds enough independent work to keep OoO cores busy + // - maps nicely onto cache line sized chunks + while (remaining >= 64) { - // Already Crc'd first ulong so skip it - b = ref Unsafe.Add(ref b, sizeof(ulong)); - length -= sizeof(ulong); + h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); + h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); + h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); + h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 24))); + + h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 32))); + h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 40))); + h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 48))); + h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 56))); + + q = ref Unsafe.Add(ref q, 64); + remaining -= 64; } - // Length is fully aligned here and b is set in place - while (length >= sizeof(ulong) * 3) + // One more half-unroll for 32 bytes if present. + // Keeps the "drain" path short and avoids a smaller loop with more branches. + if (remaining >= 32) { - // Crc32C is 3 cycle latency, 1 cycle throughput - // So we us same initial 3 times to not create a dependency chain - uint hash0 = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned(ref b)); - uint hash1 = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned(ref Unsafe.Add(ref b, sizeof(ulong)))); - uint hash2 = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned(ref Unsafe.Add(ref b, sizeof(ulong) * 2))); - b = ref Unsafe.Add(ref b, sizeof(ulong) * 3); - length -= sizeof(ulong) * 3; - // Combine the 3 hashes; performing the shift on first crc to calculate - hash = BitOperations.Crc32C(hash1, ((ulong)hash0 << (sizeof(uint) * 8)) | hash2); + h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); + h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); + h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); + h3 = BitOperations.Crc32C(h3, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 24))); + + q = ref Unsafe.Add(ref q, 32); + remaining -= 32; } - while (length > 0) + // Drain any remaining full 64-bit words (0, 8, 16, or 24 bytes). + // This is branchy but only runs once, so it is cheaper than another loop. + if (remaining != 0) { - hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned(ref b)); - b = ref Unsafe.Add(ref b, sizeof(ulong)); - length -= sizeof(ulong); + // remaining is a multiple of 8 here. + if (remaining >= 8) h0 = BitOperations.Crc32C(h0, Unsafe.ReadUnaligned(ref q)); + if (remaining >= 16) h1 = BitOperations.Crc32C(h1, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 8))); + if (remaining == 24) h2 = BitOperations.Crc32C(h2, Unsafe.ReadUnaligned(ref Unsafe.Add(ref q, 16))); } - return (int)hash; - Short: - ulong data = 0; - if ((length & sizeof(byte)) != 0) + // Fold lanes down to one 32-bit value. + // Rotates permute bit positions so each lane contributes differently. + // Adds (rather than XOR) deliberately introduce carries + // - CRC is linear over GF(2), and carry breaks that, making simple algebraic + // structure harder to exploit for collision clustering in hash tables. + h2 = BitOperations.RotateLeft(h2, 17) + BitOperations.RotateLeft(h3, 23); + h0 += BitOperations.RotateLeft(h1, 11); + uint hash = h2 + h0; + + // Handle tail bytes (1-7 bytes) that were not part of the 64-bit-aligned stream. + // This is exact, in-order processing - no overlap and no over-read. + int tailBytes = len - aligned; + if (tailBytes != 0) { - data = b; - b = ref Unsafe.Add(ref b, sizeof(byte)); + ref byte tailRef = ref Unsafe.Add(ref start, aligned); + hash = CrcTailOrdered(hash, ref tailRef, tailBytes); } - if ((length & sizeof(ushort)) != 0) + + // FinalMix breaks some remaining linearity and improves avalanche + return (int)FinalMix(hash); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static uint CrcTailOrdered(uint hash, ref byte p, int length) { - data = (data << (sizeof(ushort) * 8)) | Unsafe.ReadUnaligned(ref b); - b = ref Unsafe.Add(ref b, sizeof(ushort)); + // length is 1..7 + // Process 4-2-1 bytes in natural order + if ((length & 4) != 0) + { + hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 4); + } + if ((length & 2) != 0) + { + hash = BitOperations.Crc32C(hash, Unsafe.ReadUnaligned(ref p)); + p = ref Unsafe.Add(ref p, 2); + } + if ((length & 1) != 0) + { + hash = BitOperations.Crc32C(hash, p); + } + return hash; } - if ((length & sizeof(uint)) != 0) + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static uint FinalMix(uint x) { - data = (data << (sizeof(uint) * 8)) | Unsafe.ReadUnaligned(ref b); + // A tiny finalizer to improve avalanche: + // - xor-fold high bits down + // - multiply by an odd constant to spread changes across bits + // - xor-fold again to propagate the multiply result + x ^= x >> 16; + x *= 0x9E3779B1u; + x ^= x >> 16; + return x; } - - return (int)BitOperations.Crc32C(hash, data); } } } diff --git a/src/Nethermind/Nethermind.Core/Extensions/SpecProviderExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/SpecProviderExtensions.cs index b5205d18699..d1e0188dd5d 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/SpecProviderExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/SpecProviderExtensions.cs @@ -9,8 +9,8 @@ namespace Nethermind.Core.Extensions public static class SpecProviderExtensions { /// - /// this method is here only for getting spec related to receipts. - /// Reason of adding is that at sometime we dont know the Timestamp. + /// This method only retrieves the spec related to receipts. + /// Reason for adding it is that sometimes we don't know the timestamp. /// /// /// @@ -21,8 +21,8 @@ public static IReceiptSpec GetReceiptSpec(this ISpecProvider specProvider, long } /// - /// this method is here only for getting spec for 1559. - /// Reason of adding is that at sometime we dont know the Timestamp. + /// This method only retrieves the spec for 1559. + /// Reason for adding it is that sometimes we don't know the timestamp. /// /// /// diff --git a/src/Nethermind/Nethermind.Core/Extensions/TypeExtensions.cs b/src/Nethermind/Nethermind.Core/Extensions/TypeExtensions.cs index 04a82b79e46..20e6ab5de76 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/TypeExtensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/TypeExtensions.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using Nethermind.Core.Collections; namespace Nethermind.Core.Extensions; @@ -22,9 +23,16 @@ public static Type GetDirectInterfaceImplementation(this Type interfaceType) foreach (Type implementation in implementations) { - List interfaces = implementation.GetInterfaces().ToList(); + Type[] allInterfaces = implementation.GetInterfaces(); + using ArrayPoolListRef interfaces = new(allInterfaces.Length); - interfaces.RemoveAll(i => baseInterfaces.Contains(i)); + foreach (Type iface in allInterfaces) + { + if (!baseInterfaces.Contains(iface)) + { + interfaces.Add(iface); + } + } if (interfaces.Contains(interfaceType)) { @@ -35,18 +43,17 @@ public static Type GetDirectInterfaceImplementation(this Type interfaceType) throw new InvalidOperationException($"Couldn't find direct implementation of {interfaceType} interface"); } - private static readonly HashSet _valueTupleTypes = new HashSet( - new Type[] { - typeof(ValueTuple<>), - typeof(ValueTuple<,>), - typeof(ValueTuple<,,>), - typeof(ValueTuple<,,,>), - typeof(ValueTuple<,,,,>), - typeof(ValueTuple<,,,,,>), - typeof(ValueTuple<,,,,,,>), - typeof(ValueTuple<,,,,,,,>) - } - ); + private static readonly HashSet _valueTupleTypes = + [ + typeof(ValueTuple<>), + typeof(ValueTuple<,>), + typeof(ValueTuple<,,>), + typeof(ValueTuple<,,,>), + typeof(ValueTuple<,,,,>), + typeof(ValueTuple<,,,,,>), + typeof(ValueTuple<,,,,,,>), + typeof(ValueTuple<,,,,,,,>) + ]; public static bool IsValueTuple(this Type type) => type.IsGenericType && _valueTupleTypes.Contains(type.GetGenericTypeDefinition()); @@ -54,8 +61,7 @@ public static bool IsValueTuple(this Type type) => public static bool CanBeAssignedNull(this Type type) => !type.IsValueType || Nullable.GetUnderlyingType(type) is not null; - public static bool CannotBeAssignedNull(this Type type) => - type.IsValueType && Nullable.GetUnderlyingType(type) is null; + public static bool CannotBeAssignedNull(this Type type) => !CanBeAssignedNull(type); /// /// Returns the type name. If this is a generic type, appends diff --git a/src/Nethermind/Nethermind.Core/Extensions/UInt256Extensions.cs b/src/Nethermind/Nethermind.Core/Extensions/UInt256Extensions.cs index d28b523d305..92712f8af16 100644 --- a/src/Nethermind/Nethermind.Core/Extensions/UInt256Extensions.cs +++ b/src/Nethermind/Nethermind.Core/Extensions/UInt256Extensions.cs @@ -35,7 +35,7 @@ public static ValueHash256 ToValueHash(this in UInt256 value) Word data = Unsafe.As(ref Unsafe.AsRef(in value)); result = Avx512Vbmi.VL.PermuteVar32x8(data, shuffle); } - else if (Avx2.IsSupported) + else { Vector256 permute = Unsafe.As>(ref Unsafe.AsRef(in value)); Vector256 convert = Avx2.Permute4x64(permute, 0b_01_00_11_10); diff --git a/src/Nethermind/Nethermind.Consensus/GC/GCScheduler.cs b/src/Nethermind/Nethermind.Core/GC/GCScheduler.cs similarity index 96% rename from src/Nethermind/Nethermind.Consensus/GC/GCScheduler.cs rename to src/Nethermind/Nethermind.Core/GC/GCScheduler.cs index ddb2d5caee4..8465cc5765a 100644 --- a/src/Nethermind/Nethermind.Consensus/GC/GCScheduler.cs +++ b/src/Nethermind/Nethermind.Core/GC/GCScheduler.cs @@ -9,7 +9,7 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Memory; -namespace Nethermind.Consensus; +namespace Nethermind.Core; public sealed class GCScheduler { @@ -33,6 +33,8 @@ public sealed class GCScheduler private bool _fireGC = false; private long _countToGC = 0L; + private bool _skipNextGC = false; + // Singleton instance of GCScheduler public static GCScheduler Instance { get; } = new GCScheduler(); @@ -134,6 +136,11 @@ public static void MarkGCResumed() /// private void PerformFullGC() { + if (Interlocked.Exchange(ref _skipNextGC, false)) + { + return; + } + // Decide if the next GC should compact the large object heap bool compacting = _isNextGcBlocking && _isNextGcCompacting; @@ -189,4 +196,6 @@ public bool GCCollect(int generation, GCCollectionMode mode, bool blocking, bool return true; } + + public void SkipNextGC() => Volatile.Write(ref _skipNextGC, true); } diff --git a/src/Nethermind/Nethermind.Core/IAccountStateProvider.cs b/src/Nethermind/Nethermind.Core/IAccountStateProvider.cs index ec52457f1a5..5b949c67dea 100644 --- a/src/Nethermind/Nethermind.Core/IAccountStateProvider.cs +++ b/src/Nethermind/Nethermind.Core/IAccountStateProvider.cs @@ -38,5 +38,7 @@ ValueHash256 GetCodeHash(Address address) TryGetAccount(address, out AccountStruct account); return account.CodeHash; } + + bool HasCode(Address address) => TryGetAccount(address, out AccountStruct account) && account.HasCode; } } diff --git a/src/Nethermind/Nethermind.Core/IKeyValueStore.cs b/src/Nethermind/Nethermind.Core/IKeyValueStore.cs index 52a6b97b317..d80cfc3d40a 100644 --- a/src/Nethermind/Nethermind.Core/IKeyValueStore.cs +++ b/src/Nethermind/Nethermind.Core/IKeyValueStore.cs @@ -28,6 +28,32 @@ public interface IReadOnlyKeyValueStore /// Can return null or empty Span on missing key Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) => Get(key, flags); + /// + /// Get, C-style. Write the output to span and return the length of written data. + /// Cannot differentiate if the data is missing or does not exist. Throws if is not large enough. + /// + /// Key whose associated value should be read. + /// Destination buffer to receive the value bytes; must be large enough to hold the data. + /// Read behavior flags that control how the value is retrieved. + /// The number of bytes written into . + int Get(scoped ReadOnlySpan key, Span output, ReadFlags flags = ReadFlags.None) + { + Span span = GetSpan(key, flags); + try + { + if (span.IsNull()) + { + return 0; + } + span.CopyTo(output); + return span.Length; + } + finally + { + DangerousReleaseMemory(span); + } + } + bool KeyExists(ReadOnlySpan key) { Span span = GetSpan(key); @@ -74,12 +100,54 @@ public interface IMergeableKeyValueStore : IWriteOnlyKeyValueStore void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags flags = WriteFlags.None); } - public interface ISortedKeyValueStore : IKeyValueStore + public interface ISortedKeyValueStore : IReadOnlyKeyValueStore { byte[]? FirstKey { get; } byte[]? LastKey { get; } - ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey); + ISortedView GetViewBetween(ReadOnlySpan firstKeyInclusive, ReadOnlySpan lastKeyExclusive); + } + + /// + /// Provides the capability to create read-only snapshots of a key-value store. + /// + /// + /// Implementations expose a that represents a consistent, + /// point-in-time view of the underlying store. The snapshot is not affected by subsequent writes + /// to the parent store, but it reflects the state as it existed when + /// was called. + /// + public interface IKeyValueStoreWithSnapshot + { + /// + /// Creates a new read-only snapshot of the current state of the key-value store. + /// + /// + /// An that can be used to perform read operations + /// against a stable view of the data. + /// + /// + /// The returned snapshot must be disposed when no longer needed in order to release any + /// resources that may be held by the underlying storage engine (for example, pinned + /// iterators or file handles). The snapshot is guaranteed to be consistent with the + /// state of the store at the time of creation, regardless of concurrent modifications + /// performed afterwards. + /// + IKeyValueStoreSnapshot CreateSnapshot(); + } + + /// + /// Represents a read-only, point-in-time view of the data in an . + /// + /// + /// A snapshot exposes the API and is isolated from + /// subsequent mutations to the parent store. Implementations are expected to provide a + /// consistent view of the data as it existed when the snapshot was created. Callers must + /// dispose the snapshot via when finished with it to + /// free any underlying resources. + /// + public interface IKeyValueStoreSnapshot : IReadOnlyKeyValueStore, IDisposable + { } /// diff --git a/src/Nethermind/Nethermind.Core/Messages/TxErrorMessages.cs b/src/Nethermind/Nethermind.Core/Messages/TxErrorMessages.cs index c4ede3a46c5..f3da0ea2ef0 100644 --- a/src/Nethermind/Nethermind.Core/Messages/TxErrorMessages.cs +++ b/src/Nethermind/Nethermind.Core/Messages/TxErrorMessages.cs @@ -44,8 +44,8 @@ public static string InvalidTxChainId(ulong expected, ulong? actual) => public static string BlobTxGasLimitExceeded(ulong totalDataGas, ulong maxBlobGas) => $"BlobTxGasLimitExceeded: Transaction's totalDataGas={totalDataGas} exceeded MaxBlobGas per transaction={maxBlobGas}."; - public const string BlobTxMissingBlobs = - "blob transaction missing blob hashes"; + public static readonly string BlobTxMissingBlobs = + $"blob transaction must have at least {Eip4844Constants.MinBlobsPerTransaction} blob"; public const string MissingBlobVersionedHash = "MissingBlobVersionedHash: Must be set."; diff --git a/src/Nethermind/Nethermind.Core/Precompiles/PrecompiledAddresses.cs b/src/Nethermind/Nethermind.Core/Precompiles/PrecompiledAddresses.cs index 756bd0bfea1..a01fc358a56 100644 --- a/src/Nethermind/Nethermind.Core/Precompiles/PrecompiledAddresses.cs +++ b/src/Nethermind/Nethermind.Core/Precompiles/PrecompiledAddresses.cs @@ -19,12 +19,16 @@ public static class PrecompiledAddresses public static readonly AddressAsKey Blake2F = Address.FromNumber(0x09); public static readonly AddressAsKey PointEvaluation = Address.FromNumber(0x0a); public static readonly AddressAsKey Bls12G1Add = Address.FromNumber(0x0b); - public static readonly AddressAsKey Bls12G1Mul = Address.FromNumber(0x0c); - public static readonly AddressAsKey Bls12G1MultiExp = Address.FromNumber(0x0d); - public static readonly AddressAsKey Bls12G2Add = Address.FromNumber(0x0e); - public static readonly AddressAsKey Bls12G2Mul = Address.FromNumber(0x0f); - public static readonly AddressAsKey Bls12G2MultiExp = Address.FromNumber(0x10); - public static readonly AddressAsKey Bls12Pairing = Address.FromNumber(0x11); + public static readonly AddressAsKey Bls12G1Msm = Address.FromNumber(0x0c); + public static readonly AddressAsKey Bls12G2Add = Address.FromNumber(0x0d); + public static readonly AddressAsKey Bls12G2Msm = Address.FromNumber(0x0e); + public static readonly AddressAsKey Bls12PairingCheck = Address.FromNumber(0x0f); + public static readonly AddressAsKey Bls12MapFpToG1 = Address.FromNumber(0x10); + public static readonly AddressAsKey Bls12MapFp2ToG2 = Address.FromNumber(0x11); + + // P256Verify (RIP-7212) public static readonly AddressAsKey P256Verify = Address.FromNumber(0x0100); + + // Other precompiles public static readonly AddressAsKey L1Sload = Address.FromNumber(0x10001); } diff --git a/src/Nethermind/Nethermind.Core/Resettables/IResettable.cs b/src/Nethermind/Nethermind.Core/Resettables/IResettable.cs new file mode 100644 index 00000000000..3680f13bbf2 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Resettables/IResettable.cs @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Core.Resettables; + +/// +/// Defines a contract for objects that can be reset to their initial state. +/// +public interface IResettable +{ + /// + /// Resets the object to its initial state. + /// + void Reset(); +} diff --git a/src/Nethermind/Nethermind.Core/Resettables/IReturnable.cs b/src/Nethermind/Nethermind.Core/Resettables/IReturnable.cs new file mode 100644 index 00000000000..228bb5ebb86 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Resettables/IReturnable.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Core.Resettables; + +/// +/// Defines a contract for objects that can be returned to a pool or resettable resource manager. +/// Implementations should ensure that releases or resets the object for reuse. +/// +public interface IReturnable +{ + /// + /// Returns the object to its pool or resource manager, making it available for reuse. + /// Implementations should ensure the object is properly reset or cleaned up. + /// + void Return(); +} diff --git a/src/Nethermind/Nethermind.Core/SlotTime.cs b/src/Nethermind/Nethermind.Core/SlotTime.cs index 147710f2854..c0d1e48f7eb 100644 --- a/src/Nethermind/Nethermind.Core/SlotTime.cs +++ b/src/Nethermind/Nethermind.Core/SlotTime.cs @@ -7,7 +7,7 @@ namespace Nethermind.Core; public class SlotTime(ulong genesisTimestampMs, ITimestamper timestamper, TimeSpan slotLength, TimeSpan blockUpToDateCutoff) { - public class SlotCalulationException(string message, Exception? innerException = null) : Exception(message, innerException); + public class SlotCalculationException(string message, Exception? innerException = null) : Exception(message, innerException); public readonly ulong GenesisTimestampMs = genesisTimestampMs; @@ -25,7 +25,7 @@ public ulong GetSlot(ulong slotTimestampMs) long slotTimeSinceGenesis = (long)slotTimestampMs - (long)GenesisTimestampMs; if (slotTimeSinceGenesis < 0) { - throw new SlotCalulationException($"Slot timestamp {slotTimestampMs}ms was before than genesis timestamp {GenesisTimestampMs}ms."); + throw new SlotCalculationException($"Slot timestamp {slotTimestampMs}ms was before than genesis timestamp {GenesisTimestampMs}ms."); } return (ulong)slotTimeSinceGenesis / (ulong)slotLength.TotalMilliseconds; diff --git a/src/Nethermind/Nethermind.Core/Specs/AuRaSpecProvider.cs b/src/Nethermind/Nethermind.Core/Specs/AuRaSpecProvider.cs index f5d78c4c02a..da4c55202fe 100644 --- a/src/Nethermind/Nethermind.Core/Specs/AuRaSpecProvider.cs +++ b/src/Nethermind/Nethermind.Core/Specs/AuRaSpecProvider.cs @@ -6,10 +6,8 @@ namespace Nethermind.Core.Specs; public class AuRaSpecProvider(ISpecProvider baseSpecProvider) : SpecProviderDecorator(baseSpecProvider) { - public override IReleaseSpec GetSpecInternal(ForkActivation forkActivation) - { - return new AuRaReleaseSpecDecorator(base.GetSpecInternal(forkActivation)); - } + public override IReleaseSpec GetSpec(ForkActivation forkActivation) => + new AuRaReleaseSpecDecorator(base.GetSpec(forkActivation)); } public class AuRaReleaseSpecDecorator(IReleaseSpec spec) : ReleaseSpecDecorator(spec) diff --git a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs index a91b193b0d5..b41dc5a6614 100644 --- a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpec.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Frozen; +using System.Diagnostics.CodeAnalysis; using Nethermind.Int256; namespace Nethermind.Core.Specs @@ -15,8 +16,6 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec public string Name { get; } long MaximumExtraDataSize { get; } long MaxCodeSize { get; } - //EIP-3860: Limit and meter initcode - long MaxInitCodeSize => 2 * MaxCodeSize; long MinGasLimit { get; } long MinHistoryRetentionEpochs { get; } long GasLimitBoundDivisor { get; } @@ -202,10 +201,8 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec /// /// Should EIP158 be ignored for this account. /// - /// THis is needed for SystemUser account compatibility with Parity. - /// - /// - bool IsEip158IgnoredAccount(Address address) => false; + /// This is needed for SystemUser account compatibility with Parity. + bool IsEip158IgnoredAccount(Address address); /// /// BaseFee opcode @@ -276,23 +273,23 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec /// EIP-6110: Supply validator deposits on chain /// bool IsEip6110Enabled { get; } - bool DepositsEnabled => IsEip6110Enabled; - Address DepositContractAddress { get; } + [MemberNotNullWhen(true, nameof(IsEip6110Enabled))] + Address? DepositContractAddress { get; } /// /// Execution layer triggerable exits /// bool IsEip7002Enabled { get; } - bool WithdrawalRequestsEnabled => IsEip7002Enabled; - Address Eip7002ContractAddress { get; } + [MemberNotNullWhen(true, nameof(Eip7002ContractAddress))] + Address? Eip7002ContractAddress { get; } /// /// EIP-7251: triggered consolidations /// bool IsEip7251Enabled { get; } - bool ConsolidationRequestsEnabled => IsEip7251Enabled; - Address Eip7251ContractAddress { get; } + [MemberNotNullWhen(true, nameof(IsEip7251Enabled))] + Address? Eip7251ContractAddress { get; } /// @@ -304,13 +301,14 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec /// Fetch blockHashes from the state for BLOCKHASH opCode /// bool IsEip7709Enabled { get; } - Address Eip2935ContractAddress { get; } + [MemberNotNullWhen(true, nameof(Eip2935ContractAddress))] + Address? Eip2935ContractAddress { get; } /// /// EIP-2935 ring buffer size for historical block hash storage. /// Defaults to 8,191 blocks for Ethereum mainnet. /// - long Eip2935RingBufferSize => Eip2935Constants.RingBufferSize; + public long Eip2935RingBufferSize { get; } /// /// SELFDESTRUCT only in same transaction @@ -385,7 +383,7 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec /// Should transactions be validated against chainId. /// /// Backward compatibility for early Kovan blocks. - bool ValidateChainId => true; + public bool ValidateChainId { get; } /// /// EIP-7780: Add blob schedule to EL config files @@ -399,86 +397,6 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec public ulong Eip4844TransitionTimestamp { get; } - // STATE related - public bool ClearEmptyAccountWhenTouched => IsEip158Enabled; - - // VM - public bool LimitCodeSize => IsEip170Enabled; - - public bool UseHotAndColdStorage => IsEip2929Enabled; - - public bool UseTxAccessLists => IsEip2930Enabled; - - public bool AddCoinbaseToTxAccessList => IsEip3651Enabled; - - public bool ModExpEnabled => IsEip198Enabled; - - public bool BN254Enabled => IsEip196Enabled && IsEip197Enabled; - - public bool BlakeEnabled => IsEip152Enabled; - - public bool Bls381Enabled => IsEip2537Enabled; - - public bool ChargeForTopLevelCreate => IsEip2Enabled; - - public bool FailOnOutOfGasCodeDeposit => IsEip2Enabled; - - public bool UseShanghaiDDosProtection => IsEip150Enabled; - - public bool UseExpDDosProtection => IsEip160Enabled; - - public bool UseLargeStateDDosProtection => IsEip1884Enabled; - - public bool ReturnDataOpcodesEnabled => IsEip211Enabled; - - public bool ChainIdOpcodeEnabled => IsEip1344Enabled; - - public bool Create2OpcodeEnabled => IsEip1014Enabled; - - public bool DelegateCallEnabled => IsEip7Enabled; - - public bool StaticCallEnabled => IsEip214Enabled; - - public bool ShiftOpcodesEnabled => IsEip145Enabled; - - public bool RevertOpcodeEnabled => IsEip140Enabled; - - public bool ExtCodeHashOpcodeEnabled => IsEip1052Enabled; - - public bool SelfBalanceOpcodeEnabled => IsEip1884Enabled; - - public bool UseConstantinopleNetGasMetering => IsEip1283Enabled; - - public bool UseIstanbulNetGasMetering => IsEip2200Enabled; - - public bool UseNetGasMetering => UseConstantinopleNetGasMetering | UseIstanbulNetGasMetering; - - public bool UseNetGasMeteringWithAStipendFix => UseIstanbulNetGasMetering; - - public bool Use63Over64Rule => UseShanghaiDDosProtection; - - public bool BaseFeeEnabled => IsEip3198Enabled; - - // EVM Related - public bool IncludePush0Instruction => IsEip3855Enabled; - - public bool TransientStorageEnabled => IsEip1153Enabled; - - public bool WithdrawalsEnabled => IsEip4895Enabled; - public bool SelfdestructOnlyOnSameTransaction => IsEip6780Enabled; - - public bool IsBeaconBlockRootAvailable => IsEip4788Enabled; - public bool IsBlockHashInStateAvailable => IsEip7709Enabled; - public bool MCopyIncluded => IsEip5656Enabled; - - public bool BlobBaseFeeEnabled => IsEip4844Enabled; - - bool IsAuthorizationListEnabled => IsEip7702Enabled; - - public bool RequestsEnabled => ConsolidationRequestsEnabled || WithdrawalRequestsEnabled || DepositsEnabled; - - bool BlockLevelAccessListsEnabled => IsEip7928Enabled; - public bool IsEip7594Enabled { get; } /// @@ -508,28 +426,17 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec /// public Array? EvmInstructionsTraced { get; set; } - /// - /// Determines whether the specified address is a precompiled contract for this release specification. - /// - /// The address to check for precompile status. - /// True if the address is a precompiled contract; otherwise, false. - bool IsPrecompile(Address address) => Precompiles.Contains(address); - /// /// Gets a cached set of all precompiled contract addresses for this release specification. /// Chain-specific implementations can override this to include their own precompiled contracts. /// FrozenSet Precompiles { get; } - public ProofVersion BlobProofVersion => IsEip7594Enabled ? ProofVersion.V1 : ProofVersion.V0; - /// /// EIP-7939 - CLZ - Count leading zeros instruction /// public bool IsEip7939Enabled { get; } - public bool CLZEnabled => IsEip7939Enabled; - /// /// EIP-7907: Meter Contract Code Size And Increase Limit /// @@ -544,5 +451,7 @@ public interface IReleaseSpec : IEip1559Spec, IReceiptSpec /// EIP-7928: Block-Level Access Lists /// public bool IsEip7928Enabled { get; } + + bool BlockLevelAccessListsEnabled => IsEip7928Enabled; } } diff --git a/src/Nethermind/Nethermind.Core/Specs/IReleaseSpecExtensions.cs b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpecExtensions.cs new file mode 100644 index 00000000000..ba4d778cac7 --- /dev/null +++ b/src/Nethermind/Nethermind.Core/Specs/IReleaseSpecExtensions.cs @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Core.Specs; + +/// +/// Extension members for providing computed properties +/// and helper methods based on EIP enablement flags. +/// +public static class IReleaseSpecExtensions +{ + extension(IReleaseSpec spec) + { + //EIP-3860: Limit and meter initcode + public long MaxInitCodeSize => 2 * spec.MaxCodeSize; + public bool DepositsEnabled => spec.IsEip6110Enabled; + public bool WithdrawalRequestsEnabled => spec.IsEip7002Enabled; + public bool ConsolidationRequestsEnabled => spec.IsEip7251Enabled; + // STATE related + public bool ClearEmptyAccountWhenTouched => spec.IsEip158Enabled; + // VM + public bool LimitCodeSize => spec.IsEip170Enabled; + public bool UseHotAndColdStorage => spec.IsEip2929Enabled; + public bool UseTxAccessLists => spec.IsEip2930Enabled; + public bool AddCoinbaseToTxAccessList => spec.IsEip3651Enabled; + public bool ModExpEnabled => spec.IsEip198Enabled; + public bool BN254Enabled => spec.IsEip196Enabled && spec.IsEip197Enabled; + public bool BlakeEnabled => spec.IsEip152Enabled; + public bool Bls381Enabled => spec.IsEip2537Enabled; + public bool ChargeForTopLevelCreate => spec.IsEip2Enabled; + public bool FailOnOutOfGasCodeDeposit => spec.IsEip2Enabled; + public bool UseShanghaiDDosProtection => spec.IsEip150Enabled; + public bool UseExpDDosProtection => spec.IsEip160Enabled; + public bool UseLargeStateDDosProtection => spec.IsEip1884Enabled; + public bool ReturnDataOpcodesEnabled => spec.IsEip211Enabled; + public bool ChainIdOpcodeEnabled => spec.IsEip1344Enabled; + public bool Create2OpcodeEnabled => spec.IsEip1014Enabled; + public bool DelegateCallEnabled => spec.IsEip7Enabled; + public bool StaticCallEnabled => spec.IsEip214Enabled; + public bool ShiftOpcodesEnabled => spec.IsEip145Enabled; + public bool RevertOpcodeEnabled => spec.IsEip140Enabled; + public bool ExtCodeHashOpcodeEnabled => spec.IsEip1052Enabled; + public bool SelfBalanceOpcodeEnabled => spec.IsEip1884Enabled; + public bool UseConstantinopleNetGasMetering => spec.IsEip1283Enabled; + public bool UseIstanbulNetGasMetering => spec.IsEip2200Enabled; + public bool UseNetGasMetering => spec.UseConstantinopleNetGasMetering || spec.UseIstanbulNetGasMetering; + public bool UseNetGasMeteringWithAStipendFix => spec.UseIstanbulNetGasMetering; + public bool Use63Over64Rule => spec.UseShanghaiDDosProtection; + public bool BaseFeeEnabled => spec.IsEip3198Enabled; + // EVM Related + public bool IncludePush0Instruction => spec.IsEip3855Enabled; + public bool TransientStorageEnabled => spec.IsEip1153Enabled; + public bool WithdrawalsEnabled => spec.IsEip4895Enabled; + public bool SelfdestructOnlyOnSameTransaction => spec.IsEip6780Enabled; + public bool IsBeaconBlockRootAvailable => spec.IsEip4788Enabled; + public bool IsBlockHashInStateAvailable => spec.IsEip7709Enabled; + public bool MCopyIncluded => spec.IsEip5656Enabled; + public bool BlobBaseFeeEnabled => spec.IsEip4844Enabled; + public bool IsAuthorizationListEnabled => spec.IsEip7702Enabled; + public bool RequestsEnabled => spec.ConsolidationRequestsEnabled || spec.WithdrawalRequestsEnabled || spec.DepositsEnabled; + /// + /// Determines whether the specified address is a precompiled contract for this release specification. + /// + /// The address to check for precompile status. + /// True if the address is a precompiled contract; otherwise, false. + public bool IsPrecompile(Address address) => spec.Precompiles.Contains(address); + public ProofVersion BlobProofVersion => spec.IsEip7594Enabled ? ProofVersion.V1 : ProofVersion.V0; + public bool CLZEnabled => spec.IsEip7939Enabled; + } +} diff --git a/src/Nethermind/Nethermind.Core/Specs/ISpecProvider.cs b/src/Nethermind/Nethermind.Core/Specs/ISpecProvider.cs index 7abe0168014..76cb758023d 100644 --- a/src/Nethermind/Nethermind.Core/Specs/ISpecProvider.cs +++ b/src/Nethermind/Nethermind.Core/Specs/ISpecProvider.cs @@ -79,12 +79,11 @@ public interface ISpecProvider /// /// /// A spec that is valid at the given chain height - protected internal IReleaseSpec GetSpecInternal(ForkActivation forkActivation); + IReleaseSpec GetSpec(ForkActivation forkActivation); } public static class SpecProviderExtensions { - public static IReleaseSpec GetSpec(this ISpecProvider specProvider, ForkActivation forkActivation) => specProvider.GetSpecInternal(forkActivation); public static IReleaseSpec GetSpec(this ISpecProvider specProvider, long blockNumber, ulong? timestamp) => specProvider.GetSpec(new ForkActivation(blockNumber, timestamp)); public static IReleaseSpec GetSpec(this ISpecProvider specProvider, BlockHeader blockHeader) => specProvider.GetSpec(new ForkActivation(blockHeader.Number, blockHeader.Timestamp)); diff --git a/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs b/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs index 689137b1e49..acb6ab76afc 100644 --- a/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs +++ b/src/Nethermind/Nethermind.Core/Specs/ReleaseSpecDecorator.cs @@ -70,23 +70,25 @@ public class ReleaseSpecDecorator(IReleaseSpec spec) : IReleaseSpec public virtual bool IsEip4895Enabled => spec.IsEip4895Enabled; public virtual bool IsEip4844Enabled => spec.IsEip4844Enabled; public virtual bool IsEip4788Enabled => spec.IsEip4788Enabled; + public virtual bool ValidateChainId => spec.ValidateChainId; public virtual ulong TargetBlobCount => spec.TargetBlobCount; public virtual ulong MaxBlobCount => spec.MaxBlobCount; public virtual ulong MaxBlobsPerTx { get; init; } = spec.MaxBlobsPerTx; public virtual UInt256 BlobBaseFeeUpdateFraction => spec.BlobBaseFeeUpdateFraction; public virtual Address? Eip4788ContractAddress => spec.Eip4788ContractAddress; - public bool IsEip6110Enabled => spec.IsEip6110Enabled; - public Address DepositContractAddress => spec.DepositContractAddress; - public bool IsEip7002Enabled => spec.IsEip7002Enabled; - public Address Eip7002ContractAddress => spec.Eip7002ContractAddress; - public bool IsEip7251Enabled => spec.IsEip7251Enabled; - public Address Eip7251ContractAddress => spec.Eip7251ContractAddress; + public virtual bool IsEip6110Enabled => spec.IsEip6110Enabled; + public virtual Address? DepositContractAddress => spec.DepositContractAddress; + public virtual bool IsEip7002Enabled => spec.IsEip7002Enabled; + public virtual Address? Eip7002ContractAddress => spec.Eip7002ContractAddress; + public virtual bool IsEip7251Enabled => spec.IsEip7251Enabled; + public virtual Address? Eip7251ContractAddress => spec.Eip7251ContractAddress; public virtual bool IsEip2935Enabled => spec.IsEip2935Enabled; public virtual bool IsEip7709Enabled => spec.IsEip7709Enabled; - public virtual Address Eip2935ContractAddress => spec.Eip2935ContractAddress; + public virtual Address? Eip2935ContractAddress => spec.Eip2935ContractAddress; + public virtual long Eip2935RingBufferSize => spec.Eip2935RingBufferSize; public virtual bool IsEip6780Enabled => spec.IsEip6780Enabled; - public bool IsEip7702Enabled => spec.IsEip7702Enabled; - public bool IsEip7823Enabled => spec.IsEip7823Enabled; + public virtual bool IsEip7702Enabled => spec.IsEip7702Enabled; + public virtual bool IsEip7823Enabled => spec.IsEip7823Enabled; public virtual bool IsEip7934Enabled => spec.IsEip7934Enabled; public virtual int Eip7934MaxRlpBlockSize => spec.Eip7934MaxRlpBlockSize; public virtual bool IsEip7951Enabled => spec.IsEip7951Enabled; @@ -103,58 +105,16 @@ public class ReleaseSpecDecorator(IReleaseSpec spec) : IReleaseSpec public virtual ulong Eip4844TransitionTimestamp => spec.Eip4844TransitionTimestamp; public virtual bool IsEofEnabled => spec.IsEofEnabled; public virtual bool IsEip158IgnoredAccount(Address address) => spec.IsEip158IgnoredAccount(address); - public bool IsEip4844FeeCollectorEnabled => spec.IsEip4844FeeCollectorEnabled; - public bool IsEip7594Enabled => spec.IsEip7594Enabled; - - public virtual long MaxInitCodeSize => spec.MaxInitCodeSize; - public virtual bool ValidateChainId => spec.ValidateChainId; - public virtual bool ClearEmptyAccountWhenTouched => spec.ClearEmptyAccountWhenTouched; - // VM - public virtual bool LimitCodeSize => spec.LimitCodeSize; - public virtual bool UseHotAndColdStorage => spec.UseHotAndColdStorage; - public virtual bool UseTxAccessLists => spec.UseTxAccessLists; - public virtual bool AddCoinbaseToTxAccessList => spec.AddCoinbaseToTxAccessList; - public virtual bool ModExpEnabled => spec.ModExpEnabled; - public virtual bool BN254Enabled => spec.BN254Enabled; - public virtual bool BlakeEnabled => spec.BlakeEnabled; - public virtual bool Bls381Enabled => spec.Bls381Enabled; - public virtual bool ChargeForTopLevelCreate => spec.ChargeForTopLevelCreate; - public virtual bool FailOnOutOfGasCodeDeposit => spec.FailOnOutOfGasCodeDeposit; - public virtual bool UseShanghaiDDosProtection => spec.UseShanghaiDDosProtection; - public virtual bool UseExpDDosProtection => spec.UseExpDDosProtection; - public virtual bool UseLargeStateDDosProtection => spec.UseLargeStateDDosProtection; - public virtual bool ReturnDataOpcodesEnabled => spec.ReturnDataOpcodesEnabled; - public virtual bool ChainIdOpcodeEnabled => spec.ChainIdOpcodeEnabled; - public virtual bool Create2OpcodeEnabled => spec.Create2OpcodeEnabled; - public virtual bool DelegateCallEnabled => spec.DelegateCallEnabled; - public virtual bool StaticCallEnabled => spec.StaticCallEnabled; - public virtual bool ShiftOpcodesEnabled => spec.ShiftOpcodesEnabled; - public virtual bool RevertOpcodeEnabled => spec.RevertOpcodeEnabled; - public virtual bool ExtCodeHashOpcodeEnabled => spec.ExtCodeHashOpcodeEnabled; - public virtual bool SelfBalanceOpcodeEnabled => spec.SelfBalanceOpcodeEnabled; - public virtual bool UseConstantinopleNetGasMetering => spec.UseConstantinopleNetGasMetering; - public virtual bool UseIstanbulNetGasMetering => spec.UseIstanbulNetGasMetering; - public virtual bool UseNetGasMetering => spec.UseNetGasMetering; - public virtual bool UseNetGasMeteringWithAStipendFix => spec.UseNetGasMeteringWithAStipendFix; - public virtual bool Use63Over64Rule => spec.Use63Over64Rule; - public virtual bool BaseFeeEnabled => spec.BaseFeeEnabled; - // EVM Related - public virtual bool IncludePush0Instruction => spec.IncludePush0Instruction; - public virtual bool TransientStorageEnabled => spec.TransientStorageEnabled; - public virtual bool WithdrawalsEnabled => spec.WithdrawalsEnabled; - public virtual bool SelfdestructOnlyOnSameTransaction => spec.SelfdestructOnlyOnSameTransaction; - public virtual bool IsBeaconBlockRootAvailable => spec.IsBeaconBlockRootAvailable; - public virtual bool IsBlockHashInStateAvailable => spec.IsBlockHashInStateAvailable; - public virtual bool MCopyIncluded => spec.MCopyIncluded; - public virtual bool BlobBaseFeeEnabled => spec.BlobBaseFeeEnabled; + public virtual bool IsEip4844FeeCollectorEnabled => spec.IsEip4844FeeCollectorEnabled; + public virtual bool IsEip7594Enabled => spec.IsEip7594Enabled; public virtual Address? FeeCollector => spec.FeeCollector; public virtual UInt256? Eip1559BaseFeeMinValue => spec.Eip1559BaseFeeMinValue; public virtual bool ValidateReceipts => spec.ValidateReceipts; Array? IReleaseSpec.EvmInstructionsNoTrace { get => spec.EvmInstructionsNoTrace; set => spec.EvmInstructionsNoTrace = value; } Array? IReleaseSpec.EvmInstructionsTraced { get => spec.EvmInstructionsTraced; set => spec.EvmInstructionsTraced = value; } FrozenSet IReleaseSpec.Precompiles => spec.Precompiles; - public bool IsEip7939Enabled => spec.IsEip7939Enabled; - public bool IsEip7907Enabled => spec.IsEip7907Enabled; - public bool IsRip7728Enabled => spec.IsRip7728Enabled; - public bool IsEip7928Enabled => spec.IsEip7928Enabled; + public virtual bool IsEip7939Enabled => spec.IsEip7939Enabled; + public virtual bool IsEip7907Enabled => spec.IsEip7907Enabled; + public virtual bool IsRip7728Enabled => spec.IsRip7728Enabled; + public virtual bool IsEip7928Enabled => spec.IsEip7928Enabled; } diff --git a/src/Nethermind/Nethermind.Core/Specs/SpecProviderDecorator.cs b/src/Nethermind/Nethermind.Core/Specs/SpecProviderDecorator.cs index b7856c4f7f2..da475887053 100644 --- a/src/Nethermind/Nethermind.Core/Specs/SpecProviderDecorator.cs +++ b/src/Nethermind/Nethermind.Core/Specs/SpecProviderDecorator.cs @@ -27,5 +27,5 @@ public class SpecProviderDecorator(ISpecProvider baseSpecProvider) : ISpecProvid public ForkActivation[] TransitionActivations => baseSpecProvider.TransitionActivations; - public virtual IReleaseSpec GetSpecInternal(ForkActivation forkActivation) => baseSpecProvider.GetSpecInternal(forkActivation); + public virtual IReleaseSpec GetSpec(ForkActivation forkActivation) => baseSpecProvider.GetSpec(forkActivation); } diff --git a/src/Nethermind/Nethermind.Core/Transaction.cs b/src/Nethermind/Nethermind.Core/Transaction.cs index 50062b9a524..c672eb29a2a 100644 --- a/src/Nethermind/Nethermind.Core/Transaction.cs +++ b/src/Nethermind/Nethermind.Core/Transaction.cs @@ -277,15 +277,26 @@ public Transaction Create() public bool Return(Transaction obj) { + + // Only pool pure Transaction objects, not subclasses + // This prevents other subclasses from contaminating the pool + if (obj.GetType() != typeof(Transaction)) + return false; + obj.ClearPreHash(); obj.Hash = default; obj.ChainId = default; obj.Type = default; + obj.IsAnchorTx = default; + obj.SourceHash = default; + obj.Mint = default; + obj.IsOPSystemTransaction = default; obj.Nonce = default; obj.GasPrice = default; obj.GasBottleneck = default; obj.DecodedMaxFeePerGas = default; obj.GasLimit = default; + obj._spentGas = default; obj.To = default; obj.Value = default; obj.Data = default; @@ -309,6 +320,7 @@ public void CopyTo(Transaction tx) { tx.ChainId = ChainId; tx.Type = Type; + tx.IsAnchorTx = IsAnchorTx; tx.SourceHash = SourceHash; tx.Mint = Mint; tx.IsOPSystemTransaction = IsOPSystemTransaction; diff --git a/src/Nethermind/Nethermind.Core/TypeDiscovery.cs b/src/Nethermind/Nethermind.Core/TypeDiscovery.cs index fcd0dce56bb..0cdd6b87931 100644 --- a/src/Nethermind/Nethermind.Core/TypeDiscovery.cs +++ b/src/Nethermind/Nethermind.Core/TypeDiscovery.cs @@ -19,7 +19,7 @@ public static class TypeDiscovery public static void Initialize(Type? pluginType = null) { - // Early return if initialised + // Early return if initialized if (Volatile.Read(ref _allLoaded) == 1) return; if (pluginType is not null) @@ -34,7 +34,7 @@ private static void LoadAllImpl() { lock (_lock) { - // Early return if initialised while waiting for lock + // Early return if initialized while waiting for lock if (Volatile.Read(ref _allLoaded) == 1) return; List loadedAssemblies = new(capacity: 48); @@ -78,7 +78,7 @@ private static void LoadAllImpl() _assembliesWithNethermindTypes.Add(kv.Value); } - // Mark initialised before releasing lock + // Mark initialized before releasing lock Volatile.Write(ref _allLoaded, 1); } } diff --git a/src/Nethermind/Nethermind.Crypto/BlobProofsManagerV1.cs b/src/Nethermind/Nethermind.Crypto/BlobProofsManagerV1.cs index 50b6465d9dd..f6c222a7924 100644 --- a/src/Nethermind/Nethermind.Crypto/BlobProofsManagerV1.cs +++ b/src/Nethermind/Nethermind.Crypto/BlobProofsManagerV1.cs @@ -15,9 +15,12 @@ internal class BlobProofsManagerV1 : IBlobProofsManager public ShardBlobNetworkWrapper AllocateWrapper(params ReadOnlySpan blobs) { - ShardBlobNetworkWrapper result = new(blobs.ToArray(), new byte[blobs.Length][], new byte[blobs.Length * Ckzg.CellsPerExtBlob][], ProofVersion.V1); + int blobCount = blobs.Length; + int proofCount = blobCount * Ckzg.CellsPerExtBlob; - for (int i = 0; i < blobs.Length; i++) + ShardBlobNetworkWrapper result = new(blobs.ToArray(), new byte[blobCount][], new byte[proofCount][], ProofVersion.V1); + + for (int i = 0; i < blobCount; i++) { result.Commitments[i] = new byte[Ckzg.BytesPerCommitment]; for (int j = 0; j < Ckzg.CellsPerExtBlob; j++) @@ -48,12 +51,15 @@ public void ComputeProofsAndCommitments(ShardBlobNetworkWrapper wrapper) public bool ValidateLengths(ShardBlobNetworkWrapper wrapper) { - if (wrapper.Blobs.Length != wrapper.Commitments.Length || wrapper.Blobs.Length * Ckzg.CellsPerExtBlob != wrapper.Proofs.Length) + int blobCount = wrapper.Blobs.Length; + int proofCount = blobCount * Ckzg.CellsPerExtBlob; + + if (blobCount != wrapper.Commitments.Length || proofCount != wrapper.Proofs.Length) { return false; } - for (int i = 0; i < wrapper.Blobs.Length; i++) + for (int i = 0; i < blobCount; i++) { if (wrapper.Blobs[i].Length != Ckzg.BytesPerBlob || wrapper.Commitments[i].Length != Ckzg.BytesPerCommitment) { @@ -61,7 +67,7 @@ public bool ValidateLengths(ShardBlobNetworkWrapper wrapper) } } - for (int i = 0; i < wrapper.Proofs.Length; i++) + for (int i = 0; i < proofCount; i++) { if (wrapper.Proofs[i].Length != Ckzg.BytesPerProof) { @@ -80,14 +86,17 @@ public bool ValidateProofs(ShardBlobNetworkWrapper wrapper) return false; } - using ArrayPoolSpan cells = new(wrapper.Blobs.Length * Ckzg.BytesPerBlob * 2); - using ArrayPoolSpan flatCommitments = new(wrapper.Blobs.Length * Ckzg.BytesPerCommitment * Ckzg.CellsPerExtBlob); - using ArrayPoolSpan flatProofs = new(wrapper.Blobs.Length * Ckzg.BytesPerProof * Ckzg.CellsPerExtBlob); - using ArrayPoolSpan indices = new(wrapper.Blobs.Length * Ckzg.CellsPerExtBlob); + int blobCount = wrapper.Blobs.Length; + int cellCount = blobCount * Ckzg.CellsPerExtBlob; + + using ArrayPoolSpan cells = new(blobCount * Ckzg.BytesPerBlob * 2); + using ArrayPoolSpan flatCommitments = new(cellCount * Ckzg.BytesPerCommitment); + using ArrayPoolSpan flatProofs = new(cellCount * Ckzg.BytesPerProof); + using ArrayPoolSpan indices = new(cellCount); try { - for (int i = 0; i < wrapper.Blobs.Length; i++) + for (int i = 0; i < blobCount; i++) { Ckzg.ComputeCells(cells.Slice(i * Ckzg.BytesPerCell * Ckzg.CellsPerExtBlob, Ckzg.BytesPerCell * Ckzg.CellsPerExtBlob), wrapper.Blobs[i], KzgPolynomialCommitments.CkzgSetup); @@ -103,7 +112,7 @@ public bool ValidateProofs(ShardBlobNetworkWrapper wrapper) } return Ckzg.VerifyCellKzgProofBatch(flatCommitments, indices, cells, - flatProofs, wrapper.Blobs.Length * Ckzg.CellsPerExtBlob, KzgPolynomialCommitments.CkzgSetup); + flatProofs, cellCount, KzgPolynomialCommitments.CkzgSetup); } catch (Exception e) when (e is ArgumentException or ApplicationException or InsufficientMemoryException) { diff --git a/src/Nethermind/Nethermind.Crypto/Ecdsa.cs b/src/Nethermind/Nethermind.Crypto/Ecdsa.cs index 36b7ba7f49f..e980ed857d2 100644 --- a/src/Nethermind/Nethermind.Crypto/Ecdsa.cs +++ b/src/Nethermind/Nethermind.Crypto/Ecdsa.cs @@ -21,7 +21,7 @@ public Signature Sign(PrivateKey privateKey, in ValueHash256 message) InvalidPrivateKey(); } - byte[] signatureBytes = SpanSecP256k1.SignCompact(message.Bytes, privateKey.KeyBytes, out int recoveryId); + byte[] signatureBytes = SecP256k1.SignCompact(message.Bytes, privateKey.KeyBytes, out int recoveryId); Signature signature = new(signatureBytes, recoveryId); #if DEBUG @@ -40,7 +40,7 @@ public Signature Sign(PrivateKey privateKey, in ValueHash256 message) public PublicKey? RecoverPublicKey(Signature signature, in ValueHash256 message) { Span publicKey = stackalloc byte[65]; - bool success = SpanSecP256k1.RecoverKeyFromCompact(publicKey, message.Bytes, signature.Bytes, signature.RecoveryId, false); + bool success = SecP256k1.RecoverKeyFromCompact(publicKey, message.Bytes, signature.Bytes, signature.RecoveryId, false); if (!success) { return null; @@ -52,7 +52,7 @@ public Signature Sign(PrivateKey privateKey, in ValueHash256 message) public CompressedPublicKey? RecoverCompressedPublicKey(Signature signature, in ValueHash256 message) { Span publicKey = stackalloc byte[33]; - bool success = SpanSecP256k1.RecoverKeyFromCompact(publicKey, message.Bytes, signature.Bytes, signature.RecoveryId, true); + bool success = SecP256k1.RecoverKeyFromCompact(publicKey, message.Bytes, signature.Bytes, signature.RecoveryId, true); if (!success) { return null; diff --git a/src/Nethermind/Nethermind.Crypto/EthereumEcdsa.cs b/src/Nethermind/Nethermind.Crypto/EthereumEcdsa.cs index 73f844d8aca..fa4f58359e0 100644 --- a/src/Nethermind/Nethermind.Crypto/EthereumEcdsa.cs +++ b/src/Nethermind/Nethermind.Crypto/EthereumEcdsa.cs @@ -30,7 +30,7 @@ public class EthereumEcdsa(ulong chainId) : Ecdsa, IEthereumEcdsa private static Address? RecoverAddress(Span signatureBytes64, byte v, ReadOnlySpan message) { Span publicKey = stackalloc byte[65]; - bool success = SpanSecP256k1.RecoverKeyFromCompact( + bool success = SecP256k1.RecoverKeyFromCompact( publicKey, message, signatureBytes64, @@ -41,7 +41,7 @@ public class EthereumEcdsa(ulong chainId) : Ecdsa, IEthereumEcdsa } public static bool RecoverAddressRaw(ReadOnlySpan signatureBytes64, byte v, ReadOnlySpan message, Span resultPublicKey65) => - SpanSecP256k1.RecoverKeyFromCompact( + SecP256k1.RecoverKeyFromCompact( resultPublicKey65, message, signatureBytes64, diff --git a/src/Nethermind/Nethermind.Crypto/KeccakRlpStream.cs b/src/Nethermind/Nethermind.Crypto/KeccakRlpStream.cs index ae039b12582..636a56cbb8f 100644 --- a/src/Nethermind/Nethermind.Crypto/KeccakRlpStream.cs +++ b/src/Nethermind/Nethermind.Crypto/KeccakRlpStream.cs @@ -65,12 +65,12 @@ protected override void SkipBytes(int length) public override int Position { - get => throw new NotSupportedException("Cannot read form Keccak"); - set => throw new NotSupportedException("Cannot read form Keccak"); + get => throw new NotSupportedException("Cannot read from Keccak"); + set => throw new NotSupportedException("Cannot read from Keccak"); } - public override int Length => throw new NotSupportedException("Cannot read form Keccak"); + public override int Length => throw new NotSupportedException("Cannot read from Keccak"); - protected override string Description => "|KeccakRlpSTream|description missing|"; + protected override string Description => "|KeccakRlpStream|description missing|"; } } diff --git a/src/Nethermind/Nethermind.Crypto/KzgPolynomialCommitments.cs b/src/Nethermind/Nethermind.Crypto/KzgPolynomialCommitments.cs index 3e67ebcc309..3d0dd4151e0 100644 --- a/src/Nethermind/Nethermind.Crypto/KzgPolynomialCommitments.cs +++ b/src/Nethermind/Nethermind.Crypto/KzgPolynomialCommitments.cs @@ -15,9 +15,8 @@ namespace Nethermind.Crypto; public static class KzgPolynomialCommitments { // https://eips.ethereum.org/EIPS/eip-4844#parameters - public static readonly UInt256 BlsModulus = - UInt256.Parse("52435875175126190479447740508185965837690552500527637822603658699938581184513", - System.Globalization.NumberStyles.Integer); + // 52435875175126190479447740508185965837690552500527637822603658699938581184513 + public static readonly UInt256 BlsModulus = new(18446744069414584321ul, 6034159408538082302ul, 3691218898639771653ul, 8353516859464449352ul); public const byte KzgBlobHashVersionV1 = 1; diff --git a/src/Nethermind/Nethermind.Crypto/Secp256K1Curve.cs b/src/Nethermind/Nethermind.Crypto/Secp256K1Curve.cs index 83c8e8fb12b..1d8559f421e 100644 --- a/src/Nethermind/Nethermind.Crypto/Secp256K1Curve.cs +++ b/src/Nethermind/Nethermind.Crypto/Secp256K1Curve.cs @@ -18,7 +18,8 @@ public static class Secp256K1Curve - BigInteger.Pow(2, 4) - 1; */ - public static readonly UInt256 N = UInt256.Parse("115792089237316195423570985008687907852837564279074904382605163141518161494337"); + // 115792089237316195423570985008687907852837564279074904382605163141518161494337 + public static readonly UInt256 N = new(13822214165235122497ul, 13451932020343611451ul, 18446744073709551614ul, 18446744073709551615ul); public static readonly UInt256 NMinusOne = N - 1; public static readonly UInt256 HalfN = N / 2; public static readonly UInt256 HalfNPlusOne = HalfN + 1; diff --git a/src/Nethermind/Nethermind.Crypto/SpanSecP256k1.cs b/src/Nethermind/Nethermind.Crypto/SpanSecP256k1.cs deleted file mode 100644 index cbe4fb1aea4..00000000000 --- a/src/Nethermind/Nethermind.Crypto/SpanSecP256k1.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; - -namespace Nethermind.Crypto; - -/// -/// Span wrapper upon SecP256k1. Try to avoid allocations if given span is of Keccak size. -/// -public static class SpanSecP256k1 -{ - [ThreadStatic] private static byte[]? _signMessageHash; - [ThreadStatic] private static byte[]? _signPrivateKey; - - public static byte[]? SignCompact(ReadOnlySpan messageHash, ReadOnlySpan privateKey, out int recoveryId) - { - byte[] messageHashArray; - if (messageHash.Length == 32) - { - _signMessageHash ??= new byte[32]; - messageHash.CopyTo(_signMessageHash); - messageHashArray = _signMessageHash; - } - else - { - // Why? Dont know... - messageHashArray = messageHash.ToArray(); - } - - byte[] privateKeyArray; - if (privateKey.Length == 32) - { - _signPrivateKey ??= new byte[32]; - privateKey.CopyTo(_signPrivateKey); - privateKeyArray = _signPrivateKey; - } - else - { - // Why? Dont know... - privateKeyArray = privateKey.ToArray(); - } - - return SecP256k1.SignCompact(messageHashArray, privateKeyArray, out recoveryId); - } - - [ThreadStatic] private static byte[]? _recoverMessageHash; - - public static bool RecoverKeyFromCompact(Span publicKey, ReadOnlySpan messageHash, ReadOnlySpan signature, int recoveryId, bool compressed) - { - byte[] messageHashArray; - if (messageHash.Length == 32) - { - _recoverMessageHash ??= new byte[32]; - messageHash.CopyTo(_recoverMessageHash); - messageHashArray = _recoverMessageHash; - } - else - { - // Why? Dont know... - messageHashArray = messageHash.ToArray(); - } - - return SecP256k1.RecoverKeyFromCompact(publicKey, messageHashArray, signature, recoveryId, compressed); - } -} diff --git a/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs b/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs index 34713b992b7..46975eb1881 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/ColumnDb.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using Nethermind.Core; using Nethermind.Core.Crypto; using RocksDbSharp; @@ -11,13 +10,14 @@ namespace Nethermind.Db.Rocks; -public class ColumnDb : IDb, ISortedKeyValueStore, IMergeableKeyValueStore +public class ColumnDb : IDb, ISortedKeyValueStore, IMergeableKeyValueStore, IKeyValueStoreWithSnapshot { private readonly RocksDb _rocksDb; internal readonly DbOnTheRocks _mainDb; internal readonly ColumnFamilyHandle _columnFamily; private readonly DbOnTheRocks.IteratorManager _iteratorManager; + private readonly RocksDbReader _reader; public ColumnDb(RocksDb rocksDb, DbOnTheRocks mainDb, string name) { @@ -28,6 +28,11 @@ public ColumnDb(RocksDb rocksDb, DbOnTheRocks mainDb, string name) Name = name; _iteratorManager = new DbOnTheRocks.IteratorManager(_rocksDb, _columnFamily, _mainDb._readAheadReadOptions); + _reader = new RocksDbReader(mainDb, () => + { + // TODO: Verify checksum not set here. + return new ReadOptions(); + }, _iteratorManager, _columnFamily); } public void Dispose() @@ -37,14 +42,29 @@ public void Dispose() public string Name { get; } - public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + byte[]? IReadOnlyKeyValueStore.Get(ReadOnlySpan key, ReadFlags flags) + { + return _reader.Get(key, flags); + } + + Span IReadOnlyKeyValueStore.GetSpan(scoped ReadOnlySpan key, ReadFlags flags) + { + return _reader.GetSpan(key, flags); + } + + int IReadOnlyKeyValueStore.Get(scoped ReadOnlySpan key, Span output, ReadFlags flags) { - return _mainDb.GetWithColumnFamily(key, _columnFamily, _iteratorManager, flags); + return _reader.Get(key, output, flags); } - public Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + bool IReadOnlyKeyValueStore.KeyExists(ReadOnlySpan key) { - return _mainDb.GetSpanWithColumnFamily(key, _columnFamily, flags); + return _reader.KeyExists(key); + } + + void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan key) + { + _reader.DangerousReleaseMemory(key); } public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) @@ -62,8 +82,15 @@ public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags w _mainDb.MergeWithColumnFamily(key, _columnFamily, value, writeFlags); } - public KeyValuePair[] this[byte[][] keys] => - _rocksDb.MultiGet(keys, keys.Select(k => _columnFamily).ToArray()); + public KeyValuePair[] this[byte[][] keys] + { + get + { + ColumnFamilyHandle[] columnFamilies = new ColumnFamilyHandle[keys.Length]; + Array.Fill(columnFamilies, _columnFamily); + return _rocksDb.MultiGet(keys, columnFamilies); + } + } public IEnumerable> GetAll(bool ordered = false) { @@ -137,11 +164,6 @@ public void Remove(ReadOnlySpan key) Set(key, null); } - public bool KeyExists(ReadOnlySpan key) - { - return _mainDb.KeyExistsWithColumn(key, _columnFamily); - } - public void Flush(bool onlyWal) { _mainDb.FlushWithColumnFamily(_columnFamily); @@ -159,12 +181,7 @@ public void Compact() public void Clear() { throw new NotSupportedException(); } // Maybe it should be column specific metric? - public IDbMeta.DbMetric GatherMetric(bool includeSharedCache = false) => _mainDb.GatherMetric(includeSharedCache); - - public void DangerousReleaseMemory(in ReadOnlySpan span) - { - _mainDb.DangerousReleaseMemory(span); - } + public IDbMeta.DbMetric GatherMetric() => _mainDb.GatherMetric(); public byte[]? FirstKey { @@ -192,4 +209,20 @@ public ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan + { + ReadOptions readOptions = new(); + readOptions.SetSnapshot(snapshot); + return readOptions; + }, + _columnFamily, + snapshot); + } } diff --git a/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs b/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs index d5a240d1e96..229b3a1bcb4 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/ColumnsDb.cs @@ -18,11 +18,14 @@ public class ColumnsDb : DbOnTheRocks, IColumnsDb where T : struct, Enum private readonly IDictionary _columnDbs = new Dictionary(); public ColumnsDb(string basePath, DbSettings settings, IDbConfig dbConfig, IRocksDbConfigFactory rocksDbConfigFactory, ILogManager logManager, IReadOnlyList keys, IntPtr? sharedCache = null) - : base(basePath, settings, dbConfig, rocksDbConfigFactory, logManager, GetEnumKeys(keys).Select(static (key) => key.ToString()).ToList(), sharedCache: sharedCache) + : this(basePath, settings, dbConfig, rocksDbConfigFactory, logManager, ResolveKeys(keys), sharedCache) { - keys = GetEnumKeys(keys); + } - foreach (T key in keys) + private ColumnsDb(string basePath, DbSettings settings, IDbConfig dbConfig, IRocksDbConfigFactory rocksDbConfigFactory, ILogManager logManager, (IReadOnlyList Keys, IList ColumnNames) keyInfo, IntPtr? sharedCache) + : base(basePath, settings, dbConfig, rocksDbConfigFactory, logManager, keyInfo.ColumnNames, sharedCache: sharedCache) + { + foreach (T key in keyInfo.Keys) { _columnDbs[key] = new ColumnDb(_db, this, key.ToString()!); } @@ -61,6 +64,14 @@ private static IReadOnlyList GetEnumKeys(IReadOnlyList keys) return keys; } + private static (IReadOnlyList Keys, IList ColumnNames) ResolveKeys(IReadOnlyList keys) + { + IReadOnlyList resolvedKeys = GetEnumKeys(keys); + IList columnNames = resolvedKeys.Select(static key => key.ToString()).ToList(); + + return (resolvedKeys, columnNames); + } + protected override void BuildOptions(IRocksDbConfig dbConfig, Options options, IntPtr? sharedCache, IMergeOperator? mergeOperator) { base.BuildOptions(dbConfig, options, sharedCache, mergeOperator); @@ -145,4 +156,37 @@ public void Merge(ReadOnlySpan key, ReadOnlySpan value, WriteFlags f _writeBatch._writeBatch.Merge(key, value, _column._columnFamily, flags); } } + + IColumnDbSnapshot IColumnsDb.CreateSnapshot() + { + Snapshot snapshot = _db.CreateSnapshot(); + return new ColumnDbSnapshot(this, snapshot); + } + + private class ColumnDbSnapshot( + ColumnsDb columnsDb, + Snapshot snapshot + ) : IColumnDbSnapshot + { + private readonly Dictionary _columnDbs = columnsDb.ColumnKeys.ToDictionary(k => k, k => + (IReadOnlyKeyValueStore)new RocksDbReader( + columnsDb, + () => + { + ReadOptions options = new ReadOptions(); + options.SetSnapshot(snapshot); + return options; + }, + columnFamily: columnsDb._columnDbs[k]._columnFamily)); + + public IReadOnlyKeyValueStore GetColumn(T key) + { + return _columnDbs[key]; + } + + public void Dispose() + { + snapshot.Dispose(); + } + } } diff --git a/src/Nethermind/Nethermind.Db.Rocks/Config/AdjustedRocksdbConfig.cs b/src/Nethermind/Nethermind.Db.Rocks/Config/AdjustedRocksdbConfig.cs index 16ac59bce62..20b220b301d 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/Config/AdjustedRocksdbConfig.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/Config/AdjustedRocksdbConfig.cs @@ -1,12 +1,15 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; + namespace Nethermind.Db.Rocks.Config; public class AdjustedRocksdbConfig( IRocksDbConfig baseConfig, string additionalRocksDbOptions, - ulong writeBufferSize + ulong writeBufferSize, + IntPtr? blockCache = null ) : IRocksDbConfig { public ulong? WriteBufferSize => writeBufferSize; @@ -37,4 +40,5 @@ ulong writeBufferSize public double CompressibilityHint => baseConfig.CompressibilityHint; public bool FlushOnExit => baseConfig.FlushOnExit; + public IntPtr? BlockCache => blockCache ?? baseConfig.BlockCache; } diff --git a/src/Nethermind/Nethermind.Db.Rocks/Config/DbConfig.cs b/src/Nethermind/Nethermind.Db.Rocks/Config/DbConfig.cs index b2b506b1d47..6fd3f06a9e4 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/Config/DbConfig.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/Config/DbConfig.cs @@ -18,13 +18,14 @@ public class DbConfig : IDbConfig public uint StatsDumpPeriodSec { get; set; } = 600; public int? MaxOpenFiles { get; set; } + public bool? SkipCheckingSstFileSizesOnDbOpen { get; set; } public ulong? ReadAheadSize { get; set; } = (ulong)256.KiB(); public string RocksDbOptions { get; set; } = // This section affect the write buffer, or memtable. Note, the size of write buffer affect the size of l0 // file which affect compactions. The options here does not effect how the sst files are read... probably. - // But read does go through the write buffer first, before going through the rowcache (or is it before memtable?) + // But read does go through the write buffer first, before going through the row cache (or is it before memtable?) // block cache and then finally the LSM/SST files. "min_write_buffer_number_to_merge=1;" + "write_buffer_size=16000000;" + @@ -32,7 +33,7 @@ public class DbConfig : IDbConfig "memtable_whole_key_filtering=true;" + "memtable_prefix_bloom_size_ratio=0.02;" + - // Rocksdb turn this on by default a few release ago. But we dont want it yet, not sure the impact on read is + // Rocksdb turned this on by default a few releases ago, but we don't want it yet; the impact on reads is unclear // significant or not. "level_compaction_dynamic_level_bytes=false;" + @@ -65,7 +66,7 @@ public class DbConfig : IDbConfig "block_based_table_factory.format_version=5;" + // Two level index split the index into two level. First index point to second level index, which actually - // point to the block, which get bsearched to the value. This means potentially two iop instead of one per + // point to the block, which get binary searched to the value. This means potentially two iop instead of one per // read, and probably more processing overhead. But it significantly reduces memory usage and make block // processing time more consistent. So its enabled by default. That said, if you got the RAM, maybe disable // this. @@ -86,6 +87,8 @@ public class DbConfig : IDbConfig public string BadBlocksDbRocksDbOptions { get; set; } = ""; public string? BadBlocksDbAdditionalRocksDbOptions { get; set; } + public string BlockAccessListsDbRocksDbOptions { get; set; } = ""; + public string? BlockAccessListsDbAdditionalRocksDbOptions { get; set; } public string BlobTransactionsDbRocksDbOptions { get; set; } = "block_based_table_factory.block_cache=32000000;"; @@ -248,7 +251,7 @@ public class DbConfig : IDbConfig "max_bytes_for_level_multiplier=10;" + "max_bytes_for_level_base=350000000;" + - // Change back file size multiplier as we dont want ridiculous file size, making compaction uneven, + // Change back file size multiplier as we don't want ridiculous file sizes making compaction uneven, // but set high base size. This mean a lot of file, but you are using archive mode, so this should be expected. "target_file_size_multiplier=1;" + "target_file_size_base=256000000;" + diff --git a/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs b/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs index bdd18bb3afd..2d569b411c5 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/Config/IDbConfig.cs @@ -25,6 +25,7 @@ public interface IDbConfig : IConfig int? MaxOpenFiles { get; set; } + bool? SkipCheckingSstFileSizesOnDbOpen { get; set; } bool WriteAheadLogSync { get; set; } ulong? ReadAheadSize { get; set; } string RocksDbOptions { get; set; } @@ -37,6 +38,9 @@ public interface IDbConfig : IConfig string BadBlocksDbRocksDbOptions { get; set; } string? BadBlocksDbAdditionalRocksDbOptions { get; set; } + string BlockAccessListsDbRocksDbOptions { get; set; } + string? BlockAccessListsDbAdditionalRocksDbOptions { get; set; } + string BlobTransactionsDbRocksDbOptions { get; set; } string? BlobTransactionsDbAdditionalRocksDbOptions { get; set; } diff --git a/src/Nethermind/Nethermind.Db.Rocks/Config/IRocksDbConfig.cs b/src/Nethermind/Nethermind.Db.Rocks/Config/IRocksDbConfig.cs index f70d96006d3..c1864fdce02 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/Config/IRocksDbConfig.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/Config/IRocksDbConfig.cs @@ -1,6 +1,8 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; + namespace Nethermind.Db.Rocks.Config; public interface IRocksDbConfig @@ -19,4 +21,5 @@ public interface IRocksDbConfig bool EnableFileWarmer { get; } double CompressibilityHint { get; } bool FlushOnExit { get; } + IntPtr? BlockCache { get; } } diff --git a/src/Nethermind/Nethermind.Db.Rocks/Config/PerTableDbConfig.cs b/src/Nethermind/Nethermind.Db.Rocks/Config/PerTableDbConfig.cs index 0efa1fe7553..7b8967ad4b9 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/Config/PerTableDbConfig.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/Config/PerTableDbConfig.cs @@ -61,6 +61,7 @@ private void EnsureConfigIsAvailable(string propertyName) public bool EnableFileWarmer => ReadConfig(nameof(EnableFileWarmer)); public double CompressibilityHint => ReadConfig(nameof(CompressibilityHint)); public bool FlushOnExit => ReadConfig(nameof(FlushOnExit)) ?? true; + public IntPtr? BlockCache => null; private T? ReadConfig(string propertyName) { diff --git a/src/Nethermind/Nethermind.Db.Rocks/Config/RocksDbConfigFactory.cs b/src/Nethermind/Nethermind.Db.Rocks/Config/RocksDbConfigFactory.cs index add67c9965d..a2c661824f6 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/Config/RocksDbConfigFactory.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/Config/RocksDbConfigFactory.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Runtime.InteropServices; using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Logging; @@ -36,6 +37,13 @@ public IRocksDbConfig GetForDatabase(string databaseName, string? columnName) dbConfig.MaxOpenFiles = perDbLimit; } + + bool skipSstChecks = dbConfig.SkipCheckingSstFileSizesOnDbOpen ?? RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + if (skipSstChecks) + { + if (_logger.IsTrace) _logger.Trace("Skipping SST file size checks on DB open for faster startup."); + dbConfig.RocksDbOptions += "skip_checking_sst_file_sizes_on_db_open=true;"; + } } IRocksDbConfig rocksDbConfig = new PerTableDbConfig(dbConfig, databaseName, columnName); diff --git a/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs b/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs index a69d62475f0..42df2e4d457 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/DbOnTheRocks.cs @@ -29,7 +29,7 @@ namespace Nethermind.Db.Rocks; -public partial class DbOnTheRocks : IDb, ITunableDb, IReadOnlyNativeKeyValueStore, ISortedKeyValueStore, IMergeableKeyValueStore +public partial class DbOnTheRocks : IDb, ITunableDb, IReadOnlyNativeKeyValueStore, ISortedKeyValueStore, IMergeableKeyValueStore, IKeyValueStoreWithSnapshot { protected ILogger _logger; @@ -92,6 +92,8 @@ public partial class DbOnTheRocks : IDb, ITunableDb, IReadOnlyNativeKeyValueStor private readonly IteratorManager _iteratorManager; private ulong _writeBufferSize; private int _maxWriteBufferNumber; + private readonly RocksDbReader _reader; + private bool _isUsingSharedBlockCache; public DbOnTheRocks( string basePath, @@ -113,6 +115,13 @@ public DbOnTheRocks( _perTableDbConfig = rocksDbConfigFactory.GetForDatabase(Name, null); _db = Init(basePath, dbSettings.DbPath, dbConfig, logManager, columnFamilies, dbSettings.DeleteOnStart, sharedCache); _iteratorManager = new IteratorManager(_db, null, _readAheadReadOptions); + + _reader = new RocksDbReader(this, () => + { + ReadOptions readOptions = new ReadOptions(); + readOptions.SetVerifyChecksums(_perTableDbConfig.VerifyChecksum ?? true); + return readOptions; + }, _iteratorManager, null); } protected virtual RocksDb DoOpen(string path, (DbOptions Options, ColumnFamilies? Families) db) @@ -222,7 +231,7 @@ private void WarmupFile(string basePath, RocksDb db) { long availableMemory = GC.GetGCMemoryInfo().TotalAvailableMemoryBytes; _logger.Info($"Warming up database {Name} assuming {availableMemory} bytes of available memory"); - List<(FileMetadata metadata, DateTime creationTime)> fileMetadatas = new(); + List<(FileMetadata metadata, DateTime creationTime)> fileMetadataEntries = new(); foreach (LiveFileMetadata liveFileMetadata in db.GetLiveFilesMetadata()) { @@ -230,7 +239,7 @@ private void WarmupFile(string basePath, RocksDb db) try { DateTime creationTime = File.GetCreationTimeUtc(fullPath); - fileMetadatas.Add((liveFileMetadata.FileMetadata, creationTime)); + fileMetadataEntries.Add((liveFileMetadata.FileMetadata, creationTime)); } catch (IOException) { @@ -238,7 +247,7 @@ private void WarmupFile(string basePath, RocksDb db) } } - fileMetadatas.Sort((item1, item2) => + fileMetadataEntries.Sort((item1, item2) => { // Sort them by level so that lower level get priority int levelDiff = item1.metadata.FileLevel - item2.metadata.FileLevel; @@ -249,23 +258,23 @@ private void WarmupFile(string basePath, RocksDb db) }); long totalSize = 0; - fileMetadatas = fileMetadatas.TakeWhile(metadata => - { - availableMemory -= (long)metadata.metadata.FileSize; - bool take = availableMemory > 0; - if (take) + fileMetadataEntries = fileMetadataEntries.TakeWhile(metadata => { - totalSize += (long)metadata.metadata.FileSize; - } - return take; - }) + availableMemory -= (long)metadata.metadata.FileSize; + bool take = availableMemory > 0; + if (take) + { + totalSize += (long)metadata.metadata.FileSize; + } + return take; + }) // We reverse them again so that lower level goes last so that it is the freshest. // Not all of the available memory is actually available so we are probably over reading things. .Reverse() .ToList(); long totalRead = 0; - Parallel.ForEach(fileMetadatas, (task) => + Parallel.ForEach(fileMetadataEntries, (task) => { string fullPath = Path.Join(basePath, task.metadata.FileName); _logger.Info($"{(totalRead * 100 / (double)totalSize):00.00}% Warming up file {fullPath}"); @@ -344,7 +353,7 @@ protected virtual long FetchTotalPropertyValue(string propertyName) return value; } - public IDbMeta.DbMetric GatherMetric(bool isUsingSharedCache = false) + public IDbMeta.DbMetric GatherMetric() { if (_isDisposed) { @@ -361,7 +370,7 @@ public IDbMeta.DbMetric GatherMetric(bool isUsingSharedCache = false) return new IDbMeta.DbMetric() { Size = GetSize(), - CacheSize = GetCacheSize(isUsingSharedCache), + CacheSize = GetCacheSize(), IndexSize = GetIndexSize(), MemtableSize = GetMemtableSize(), TotalReads = _totalReads, @@ -386,11 +395,11 @@ private long GetSize() return 0; } - private long GetCacheSize(bool isUsingSharedCache = false) + private long GetCacheSize() { try { - if (isUsingSharedCache) + if (_isUsingSharedBlockCache) { // returning 0 as we are using shared cache. return 0; @@ -452,6 +461,38 @@ public static IDictionary ExtractOptions(string dbOptions) return asDict; } + private const string OptimizeFiltersForHitsOption = "optimize_filters_for_hits="; + + /// + /// Normalizes a RocksDB options string by removing earlier occurrences of optimize_filters_for_hits, + /// keeping only the last one. This is needed because RocksDB does not allow overriding this option + /// when specified multiple times in the same options string. + /// + public static string NormalizeRocksDbOptions(string dbOptions) + { + if (string.IsNullOrEmpty(dbOptions)) return dbOptions ?? string.Empty; + + int lastIndex = dbOptions.LastIndexOf(OptimizeFiltersForHitsOption, StringComparison.Ordinal); + if (lastIndex == -1) return dbOptions; + + // Remove all earlier occurrences, keep only the last one + int searchStart = 0; + while (true) + { + int index = dbOptions.IndexOf(OptimizeFiltersForHitsOption, searchStart, StringComparison.Ordinal); + if (index == -1 || index == lastIndex) break; + + // Find the end of this option (next semicolon) + int endIndex = dbOptions.IndexOf(';', index); + if (endIndex == -1) break; + + dbOptions = dbOptions.Remove(index, endIndex - index + 1); + lastIndex = dbOptions.LastIndexOf(OptimizeFiltersForHitsOption, StringComparison.Ordinal); + } + + return dbOptions; + } + protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options options, IntPtr? sharedCache, IMergeOperator? mergeOperator) where T : Options { // This section is about the table factory.. and block cache apparently. @@ -467,27 +508,35 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio _writeBufferSize = ulong.Parse(optionsAsDict["write_buffer_size"]); _maxWriteBufferNumber = int.Parse(optionsAsDict["max_write_buffer_number"]); - BlockBasedTableOptions tableOptions = new(); - options.SetBlockBasedTableFactory(tableOptions); - IntPtr optsPtr = Marshal.StringToHGlobalAnsi(dbConfig.RocksDbOptions); - try - { - _rocksDbNative.rocksdb_get_options_from_string(options.Handle, optsPtr, options.Handle); - } - finally - { - Marshal.FreeHGlobal(optsPtr); - } - ulong blockCacheSize = 0; if (optionsAsDict.TryGetValue("block_based_table_factory.block_cache", out string? blockCacheSizeStr)) { blockCacheSize = ulong.Parse(blockCacheSizeStr); } - if (sharedCache is not null && blockCacheSize == 0) + BlockBasedTableOptions? tableOptions = new(); + if (dbConfig.BlockCache is not null) + { + tableOptions.SetBlockCache(dbConfig.BlockCache.Value); + } + else if (sharedCache is not null && blockCacheSize == 0) { tableOptions.SetBlockCache(sharedCache.Value); + _isUsingSharedBlockCache = true; + } + + // Note: the ordering is important. + // changes to the table options must be applied before setting to set. + options.SetBlockBasedTableFactory(tableOptions); + + IntPtr optsPtr = Marshal.StringToHGlobalAnsi(NormalizeRocksDbOptions(dbConfig.RocksDbOptions)); + try + { + _rocksDbNative.rocksdb_get_options_from_string(options.Handle, optsPtr, options.Handle); + } + finally + { + Marshal.FreeHGlobal(optsPtr); } if (dbConfig.WriteBufferSize is not null) @@ -571,7 +620,7 @@ protected virtual void BuildOptions(IRocksDbConfig dbConfig, Options optio if (dbConfig.AdditionalRocksDbOptions is not null) { - optsPtr = Marshal.StringToHGlobalAnsi(dbConfig.AdditionalRocksDbOptions); + optsPtr = Marshal.StringToHGlobalAnsi(NormalizeRocksDbOptions(dbConfig.AdditionalRocksDbOptions)); try { _rocksDbNative.rocksdb_get_options_from_string(options.Handle, optsPtr, options.Handle); @@ -635,44 +684,32 @@ private static WriteOptions CreateWriteOptions(IRocksDbConfig dbConfig) return options; } - public byte[]? this[ReadOnlySpan key] + byte[]? IReadOnlyKeyValueStore.Get(ReadOnlySpan key, ReadFlags flags) { - get => Get(key, ReadFlags.None); - set => Set(key, value, WriteFlags.None); + return _reader.Get(key, flags); } - public byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + Span IReadOnlyKeyValueStore.GetSpan(scoped ReadOnlySpan key, ReadFlags flags) { - return GetWithColumnFamily(key, null, _iteratorManager, flags); + return _reader.GetSpan(key, flags); } - internal byte[]? GetWithColumnFamily(ReadOnlySpan key, ColumnFamilyHandle? cf, IteratorManager iteratorManager, ReadFlags flags = ReadFlags.None) + int IReadOnlyKeyValueStore.Get(scoped ReadOnlySpan key, Span output, ReadFlags flags) { - ObjectDisposedException.ThrowIf(_isDisposing, this); - - UpdateReadMetrics(); + return _reader.Get(key, output, flags); + } - try - { - if (_readAheadReadOptions is not null && (flags & ReadFlags.HintReadAhead) != 0) - { - byte[]? result = GetWithIterator(key, cf, iteratorManager, flags, out bool success); - if (success) - { - return result; - } - } + bool IReadOnlyKeyValueStore.KeyExists(ReadOnlySpan key) + { + return _reader.KeyExists(key); + } - return Get(key, cf, flags); - } - catch (RocksDbSharpException e) - { - CreateMarkerIfCorrupt(e); - throw; - } + void IReadOnlyKeyValueStore.DangerousReleaseMemory(in ReadOnlySpan span) + { + _reader.DangerousReleaseMemory(span); } - private unsafe byte[]? GetWithIterator(ReadOnlySpan key, ColumnFamilyHandle? cf, IteratorManager iteratorManager, ReadFlags flags, out bool success) + internal unsafe byte[]? GetWithIterator(ReadOnlySpan key, ColumnFamilyHandle? cf, IteratorManager iteratorManager, ReadFlags flags, out bool success) { success = true; @@ -694,21 +731,21 @@ public byte[]? this[ReadOnlySpan key] return null; } - private unsafe byte[]? Get(ReadOnlySpan key, ColumnFamilyHandle? cf, ReadFlags flags) + internal unsafe byte[]? Get(ReadOnlySpan key, ColumnFamilyHandle? cf, ReadOptions readOptions) { // TODO: update when merged upstream: https://github.com/curiosity-ai/rocksdb-sharp/pull/61 // return _db.Get(key, cf, (flags & ReadFlags.HintCacheMiss) != 0 ? _hintCacheMissOptions : _defaultReadOptions); nint db = _db.Handle; - nint read_options = ((flags & ReadFlags.HintCacheMiss) != 0 ? _hintCacheMissOptions : _defaultReadOptions).Handle; + nint read_options = readOptions.Handle; UIntPtr skLength = (UIntPtr)key.Length; IntPtr handle; IntPtr errPtr; fixed (byte* ptr = &MemoryMarshal.GetReference(key)) { handle = cf is null - ? Native.Instance.rocksdb_get_pinned(db, read_options, ptr, skLength, out errPtr) - : Native.Instance.rocksdb_get_pinned_cf(db, read_options, cf.Handle, ptr, skLength, out errPtr); + ? Native.Instance.rocksdb_get_pinned(db, read_options, ptr, skLength, out errPtr) + : Native.Instance.rocksdb_get_pinned_cf(db, read_options, cf.Handle, ptr, skLength, out errPtr); } if (errPtr != IntPtr.Zero) ThrowRocksDbException(errPtr); @@ -871,12 +908,7 @@ internal void SetWithColumnFamily(ReadOnlySpan key, ColumnFamilyHandle? cf } } - public Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags) - { - return GetSpanWithColumnFamily(key, null, flags); - } - - internal Span GetSpanWithColumnFamily(scoped ReadOnlySpan key, ColumnFamilyHandle? cf, ReadFlags flags) + internal Span GetSpanWithColumnFamily(scoped ReadOnlySpan key, ColumnFamilyHandle? cf, ReadOptions readOptions) { ObjectDisposedException.ThrowIf(_isDisposing, this); @@ -884,7 +916,7 @@ internal Span GetSpanWithColumnFamily(scoped ReadOnlySpan key, Colum try { - Span span = _db.GetSpan(key, cf, (flags & ReadFlags.HintCacheMiss) != 0 ? _hintCacheMissOptions : _defaultReadOptions); + Span span = _db.GetSpan(key, cf, readOptions); if (!span.IsNullOrEmpty()) { @@ -900,6 +932,58 @@ internal Span GetSpanWithColumnFamily(scoped ReadOnlySpan key, Colum } } + internal unsafe int GetCStyleWithColumnFamily(scoped ReadOnlySpan key, Span output, ColumnFamilyHandle? cf, ReadOptions readOptions) + { + ObjectDisposedException.ThrowIf(_isDisposing, this); + + UpdateReadMetrics(); + + nint db = _db.Handle; + nint read_options = readOptions.Handle; + UIntPtr skLength = (UIntPtr)key.Length; + IntPtr errPtr; + IntPtr slice; + fixed (byte* ptr = &MemoryMarshal.GetReference(key)) + { + slice = cf is null + ? Native.Instance.rocksdb_get_pinned(db, read_options, ptr, skLength, out errPtr) + : Native.Instance.rocksdb_get_pinned_cf(db, read_options, cf.Handle, ptr, skLength, out errPtr); + } + + if (errPtr != IntPtr.Zero) ThrowRocksDbException(errPtr); + if (slice == IntPtr.Zero) return 0; + + IntPtr valuePtr = Native.Instance.rocksdb_pinnableslice_value(slice, out UIntPtr valueLength); + if (valuePtr == IntPtr.Zero) + { + Native.Instance.rocksdb_pinnableslice_destroy(slice); + return 0; + } + + int length = (int)valueLength; + if (output.Length < length) + { + Native.Instance.rocksdb_pinnableslice_destroy(slice); + ThrowNotEnoughMemory(length, output.Length); + } + + new ReadOnlySpan((void*)valuePtr, length).CopyTo(output); + Native.Instance.rocksdb_pinnableslice_destroy(slice); + return length; + + [DoesNotReturn, StackTraceHidden] + static unsafe void ThrowRocksDbException(nint errPtr) + { + throw new RocksDbException(errPtr); + } + + [DoesNotReturn, StackTraceHidden] + static unsafe void ThrowNotEnoughMemory(int length, int bufferLength) + { + throw new ArgumentException($"Output buffer not large enough. Output size: {length}, Buffer size: {bufferLength}"); + } + } + public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags) { SetWithColumnFamily(key, null, value, writeFlags); @@ -1193,11 +1277,6 @@ internal IEnumerable GetAllKeysCore(Iterator iterator) } } - public bool KeyExists(ReadOnlySpan key) - { - return KeyExistsWithColumn(key, null); - } - protected internal bool KeyExistsWithColumn(ReadOnlySpan key, ColumnFamilyHandle? cf) { ObjectDisposedException.ThrowIf(_isDisposing, this); @@ -1752,7 +1831,7 @@ private static IDictionary GetBlobFilesOptions() /// /// This class handles a periodic timer which periodically dispose all iterator. /// - internal class IteratorManager : IDisposable + public class IteratorManager : IDisposable { private readonly ManagedIterators _readaheadIterators = new(); private readonly ManagedIterators _readaheadIterators2 = new(); @@ -1927,18 +2006,45 @@ internal ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan(iterateLowerBound.ToPointer(), firstKey.Length)); Native.Instance.rocksdb_readoptions_set_iterate_lower_bound(readOptions.Handle, iterateLowerBound, (UIntPtr)firstKey.Length); - IntPtr iterateUpperBound = Marshal.AllocHGlobal(lastKey.Length); + iterateUpperBound = Marshal.AllocHGlobal(lastKey.Length); lastKey.CopyTo(new Span(iterateUpperBound.ToPointer(), lastKey.Length)); Native.Instance.rocksdb_readoptions_set_iterate_upper_bound(readOptions.Handle, iterateUpperBound, (UIntPtr)lastKey.Length); } Iterator iterator = CreateIterator(readOptions, cf); - return new RocksdbSortedView(iterator); + return new RocksdbSortedView(iterator, iterateLowerBound, iterateUpperBound); + } + + public IKeyValueStoreSnapshot CreateSnapshot() + { + Snapshot snapshot = _db.CreateSnapshot(); + return new RocksDbSnapshot(this, () => + { + ReadOptions readOptions = new ReadOptions(); + readOptions.SetSnapshot(snapshot); + return readOptions; + }, null, snapshot); + } + + public class RocksDbSnapshot( + DbOnTheRocks mainDb, + Func readOptionsFactory, + ColumnFamilyHandle? columnFamily, + Snapshot snapshot + ) : RocksDbReader(mainDb, readOptionsFactory, null, columnFamily), IKeyValueStoreSnapshot + { + public void Dispose() + { + snapshot.Dispose(); + } } } diff --git a/src/Nethermind/Nethermind.Db.Rocks/HyperClockCacheWrapper.cs b/src/Nethermind/Nethermind.Db.Rocks/HyperClockCacheWrapper.cs new file mode 100644 index 00000000000..a47654fbd37 --- /dev/null +++ b/src/Nethermind/Nethermind.Db.Rocks/HyperClockCacheWrapper.cs @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Microsoft.Win32.SafeHandles; +using RocksDbSharp; + +namespace Nethermind.Db.Rocks; + +public class HyperClockCacheWrapper : SafeHandleZeroOrMinusOneIsInvalid +{ + public HyperClockCacheWrapper(ulong capacity = 32_000_000) : base(ownsHandle: true) + { + SetHandle(RocksDbSharp.Native.Instance.rocksdb_cache_create_hyper_clock(new UIntPtr(capacity), 0)); + } + + public IntPtr Handle => DangerousGetHandle(); + + protected override bool ReleaseHandle() + { + RocksDbSharp.Native.Instance.rocksdb_cache_destroy(handle); + return true; + } + + public long GetUsage() + { + ObjectDisposedException.ThrowIf(IsClosed, this); + return (long)Native.Instance.rocksdb_cache_get_usage(DangerousGetHandle()); + } +} diff --git a/src/Nethermind/Nethermind.Db.Rocks/RocksDbFactory.cs b/src/Nethermind/Nethermind.Db.Rocks/RocksDbFactory.cs index 3883bb2d4cb..a008701a9f4 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/RocksDbFactory.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/RocksDbFactory.cs @@ -16,16 +16,16 @@ public class RocksDbFactory : IDbFactory private readonly string _basePath; - private readonly IntPtr _sharedCache; + private readonly HyperClockCacheWrapper _sharedCache; private readonly IRocksDbConfigFactory _rocksDbConfigFactory; - public RocksDbFactory(IRocksDbConfigFactory rocksDbConfigFactory, IDbConfig dbConfig, IInitConfig initConfig, ILogManager logManager) - : this(rocksDbConfigFactory, dbConfig, logManager, initConfig.BaseDbPath) + public RocksDbFactory(IRocksDbConfigFactory rocksDbConfigFactory, IDbConfig dbConfig, IInitConfig initConfig, HyperClockCacheWrapper sharedCache, ILogManager logManager) + : this(rocksDbConfigFactory, dbConfig, sharedCache, logManager, initConfig.BaseDbPath) { } - public RocksDbFactory(IRocksDbConfigFactory rocksDbConfigFactory, IDbConfig dbConfig, ILogManager logManager, string basePath) + public RocksDbFactory(IRocksDbConfigFactory rocksDbConfigFactory, IDbConfig dbConfig, HyperClockCacheWrapper sharedCache, ILogManager logManager, string basePath) { _rocksDbConfigFactory = rocksDbConfigFactory; _dbConfig = dbConfig; @@ -33,20 +33,16 @@ public RocksDbFactory(IRocksDbConfigFactory rocksDbConfigFactory, IDbConfig dbCo _basePath = basePath; ILogger logger = _logManager.GetClassLogger(); + if (logger.IsDebug) logger.Debug($"Shared memory size is {dbConfig.SharedBlockCacheSize}"); - if (logger.IsDebug) - { - logger.Debug($"Shared memory size is {dbConfig.SharedBlockCacheSize}"); - } - - _sharedCache = RocksDbSharp.Native.Instance.rocksdb_cache_create_lru(new UIntPtr(dbConfig.SharedBlockCacheSize)); + _sharedCache = sharedCache; } public IDb CreateDb(DbSettings dbSettings) => - new DbOnTheRocks(_basePath, dbSettings, _dbConfig, _rocksDbConfigFactory, _logManager, sharedCache: _sharedCache); + new DbOnTheRocks(_basePath, dbSettings, _dbConfig, _rocksDbConfigFactory, _logManager, sharedCache: _sharedCache.Handle); public IColumnsDb CreateColumnsDb(DbSettings dbSettings) where T : struct, Enum => - new ColumnsDb(_basePath, dbSettings, _dbConfig, _rocksDbConfigFactory, _logManager, Array.Empty(), sharedCache: _sharedCache); + new ColumnsDb(_basePath, dbSettings, _dbConfig, _rocksDbConfigFactory, _logManager, Array.Empty(), sharedCache: _sharedCache.Handle); public string GetFullDbPath(DbSettings dbSettings) => DbOnTheRocks.GetFullDbPath(dbSettings.DbPath, _basePath); } diff --git a/src/Nethermind/Nethermind.Db.Rocks/RocksDbReader.cs b/src/Nethermind/Nethermind.Db.Rocks/RocksDbReader.cs new file mode 100644 index 00000000000..0b5957e2273 --- /dev/null +++ b/src/Nethermind/Nethermind.Db.Rocks/RocksDbReader.cs @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Runtime.InteropServices; +using Nethermind.Core; +using Nethermind.Core.Extensions; +using RocksDbSharp; + +namespace Nethermind.Db.Rocks; + +/// +/// Used by `DbOnTheRocks`, `ColumnDb` and `RocksDbSnapshot` to ensure all the methods of +/// `ISortedKeyValueStore` are implemented for the three classes. The three classes are expected +/// to create their relevant read options and create this class then call this class instead of +/// implementing `ISortedKeyValueStore` implementation themselves. +/// This tends to call `DbOnTheRocks` back though. +/// +public class RocksDbReader : ISortedKeyValueStore +{ + private readonly DbOnTheRocks _mainDb; + private readonly Func _readOptionsFactory; + private readonly DbOnTheRocks.IteratorManager? _iteratorManager; + private readonly ColumnFamilyHandle? _columnFamily; + + readonly ReadOptions _options; + readonly ReadOptions _hintCacheMissOptions; + + public RocksDbReader(DbOnTheRocks mainDb, + Func readOptionsFactory, + DbOnTheRocks.IteratorManager? iteratorManager = null, + ColumnFamilyHandle? columnFamily = null) + { + _mainDb = mainDb; + _readOptionsFactory = readOptionsFactory; + _iteratorManager = iteratorManager; + _columnFamily = columnFamily; + + _options = readOptionsFactory(); + _hintCacheMissOptions = readOptionsFactory(); + _hintCacheMissOptions.SetFillCache(false); + } + + public byte[]? Get(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + { + if ((flags & ReadFlags.HintReadAhead) != 0 && _iteratorManager is not null) + { + byte[]? result = _mainDb.GetWithIterator(key, _columnFamily, _iteratorManager, flags, out bool success); + if (success) + { + return result; + } + } + + ReadOptions readOptions = ((flags & ReadFlags.HintCacheMiss) != 0 ? _hintCacheMissOptions : _options); + return _mainDb.Get(key, _columnFamily, readOptions); + } + + public int Get(scoped ReadOnlySpan key, Span output, ReadFlags flags = ReadFlags.None) + { + ReadOptions readOptions = ((flags & ReadFlags.HintCacheMiss) != 0 ? _hintCacheMissOptions : _options); + return _mainDb.GetCStyleWithColumnFamily(key, output, _columnFamily, readOptions); + } + + public Span GetSpan(scoped ReadOnlySpan key, ReadFlags flags = ReadFlags.None) + { + ReadOptions readOptions = ((flags & ReadFlags.HintCacheMiss) != 0 ? _hintCacheMissOptions : _options); + return _mainDb.GetSpanWithColumnFamily(key, _columnFamily, readOptions); + } + + public void DangerousReleaseMemory(in ReadOnlySpan span) + { + _mainDb.DangerousReleaseMemory(span); + } + + public bool KeyExists(ReadOnlySpan key) + { + return _mainDb.KeyExistsWithColumn(key, _columnFamily); + } + + + public byte[]? FirstKey + { + get + { + using Iterator iterator = _mainDb.CreateIterator(_options, _columnFamily); + iterator.SeekToFirst(); + return iterator.Valid() ? iterator.GetKeySpan().ToArray() : null; + } + } + + public byte[]? LastKey + { + get + { + using Iterator iterator = _mainDb.CreateIterator(_options, _columnFamily); + iterator.SeekToLast(); + return iterator.Valid() ? iterator.GetKeySpan().ToArray() : null; + } + } + + public ISortedView GetViewBetween(ReadOnlySpan firstKey, ReadOnlySpan lastKey) + { + ReadOptions readOptions = _readOptionsFactory(); + + IntPtr iterateLowerBound = IntPtr.Zero; + IntPtr iterateUpperBound = IntPtr.Zero; + + unsafe + { + iterateLowerBound = Marshal.AllocHGlobal(firstKey.Length); + firstKey.CopyTo(new Span(iterateLowerBound.ToPointer(), firstKey.Length)); + Native.Instance.rocksdb_readoptions_set_iterate_lower_bound(readOptions.Handle, iterateLowerBound, (UIntPtr)firstKey.Length); + + iterateUpperBound = Marshal.AllocHGlobal(lastKey.Length); + lastKey.CopyTo(new Span(iterateUpperBound.ToPointer(), lastKey.Length)); + Native.Instance.rocksdb_readoptions_set_iterate_upper_bound(readOptions.Handle, iterateUpperBound, (UIntPtr)lastKey.Length); + } + + Iterator iterator = _mainDb.CreateIterator(readOptions, _columnFamily); + return new RocksdbSortedView(iterator, iterateLowerBound, iterateUpperBound); + } +} diff --git a/src/Nethermind/Nethermind.Db.Rocks/RocksdbSortedView.cs b/src/Nethermind/Nethermind.Db.Rocks/RocksdbSortedView.cs index a64fe85d5b4..02e9a027681 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/RocksdbSortedView.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/RocksdbSortedView.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Runtime.InteropServices; using Nethermind.Core; using RocksDbSharp; @@ -10,16 +11,28 @@ namespace Nethermind.Db.Rocks; internal class RocksdbSortedView : ISortedView { private readonly Iterator _iterator; + private readonly IntPtr _lowerBound; + private readonly IntPtr _upperBound; private bool _started = false; - public RocksdbSortedView(Iterator iterator) + public RocksdbSortedView(Iterator iterator, IntPtr lowerBound = default, IntPtr upperBound = default) { _iterator = iterator; + _lowerBound = lowerBound; + _upperBound = upperBound; } public void Dispose() { _iterator.Dispose(); + if (_lowerBound != IntPtr.Zero) + { + Marshal.FreeHGlobal(_lowerBound); + } + if (_upperBound != IntPtr.Zero) + { + Marshal.FreeHGlobal(_upperBound); + } } public bool StartBefore(ReadOnlySpan value) diff --git a/src/Nethermind/Nethermind.Db.Rocks/Statistics/DbMetricsUpdater.cs b/src/Nethermind/Nethermind.Db.Rocks/Statistics/DbMetricsUpdater.cs index db5ffcb1600..e541f05e099 100644 --- a/src/Nethermind/Nethermind.Db.Rocks/Statistics/DbMetricsUpdater.cs +++ b/src/Nethermind/Nethermind.Db.Rocks/Statistics/DbMetricsUpdater.cs @@ -89,7 +89,7 @@ public void ProcessCompactionStats(string compactionStatsString) if (!string.IsNullOrEmpty(compactionStatsString)) { ExtractStatsPerLevel(compactionStatsString); - ExctractIntervalCompaction(compactionStatsString); + ExtractIntervalCompaction(compactionStatsString); } else { @@ -137,7 +137,7 @@ private void ExtractStatsPerLevel(string compactionStatsDump) /// Example line: /// Interval compaction: 0.00 GB write, 0.00 MB/s write, 0.00 GB read, 0.00 MB/s read, 0.0 seconds /// - private void ExctractIntervalCompaction(string compactionStatsDump) + private void ExtractIntervalCompaction(string compactionStatsDump) { if (!string.IsNullOrEmpty(compactionStatsDump)) { @@ -154,7 +154,7 @@ private void ExctractIntervalCompaction(string compactionStatsDump) } else { - logger.Warn($"Cannot find 'Interval compaction' stats for {dbName} database in the compation stats dump:{Environment.NewLine}{compactionStatsDump}"); + logger.Warn($"Cannot find 'Interval compaction' stats for {dbName} database in the compaction stats dump:{Environment.NewLine}{compactionStatsDump}"); } } } diff --git a/src/Nethermind/Nethermind.Db.Rpc/RpcColumnsDb.cs b/src/Nethermind/Nethermind.Db.Rpc/RpcColumnsDb.cs index 8ddcde89941..25de2cef661 100644 --- a/src/Nethermind/Nethermind.Db.Rpc/RpcColumnsDb.cs +++ b/src/Nethermind/Nethermind.Db.Rpc/RpcColumnsDb.cs @@ -45,6 +45,12 @@ public IColumnsWriteBatch StartWriteBatch() { return new InMemoryColumnWriteBatch(this); } + + public IColumnDbSnapshot CreateSnapshot() + { + throw new NotSupportedException("Snapshot not implemented"); + } + public void Dispose() { } public void Flush(bool onlyWal = false) { } } diff --git a/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs b/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs index 98279f33c4a..61bb02e57e1 100644 --- a/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs +++ b/src/Nethermind/Nethermind.Db.Rpc/RpcDb.cs @@ -51,7 +51,7 @@ public byte[] this[ReadOnlySpan key] public void Set(ReadOnlySpan key, byte[] value, WriteFlags flags = WriteFlags.None) { - throw new InvalidOperationException("RPC DB does not support writes"); + ThrowWritesNotSupported(); } public byte[] Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) @@ -63,7 +63,7 @@ public byte[] Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) public void Remove(ReadOnlySpan key) { - throw new InvalidOperationException("RPC DB does not support writes"); + ThrowWritesNotSupported(); } public bool KeyExists(ReadOnlySpan key) @@ -85,7 +85,9 @@ public void Clear() { } public IWriteBatch StartWriteBatch() { - throw new InvalidOperationException("RPC DB does not support writes"); + ThrowWritesNotSupported(); + // Make compiler happy + return null!; } private byte[] GetThroughRpc(ReadOnlySpan key) @@ -113,9 +115,11 @@ public Span GetSpan(ReadOnlySpan key) public void PutSpan(ReadOnlySpan key, ReadOnlySpan value, WriteFlags writeFlags) { - Set(key, value.ToArray(), writeFlags); + ThrowWritesNotSupported(); } + private static void ThrowWritesNotSupported() => throw new InvalidOperationException("RPC DB does not support writes"); + public void DangerousReleaseMemory(in ReadOnlySpan span) { } diff --git a/src/Nethermind/Nethermind.Db.Test/ColumnsDbTests.cs b/src/Nethermind/Nethermind.Db.Test/ColumnsDbTests.cs index fa6e59d0b24..c5990b943b1 100644 --- a/src/Nethermind/Nethermind.Db.Test/ColumnsDbTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/ColumnsDbTests.cs @@ -107,4 +107,21 @@ public void TestWriteBatch_WriteToAllColumn() _db.GetColumnDb(ReceiptsColumns.Transactions).Get(TestItem.KeccakA).Should() .BeEquivalentTo(TestItem.KeccakB.BytesToArray()); } + + [Test] + public void SmokeTest_Snapshot() + { + IColumnsDb asColumnsDb = _db; + IDb colA = _db.GetColumnDb(ReceiptsColumns.Blocks); + + colA.Set(TestItem.KeccakA, TestItem.KeccakA.BytesToArray()); + + using IColumnDbSnapshot snapshot = asColumnsDb.CreateSnapshot(); + + colA.Set(TestItem.KeccakA, TestItem.KeccakB.BytesToArray()); + colA.Get(TestItem.KeccakA).Should().BeEquivalentTo(TestItem.KeccakB.BytesToArray()); + + snapshot.GetColumn(ReceiptsColumns.Blocks) + .Get(TestItem.KeccakA).Should().BeEquivalentTo(TestItem.KeccakA.BytesToArray()); + } } diff --git a/src/Nethermind/Nethermind.Db.Test/DbOnTheRocksTests.cs b/src/Nethermind/Nethermind.Db.Test/DbOnTheRocksTests.cs index 304cb44ce9f..2028e252c3a 100644 --- a/src/Nethermind/Nethermind.Db.Test/DbOnTheRocksTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/DbOnTheRocksTests.cs @@ -31,6 +31,7 @@ namespace Nethermind.Db.Test public class DbOnTheRocksTests { private RocksDbConfigFactory _rocksdbConfigFactory; + private DbConfig _dbConfig = new DbConfig(); string DbPath => "testdb/" + TestContext.CurrentContext.Test.Name; @@ -38,13 +39,13 @@ public class DbOnTheRocksTests public void Setup() { Directory.CreateDirectory(DbPath); - _rocksdbConfigFactory = new RocksDbConfigFactory(new DbConfig(), new PruningConfig(), new TestHardwareInfo(1.GiB()), LimboLogs.Instance); + _rocksdbConfigFactory = new RocksDbConfigFactory(_dbConfig, new PruningConfig(), new TestHardwareInfo(1.GiB()), LimboLogs.Instance); } [TearDown] public void TearDown() { - Directory.Delete(DbPath, true); + if (Directory.Exists(DbPath)) Directory.Delete(DbPath, true); } [Test] @@ -124,9 +125,10 @@ public void CanOpenWithFileWarmer() config.EnableFileWarmer = true; { using DbOnTheRocks db = new("testFileWarmer", GetRocksDbSettings("testFileWarmer", "FileWarmerTest"), config, _rocksdbConfigFactory, LimboLogs.Instance); + IKeyValueStore asKv = db; for (int i = 0; i < 1000; i++) { - db[i.ToBigEndianByteArray()] = i.ToBigEndianByteArray(); + asKv[i.ToBigEndianByteArray()] = i.ToBigEndianByteArray(); } } @@ -159,6 +161,85 @@ public void CanOpenWithAdditionalConfig(string opts, bool success) } } + [TestCase(true)] + [TestCase(false)] + public void UseSharedCacheIfNoCacheIsSpecified(bool explicitCache) + { + if (Directory.Exists(DbPath)) Directory.Delete(DbPath, true); + long sharedCacheSize = 10.KiB(); + + using HyperClockCacheWrapper cache = new HyperClockCacheWrapper((ulong)sharedCacheSize); + _dbConfig.BlocksDbRocksDbOptions = "block_based_table_factory.block_size=512;block_based_table_factory.prepopulate_block_cache=kFlushOnly;"; + if (explicitCache) + { + _dbConfig.BlocksDbRocksDbOptions += "block_based_table_factory.block_cache=1000000;"; + } + + using DbOnTheRocks db = new("testBlockCache", GetRocksDbSettings("testBlockCache", DbNames.Blocks), _dbConfig, + _rocksdbConfigFactory, LimboLogs.Instance, sharedCache: cache.Handle); + + Random rng = new Random(); + byte[] buffer = new byte[1024]; + for (int i = 0; i < 100; i++) + { + Hash256 someKey = Keccak.Compute(i.ToBigEndianByteArray()); + rng.NextBytes(buffer); + db.PutSpan(someKey.Bytes, buffer, WriteFlags.None); + } + db.Flush(); + + if (explicitCache) + { + Assert.That(db.GatherMetric().CacheSize, Is.GreaterThan(sharedCacheSize)); + } + else + { + Assert.That(db.GatherMetric().CacheSize, Is.LessThan(sharedCacheSize)); + } + } + + [Test] + public void UseExplicitlyGivenCache() + { + _dbConfig.BlocksDbRocksDbOptions = "block_based_table_factory.block_size=512;block_based_table_factory.prepopulate_block_cache=kFlushOnly;"; + + long cacheSize = 10.KiB(); + using HyperClockCacheWrapper cache = new HyperClockCacheWrapper((ulong)cacheSize); + + IRocksDbConfigFactory rocksDbConfigFactory = Substitute.For(); + rocksDbConfigFactory.GetForDatabase(Arg.Any(), Arg.Any()) + .Returns((c) => + { + string? arg1 = (string?)c[0]; + string? arg2 = (string?)c[0]; + + IRocksDbConfig baseConfig = _rocksdbConfigFactory.GetForDatabase(arg1, arg2); + + baseConfig = new AdjustedRocksdbConfig(baseConfig, + "", + 0, + cache.Handle); + + return baseConfig; + }); + + using DbOnTheRocks db = new("testBlockCache", GetRocksDbSettings("testBlockCache", DbNames.Blocks), _dbConfig, + rocksDbConfigFactory, LimboLogs.Instance); + + Random rng = new Random(); + byte[] buffer = new byte[1024]; + for (int i = 0; i < 100; i++) + { + Hash256 someKey = Keccak.Compute(i.ToBigEndianByteArray()); + rng.NextBytes(buffer); + db.PutSpan(someKey.Bytes, buffer, WriteFlags.None); + } + db.Flush(); + + Assert.That(db.GatherMetric().CacheSize, Is.EqualTo(cache.GetUsage())); + Assert.That(cache.GetUsage(), Is.LessThan(cacheSize)); + } + [Test] public void Corrupted_exception_on_open_would_create_marker() { @@ -289,11 +370,32 @@ public void TearDown() [Test] public void Smoke_test() { - _db[new byte[] { 1, 2, 3 }] = new byte[] { 4, 5, 6 }; - Assert.That(_db[new byte[] { 1, 2, 3 }], Is.EqualTo(new byte[] { 4, 5, 6 })); + _db[[1, 2, 3]] = [4, 5, 6]; + AssertCanGetViaAllMethod(_db, [1, 2, 3], [4, 5, 6]); - _db.Set(new byte[] { 2, 3, 4 }, new byte[] { 5, 6, 7 }, WriteFlags.LowPriority); - Assert.That(_db[new byte[] { 2, 3, 4 }], Is.EqualTo(new byte[] { 5, 6, 7 })); + _db.Set([2, 3, 4], [5, 6, 7], WriteFlags.LowPriority); + AssertCanGetViaAllMethod(_db, [2, 3, 4], [5, 6, 7]); + } + + [Test] + public void Snapshot_test() + { + IKeyValueStoreWithSnapshot withSnapshot = (IKeyValueStoreWithSnapshot)_db; + + byte[] key = new byte[] { 1, 2, 3 }; + + _db[key] = new byte[] { 4, 5, 6 }; + AssertCanGetViaAllMethod(_db, key, new byte[] { 4, 5, 6 }); + + using IKeyValueStoreSnapshot snapshot = withSnapshot.CreateSnapshot(); + AssertCanGetViaAllMethod(snapshot, key, new byte[] { 4, 5, 6 }); + + _db.Set(key, new byte[] { 5, 6, 7 }); + AssertCanGetViaAllMethod(_db, key, new byte[] { 5, 6, 7 }); + + AssertCanGetViaAllMethod(snapshot, key, new byte[] { 4, 5, 6 }); + + Assert.That(_db.KeyExists(new byte[] { 99, 99, 99 }), Is.False); } [Test] @@ -310,7 +412,7 @@ public void Smoke_test_large_writes_with_nowal() for (int i = 0; i < 1000; i++) { - _db[i.ToBigEndianByteArray()].Should().BeEquivalentTo(i.ToBigEndianByteArray()); + AssertCanGetViaAllMethod(_db, i.ToBigEndianByteArray(), i.ToBigEndianByteArray()); } } @@ -401,20 +503,33 @@ public void IteratorWorks() } i--; - sortedKeyValue.FirstKey.Should().BeEquivalentTo(new byte[] { 0, 0, 0 }); - sortedKeyValue.LastKey.Should().BeEquivalentTo(new byte[] { i, i, i }); - using var view = sortedKeyValue.GetViewBetween([0], [9]); + void CheckView(ISortedKeyValueStore sortedKeyValueStore) + { + sortedKeyValue.FirstKey.Should().BeEquivalentTo(new byte[] { 0, 0, 0 }); + sortedKeyValue.LastKey.Should().BeEquivalentTo(new byte[] { (byte)(entryCount - 1), (byte)(entryCount - 1), (byte)(entryCount - 1) }); + using var view = sortedKeyValueStore.GetViewBetween([0], [9]); + + i = 0; + while (view.MoveNext()) + { + view.CurrentKey.ToArray().Should().BeEquivalentTo([i, i, i]); + view.CurrentValue.ToArray().Should().BeEquivalentTo([i, i, i]); + i++; + } - i = 0; - while (view.MoveNext()) + i.Should().Be((byte)entryCount); + } + + CheckView(sortedKeyValue); + + using var snapshot = ((IKeyValueStoreWithSnapshot)_db).CreateSnapshot(); + for (i = 0; i < entryCount; i++) { - view.CurrentKey.ToArray().Should().BeEquivalentTo([i, i, i]); - view.CurrentValue.ToArray().Should().BeEquivalentTo([i, i, i]); - i++; + _db[[i, i, i]] = [(byte)(i + 1), (byte)(i + 1), (byte)(i + 1)]; } - i.Should().Be((byte)entryCount); + CheckView((ISortedKeyValueStore)snapshot); } [Test] @@ -428,12 +543,108 @@ public void TestExtractOptions() parsedOptions["memtable_whole_key_filtering"].Should().Be("true"); } + [Test] + public void TestNormalizeRocksDbOptions_RemovesDuplicateOptimizeFiltersForHits() + { + string options = "optimize_filters_for_hits=true;compression=kSnappyCompression;optimize_filters_for_hits=false;"; + string normalized = DbOnTheRocks.NormalizeRocksDbOptions(options); + + // Should contain only one optimize_filters_for_hits with the last value (false) + normalized.Should().Be("compression=kSnappyCompression;optimize_filters_for_hits=false;"); + } + + [Test] + public void TestNormalizeRocksDbOptions_HandlesEmptyString() + { + DbOnTheRocks.NormalizeRocksDbOptions("").Should().Be(""); + DbOnTheRocks.NormalizeRocksDbOptions(null!).Should().Be(""); + } + + [Test] + public void TestNormalizeRocksDbOptions_PreservesStringWithoutDuplicates() + { + string options = "compression=kSnappyCompression;block_size=16000;optimize_filters_for_hits=true;"; + string normalized = DbOnTheRocks.NormalizeRocksDbOptions(options); + + normalized.Should().Be(options); + } + + [Test] + public void TestNormalizeRocksDbOptions_HandlesMultipleDuplicates() + { + string options = "optimize_filters_for_hits=true;foo=bar;optimize_filters_for_hits=false;baz=qux;optimize_filters_for_hits=true;"; + string normalized = DbOnTheRocks.NormalizeRocksDbOptions(options); + + normalized.Should().Be("foo=bar;baz=qux;optimize_filters_for_hits=true;"); + } + [Test] public void Can_GetMetric_AfterDispose() { _db.Dispose(); _db.GatherMetric().Size.Should().Be(0); } + + private void AssertCanGetViaAllMethod(IReadOnlyKeyValueStore kv, ReadOnlySpan key, ReadOnlySpan value) + { + Assert.That(kv[key], Is.EqualTo(value.ToArray())); + Assert.That(kv.KeyExists(key), Is.True); + + ReadFlags[] flags = [ReadFlags.None, ReadFlags.HintReadAhead, ReadFlags.HintCacheMiss]; + Span outBuffer = stackalloc byte[value.Length]; + foreach (ReadFlags flag in flags) + { + Assert.That(kv.Get(key, flags: flag), Is.EqualTo(value.ToArray())); + + Span buffer = kv.GetSpan(key, flag); + Assert.That(buffer.ToArray(), Is.EqualTo(value.ToArray())); + kv.DangerousReleaseMemory(buffer); + + int length = kv.Get(key, outBuffer); + Assert.That(outBuffer[..length].ToArray(), Is.EqualTo(value.ToArray())); + } + + using ISortedView iterator = ((ISortedKeyValueStore)kv).GetViewBetween(key, CreateNextKey(key)); + if (iterator.MoveNext()) + { + Assert.That(iterator.CurrentKey.ToArray(), Is.EqualTo(key.ToArray())); + Assert.That(iterator.CurrentValue.ToArray(), Is.EqualTo(value.ToArray())); + } + + Assert.That(iterator.MoveNext(), Is.False); + + // Ai generated + byte[] CreateNextKey(ReadOnlySpan key) + { + // 1. Create a copy of the key to modify + byte[] nextKey = key.ToArray(); + + // 2. Iterate backwards (from the last byte to the first) + for (int i = nextKey.Length - 1; i >= 0; i--) + { + // If the byte is NOT 0xFF (255), we can just increment it and we are done. + if (nextKey[i] < 0xFF) + { + nextKey[i]++; + return nextKey; + } + + // If the byte IS 0xFF, it rolls over to 0x00, and we "carry" the 1 to the next byte loop. + nextKey[i] = 0x00; + } + + // 3. Handle Overflow (Edge Case: All bytes were 0xFF) + // If we are here, the key was something like [FF, FF, FF]. + // The loop turned it into [00, 00, 00]. + // The "Next" lexicographical key is mathematically [01, 00, 00, 00]. + + // Resize array to fit the new leading '1' + var overflowKey = new byte[nextKey.Length + 1]; + overflowKey[0] = 1; + // The rest are already 0 from default initialization, so we return. + return overflowKey; + } + } } class CorruptedDbOnTheRocks : DbOnTheRocks diff --git a/src/Nethermind/Nethermind.Db.Test/DbTrackerTests.cs b/src/Nethermind/Nethermind.Db.Test/DbTrackerTests.cs index 27a1344fc3f..14b5c43bfde 100644 --- a/src/Nethermind/Nethermind.Db.Test/DbTrackerTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/DbTrackerTests.cs @@ -1,9 +1,25 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Linq; using Autofac; using FluentAssertions; +using Nethermind.Api; +using Nethermind.Blockchain.Receipts; +using Nethermind.Blockchain.Synchronization; +using Nethermind.Consensus.Processing; using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test; +using Nethermind.Core.Test.Builders; +using Nethermind.Db.Rocks; +using Nethermind.Db.Rocks.Config; +using Nethermind.Init.Modules; +using Nethermind.Logging; +using Nethermind.Monitoring; +using Nethermind.Monitoring.Config; +using NSubstitute; using NUnit.Framework; namespace Nethermind.Db.Test; @@ -14,14 +30,19 @@ public class DbTrackerTests public void TestTrackOnlyCreatedDb() { using IContainer container = new ContainerBuilder() - .AddSingleton() - .AddDecorator() + .AddSingleton() + .AddSingleton(new DbConfig()) + .AddSingleton() + .AddSingleton(new MetricsConfig()) + .AddSingleton(LimboLogs.Instance) + .AddSingleton(NoopMonitoringService.Instance) + .AddDecorator() .AddSingleton() .Build(); IDbFactory dbFactory = container.Resolve(); - DbTracker tracker = container.Resolve(); + DbMonitoringModule.DbTracker tracker = container.Resolve(); tracker.GetAllDbMeta().Count().Should().Be(0); dbFactory.CreateDb(new DbSettings("TestDb", "TestDb")); @@ -30,4 +51,108 @@ public void TestTrackOnlyCreatedDb() var firstEntry = tracker.GetAllDbMeta().First(); firstEntry.Key.Should().Be("TestDb"); } + + [Parallelizable(ParallelScope.None)] + [TestCase(true)] + [TestCase(false)] + public void TestUpdateDbMetric(bool isProcessing) + { + IBlockProcessingQueue queue = Substitute.For(); + (IContainer container, Action updateAction, FakeDb fakeDb) = ConfigureMetricUpdater((builder) => builder.AddSingleton(queue)); + using var _ = container; + + // Reset + Metrics.DbReads["TestDb"] = 0; + + if (isProcessing) + { + container.Resolve(); // Only setup is something requested the block processing queue. + queue.IsEmpty.Returns(false); + queue.BlockAdded += Raise.EventWith(new BlockEventArgs(Build.A.Block.TestObject)); + } + + updateAction!(); + + // Assert + Assert.That(Metrics.DbReads["TestDb"], isProcessing ? Is.EqualTo(0) : Is.EqualTo(10)); + } + + [Parallelizable(ParallelScope.None)] + [Test] + public void DoesNotUpdateIfIntervalHasNotPassed() + { + (IContainer container, Action updateAction, FakeDb fakeDb) = ConfigureMetricUpdater(); + using var _ = container; + + container.Resolve().CreateDb(new DbSettings("TestDb", "TestDb")); + + // Reset + Metrics.DbReads["TestDb"] = 0; + + updateAction!(); + Assert.That(Metrics.DbReads["TestDb"], Is.EqualTo(10)); + + fakeDb.SetMetric(new IDbMeta.DbMetric() + { + TotalReads = 11 + }); + + updateAction!(); + Assert.That(Metrics.DbReads["TestDb"], Is.EqualTo(10)); + } + + private (IContainer, Action, FakeDb) ConfigureMetricUpdater(Action? configurer = null) + { + IDbFactory fakeDbFactory = Substitute.For(); + + IMonitoringService monitoringService = Substitute.For(); + ContainerBuilder builder = new ContainerBuilder() + .AddModule(new DbModule(new InitConfig(), new ReceiptConfig(), new SyncConfig())) + .AddModule(new DbMonitoringModule()) + .AddSingleton(new DbConfig()) + .AddSingleton(new MetricsConfig()) + .AddSingleton(LimboLogs.Instance) + .AddSingleton(monitoringService) + .AddDecorator() + .AddSingleton(fakeDbFactory); + + configurer?.Invoke(builder); + + IContainer container = builder + .Build(); + + IDbMeta.DbMetric metric = new IDbMeta.DbMetric() + { + TotalReads = 10 + }; + FakeDb fakeDb = new FakeDb(metric); + fakeDbFactory.CreateDb(Arg.Any()).Returns(fakeDb); + + Action updateAction = null; + monitoringService + .When((m) => m.AddMetricsUpdateAction(Arg.Any())) + .Do((c) => + { + updateAction = (Action)c[0]; + }); + + container.Resolve().CreateDb(new DbSettings("TestDb", "TestDb")); + + return (container, updateAction, fakeDb); + } + + private class FakeDb(IDbMeta.DbMetric metric) : TestMemDb, IDbMeta + { + private IDbMeta.DbMetric _metric = metric; + + public override IDbMeta.DbMetric GatherMetric() + { + return _metric; + } + + internal void SetMetric(IDbMeta.DbMetric metric) + { + _metric = metric; + } + } } diff --git a/src/Nethermind/Nethermind.Db.Test/FullPruning/FullPruningDbTests.cs b/src/Nethermind/Nethermind.Db.Test/FullPruning/FullPruningDbTests.cs index 7656307b45a..77cd44d8df1 100644 --- a/src/Nethermind/Nethermind.Db.Test/FullPruning/FullPruningDbTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/FullPruning/FullPruningDbTests.cs @@ -81,7 +81,7 @@ public void during_pruning_duplicate_on_read() } [Test] - public void during_pruning_dont_duplicate_read_with_skip_duplicate_read() + public void during_pruning_do_not_duplicate_read_with_skip_duplicate_read() { TestContext test = new(); byte[] key = { 1, 2 }; diff --git a/src/Nethermind/Nethermind.Db.Test/RocksDbConfigFactoryTests.cs b/src/Nethermind/Nethermind.Db.Test/RocksDbConfigFactoryTests.cs index ad1d4d04c31..d79af859643 100644 --- a/src/Nethermind/Nethermind.Db.Test/RocksDbConfigFactoryTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/RocksDbConfigFactoryTests.cs @@ -23,7 +23,6 @@ public void CanFetchNormally() var dbConfig = new DbConfig(); var factory = new RocksDbConfigFactory(dbConfig, new PruningConfig(), new TestHardwareInfo(0), LimboLogs.Instance); IRocksDbConfig config = factory.GetForDatabase("State0", null); - Console.Error.WriteLine(config.RocksDbOptions); config.RocksDbOptions.Should().Be(dbConfig.RocksDbOptions + dbConfig.StateDbRocksDbOptions); } @@ -100,4 +99,24 @@ public void WillEnforceMinimumMaxOpenFiles() // With system limit of 1000, would be 1000 * 0.8 = 800 config.MaxOpenFiles.Should().Be(800); } + + [Test] + public void WillApplySkipSstFileSizeChecksWhenConfigExplicitlyEnabled() + { + var dbConfig = new DbConfig(); + dbConfig.SkipCheckingSstFileSizesOnDbOpen = true; + var factory = new RocksDbConfigFactory(dbConfig, new PruningConfig(), new TestHardwareInfo(0), LimboLogs.Instance); + IRocksDbConfig config = factory.GetForDatabase("State0", null); + config.RocksDbOptions.Should().Contain("skip_checking_sst_file_sizes_on_db_open=true;"); + } + + [Test] + public void WillNotApplySkipSstFileSizeChecksWhenConfigExplicitlyDisabled() + { + var dbConfig = new DbConfig(); + dbConfig.SkipCheckingSstFileSizesOnDbOpen = false; + var factory = new RocksDbConfigFactory(dbConfig, new PruningConfig(), new TestHardwareInfo(0), LimboLogs.Instance); + IRocksDbConfig config = factory.GetForDatabase("State0", null); + config.RocksDbOptions.Should().NotContain("skip_checking_sst_file_sizes_on_db_open"); + } } diff --git a/src/Nethermind/Nethermind.Db.Test/StandardDbInitializerTests.cs b/src/Nethermind/Nethermind.Db.Test/StandardDbInitializerTests.cs index e61cf1a1078..b8323d308f4 100644 --- a/src/Nethermind/Nethermind.Db.Test/StandardDbInitializerTests.cs +++ b/src/Nethermind/Nethermind.Db.Test/StandardDbInitializerTests.cs @@ -10,6 +10,7 @@ using Nethermind.Api; using Nethermind.Blockchain.Receipts; using Nethermind.Blockchain.Synchronization; +using Nethermind.Config; using Nethermind.Core; using Nethermind.Core.Test; using Nethermind.Core.Test.Db; diff --git a/src/Nethermind/Nethermind.Db/Blooms/BloomStorage.cs b/src/Nethermind/Nethermind.Db/Blooms/BloomStorage.cs index 50fffb6da71..f21903dabf7 100644 --- a/src/Nethermind/Nethermind.Db/Blooms/BloomStorage.cs +++ b/src/Nethermind/Nethermind.Db/Blooms/BloomStorage.cs @@ -68,7 +68,7 @@ public BloomStorage(IBloomConfig config, [KeyFilter(DbNames.Bloom)] IDb bloomDb, long Get(Hash256 key, long defaultValue) => bloomDb.Get(key)?.ToLongFromBigEndianByteArrayWithoutLeadingZeros() ?? defaultValue; _config = config ?? throw new ArgumentNullException(nameof(config)); - _bloomInfoDb = bloomDb ?? throw new ArgumentNullException(nameof(_bloomInfoDb)); + _bloomInfoDb = bloomDb ?? throw new ArgumentNullException(nameof(bloomDb)); _fileStoreFactory = fileStoreFactory; _storageLevels = CreateStorageLevels(config); Levels = (byte)_storageLevels.Length; diff --git a/src/Nethermind/Nethermind.Db/CompressingDb.cs b/src/Nethermind/Nethermind.Db/CompressingDb.cs index a03e69ff103..881b839f25b 100644 --- a/src/Nethermind/Nethermind.Db/CompressingDb.cs +++ b/src/Nethermind/Nethermind.Db/CompressingDb.cs @@ -148,7 +148,7 @@ public IEnumerable GetAllValues(bool ordered = false) => public void Clear() => _wrapped.Clear(); - public IDbMeta.DbMetric GatherMetric(bool includeSharedCache = false) => _wrapped.GatherMetric(includeSharedCache); + public IDbMeta.DbMetric GatherMetric() => _wrapped.GatherMetric(); public void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags = WriteFlags.None) => _wrapped.Set(key, Compress(value), flags); diff --git a/src/Nethermind/Nethermind.Db/DbTracker.cs b/src/Nethermind/Nethermind.Db/DbTracker.cs deleted file mode 100644 index 95829f161be..00000000000 --- a/src/Nethermind/Nethermind.Db/DbTracker.cs +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using NonBlocking; - -namespace Nethermind.Db; - -public class DbTracker -{ - private readonly ConcurrentDictionary _createdDbs = new ConcurrentDictionary(); - - public void AddDb(string name, IDbMeta dbMeta) - { - _createdDbs.TryAdd(name, dbMeta); - } - - public IEnumerable> GetAllDbMeta() - { - return _createdDbs; - } - - public class DbFactoryInterceptor(DbTracker tracker, IDbFactory baseFactory) : IDbFactory - { - public IDb CreateDb(DbSettings dbSettings) - { - IDb db = baseFactory.CreateDb(dbSettings); - if (db is IDbMeta dbMeta) - { - tracker.AddDb(dbSettings.DbName, dbMeta); - } - return db; - } - - public IColumnsDb CreateColumnsDb(DbSettings dbSettings) where T : struct, Enum - { - IColumnsDb db = baseFactory.CreateColumnsDb(dbSettings); - if (db is IDbMeta dbMeta) - { - tracker.AddDb(dbSettings.DbName, dbMeta); - } - return db; - } - - public string GetFullDbPath(DbSettings dbSettings) => baseFactory.GetFullDbPath(dbSettings); - } -} diff --git a/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs b/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs index f79c010c988..32979c96383 100755 --- a/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs +++ b/src/Nethermind/Nethermind.Db/FullPruning/FullPruningDb.cs @@ -17,7 +17,7 @@ namespace Nethermind.Db.FullPruning /// Allows to start pruning with in a thread safe way. /// When pruning is started it duplicates all writes to current DB as well as the new one for full pruning, this includes write batches. /// When returned in is d it will delete the pruning DB if the pruning was not successful. - /// It uses to create new pruning DB's. Check to see how inner sub DB's are organised. + /// It uses to create new pruning DB's. Check to see how inner sub DB's are organized. /// public class FullPruningDb : IDb, IFullPruningDb, ITunableDb { @@ -121,10 +121,7 @@ public void Dispose() _pruningContext?.CloningDb.Dispose(); } - public IDbMeta.DbMetric GatherMetric(bool includeSharedCache = false) - { - return _currentDb.GatherMetric(includeSharedCache); - } + public IDbMeta.DbMetric GatherMetric() => _currentDb.GatherMetric(); public string Name => _settings.DbName; diff --git a/src/Nethermind/Nethermind.Db/IColumnsDb.cs b/src/Nethermind/Nethermind.Db/IColumnsDb.cs index 4f1eddcbd44..5ffddced6ab 100644 --- a/src/Nethermind/Nethermind.Db/IColumnsDb.cs +++ b/src/Nethermind/Nethermind.Db/IColumnsDb.cs @@ -13,10 +13,17 @@ public interface IColumnsDb : IDbMeta, IDisposable IEnumerable ColumnKeys { get; } public IReadOnlyColumnDb CreateReadOnly(bool createInMemWriteStore) => new ReadOnlyColumnsDb(this, createInMemWriteStore); IColumnsWriteBatch StartWriteBatch(); + IColumnDbSnapshot CreateSnapshot(); } public interface IColumnsWriteBatch : IDisposable { IWriteBatch GetColumnBatch(TKey key); } + + + public interface IColumnDbSnapshot : IDisposable + { + IReadOnlyKeyValueStore GetColumn(TKey key); + } } diff --git a/src/Nethermind/Nethermind.Db/IDb.cs b/src/Nethermind/Nethermind.Db/IDb.cs index 73f9e4ba171..cc2a6fa26c5 100644 --- a/src/Nethermind/Nethermind.Db/IDb.cs +++ b/src/Nethermind/Nethermind.Db/IDb.cs @@ -21,7 +21,7 @@ public interface IDb : IKeyValueStoreWithBatching, IDbMeta, IDisposable // Some metadata options public interface IDbMeta { - DbMetric GatherMetric(bool includeSharedCache = false) => new DbMetric(); + DbMetric GatherMetric() => new DbMetric(); void Flush(bool onlyWal = false); void Clear() { } diff --git a/src/Nethermind/Nethermind.Db/IDbFactory.cs b/src/Nethermind/Nethermind.Db/IDbFactory.cs index 3a45f50e476..2ce63caca62 100644 --- a/src/Nethermind/Nethermind.Db/IDbFactory.cs +++ b/src/Nethermind/Nethermind.Db/IDbFactory.cs @@ -13,21 +13,21 @@ public interface IDbFactory /// /// Creates a standard Db. /// - /// Setting to use for DB creation. + /// Settings to use for DB creation. /// Standard DB. IDb CreateDb(DbSettings dbSettings); /// /// Creates a column Db. /// - /// Setting to use for DB creation. + /// Settings to use for DB creation. /// Column DB. IColumnsDb CreateColumnsDb(DbSettings dbSettings) where T : struct, Enum; /// /// Gets the file system path for the DB. /// - /// Setting to use for DB creation. + /// Settings to use for DB creation. /// File system path for the DB. public string GetFullDbPath(DbSettings dbSettings) => dbSettings.DbPath; } diff --git a/src/Nethermind/Nethermind.Db/IPruningConfig.cs b/src/Nethermind/Nethermind.Db/IPruningConfig.cs index b700e2e2066..efd534d6952 100644 --- a/src/Nethermind/Nethermind.Db/IPruningConfig.cs +++ b/src/Nethermind/Nethermind.Db/IPruningConfig.cs @@ -16,10 +16,10 @@ public interface IPruningConfig : IConfig [ConfigItem(Description = "The pruning mode.", DefaultValue = "Hybrid")] PruningMode Mode { get; set; } - [ConfigItem(Description = "The in-memory cache size, in MB. Bigger size tend to improve performance.", DefaultValue = "1280")] + [ConfigItem(Description = "The in-memory cache size, in MB. Bigger size tend to improve performance.", DefaultValue = "1792")] long CacheMb { get; set; } - [ConfigItem(Description = "The in-memory cache size for dirty nodes, in MB. Increasing this reduces pruning interval but cause increased pruning time.", DefaultValue = "1024")] + [ConfigItem(Description = "The in-memory cache size for dirty nodes, in MB. Increasing this reduces pruning interval but cause increased pruning time.", DefaultValue = "1536")] long DirtyCacheMb { get; set; } [ConfigItem( @@ -92,7 +92,7 @@ The max number of parallel tasks that can be used by full pruning. [ConfigItem(Description = "Minimum persisted cache prune target", DefaultValue = "50000000")] long PrunePersistedNodeMinimumTarget { get; set; } - [ConfigItem(Description = "Maximimum number of block worth of unpersisted state in memory. Default is 297, which is number of mainnet block per hour.", DefaultValue = "297")] + [ConfigItem(Description = "Maximum number of blocks worth of unpersisted state in memory. Default is 297, which is the number of mainnet blocks per hour.", DefaultValue = "297")] long MaxUnpersistedBlockCount { get; set; } [ConfigItem(Description = "Minimum number of block worth of unpersisted state in memory. Prevent memory pruning too often due to insufficient dirty cache memory.", DefaultValue = "8")] @@ -103,4 +103,7 @@ The max number of parallel tasks that can be used by full pruning. [ConfigItem(Description = "[TECHNICAL] Simulate long finalization by not moving finalized block pointer until after this depth.", DefaultValue = "0", HiddenFromDocs = true)] int SimulateLongFinalizationDepth { get; set; } + + [ConfigItem(Description = "If in-memory pruning is scheduled, the duration between `newPayload` and the GC trigger. If too short, it may clash with fork choice; if too long, it may overlap with GC.", DefaultValue = "75", HiddenFromDocs = true)] + int PruneDelayMilliseconds { get; set; } } diff --git a/src/Nethermind/Nethermind.Db/MemColumnsDb.cs b/src/Nethermind/Nethermind.Db/MemColumnsDb.cs index ea37042e51b..7d145ce1bc1 100644 --- a/src/Nethermind/Nethermind.Db/MemColumnsDb.cs +++ b/src/Nethermind/Nethermind.Db/MemColumnsDb.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using Nethermind.Core; namespace Nethermind.Db { @@ -38,6 +40,12 @@ public IColumnsWriteBatch StartWriteBatch() { return new InMemoryColumnWriteBatch(this); } + + public IColumnDbSnapshot CreateSnapshot() + { + throw new NotSupportedException("Snapshot not supported"); + } + public void Dispose() { } public void Flush(bool onlyWal = false) { } } diff --git a/src/Nethermind/Nethermind.Db/MemDb.cs b/src/Nethermind/Nethermind.Db/MemDb.cs index af6f4313e07..39490693200 100644 --- a/src/Nethermind/Nethermind.Db/MemDb.cs +++ b/src/Nethermind/Nethermind.Db/MemDb.cs @@ -18,8 +18,14 @@ public class MemDb : IFullDb public long ReadsCount { get; private set; } public long WritesCount { get; private set; } +#if ZK + private readonly Dictionary _db; + private readonly Dictionary.AlternateLookup> _spanDb; +#else private readonly ConcurrentDictionary _db; private readonly ConcurrentDictionary.AlternateLookup> _spanDb; +#endif + public MemDb(string name) : this(0, 0) @@ -46,11 +52,15 @@ public MemDb(int writeDelay, int readDelay) { _writeDelay = writeDelay; _readDelay = readDelay; +#if ZK + _db = new Dictionary(Bytes.EqualityComparer); +#else _db = new ConcurrentDictionary(Bytes.EqualityComparer); +#endif _spanDb = _db.GetAlternateLookup>(); } - public string Name { get; } + public string Name { get; } = nameof(MemDb); public virtual byte[]? this[ReadOnlySpan key] { @@ -80,22 +90,18 @@ public virtual byte[]? this[ReadOnlySpan key] public virtual void Remove(ReadOnlySpan key) { +#if ZK + _spanDb.Remove(key); +#else _spanDb.TryRemove(key, out _); +#endif } - public bool KeyExists(ReadOnlySpan key) - { - return _spanDb.ContainsKey(key); - } - - public IDb Innermost => this; + public bool KeyExists(ReadOnlySpan key) => _spanDb.ContainsKey(key); public virtual void Flush(bool onlyWal = false) { } - public void Clear() - { - _db.Clear(); - } + public void Clear() => _db.Clear(); public IEnumerable> GetAll(bool ordered = false) => ordered ? OrderedDb : _db; @@ -103,35 +109,20 @@ public void Clear() public IEnumerable GetAllValues(bool ordered = false) => ordered ? OrderedDb.Select(kvp => kvp.Value) : Values; - public virtual IWriteBatch StartWriteBatch() - { - return this.LikeABatch(); - } + public virtual IWriteBatch StartWriteBatch() => this.LikeABatch(); public ICollection Keys => _db.Keys; public ICollection Values => _db.Values; public int Count => _db.Count; - public static long GetSize() => 0; - public static long GetCacheSize(bool includeCacheSize) => 0; - public static long GetIndexSize() => 0; - public static long GetMemtableSize() => 0; - - public void Dispose() - { - } + public void Dispose() { } public bool PreferWriteByArray => true; - public virtual Span GetSpan(ReadOnlySpan key) - { - return Get(key).AsSpan(); - } + public virtual Span GetSpan(ReadOnlySpan key) => Get(key).AsSpan(); - public void DangerousReleaseMemory(in ReadOnlySpan span) - { - } + public void DangerousReleaseMemory(in ReadOnlySpan span) { } public virtual byte[]? Get(ReadOnlySpan key, ReadFlags flags = ReadFlags.None) { @@ -154,19 +145,13 @@ public virtual void Set(ReadOnlySpan key, byte[]? value, WriteFlags flags WritesCount++; if (value is null) { - _spanDb.TryRemove(key, out _); + Remove(key); return; } _spanDb[key] = value; } - public IDbMeta.DbMetric GatherMetric(bool includeSharedCache = false) - { - return new IDbMeta.DbMetric() - { - Size = Count - }; - } + public virtual IDbMeta.DbMetric GatherMetric() => new() { Size = Count }; private IEnumerable> OrderedDb => _db.OrderBy(kvp => kvp.Key, Bytes.Comparer); } diff --git a/src/Nethermind/Nethermind.Db/Metrics.cs b/src/Nethermind/Nethermind.Db/Metrics.cs index b4889866ab9..296b1a5eb63 100644 --- a/src/Nethermind/Nethermind.Db/Metrics.cs +++ b/src/Nethermind/Nethermind.Db/Metrics.cs @@ -85,7 +85,7 @@ public static class Metrics internal static void IncrementStorageSkippedWrites(long value) => Interlocked.Add(ref _storageSkippedWrites, value); [GaugeMetric] - [Description("Indicator if StadeDb is being pruned.")] + [Description("Indicator if StateDb is being pruned.")] public static int StateDbPruning { get; set; } [GaugeMetric] diff --git a/src/Nethermind/Nethermind.Db/PruningConfig.cs b/src/Nethermind/Nethermind.Db/PruningConfig.cs index 76a0296cb5f..d02de3caa1c 100644 --- a/src/Nethermind/Nethermind.Db/PruningConfig.cs +++ b/src/Nethermind/Nethermind.Db/PruningConfig.cs @@ -26,8 +26,8 @@ public bool Enabled } public PruningMode Mode { get; set; } = PruningMode.Hybrid; - public long CacheMb { get; set; } = 1280; - public long DirtyCacheMb { get; set; } = 1024; + public long CacheMb { get; set; } = 1792; + public long DirtyCacheMb { get; set; } = 1536; public long PersistenceInterval { get; set; } = 1; public long FullPruningThresholdMb { get; set; } = 256000; public FullPruningTrigger FullPruningTrigger { get; set; } = FullPruningTrigger.Manual; @@ -64,5 +64,6 @@ public int DirtyNodeShardBit public long MinUnpersistedBlockCount { get; set; } = 8; // About slightly more than 1 minute public int MaxBufferedCommitCount { get; set; } = 128; public int SimulateLongFinalizationDepth { get; set; } = 0; + public int PruneDelayMilliseconds { get; set; } = 75; } } diff --git a/src/Nethermind/Nethermind.Db/ReadOnlyColumnsDb.cs b/src/Nethermind/Nethermind.Db/ReadOnlyColumnsDb.cs index 3b7bafd750d..0c8483d09e7 100644 --- a/src/Nethermind/Nethermind.Db/ReadOnlyColumnsDb.cs +++ b/src/Nethermind/Nethermind.Db/ReadOnlyColumnsDb.cs @@ -10,9 +10,11 @@ namespace Nethermind.Db public class ReadOnlyColumnsDb : IReadOnlyColumnDb, IDisposable { private readonly IDictionary _readOnlyColumns; + private readonly IColumnsDb _baseColumnDb; public ReadOnlyColumnsDb(IColumnsDb baseColumnDb, bool createInMemWriteStore) { + _baseColumnDb = baseColumnDb; _readOnlyColumns = baseColumnDb.ColumnKeys .Select(key => (key, db: baseColumnDb.GetColumnDb(key).CreateReadOnly(createInMemWriteStore))) .ToDictionary(it => it.key, it => it.db); @@ -29,6 +31,11 @@ public IColumnsWriteBatch StartWriteBatch() return new InMemoryColumnWriteBatch(this); } + public IColumnDbSnapshot CreateSnapshot() + { + return _baseColumnDb.CreateSnapshot(); + } + public void ClearTempChanges() { foreach (KeyValuePair readOnlyColumn in _readOnlyColumns) diff --git a/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs b/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs index ff73f260a3a..bc734119beb 100644 --- a/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs +++ b/src/Nethermind/Nethermind.Db/ReadOnlyDb.cs @@ -61,7 +61,7 @@ public KeyValuePair[] this[byte[][] keys] public IWriteBatch StartWriteBatch() => this.LikeABatch(); - public IDbMeta.DbMetric GatherMetric(bool includeSharedCache = false) => wrappedDb.GatherMetric(includeSharedCache); + public IDbMeta.DbMetric GatherMetric() => wrappedDb.GatherMetric(); public void Remove(ReadOnlySpan key) { } diff --git a/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs b/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs index 979ff10526d..d69a532c873 100644 --- a/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs +++ b/src/Nethermind/Nethermind.Db/SimpleFilePublicKeyDb.cs @@ -240,7 +240,7 @@ private void LoadData() bytes = rentedBuffer.AsSpan(0, read + bytes.Length); while (true) { - // Store the original span incase need to undo the key slicing if end of line not found + // Store the original span in case we need to undo the key slicing when the end of line is not found Span iterationSpan = bytes; int commaIndex = bytes.IndexOf((byte)','); Span key = default; diff --git a/src/Nethermind/Nethermind.Era1.Test/AdminEraServiceTests.cs b/src/Nethermind/Nethermind.Era1.Test/AdminEraServiceTests.cs index 7a0f245b705..461f0ff4a51 100644 --- a/src/Nethermind/Nethermind.Era1.Test/AdminEraServiceTests.cs +++ b/src/Nethermind/Nethermind.Era1.Test/AdminEraServiceTests.cs @@ -10,7 +10,7 @@ namespace Nethermind.Era1.Test; public class AdminEraServiceTests { [Test] - public void CanCallcExport() + public void CanCallImport() { IEraImporter importer = Substitute.For(); AdminEraService adminEraService = new AdminEraService( diff --git a/src/Nethermind/Nethermind.Era1.Test/E2StoreWriterTests.cs b/src/Nethermind/Nethermind.Era1.Test/E2StoreWriterTests.cs index 174bed9663a..9be200e2fb9 100644 --- a/src/Nethermind/Nethermind.Era1.Test/E2StoreWriterTests.cs +++ b/src/Nethermind/Nethermind.Era1.Test/E2StoreWriterTests.cs @@ -43,7 +43,7 @@ public async Task WriteEntry_WritingAnEntry_WritesCorrectLengthInHeader(int leng [TestCase(1)] [TestCase(5)] [TestCase(12)] - public async Task WriteEntry_WritingAnEntry_ReturnCorrectNumberofBytesWritten(int length) + public async Task WriteEntry_WritingAnEntry_ReturnCorrectNumberOfBytesWritten(int length) { using MemoryStream stream = new MemoryStream(); using E2StoreWriter sut = new E2StoreWriter(stream); diff --git a/src/Nethermind/Nethermind.Era1.Test/Era1ModuleTests.cs b/src/Nethermind/Nethermind.Era1.Test/Era1ModuleTests.cs index 2cbcdb0a7fc..0dfb24a1d47 100644 --- a/src/Nethermind/Nethermind.Era1.Test/Era1ModuleTests.cs +++ b/src/Nethermind/Nethermind.Era1.Test/Era1ModuleTests.cs @@ -118,8 +118,12 @@ public async Task ImportAndExportGethFiles(string network) [Test] public async Task CreateEraAndVerifyAccumulators() { - TestBlockchain testBlockchain = await BasicTestBlockchain.Create(); - IWorldState worldState = testBlockchain.WorldStateManager.GlobalWorldState; + TestBlockchain testBlockchain = await BasicTestBlockchain.Create( + configurer: (builder) => builder.WithGenesisPostProcessor((block, state, specProvider) => + { + state.AddToBalance(TestItem.AddressA, 10.Ether(), specProvider.GenesisSpec); + }) + ); using TempPath tmpFile = TempPath.GetTempFile(); Block genesis = testBlockchain.BlockFinder.FindBlock(0)!; @@ -129,16 +133,7 @@ public async Task CreateEraAndVerifyAccumulators() UInt256 nonce = 0; List blocks = []; - using (worldState.BeginScope(genesis.Header)) - { - worldState.AddToBalance(TestItem.AddressA, 10.Ether(), testBlockchain.SpecProvider.GenesisSpec); - worldState.RecalculateStateRoot(); - - genesis.Header.StateRoot = worldState.StateRoot; - worldState.CommitTree(0); - - blocks.Add(genesis); - } + blocks.Add(genesis); BlockHeader uncle = Build.A.BlockHeader.TestObject; diff --git a/src/Nethermind/Nethermind.Era1/AdminEraService.cs b/src/Nethermind/Nethermind.Era1/AdminEraService.cs index 16eb9cc7144..d8e89290ae9 100644 --- a/src/Nethermind/Nethermind.Era1/AdminEraService.cs +++ b/src/Nethermind/Nethermind.Era1/AdminEraService.cs @@ -66,7 +66,7 @@ public string ImportHistory(string source, long from, long to, string? accumulat private async Task StartExportTask(string destination, long from, long to) { - // Creating the task is outside the try block so that argument exception can be cought + // Creating the task is outside the try block so that argument exceptions can be caught Task task = _eraExporter.Export( destination, from, diff --git a/src/Nethermind/Nethermind.Era1/EraImporter.cs b/src/Nethermind/Nethermind.Era1/EraImporter.cs index 76baecd536c..515e309b58c 100644 --- a/src/Nethermind/Nethermind.Era1/EraImporter.cs +++ b/src/Nethermind/Nethermind.Era1/EraImporter.cs @@ -49,7 +49,7 @@ public async Task Import(string src, long from, long to, string? accumulatorFile trustedAccumulators = (await fileSystem.File.ReadAllLinesAsync(accumulatorFile, cancellation)).Select(EraPathUtils.ExtractHashFromAccumulatorAndCheckSumEntry).ToHashSet(); } - IEraStore eraStore = eraStoreFactory.Create(src, trustedAccumulators); + using IEraStore eraStore = eraStoreFactory.Create(src, trustedAccumulators); long lastBlockInStore = eraStore.LastBlock; if (to == 0) to = long.MaxValue; diff --git a/src/Nethermind/Nethermind.Era1/EraReader.cs b/src/Nethermind/Nethermind.Era1/EraReader.cs index cb6d483355a..740d2167c6c 100644 --- a/src/Nethermind/Nethermind.Era1/EraReader.cs +++ b/src/Nethermind/Nethermind.Era1/EraReader.cs @@ -88,7 +88,7 @@ public async Task VerifyContent(ISpecProvider specProvider, IBlock if (!blockValidator.ValidateBodyAgainstHeader(err.Block.Header, err.Block.Body, out string? error)) { - throw new EraVerificationException($"Mismatched block body againts header: {error}. Block number {blockNumber}."); + throw new EraVerificationException($"Mismatched block body against header: {error}. Block number {blockNumber}."); } if (!blockValidator.ValidateOrphanedBlock(err.Block, out error)) @@ -178,8 +178,8 @@ private async Task ReadBlockAndReceipts(long blockNumber, bool position, static (buffer) => new UInt256(buffer.Span, isBigEndian: false), EntryTypes.TotalDifficulty, - out UInt256 currentTotalDiffulty); - header.TotalDifficulty = currentTotalDiffulty; + out UInt256 currentTotalDifficulty); + header.TotalDifficulty = currentTotalDifficulty; Block block = new Block(header, body); return new EntryReadResult(block, receipts); diff --git a/src/Nethermind/Nethermind.Era1/EraStore.cs b/src/Nethermind/Nethermind.Era1/EraStore.cs index 9086fcd4b06..e2a0d0da9ac 100644 --- a/src/Nethermind/Nethermind.Era1/EraStore.cs +++ b/src/Nethermind/Nethermind.Era1/EraStore.cs @@ -81,14 +81,14 @@ public EraStore( IFileSystem fileSystem, string networkName, int maxEraSize, - ISet? trustedAcccumulators, + ISet? trustedAccumulators, string directory, int verifyConcurrency = 0 ) { _specProvider = specProvider; _blockValidator = blockValidator; - _trustedAccumulators = trustedAcccumulators; + _trustedAccumulators = trustedAccumulators; _maxEraFile = maxEraSize; _maxOpenFile = Environment.ProcessorCount * 2; if (_verifyConcurrency == 0) _verifyConcurrency = Environment.ProcessorCount; diff --git a/src/Nethermind/Nethermind.Era1/JsonRpc/IEraAdminRpcModule.cs b/src/Nethermind/Nethermind.Era1/JsonRpc/IEraAdminRpcModule.cs index c1591be7fe7..ebb021f0ef3 100644 --- a/src/Nethermind/Nethermind.Era1/JsonRpc/IEraAdminRpcModule.cs +++ b/src/Nethermind/Nethermind.Era1/JsonRpc/IEraAdminRpcModule.cs @@ -24,7 +24,7 @@ int to [JsonRpcMethod(Description = "Import a range of historic block from era1 directory.", EdgeCaseHint = "", - ExampleResponse = "\"Export task started.\"", + ExampleResponse = "\"Import task started.\"", IsImplemented = true)] Task> admin_importHistory( [JsonRpcParameter(Description = "Source path to import from.", ExampleValue = "/tmp/eradir")] diff --git a/src/Nethermind/Nethermind.EthStats/Clients/EthStatsClient.cs b/src/Nethermind/Nethermind.EthStats/Clients/EthStatsClient.cs index e6d47686a70..cf34ee2e6b9 100644 --- a/src/Nethermind/Nethermind.EthStats/Clients/EthStatsClient.cs +++ b/src/Nethermind/Nethermind.EthStats/Clients/EthStatsClient.cs @@ -32,7 +32,7 @@ public EthStatsClient( _urlFromConfig = urlFromConfig ?? throw new ArgumentNullException(nameof(urlFromConfig)); _reconnectionInterval = reconnectionInterval; _messageSender = messageSender ?? throw new ArgumentNullException(nameof(messageSender)); - _logger = logManager?.GetClassLogger() ?? throw new ArgumentException(nameof(logManager)); + _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); } internal string BuildUrl() diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs index 20496ca0c67..1d3225fe6b6 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/EvmBenchmarks.cs @@ -11,6 +11,7 @@ using Nethermind.Db; using Nethermind.Evm.CodeAnalysis; using Nethermind.Specs; +using Nethermind.Evm.GasPolicy; using Nethermind.Evm.State; using Nethermind.Evm.Tracing; using Nethermind.Int256; @@ -32,7 +33,7 @@ public class EvmBenchmarks private IVirtualMachine _virtualMachine; private BlockHeader _header = new BlockHeader(Keccak.Zero, Keccak.Zero, Address.Zero, UInt256.One, MainnetSpecProvider.IstanbulBlockNumber, Int64.MaxValue, 1UL, Bytes.Empty); private IBlockhashProvider _blockhashProvider = new TestBlockhashProvider(); - private EvmState _evmState; + private VmState _evmState; private IWorldState _stateProvider; [GlobalSetup] @@ -45,12 +46,11 @@ public void GlobalSetup() _stateProvider.CreateAccount(Address.Zero, 1000.Ether()); _stateProvider.Commit(_spec); EthereumCodeInfoRepository codeInfoRepository = new(_stateProvider); - _virtualMachine = new VirtualMachine(_blockhashProvider, MainnetSpecProvider.Instance, LimboLogs.Instance); + _virtualMachine = new EthereumVirtualMachine(_blockhashProvider, MainnetSpecProvider.Instance, LimboLogs.Instance); _virtualMachine.SetBlockExecutionContext(new BlockExecutionContext(_header, _spec)); _virtualMachine.SetTxExecutionContext(new TxExecutionContext(Address.Zero, codeInfoRepository, null, 0)); - _environment = new ExecutionEnvironment - ( + _environment = ExecutionEnvironment.Rent( executingAccount: Address.Zero, codeSource: Address.Zero, caller: Address.Zero, @@ -61,7 +61,14 @@ public void GlobalSetup() inputData: default ); - _evmState = EvmState.RentTopLevel(long.MaxValue, ExecutionType.TRANSACTION, _environment, new StackAccessTracker(), _stateProvider.TakeSnapshot()); + _evmState = VmState.RentTopLevel(EthereumGasPolicy.FromLong(long.MaxValue), ExecutionType.TRANSACTION, _environment, new StackAccessTracker(), _stateProvider.TakeSnapshot()); + } + + [GlobalCleanup] + public void GlobalCleanup() + { + _evmState.Dispose(); + _environment.Dispose(); } [Benchmark] diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs b/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs index 336da145475..4f2d0447718 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/MultipleUnsignedOperations.cs @@ -11,6 +11,7 @@ using Nethermind.Core.Test; using Nethermind.Db; using Nethermind.Evm.CodeAnalysis; +using Nethermind.Evm.GasPolicy; using Nethermind.Evm.State; using Nethermind.Evm.Tracing; using Nethermind.Int256; @@ -31,7 +32,7 @@ public class MultipleUnsignedOperations private IVirtualMachine _virtualMachine; private readonly BlockHeader _header = new(Keccak.Zero, Keccak.Zero, Address.Zero, UInt256.One, MainnetSpecProvider.MuirGlacierBlockNumber, Int64.MaxValue, 1UL, Bytes.Empty); private readonly IBlockhashProvider _blockhashProvider = new TestBlockhashProvider(); - private EvmState _evmState; + private VmState _evmState; private IWorldState _stateProvider; private readonly byte[] _bytecode = Prepare.EvmCode @@ -77,12 +78,11 @@ public void GlobalSetup() Console.WriteLine(MuirGlacier.Instance); EthereumCodeInfoRepository codeInfoRepository = new(_stateProvider); - _virtualMachine = new VirtualMachine(_blockhashProvider, MainnetSpecProvider.Instance, new OneLoggerLogManager(NullLogger.Instance)); + _virtualMachine = new EthereumVirtualMachine(_blockhashProvider, MainnetSpecProvider.Instance, new OneLoggerLogManager(NullLogger.Instance)); _virtualMachine.SetBlockExecutionContext(new BlockExecutionContext(_header, _spec)); _virtualMachine.SetTxExecutionContext(new TxExecutionContext(Address.Zero, codeInfoRepository, null, 0)); - _environment = new ExecutionEnvironment - ( + _environment = ExecutionEnvironment.Rent( executingAccount: Address.Zero, codeSource: Address.Zero, caller: Address.Zero, @@ -93,7 +93,14 @@ public void GlobalSetup() inputData: default ); - _evmState = EvmState.RentTopLevel(100_000_000L, ExecutionType.TRANSACTION, _environment, new StackAccessTracker(), _stateProvider.TakeSnapshot()); + _evmState = VmState.RentTopLevel(EthereumGasPolicy.FromLong(100_000_000L), ExecutionType.TRANSACTION, _environment, new StackAccessTracker(), _stateProvider.TakeSnapshot()); + } + + [GlobalCleanup] + public void GlobalCleanup() + { + _evmState.Dispose(); + _environment.Dispose(); } [Benchmark] diff --git a/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs b/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs index 93916272630..600590d9dcd 100644 --- a/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs +++ b/src/Nethermind/Nethermind.Evm.Benchmark/StaticCallBenchmarks.cs @@ -12,6 +12,7 @@ using Nethermind.Db; using Nethermind.Evm.CodeAnalysis; using Nethermind.Specs; +using Nethermind.Evm.GasPolicy; using Nethermind.Evm.State; using Nethermind.Evm.Tracing; using Nethermind.Int256; @@ -32,7 +33,7 @@ public class StaticCallBenchmarks private IVirtualMachine _virtualMachine; private BlockHeader _header = new BlockHeader(Keccak.Zero, Keccak.Zero, Address.Zero, UInt256.One, MainnetSpecProvider.MuirGlacierBlockNumber, Int64.MaxValue, 1UL, Bytes.Empty); private IBlockhashProvider _blockhashProvider = new TestBlockhashProvider(); - private EvmState _evmState; + private VmState _evmState; private IWorldState _stateProvider; public IEnumerable Bytecodes @@ -88,11 +89,10 @@ public void GlobalSetup() Console.WriteLine(MuirGlacier.Instance); EthereumCodeInfoRepository codeInfoRepository = new(_stateProvider); - _virtualMachine = new VirtualMachine(_blockhashProvider, MainnetSpecProvider.Instance, new OneLoggerLogManager(NullLogger.Instance)); + _virtualMachine = new EthereumVirtualMachine(_blockhashProvider, MainnetSpecProvider.Instance, new OneLoggerLogManager(NullLogger.Instance)); _virtualMachine.SetBlockExecutionContext(new BlockExecutionContext(_header, _spec)); _virtualMachine.SetTxExecutionContext(new TxExecutionContext(Address.Zero, codeInfoRepository, null, 0)); - _environment = new ExecutionEnvironment - ( + _environment = ExecutionEnvironment.Rent( executingAccount: Address.Zero, codeSource: Address.Zero, caller: Address.Zero, @@ -103,7 +103,14 @@ public void GlobalSetup() inputData: default ); - _evmState = EvmState.RentTopLevel(100_000_000L, ExecutionType.TRANSACTION, _environment, new StackAccessTracker(), _stateProvider.TakeSnapshot()); + _evmState = VmState.RentTopLevel(EthereumGasPolicy.FromLong(100_000_000L), ExecutionType.TRANSACTION, _environment, new StackAccessTracker(), _stateProvider.TakeSnapshot()); + } + + [GlobalCleanup] + public void GlobalCleanup() + { + _evmState.Dispose(); + _environment.Dispose(); } [Benchmark(Baseline = true)] diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G1MSMPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G1MSMPrecompile.cs index 2880ab0a3e2..8b8018f557e 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G1MSMPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G1MSMPrecompile.cs @@ -48,7 +48,7 @@ public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSp return Errors.InvalidInputLength; } - // use Mul to optimise single point multiplication + // use Mul to optimize single point multiplication int nItems = inputData.Length / ItemSize; return nItems == 1 ? Mul(inputData) : MSM(inputData, nItems); } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G2MSMPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G2MSMPrecompile.cs index 54879d48471..87681710ba4 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G2MSMPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/Bls/G2MSMPrecompile.cs @@ -45,7 +45,7 @@ public Result Run(ReadOnlyMemory inputData, IReleaseSpec releaseSp if (inputData.Length % ItemSize > 0 || inputData.Length == 0) return Errors.InvalidInputLength; - // use Mul to optimise single point multiplication + // use Mul to optimize single point multiplication int nItems = inputData.Length / ItemSize; return nItems == 1 ? Mul(inputData) : MSM(inputData, nItems); } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/EcRecoverPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/EcRecoverPrecompile.cs index c6e0b475962..ba809cc8a67 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/EcRecoverPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/EcRecoverPrecompile.cs @@ -3,6 +3,7 @@ using System; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; @@ -70,8 +71,13 @@ private Result RunInternal(ReadOnlySpan inputDataSpan) return Empty; } - byte[] result = ValueKeccak.Compute(publicKey.Slice(1, 64)).ToByteArray(); - result.AsSpan(0, 12).Clear(); + byte[] result = new byte[32]; + ref byte refResult = ref MemoryMarshal.GetArrayDataReference(result); + + KeccakCache.ComputeTo(publicKey.Slice(1, 64), out Unsafe.As(ref refResult)); + + // Clear first 12 bytes, as address is last 20 bytes of the hash + Unsafe.InitBlockUnaligned(ref refResult, 0, 12); return result; } } diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/Extensions.cs b/src/Nethermind/Nethermind.Evm.Precompiles/Extensions.cs index ebe22dcda94..d5d6b96e70f 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/Extensions.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/Extensions.cs @@ -73,7 +73,7 @@ public static OrderedDictionary ListSystemContracts(this IRelea if (spec.IsBeaconBlockRootAvailable) systemContracts[Eip4788Constants.ContractAddressKey] = Eip4788Constants.BeaconRootsAddress; if (spec.ConsolidationRequestsEnabled) systemContracts[Eip7251Constants.ContractAddressKey] = Eip7251Constants.ConsolidationRequestPredeployAddress; - if (spec.DepositsEnabled) systemContracts[Eip6110Constants.ContractAddressKey] = spec.DepositContractAddress; + if (spec.DepositsEnabled) systemContracts[Eip6110Constants.ContractAddressKey] = spec.DepositContractAddress!; if (spec.IsEip2935Enabled) systemContracts[Eip2935Constants.ContractAddressKey] = Eip2935Constants.BlockHashHistoryAddress; if (spec.WithdrawalRequestsEnabled) systemContracts[Eip7002Constants.ContractAddressKey] = Eip7002Constants.WithdrawalRequestPredeployAddress; diff --git a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs index 44bb8bc15fc..9cc89cc3a33 100644 --- a/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs +++ b/src/Nethermind/Nethermind.Evm.Precompiles/ModExpPrecompile.cs @@ -93,8 +93,16 @@ private static long DataGasCostInternal(ReadOnlySpan inputData, IReleaseSp ulong complexity = MultComplexity(baseLength, modulusLength, releaseSpec.IsEip7883Enabled); uint expLengthUpTo32 = Math.Min(LengthSize, expLength); - uint startIndex = LengthsLengths + baseLength; //+ expLength - expLengthUpTo32; // Geth takes head here, why? - UInt256 exp = new(inputData.SliceWithZeroPaddingEmptyOnError((int)startIndex, (int)expLengthUpTo32), isBigEndian: true); + + ReadOnlySpan data = []; + + if (baseLength < uint.MaxValue - LengthsLengths) + { + uint startIndex = LengthsLengths + baseLength; //+ expLength - expLengthUpTo32; // Geth takes head here, why? + data = inputData.SliceWithZeroPaddingEmptyOnError(startIndex, expLengthUpTo32); + } + + UInt256 exp = new(data, isBigEndian: true); UInt256 iterationCount = CalculateIterationCount(expLength, exp, releaseSpec.IsEip7883Enabled); bool overflow = UInt256.MultiplyOverflow(complexity, iterationCount, out UInt256 result); @@ -113,7 +121,7 @@ private static bool ExceedsMaxInputSize(IReleaseSpec releaseSpec, uint baseLengt { return releaseSpec.IsEip7823Enabled ? (baseLength > ModExpMaxInputSizeEip7823 | expLength > ModExpMaxInputSizeEip7823 | modulusLength > ModExpMaxInputSizeEip7823) - : (baseLength | modulusLength) > int.MaxValue; + : (baseLength | modulusLength) >= uint.MaxValue; } [MethodImpl(MethodImplOptions.NoInlining)] @@ -130,7 +138,7 @@ private static bool ExceedsMaxInputSize(IReleaseSpec releaseSpec, uint baseLengt private static (uint baseLength, uint expLength, uint modulusLength) GetInputLengths(ReadOnlySpan inputData) { // Test if too high - if (Vector256.IsSupported) + if (Vector256.IsHardwareAccelerated) { ref var firstByte = ref MemoryMarshal.GetReference(inputData); Vector256 mask = ~Vector256.Create(0, 0, 0, 0, 0, 0, 0, uint.MaxValue).AsByte(); @@ -145,7 +153,7 @@ private static (uint baseLength, uint expLength, uint modulusLength) GetInputLen return GetInputLengthsMayOverflow(inputData); } } - else if (Vector128.IsSupported) + else if (Vector128.IsHardwareAccelerated) { ref var firstByte = ref MemoryMarshal.GetReference(inputData); Vector128 mask = ~Vector128.Create(0, 0, 0, uint.MaxValue).AsByte(); @@ -184,7 +192,7 @@ private static (uint baseLength, uint expLength, uint modulusLength) GetInputLen private static (uint baseLength, uint expLength, uint modulusLength) GetInputLengthsMayOverflow(ReadOnlySpan inputData) { // Only valid if baseLength and modulusLength are zero; when expLength doesn't matter - if (Vector256.IsSupported) + if (Vector256.IsHardwareAccelerated) { ref var firstByte = ref MemoryMarshal.GetReference(inputData); if (Vector256.BitwiseOr( @@ -196,7 +204,7 @@ private static (uint baseLength, uint expLength, uint modulusLength) GetInputLen return (uint.MaxValue, uint.MaxValue, uint.MaxValue); } } - else if (Vector128.IsSupported) + else if (Vector128.IsHardwareAccelerated) { ref var firstByte = ref MemoryMarshal.GetReference(inputData); if (Vector128.BitwiseOr( diff --git a/src/Nethermind/Nethermind.Evm.Test/BytecodeBuilderExtensionsTests.cs b/src/Nethermind/Nethermind.Evm.Test/BytecodeBuilderExtensionsTests.cs index 0af189691f4..993f303edf1 100644 --- a/src/Nethermind/Nethermind.Evm.Test/BytecodeBuilderExtensionsTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/BytecodeBuilderExtensionsTests.cs @@ -24,7 +24,7 @@ public class TestCase public static MethodInfo GetFluentOpcodeFunction(Instruction opcode) { - static bool HasDigit(Instruction opcode, Instruction[] treatLikeSuffexedOpcode, Instruction[] treatLikeNonSuffexedOpcode, out int prefixLen, out string opcodeAsString) + static bool HasDigit(Instruction opcode, Instruction[] treatLikeSuffixedOpcode, Instruction[] treatLikeNonSuffixedOpcode, out int prefixLen, out string opcodeAsString) { // opcode with multiple indexes at the end like PUSH or DUP or SWAP are represented as one function // with the char 'x' instead of the number with one byte argument to diff i.g : PUSH32 => PUSHx(32, ...) @@ -32,13 +32,13 @@ static bool HasDigit(Instruction opcode, Instruction[] treatLikeSuffexedOpcode, prefixLen = opcodeAsString.Length; // STORE8 is excluded from filter and always returns false cause it is one of it own and has a function mapped directly to it - if (treatLikeSuffexedOpcode.Contains(opcode)) + if (treatLikeSuffixedOpcode.Contains(opcode)) { return false; } // CREATE is included from filter and always return true it is mapped to CREATE(byte) - if (treatLikeNonSuffexedOpcode.Contains(opcode)) + if (treatLikeNonSuffixedOpcode.Contains(opcode)) { return true; } @@ -604,7 +604,7 @@ public static IEnumerable FluentBuilderTestCases } [Test] - public void code_emited_by_fluent_is_same_as_expected([ValueSource(nameof(FluentBuilderTestCases))] TestCase test) + public void code_emitted_by_fluent_is_same_as_expected([ValueSource(nameof(FluentBuilderTestCases))] TestCase test) { test.FluentCodes.Should().BeEquivalentTo(test.ResultCodes, test.Description); } diff --git a/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs b/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs index a297fe3945b..9865e344167 100644 --- a/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/CodeInfoRepositoryTests.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Collections.Frozen; using Nethermind.Core.Crypto; using Nethermind.Core; using NSubstitute; @@ -14,13 +15,20 @@ using Nethermind.Evm.CodeAnalysis; using Nethermind.Core.Extensions; using Nethermind.Core.Test; -using Nethermind.State; namespace Nethermind.Evm.Test; [TestFixture, Parallelizable] public class CodeInfoRepositoryTests { + private static readonly IReleaseSpec _releaseSpec; + + static CodeInfoRepositoryTests() + { + _releaseSpec = Substitute.For(); + _releaseSpec.Precompiles.Returns(FrozenSet.Empty); + } + public static IEnumerable NotDelegationCodeCases() { byte[] rndAddress = new byte[20]; @@ -28,45 +36,31 @@ public static IEnumerable NotDelegationCodeCases() //Change first byte of the delegation header byte[] code = [.. Eip7702Constants.DelegationHeader, .. rndAddress]; code[0] = TestContext.CurrentContext.Random.NextByte(0xee); - yield return new object[] - { - code - }; + yield return [code]; //Change second byte of the delegation header code = [.. Eip7702Constants.DelegationHeader, .. rndAddress]; code[1] = TestContext.CurrentContext.Random.NextByte(0x2, 0xff); - yield return new object[] - { - code - }; + yield return [code]; //Change third byte of the delegation header code = [.. Eip7702Constants.DelegationHeader, .. rndAddress]; code[2] = TestContext.CurrentContext.Random.NextByte(0x1, 0xff); - yield return new object[] - { - code - }; + yield return [code]; code = [.. Eip7702Constants.DelegationHeader, .. new byte[21]]; - yield return new object[] - { - code - }; + yield return [code]; code = [.. Eip7702Constants.DelegationHeader, .. new byte[19]]; - yield return new object[] - { - code - }; + yield return [code]; } + [TestCaseSource(nameof(NotDelegationCodeCases))] public void TryGetDelegation_CodeIsNotDelegation_ReturnsFalse(byte[] code) { IWorldState stateProvider = TestWorldStateFactory.CreateForTest(); using var _scope = stateProvider.BeginScope(IWorldState.PreGenesis); stateProvider.CreateAccount(TestItem.AddressA, 0); - stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + stateProvider.InsertCode(TestItem.AddressA, code, _releaseSpec); EthereumCodeInfoRepository sut = new(stateProvider); - sut.TryGetDelegation(TestItem.AddressA, Substitute.For(), out _).Should().Be(false); + sut.TryGetDelegation(TestItem.AddressA, _releaseSpec, out _).Should().Be(false); } @@ -74,27 +68,22 @@ public static IEnumerable DelegationCodeCases() { byte[] address = new byte[20]; byte[] code = [.. Eip7702Constants.DelegationHeader, .. address]; - yield return new object[] - { - code - }; + yield return [code]; TestContext.CurrentContext.Random.NextBytes(address); code = [.. Eip7702Constants.DelegationHeader, .. address]; - yield return new object[] - { - code - }; + yield return [code]; } + [TestCaseSource(nameof(DelegationCodeCases))] public void TryGetDelegation_CodeTryGetDelegation_ReturnsTrue(byte[] code) { IWorldState stateProvider = TestWorldStateFactory.CreateForTest(); using var _scope = stateProvider.BeginScope(IWorldState.PreGenesis); stateProvider.CreateAccount(TestItem.AddressA, 0); - stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + stateProvider.InsertCode(TestItem.AddressA, code, _releaseSpec); EthereumCodeInfoRepository sut = new(stateProvider); - sut.TryGetDelegation(TestItem.AddressA, Substitute.For(), out _).Should().Be(true); + sut.TryGetDelegation(TestItem.AddressA, _releaseSpec, out _).Should().Be(true); } [TestCaseSource(nameof(DelegationCodeCases))] @@ -103,11 +92,11 @@ public void TryGetDelegation_CodeTryGetDelegation_CorrectDelegationAddressIsSet( IWorldState stateProvider = TestWorldStateFactory.CreateForTest(); using var _ = stateProvider.BeginScope(IWorldState.PreGenesis); stateProvider.CreateAccount(TestItem.AddressA, 0); - stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + stateProvider.InsertCode(TestItem.AddressA, code, _releaseSpec); EthereumCodeInfoRepository sut = new(stateProvider); Address result; - sut.TryGetDelegation(TestItem.AddressA, Substitute.For(), out result); + sut.TryGetDelegation(TestItem.AddressA, _releaseSpec, out result); result.Should().Be(new Address(code.Slice(3, Address.Size))); } @@ -118,15 +107,15 @@ public void GetExecutableCodeHash_CodeTryGetDelegation_ReturnsHashOfDelegated(by IWorldState stateProvider = TestWorldStateFactory.CreateForTest(); using var _ = stateProvider.BeginScope(IWorldState.PreGenesis); stateProvider.CreateAccount(TestItem.AddressA, 0); - stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + stateProvider.InsertCode(TestItem.AddressA, code, _releaseSpec); Address delegationAddress = new Address(code.Slice(3, Address.Size)); byte[] delegationCode = new byte[32]; stateProvider.CreateAccount(delegationAddress, 0); - stateProvider.InsertCode(delegationAddress, delegationCode, Substitute.For()); + stateProvider.InsertCode(delegationAddress, delegationCode, _releaseSpec); EthereumCodeInfoRepository sut = new(stateProvider); - sut.GetExecutableCodeHash(TestItem.AddressA, Substitute.For()).Should().Be(Keccak.Compute(code).ValueHash256); + sut.GetExecutableCodeHash(TestItem.AddressA, _releaseSpec).Should().Be(Keccak.Compute(code).ValueHash256); } [TestCaseSource(nameof(NotDelegationCodeCases))] @@ -135,11 +124,11 @@ public void GetExecutableCodeHash_CodeIsNotDelegation_ReturnsCodeHashOfAddress(b IWorldState stateProvider = TestWorldStateFactory.CreateForTest(); using var _ = stateProvider.BeginScope(IWorldState.PreGenesis); stateProvider.CreateAccount(TestItem.AddressA, 0); - stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + stateProvider.InsertCode(TestItem.AddressA, code, _releaseSpec); EthereumCodeInfoRepository sut = new(stateProvider); - sut.GetExecutableCodeHash(TestItem.AddressA, Substitute.For()).Should().Be(Keccak.Compute(code).ValueHash256); + sut.GetExecutableCodeHash(TestItem.AddressA, _releaseSpec).Should().Be(Keccak.Compute(code).ValueHash256); } [TestCaseSource(nameof(DelegationCodeCases))] @@ -148,14 +137,14 @@ public void GetCachedCodeInfo_CodeTryGetDelegation_ReturnsCodeOfDelegation(byte[ IWorldState stateProvider = TestWorldStateFactory.CreateForTest(); using var _ = stateProvider.BeginScope(IWorldState.PreGenesis); stateProvider.CreateAccount(TestItem.AddressA, 0); - stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + stateProvider.InsertCode(TestItem.AddressA, code, _releaseSpec); Address delegationAddress = new Address(code.Slice(3, Address.Size)); stateProvider.CreateAccount(delegationAddress, 0); byte[] delegationCode = new byte[32]; - stateProvider.InsertCode(delegationAddress, delegationCode, Substitute.For()); + stateProvider.InsertCode(delegationAddress, delegationCode, _releaseSpec); EthereumCodeInfoRepository sut = new(stateProvider); - ICodeInfo result = sut.GetCachedCodeInfo(TestItem.AddressA, Substitute.For()); + ICodeInfo result = sut.GetCachedCodeInfo(TestItem.AddressA, _releaseSpec); result.CodeSpan.ToArray().Should().BeEquivalentTo(delegationCode); } @@ -165,10 +154,10 @@ public void GetCachedCodeInfo_CodeIsNotDelegation_ReturnsCodeOfAddress(byte[] co IWorldState stateProvider = TestWorldStateFactory.CreateForTest(); using var _ = stateProvider.BeginScope(IWorldState.PreGenesis); stateProvider.CreateAccount(TestItem.AddressA, 0); - stateProvider.InsertCode(TestItem.AddressA, code, Substitute.For()); + stateProvider.InsertCode(TestItem.AddressA, code, _releaseSpec); EthereumCodeInfoRepository sut = new(stateProvider); - sut.GetCachedCodeInfo(TestItem.AddressA, Substitute.For()).Should().BeEquivalentTo(new CodeInfo(code)); + sut.GetCachedCodeInfo(TestItem.AddressA, _releaseSpec).Should().BeEquivalentTo(new CodeInfo(code)); } } diff --git a/src/Nethermind/Nethermind.Evm.Test/CoinbaseTests.cs b/src/Nethermind/Nethermind.Evm.Test/CoinbaseTests.cs index 621fa480743..1f84f9e6fba 100644 --- a/src/Nethermind/Nethermind.Evm.Test/CoinbaseTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/CoinbaseTests.cs @@ -13,7 +13,7 @@ public class CoinbaseTests : VirtualMachineTestsBase { private bool _setAuthor; - protected override Block BuildBlock(ForkActivation activation, SenderRecipientAndMiner senderRecipientAndMiner, Transaction transaction, long blockGasLimit = DefaultBlockGasLimit, ulong exessBlobGas = 0) + protected override Block BuildBlock(ForkActivation activation, SenderRecipientAndMiner senderRecipientAndMiner, Transaction transaction, long blockGasLimit = DefaultBlockGasLimit, ulong excessBlobGas = 0) { senderRecipientAndMiner ??= new SenderRecipientAndMiner(); Block block = base.BuildBlock(activation, senderRecipientAndMiner, transaction, blockGasLimit); diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip1153Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip1153Tests.cs index 9ce12f75464..158ec5da763 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip1153Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip1153Tests.cs @@ -409,7 +409,7 @@ public void revert_resets_all_transient_state() /// Revert undoes transient storage writes from inner calls that successfully returned /// [Test] - public void revert_resets_transient_state_from_succesful_calls() + public void revert_resets_transient_state_from_successful_calls() { // If caller is self, TLOAD and return value (break recursion) // Else, TSTORE and call self, return the response diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip1283Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip1283Tests.cs index 995cdf9d043..0f75f89071d 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip1283Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip1283Tests.cs @@ -5,6 +5,7 @@ using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; +using Nethermind.Evm.State; using Nethermind.Specs; using Nethermind.Specs.Forks; using Nethermind.Specs.Test; diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip152Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip152Tests.cs index 2431e6ae167..dae7f1845b3 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip152Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip152Tests.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core; +using Nethermind.Core.Specs; using Nethermind.Specs; using Nethermind.Evm.Precompiles; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip2028Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip2028Tests.cs index e0286a9a48d..cb82a017a79 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip2028Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip2028Tests.cs @@ -23,8 +23,8 @@ private class AfterIstanbul : Eip2028Tests public void non_zero_transaction_data_cost_should_be_16() { Transaction transaction = new Transaction { Data = new byte[] { 1 }, To = Address.Zero }; - IntrinsicGas cost = IntrinsicGasCalculator.Calculate(transaction, Spec); - cost.Should().Be(new IntrinsicGas(Standard: GasCostOf.Transaction + GasCostOf.TxDataNonZeroEip2028, + EthereumIntrinsicGas cost = IntrinsicGasCalculator.Calculate(transaction, Spec); + cost.Should().Be(new EthereumIntrinsicGas(Standard: GasCostOf.Transaction + GasCostOf.TxDataNonZeroEip2028, FloorGas: 0)); } @@ -32,8 +32,8 @@ public void non_zero_transaction_data_cost_should_be_16() public void zero_transaction_data_cost_should_be_4() { Transaction transaction = new Transaction { Data = new byte[] { 0 }, To = Address.Zero }; - IntrinsicGas cost = IntrinsicGasCalculator.Calculate(transaction, Spec); - cost.Should().Be(new IntrinsicGas(Standard: GasCostOf.Transaction + GasCostOf.TxDataZero, + EthereumIntrinsicGas cost = IntrinsicGasCalculator.Calculate(transaction, Spec); + cost.Should().Be(new EthereumIntrinsicGas(Standard: GasCostOf.Transaction + GasCostOf.TxDataZero, FloorGas: 0)); } } @@ -47,8 +47,8 @@ private class BeforeIstanbul : Eip2028Tests public void non_zero_transaction_data_cost_should_be_68() { Transaction transaction = new Transaction { Data = new byte[] { 1 }, To = Address.Zero }; - IntrinsicGas cost = IntrinsicGasCalculator.Calculate(transaction, Spec); - cost.Should().Be(new IntrinsicGas(Standard: GasCostOf.Transaction + GasCostOf.TxDataNonZero, + EthereumIntrinsicGas cost = IntrinsicGasCalculator.Calculate(transaction, Spec); + cost.Should().Be(new EthereumIntrinsicGas(Standard: GasCostOf.Transaction + GasCostOf.TxDataNonZero, FloorGas: 0)); } @@ -56,8 +56,8 @@ public void non_zero_transaction_data_cost_should_be_68() public void zero_transaction_data_cost_should_be_4() { Transaction transaction = new Transaction { Data = new byte[] { 0 }, To = Address.Zero }; - IntrinsicGas cost = IntrinsicGasCalculator.Calculate(transaction, Spec); - cost.Should().Be(new IntrinsicGas(Standard: GasCostOf.Transaction + GasCostOf.TxDataZero, + EthereumIntrinsicGas cost = IntrinsicGasCalculator.Calculate(transaction, Spec); + cost.Should().Be(new EthereumIntrinsicGas(Standard: GasCostOf.Transaction + GasCostOf.TxDataZero, FloorGas: 0)); } } diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip2200Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip2200Tests.cs index f7e749380a7..df8b99a330d 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip2200Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip2200Tests.cs @@ -5,6 +5,7 @@ using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; +using Nethermind.Evm.State; using Nethermind.Specs; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip2537Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip2537Tests.cs index b91632f29ab..5dfcd9f1e69 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip2537Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip2537Tests.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using Nethermind.Core.Specs; using NUnit.Framework; using Nethermind.Evm.Precompiles.Bls; using Nethermind.Evm.Precompiles; diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip2565Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip2565Tests.cs index 14e715eb990..37816776dae 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip2565Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip2565Tests.cs @@ -6,6 +6,7 @@ using System.Numerics; using FluentAssertions; using MathNet.Numerics.Random; +using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Evm.Precompiles; using Nethermind.Int256; @@ -53,6 +54,30 @@ public void ModExp_run_should_not_throw_exception(string inputStr) gas.Should().Be(200); } + // empty base + [TestCase("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000101F0", "00")] + // empty exp + [TestCase("0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010001020304050607F0", "01")] + // empty mod + [TestCase("000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000102030405060701", "")] + // empty args + [TestCase("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "")] + // empty args (even sizes) + [TestCase("", "")] + // zero mod + [TestCase("000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001010100", "00")] + // 65-byte args (empty base, empty exp, one-byte mod length input) + [TestCase("0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011", "", true)] + public void ModExp_return_expected_values(string inputHex, string expectedResult, bool isError = false) + { + byte[] input = Bytes.FromHexString(inputHex); + byte[] expected = Bytes.FromHexString(expectedResult); + + Result result = ModExpPrecompile.Instance.Run(input, Osaka.Instance); + Assert.That(result.Data, Is.EqualTo(expected)); + Assert.That(result.Error, isError ? Is.Not.Null : Is.Null); + } + private static (byte[], bool) BigIntegerModExp(byte[] inputData) { (int baseLength, int expLength, int modulusLength) = GetInputLengths(inputData); diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip2935Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip2935Tests.cs index 02869cbc12e..b1f12b1bd27 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip2935Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip2935Tests.cs @@ -6,6 +6,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; +using Nethermind.Evm.State; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; using Nethermind.Specs; diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs index 6f65342dcc8..22bc16baa34 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip3198BaseFeeTests.cs @@ -27,7 +27,7 @@ public class Eip3198BaseFeeTests : VirtualMachineTestsBase [TestCase(false, 0, false)] public void Base_fee_opcode_should_return_expected_results(bool eip3198Enabled, int baseFee, bool send1559Tx) { - _processor = new TransactionProcessor(BlobBaseFeeCalculator.Instance, SpecProvider, TestState, Machine, CodeInfoRepository, LimboLogs.Instance); + _processor = new EthereumTransactionProcessor(BlobBaseFeeCalculator.Instance, SpecProvider, TestState, Machine, CodeInfoRepository, LimboLogs.Instance); byte[] code = Prepare.EvmCode .Op(Instruction.BASEFEE) .PushData(0) diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip3529RefundsTests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip3529RefundsTests.cs index d170bf21d2c..09d1ce61a4a 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip3529RefundsTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip3529RefundsTests.cs @@ -8,6 +8,7 @@ using Nethermind.Core.Test.Builders; using Nethermind.Crypto; using Nethermind.Blockchain.Tracing.ParityStyle; +using Nethermind.Evm.State; using Nethermind.Evm.TransactionProcessing; using Nethermind.Logging; using Nethermind.Specs; @@ -74,7 +75,7 @@ private void Test(string codeHex, long gasUsed, long refund, byte originalValue, TestState.CreateAccount(Recipient, 1.Ether()); TestState.Set(new StorageCell(Recipient, 0), new[] { originalValue }); TestState.Commit(eip3529Enabled ? London.Instance : Berlin.Instance); - _processor = new TransactionProcessor(BlobBaseFeeCalculator.Instance, SpecProvider, TestState, Machine, CodeInfoRepository, LimboLogs.Instance); + _processor = new EthereumTransactionProcessor(BlobBaseFeeCalculator.Instance, SpecProvider, TestState, Machine, CodeInfoRepository, LimboLogs.Instance); long blockNumber = eip3529Enabled ? MainnetSpecProvider.LondonBlockNumber : MainnetSpecProvider.LondonBlockNumber - 1; (Block block, Transaction transaction) = PrepareTx(blockNumber, 100000, Bytes.FromHexString(codeHex)); diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip3541Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip3541Tests.cs index d9585be6ec8..d28235fa0ac 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip3541Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip3541Tests.cs @@ -89,7 +89,7 @@ void DeployCodeAndAssertTx(string code, bool eip3541Enabled, ContractDeployment ContractDeployment.CREATE2 => Prepare.EvmCode.Create2(byteCode, salt, UInt256.Zero).Done, _ => byteCode, }; - _processor = new TransactionProcessor(BlobBaseFeeCalculator.Instance, SpecProvider, TestState, Machine, CodeInfoRepository, LimboLogs.Instance); + _processor = new EthereumTransactionProcessor(BlobBaseFeeCalculator.Instance, SpecProvider, TestState, Machine, CodeInfoRepository, LimboLogs.Instance); long blockNumber = eip3541Enabled ? MainnetSpecProvider.LondonBlockNumber : MainnetSpecProvider.LondonBlockNumber - 1; (Block block, Transaction transaction) = PrepareTx(blockNumber, 100000, createContract); diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip3860Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip3860Tests.cs index 4e3b3223c7b..b02611c9107 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip3860Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip3860Tests.cs @@ -49,13 +49,13 @@ public void Test_EIP_3860_GasCost_Create(string createCode, bool eip3860Enabled, [TestCase("60006000F5")] public void Test_EIP_3860_InitCode_Create_Exceeds_Limit(string createCode) { - string dataLenghtHex = (Spec.MaxInitCodeSize + 1).ToString("X"); - Instruction dataPush = Instruction.PUSH1 + (byte)(dataLenghtHex.Length / 2 - 1); + string dataLengthHex = (Spec.MaxInitCodeSize + 1).ToString("X"); + Instruction dataPush = Instruction.PUSH1 + (byte)(dataLengthHex.Length / 2 - 1); bool isCreate2 = createCode[^2..] == Instruction.CREATE2.ToString("X"); byte[] evmCode = isCreate2 - ? Prepare.EvmCode.PushSingle(0).FromCode(dataPush.ToString("X") + dataLenghtHex + createCode).Done - : Prepare.EvmCode.FromCode(dataPush.ToString("X") + dataLenghtHex + createCode).Done; + ? Prepare.EvmCode.PushSingle(0).FromCode(dataPush.ToString("X") + dataLengthHex + createCode).Done + : Prepare.EvmCode.FromCode(dataPush.ToString("X") + dataLengthHex + createCode).Done; TestState.CreateAccount(TestItem.AddressC, 1.Ether()); TestState.InsertCode(TestItem.AddressC, evmCode, Spec); diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip6780Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip6780Tests.cs index 2722a62827f..12d4704a5d2 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip6780Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip6780Tests.cs @@ -26,6 +26,7 @@ using Nethermind.Core.Crypto; using System; using Nethermind.Core.Specs; +using Nethermind.Evm.State; using Nethermind.Evm.TransactionProcessing; namespace Nethermind.Evm.Test diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip7516BlobBaseFeeTests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip7516BlobBaseFeeTests.cs index 74d5b538a98..5344bdae0d4 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip7516BlobBaseFeeTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip7516BlobBaseFeeTests.cs @@ -28,7 +28,7 @@ public class Eip7516BlobBaseFeeTests : VirtualMachineTestsBase [TestCase(false, 0ul)] public void Blob_Base_fee_opcode_should_return_expected_results(bool eip7516Enabled, ulong excessBlobGas) { - _processor = new TransactionProcessor(BlobBaseFeeCalculator.Instance, SpecProvider, TestState, Machine, CodeInfoRepository, LimboLogs.Instance); + _processor = new EthereumTransactionProcessor(BlobBaseFeeCalculator.Instance, SpecProvider, TestState, Machine, CodeInfoRepository, LimboLogs.Instance); byte[] code = Prepare.EvmCode .Op(Instruction.BLOBBASEFEE) .PushData(0) diff --git a/src/Nethermind/Nethermind.Evm.Test/Eip7623Tests.cs b/src/Nethermind/Nethermind.Evm.Test/Eip7623Tests.cs index b67e8283c53..674159d89f2 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Eip7623Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Eip7623Tests.cs @@ -18,8 +18,8 @@ public class Eip7623Tests : VirtualMachineTestsBase public void non_zero_data_transaction_floor_cost_should_be_40() { var transaction = new Transaction { Data = new byte[] { 1 }, To = Address.Zero }; - IntrinsicGas cost = IntrinsicGasCalculator.Calculate(transaction, Spec); - cost.Should().Be(new IntrinsicGas(Standard: GasCostOf.Transaction + GasCostOf.TxDataNonZeroEip2028, + EthereumIntrinsicGas cost = IntrinsicGasCalculator.Calculate(transaction, Spec); + cost.Should().Be(new EthereumIntrinsicGas(Standard: GasCostOf.Transaction + GasCostOf.TxDataNonZeroEip2028, FloorGas: GasCostOf.Transaction + GasCostOf.TotalCostFloorPerTokenEip7623 * 4)); } @@ -27,8 +27,8 @@ public void non_zero_data_transaction_floor_cost_should_be_40() public void zero_data_transaction_floor_cost_should_be_10() { var transaction = new Transaction { Data = new byte[] { 0 }, To = Address.Zero }; - IntrinsicGas cost = IntrinsicGasCalculator.Calculate(transaction, Spec); - cost.Should().Be(new IntrinsicGas(Standard: GasCostOf.Transaction + GasCostOf.TxDataZero, + EthereumIntrinsicGas cost = IntrinsicGasCalculator.Calculate(transaction, Spec); + cost.Should().Be(new EthereumIntrinsicGas(Standard: GasCostOf.Transaction + GasCostOf.TxDataZero, FloorGas: GasCostOf.Transaction + GasCostOf.TotalCostFloorPerTokenEip7623)); } } diff --git a/src/Nethermind/Nethermind.Evm.Test/EvmMemoryTestsBase.cs b/src/Nethermind/Nethermind.Evm.Test/EvmMemoryTestsBase.cs index c4b6c63bfc1..7838ed213ef 100644 --- a/src/Nethermind/Nethermind.Evm.Test/EvmMemoryTestsBase.cs +++ b/src/Nethermind/Nethermind.Evm.Test/EvmMemoryTestsBase.cs @@ -18,7 +18,8 @@ public void Save_empty_beyond_reasonable_size_does_not_throw() { IEvmMemory memory = CreateEvmMemory(); UInt256 dest = (UInt256)int.MaxValue + 1; - memory.Save(in dest, Array.Empty()); + bool outOfGas = !memory.TrySave(in dest, Array.Empty()); + Assert.That(outOfGas, Is.EqualTo(false)); } [Test] @@ -26,7 +27,8 @@ public void Trace_one_word() { IEvmMemory memory = CreateEvmMemory(); UInt256 dest = UInt256.Zero; - memory.SaveWord(in dest, new byte[EvmPooledMemory.WordSize]); + bool outOfGas = !memory.TrySaveWord(in dest, new byte[EvmPooledMemory.WordSize]); + Assert.That(outOfGas, Is.EqualTo(false)); var trace = memory.GetTrace(); Assert.That(trace.ToHexWordList().Count, Is.EqualTo(1)); } @@ -36,7 +38,8 @@ public void Trace_two_words() { IEvmMemory memory = CreateEvmMemory(); UInt256 dest = EvmPooledMemory.WordSize; - memory.SaveWord(in dest, new byte[EvmPooledMemory.WordSize]); + bool outOfGas = !memory.TrySaveWord(in dest, new byte[EvmPooledMemory.WordSize]); + Assert.That(outOfGas, Is.EqualTo(false)); var trace = memory.GetTrace(); Assert.That(trace.ToHexWordList().Count, Is.EqualTo(2)); } @@ -46,8 +49,10 @@ public void Trace_overwrite() { IEvmMemory memory = CreateEvmMemory(); UInt256 dest = EvmPooledMemory.WordSize; - memory.SaveWord(in dest, new byte[EvmPooledMemory.WordSize]); - memory.SaveWord(in dest, new byte[EvmPooledMemory.WordSize]); + bool outOfGas = !memory.TrySaveWord(in dest, new byte[EvmPooledMemory.WordSize]); + Assert.That(outOfGas, Is.EqualTo(false)); + outOfGas = !memory.TrySaveWord(in dest, new byte[EvmPooledMemory.WordSize]); + Assert.That(outOfGas, Is.EqualTo(false)); var trace = memory.GetTrace(); Assert.That(trace.ToHexWordList().Count, Is.EqualTo(2)); } @@ -57,7 +62,8 @@ public void Trace_when_position_not_on_word_border() { IEvmMemory memory = CreateEvmMemory(); UInt256 dest = EvmPooledMemory.WordSize / 2; - memory.SaveByte(in dest, 1); + bool outOfGas = !memory.TrySaveByte(in dest, 1); + Assert.That(outOfGas, Is.EqualTo(false)); var trace = memory.GetTrace(); Assert.That(trace.ToHexWordList().Count, Is.EqualTo(1)); } @@ -67,8 +73,10 @@ public void Calculate_memory_cost_returns_0_for_subsequent_calls() { IEvmMemory memory = CreateEvmMemory(); UInt256 dest = UInt256.One; - memory.CalculateMemoryCost(in dest, UInt256.One); - long cost = memory.CalculateMemoryCost(in dest, UInt256.One); + memory.CalculateMemoryCost(in dest, UInt256.One, out bool outOfGas); + Assert.That(outOfGas, Is.EqualTo(false)); + long cost = memory.CalculateMemoryCost(in dest, UInt256.One, out outOfGas); + Assert.That(outOfGas, Is.EqualTo(false)); Assert.That(cost, Is.EqualTo(0L)); } @@ -77,7 +85,8 @@ public void Calculate_memory_cost_returns_0_for_0_length() { IEvmMemory memory = CreateEvmMemory(); UInt256 dest = long.MaxValue; - long cost = memory.CalculateMemoryCost(in dest, UInt256.Zero); + long cost = memory.CalculateMemoryCost(in dest, UInt256.Zero, out bool outOfGas); + Assert.That(outOfGas, Is.EqualTo(false)); Assert.That(cost, Is.EqualTo(0L)); } } diff --git a/src/Nethermind/Nethermind.Evm.Test/EvmPooledMemoryTests.cs b/src/Nethermind/Nethermind.Evm.Test/EvmPooledMemoryTests.cs index 47b649cff51..e5247e92c5a 100644 --- a/src/Nethermind/Nethermind.Evm.Test/EvmPooledMemoryTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/EvmPooledMemoryTests.cs @@ -54,20 +54,184 @@ public void Div32Ceiling(int input, int expectedResult) [TestCase(100 * MaxCodeSize, MaxCodeSize)] [TestCase(1000 * MaxCodeSize, MaxCodeSize)] [TestCase(0, 1024 * 1024)] - [TestCase(0, Int32.MaxValue)] + // Note: Int32.MaxValue was removed as a test case because after word alignment + // it exceeds the maximum allowed memory size and correctly returns out-of-gas. public void MemoryCost(int destination, int memoryAllocation) { EvmPooledMemory memory = new(); UInt256 dest = (UInt256)destination; - long result = memory.CalculateMemoryCost(in dest, (UInt256)memoryAllocation); + long result = memory.CalculateMemoryCost(in dest, (UInt256)memoryAllocation, out bool outOfGas); + Assert.That(outOfGas, Is.EqualTo(false)); TestContext.Out.WriteLine($"Gas cost of allocating {memoryAllocation} starting from {dest}: {result}"); } + [Test] + public void CalculateMemoryCost_LocationExceedsULong_ShouldReturnOutOfGas() + { + EvmPooledMemory memory = new(); + UInt256 location = new(0, 1, 0, 0); // value larger than ulong max (u1 != 0) + long result = memory.CalculateMemoryCost(in location, 32, out bool outOfGas); + Assert.That(outOfGas, Is.EqualTo(true)); + Assert.That(result, Is.EqualTo(0L)); + } + + [Test] + public void CalculateMemoryCost_LengthExceedsULong_ShouldReturnOutOfGas() + { + EvmPooledMemory memory = new(); + UInt256 length = new(0, 1, 0, 0); // value larger than ulong max (u1 != 0) + long result = memory.CalculateMemoryCost(0, in length, out bool outOfGas); + Assert.That(outOfGas, Is.EqualTo(true)); + Assert.That(result, Is.EqualTo(0L)); + } + + [Test] + public void CalculateMemoryCost_LengthExceedsLongMax_ShouldReturnOutOfGas() + { + EvmPooledMemory memory = new(); + UInt256 length = (UInt256)long.MaxValue + 1; // just over long.MaxValue + long result = memory.CalculateMemoryCost(0, in length, out bool outOfGas); + Assert.That(outOfGas, Is.EqualTo(true)); + Assert.That(result, Is.EqualTo(0L)); + } + + [Test] + public void CalculateMemoryCost_LocationPlusLengthOverflows_ShouldReturnOutOfGas() + { + EvmPooledMemory memory = new(); + UInt256 location = ulong.MaxValue; + long result = memory.CalculateMemoryCost(in location, 1, out bool outOfGas); + Assert.That(outOfGas, Is.EqualTo(true)); + Assert.That(result, Is.EqualTo(0L)); + } + + [Test] + public void CalculateMemoryCost_TotalSizeExceedsLongMax_ShouldReturnOutOfGas() + { + EvmPooledMemory memory = new(); + UInt256 location = (UInt256)long.MaxValue; + long result = memory.CalculateMemoryCost(in location, 1, out bool outOfGas); + Assert.That(outOfGas, Is.EqualTo(true)); + Assert.That(result, Is.EqualTo(0L)); + } + + [Test] + public void CalculateMemoryCost_TotalSizeExceedsIntMaxAfterWordAlignment_ShouldReturnOutOfGas() + { + // Test that memory requests that would overflow int.MaxValue after word alignment + // are properly rejected. This prevents crashes in .NET array operations. + // The limit is int.MaxValue - WordSize + 1 to ensure word-aligned size fits in int. + EvmPooledMemory memory = new(); + + // Request exactly at the limit should succeed + UInt256 maxAllowedSize = (UInt256)(int.MaxValue - EvmPooledMemory.WordSize + 1); + long result = memory.CalculateMemoryCost(0, in maxAllowedSize, out bool outOfGas); + Assert.That(outOfGas, Is.EqualTo(false), "Size at limit should be allowed"); + + // Request one byte over the limit should fail + UInt256 overLimitSize = maxAllowedSize + 1; + result = memory.CalculateMemoryCost(0, in overLimitSize, out outOfGas); + Assert.That(outOfGas, Is.EqualTo(true), "Size over limit should return out of gas"); + Assert.That(result, Is.EqualTo(0L)); + } + + [Test] + public void CalculateMemoryCost_4GBMemoryRequest_ShouldReturnOutOfGas() + { + // Regression test: 4GB memory request (0xffffffff) should return out-of-gas + // instead of causing integer overflow crash in array operations. + EvmPooledMemory memory = new(); + UInt256 size4GB = 0xffffffffUL; + long result = memory.CalculateMemoryCost(0, in size4GB, out bool outOfGas); + Assert.That(outOfGas, Is.EqualTo(true), "4GB memory request should return out of gas"); + Assert.That(result, Is.EqualTo(0L)); + } + + [Test] + public void CalculateMemoryCost_LargeOffsetPlusLength_ShouldReturnOutOfGas() + { + // Test that location + length exceeding int.MaxValue - WordSize + 1 returns out-of-gas + EvmPooledMemory memory = new(); + UInt256 location = (UInt256)(int.MaxValue / 2); + UInt256 length = (UInt256)(int.MaxValue / 2 + 100); // Sum exceeds limit + long result = memory.CalculateMemoryCost(in location, in length, out bool outOfGas); + Assert.That(outOfGas, Is.EqualTo(true), "Location + length exceeding limit should return out of gas"); + Assert.That(result, Is.EqualTo(0L)); + } + + [Test] + public void Save_LocationExceedsULong_ShouldReturnOutOfGas() + { + EvmPooledMemory memory = new(); + UInt256 location = new(0, 1, 0, 0); + bool outOfGas = !memory.TrySave(in location, new byte[32]); + Assert.That(outOfGas, Is.EqualTo(true)); + } + + [Test] + public void SaveWord_LocationExceedsULong_ShouldReturnOutOfGas() + { + EvmPooledMemory memory = new(); + UInt256 location = new(0, 1, 0, 0); + bool outOfGas = !memory.TrySaveWord(in location, new byte[32]); + Assert.That(outOfGas, Is.EqualTo(true)); + } + + [Test] + public void SaveByte_LocationExceedsULong_ShouldReturnOutOfGas() + { + EvmPooledMemory memory = new(); + UInt256 location = new(0, 1, 0, 0); + bool outOfGas = !memory.TrySaveByte(in location, 0x42); + Assert.That(outOfGas, Is.EqualTo(true)); + } + + [Test] + public void LoadSpan_LocationExceedsULong_ShouldReturnOutOfGas() + { + EvmPooledMemory memory = new(); + UInt256 location = new(0, 1, 0, 0); + bool outOfGas = !memory.TryLoadSpan(in location, out Span result); + Assert.That(outOfGas, Is.EqualTo(true)); + Assert.That(result.IsEmpty, Is.EqualTo(true)); + } + + [Test] + public void LoadSpan_LengthExceedsULong_ShouldReturnOutOfGas() + { + EvmPooledMemory memory = new(); + UInt256 length = new(0, 1, 0, 0); + bool outOfGas = !memory.TryLoadSpan(0, in length, out Span result); + Assert.That(outOfGas, Is.EqualTo(true)); + Assert.That(result.IsEmpty, Is.EqualTo(true)); + } + + [Test] + public void Load_LocationExceedsULong_ShouldReturnOutOfGas() + { + EvmPooledMemory memory = new(); + UInt256 location = new(0, 1, 0, 0); + bool outOfGas = !memory.TryLoad(in location, 32, out ReadOnlyMemory result); + Assert.That(outOfGas, Is.EqualTo(true)); + Assert.That(result.IsEmpty, Is.EqualTo(true)); + } + + [Test] + public void Load_LengthExceedsULong_ShouldReturnOutOfGas() + { + EvmPooledMemory memory = new(); + UInt256 length = new(0, 1, 0, 0); + bool outOfGas = !memory.TryLoad(0, in length, out ReadOnlyMemory result); + Assert.That(outOfGas, Is.EqualTo(true)); + Assert.That(result.IsEmpty, Is.EqualTo(true)); + } + [Test] public void Inspect_should_not_change_evm_memory() { EvmPooledMemory memory = new(); - memory.Save(3, TestItem.KeccakA.Bytes); + bool outOfGas = !memory.TrySave(3, TestItem.KeccakA.Bytes); + Assert.That(outOfGas, Is.EqualTo(false)); ulong initialSize = memory.Size; ReadOnlyMemory result = memory.Inspect(initialSize + 32, 32); Assert.That(memory.Size, Is.EqualTo(initialSize)); @@ -81,7 +245,8 @@ public void Inspect_can_read_memory() byte[] expectedEmptyRead = new byte[32 - offset]; byte[] expectedKeccakRead = TestItem.KeccakA.BytesToArray(); EvmPooledMemory memory = new(); - memory.Save((UInt256)offset, expectedKeccakRead); + bool outOfGas = !memory.TrySave((UInt256)offset, expectedKeccakRead); + Assert.That(outOfGas, Is.EqualTo(false)); ulong initialSize = memory.Size; ReadOnlyMemory actualKeccakMemoryRead = memory.Inspect((UInt256)offset, 32); ReadOnlyMemory actualEmptyRead = memory.Inspect(32 + (UInt256)offset, 32 - (UInt256)offset); @@ -95,9 +260,11 @@ public void Load_should_update_size_of_memory() { byte[] expectedResult = new byte[32]; EvmPooledMemory memory = new(); - memory.Save(3, TestItem.KeccakA.Bytes); + bool outOfGas = !memory.TrySave(3, TestItem.KeccakA.Bytes); + Assert.That(outOfGas, Is.EqualTo(false)); ulong initialSize = memory.Size; - ReadOnlyMemory result = memory.Load(initialSize + 32, 32); + outOfGas = !memory.TryLoad(initialSize + 32, 32, out ReadOnlyMemory result); + Assert.That(outOfGas, Is.EqualTo(false)); Assert.That(memory.Size, Is.Not.EqualTo(initialSize)); Assert.That(result.ToArray(), Is.EqualTo(expectedResult)); } @@ -106,7 +273,8 @@ public void Load_should_update_size_of_memory() public void GetTrace_should_not_throw_on_not_initialized_memory() { EvmPooledMemory memory = new(); - memory.CalculateMemoryCost(0, 32); + memory.CalculateMemoryCost(0, 32, out bool outOfGas); + Assert.That(outOfGas, Is.EqualTo(false)); memory.GetTrace().ToHexWordList().Should().BeEquivalentTo(new string[] { "0000000000000000000000000000000000000000000000000000000000000000" }); } @@ -151,11 +319,11 @@ private static string Run(byte[] input) IWorldState stateProvider = TestWorldStateFactory.CreateForTest(); ISpecProvider specProvider = new TestSpecProvider(London.Instance); EthereumCodeInfoRepository codeInfoRepository = new(stateProvider); - VirtualMachine virtualMachine = new( + EthereumVirtualMachine virtualMachine = new( new TestBlockhashProvider(specProvider), specProvider, LimboLogs.Instance); - ITransactionProcessor transactionProcessor = new TransactionProcessor( + ITransactionProcessor transactionProcessor = new EthereumTransactionProcessor( BlobBaseFeeCalculator.Instance, specProvider, stateProvider, diff --git a/src/Nethermind/Nethermind.Evm.Test/EvmStateTests.cs b/src/Nethermind/Nethermind.Evm.Test/EvmStateTests.cs deleted file mode 100644 index f19ce925c27..00000000000 --- a/src/Nethermind/Nethermind.Evm.Test/EvmStateTests.cs +++ /dev/null @@ -1,242 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using FluentAssertions; -using Nethermind.Core; -using Nethermind.Core.Extensions; -using Nethermind.Core.Test.Builders; -using Nethermind.Evm.State; -using NUnit.Framework; - -namespace Nethermind.Evm.Test -{ - public class EvmStateTests - { - [Test] - public void Things_are_cold_to_start_with() - { - EvmState evmState = CreateEvmState(); - StorageCell storageCell = new(TestItem.AddressA, 1); - evmState.AccessTracker.IsCold(TestItem.AddressA).Should().BeTrue(); - evmState.AccessTracker.IsCold(storageCell).Should().BeTrue(); - } - - [Test] - public void Can_warm_address_up_twice() - { - EvmState evmState = CreateEvmState(); - Address address = TestItem.AddressA; - evmState.AccessTracker.WarmUp(address); - evmState.AccessTracker.WarmUp(address); - evmState.AccessTracker.IsCold(address).Should().BeFalse(); - } - - [Test] - public void Can_warm_up_many() - { - EvmState evmState = CreateEvmState(); - for (int i = 0; i < TestItem.Addresses.Length; i++) - { - evmState.AccessTracker.WarmUp(TestItem.Addresses[i]); - evmState.AccessTracker.WarmUp(new StorageCell(TestItem.Addresses[i], 1)); - } - - for (int i = 0; i < TestItem.Addresses.Length; i++) - { - evmState.AccessTracker.IsCold(TestItem.Addresses[i]).Should().BeFalse(); - evmState.AccessTracker.IsCold(new StorageCell(TestItem.Addresses[i], 1)).Should().BeFalse(); - } - } - - [Test] - public void Can_warm_storage_up_twice() - { - EvmState evmState = CreateEvmState(); - Address address = TestItem.AddressA; - StorageCell storageCell = new(address, 1); - evmState.AccessTracker.WarmUp(storageCell); - evmState.AccessTracker.WarmUp(storageCell); - evmState.AccessTracker.IsCold(storageCell).Should().BeFalse(); - } - - [Test] - public void Nothing_to_commit() - { - EvmState parentEvmState = CreateEvmState(); - using (EvmState evmState = CreateEvmState(parentEvmState)) - { - evmState.CommitToParent(parentEvmState); - } - } - - [Test] - public void Nothing_to_restore() - { - EvmState parentEvmState = CreateEvmState(); - using EvmState evmState = CreateEvmState(parentEvmState); - } - - [Test] - public void Address_to_commit_keeps_it_warm() - { - EvmState parentEvmState = CreateEvmState(); - using (EvmState evmState = CreateEvmState(parentEvmState)) - { - evmState.AccessTracker.WarmUp(TestItem.AddressA); - evmState.CommitToParent(parentEvmState); - } - - parentEvmState.AccessTracker.IsCold(TestItem.AddressA).Should().BeFalse(); - } - - [Test] - public void Address_to_restore_keeps_it_cold() - { - EvmState parentEvmState = CreateEvmState(); - using (EvmState evmState = CreateEvmState(parentEvmState)) - { - evmState.AccessTracker.WarmUp(TestItem.AddressA); - } - - parentEvmState.AccessTracker.IsCold(TestItem.AddressA).Should().BeTrue(); - } - - [Test] - public void Storage_to_commit_keeps_it_warm() - { - EvmState parentEvmState = CreateEvmState(); - StorageCell storageCell = new(TestItem.AddressA, 1); - using (EvmState evmState = CreateEvmState(parentEvmState)) - { - evmState.AccessTracker.WarmUp(storageCell); - evmState.CommitToParent(parentEvmState); - } - - parentEvmState.AccessTracker.IsCold(storageCell).Should().BeFalse(); - } - - [Test] - public void Storage_to_restore_keeps_it_cold() - { - EvmState parentEvmState = CreateEvmState(); - StorageCell storageCell = new(TestItem.AddressA, 1); - using (EvmState evmState = CreateEvmState(parentEvmState)) - { - evmState.AccessTracker.WarmUp(storageCell); - } - - parentEvmState.AccessTracker.IsCold(storageCell).Should().BeTrue(); - } - - [Test] - public void Logs_are_committed() - { - EvmState parentEvmState = CreateEvmState(); - LogEntry logEntry = new(Address.Zero, Bytes.Empty, []); - using (EvmState evmState = CreateEvmState(parentEvmState)) - { - evmState.AccessTracker.Logs.Add(logEntry); - evmState.CommitToParent(parentEvmState); - } - - parentEvmState.AccessTracker.Logs.Contains(logEntry).Should().BeTrue(); - } - - [Test] - public void Logs_are_restored() - { - EvmState parentEvmState = CreateEvmState(); - LogEntry logEntry = new(Address.Zero, Bytes.Empty, []); - using (EvmState evmState = CreateEvmState(parentEvmState)) - { - evmState.AccessTracker.Logs.Add(logEntry); - } - - parentEvmState.AccessTracker.Logs.Contains(logEntry).Should().BeFalse(); - } - - [Test] - public void Destroy_list_is_committed() - { - EvmState parentEvmState = CreateEvmState(); - using (EvmState evmState = CreateEvmState(parentEvmState)) - { - evmState.AccessTracker.ToBeDestroyed(Address.Zero); - evmState.CommitToParent(parentEvmState); - } - - parentEvmState.AccessTracker.DestroyList.Contains(Address.Zero).Should().BeTrue(); - } - - [Test] - public void Destroy_list_is_restored() - { - EvmState parentEvmState = CreateEvmState(); - using (EvmState evmState = CreateEvmState(parentEvmState)) - { - evmState.AccessTracker.ToBeDestroyed(Address.Zero); - } - - parentEvmState.AccessTracker.DestroyList.Contains(Address.Zero).Should().BeFalse(); - } - - [Test] - public void Commit_adds_refunds() - { - EvmState parentEvmState = CreateEvmState(); - using (EvmState evmState = CreateEvmState(parentEvmState)) - { - evmState.Refund = 333; - evmState.CommitToParent(parentEvmState); - } - - parentEvmState.Refund.Should().Be(333); - } - - [Test] - public void Restore_doesnt_add_refunds() - { - EvmState parentEvmState = CreateEvmState(); - using (EvmState evmState = CreateEvmState(parentEvmState)) - { - evmState.Refund = 333; - } - - parentEvmState.Refund.Should().Be(0); - } - - [Test] - public void Can_dispose_without_init() - { - EvmState evmState = CreateEvmState(); - evmState.Dispose(); - } - - [Test] - public void Can_dispose_after_init() - { - EvmState evmState = CreateEvmState(); - evmState.InitializeStacks(); - evmState.Dispose(); - } - - private static EvmState CreateEvmState(EvmState parentEvmState = null, bool isContinuation = false) => - parentEvmState is null - ? EvmState.RentTopLevel(10000, - ExecutionType.CALL, - new ExecutionEnvironment(), - new StackAccessTracker(), - Snapshot.Empty) - : EvmState.RentFrame(10000, - 0, - 0, - ExecutionType.CALL, - false, - false, - new ExecutionEnvironment(), - parentEvmState.AccessTracker, - Snapshot.Empty); - - public class Context { } - } -} diff --git a/src/Nethermind/Nethermind.Evm.Test/GasPriceExtractorTests.cs b/src/Nethermind/Nethermind.Evm.Test/GasPriceExtractorTests.cs index 2705534a68f..1af4cd3301a 100644 --- a/src/Nethermind/Nethermind.Evm.Test/GasPriceExtractorTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/GasPriceExtractorTests.cs @@ -35,7 +35,7 @@ public void Intrinsic_gas_cost_assumption_is_correct() Rlp rlp = BuildHeader(); Transaction tx = Build.A.Transaction.WithData(rlp.Bytes).TestObject; - IntrinsicGas gasCost = IntrinsicGasCalculator.Calculate(tx, Spec); + EthereumIntrinsicGas gasCost = IntrinsicGasCalculator.Calculate(tx, Spec); gasCost.FloorGas.Should().Be(0); gasCost.Standard.Should().BeLessThan(21000 + 9600); } diff --git a/src/Nethermind/Nethermind.Evm.Test/IntrinsicGasCalculatorTests.cs b/src/Nethermind/Nethermind.Evm.Test/IntrinsicGasCalculatorTests.cs index cac1206d630..23d53fa5540 100644 --- a/src/Nethermind/Nethermind.Evm.Test/IntrinsicGasCalculatorTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/IntrinsicGasCalculatorTests.cs @@ -54,8 +54,8 @@ public class IntrinsicGasCalculatorTests [TestCaseSource(nameof(TestCaseSource))] public void Intrinsic_cost_is_calculated_properly((Transaction Tx, long Cost, string Description) testCase) { - IntrinsicGas gas = IntrinsicGasCalculator.Calculate(testCase.Tx, Berlin.Instance); - gas.Should().Be(new IntrinsicGas(Standard: testCase.Cost, FloorGas: 0)); + EthereumIntrinsicGas gas = IntrinsicGasCalculator.Calculate(testCase.Tx, Berlin.Instance); + gas.Should().Be(new EthereumIntrinsicGas(Standard: testCase.Cost, FloorGas: 0)); } [TestCaseSource(nameof(AccessTestCaseSource))] @@ -85,8 +85,8 @@ void Test(IReleaseSpec spec, bool supportsAccessLists) } else { - IntrinsicGas gas = IntrinsicGasCalculator.Calculate(tx, spec); - gas.Should().Be(new IntrinsicGas(Standard: 21000 + testCase.Cost, FloorGas: 0), spec.Name); + EthereumIntrinsicGas gas = IntrinsicGasCalculator.Calculate(tx, spec); + gas.Should().Be(new EthereumIntrinsicGas(Standard: 21000 + testCase.Cost, FloorGas: 0), spec.Name); } } @@ -110,7 +110,7 @@ public void Intrinsic_cost_of_data_is_calculated_properly((byte[] Data, int OldC void Test(IReleaseSpec spec, GasOptions options) { - IntrinsicGas gas = IntrinsicGasCalculator.Calculate(tx, spec); + EthereumIntrinsicGas gas = IntrinsicGasCalculator.Calculate(tx, spec); bool isAfterRepricing = options.HasFlag(GasOptions.AfterRepricing); bool floorCostEnabled = options.HasFlag(GasOptions.FloorCostEnabled); @@ -120,7 +120,7 @@ void Test(IReleaseSpec spec, GasOptions options) testCase.Data.ToHexString()); gas.FloorGas.Should().Be(floorCostEnabled ? testCase.FloorCost : 0); - gas.Should().Be(new IntrinsicGas( + gas.Should().Be(new EthereumIntrinsicGas( Standard: 21000 + (isAfterRepricing ? testCase.NewCost : testCase.OldCost), FloorGas: floorCostEnabled ? testCase.FloorCost : 0), spec.Name, testCase.Data.ToHexString()); @@ -205,7 +205,7 @@ public void Calculate_TxHasAuthorizationList_ReturnsExpectedCostOfTx((Authorizat .WithAuthorizationCode(testCase.AuthorizationList) .TestObject; - IntrinsicGas gas = IntrinsicGasCalculator.Calculate(tx, Prague.Instance); + EthereumIntrinsicGas gas = IntrinsicGasCalculator.Calculate(tx, Prague.Instance); gas.Standard.Should().Be(GasCostOf.Transaction + (testCase.ExpectedCost)); } diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/AccessTxTracerTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/AccessTxTracerTests.cs index 7be709f27d2..59fc7149053 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/AccessTxTracerTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/AccessTxTracerTests.cs @@ -75,7 +75,7 @@ public static IEnumerable OptimizedAddressCases } [TestCaseSource(nameof(OptimizedAddressCases))] - public void ReportAccess_AddressIsSetToOptmizedWithNoStorageCells_OnlyAddressesNotOptimizedIsInTheAccesslist(IEnumerable
optimized, IEnumerable
expected) + public void ReportAccess_AddressIsSetToOptimizedWithNoStorageCells_OnlyAddressesNotOptimizedIsInTheAccessList(IEnumerable
optimized, IEnumerable
expected) { JournalSet
accessedAddresses = [TestItem.AddressA, TestItem.AddressB]; JournalSet accessedStorageCells = []; @@ -87,7 +87,7 @@ public void ReportAccess_AddressIsSetToOptmizedWithNoStorageCells_OnlyAddressesN } [Test] - public void ReportAccess_AddressAIsSetToOptmizedAndHasStorageCell_AddressAAndBIsInTheAccesslist() + public void ReportAccess_AddressAIsSetToOptimizedAndHasStorageCell_AddressAAndBIsInTheAccessList() { JournalSet
accessedAddresses = [TestItem.AddressA, TestItem.AddressB]; JournalSet accessedStorageCells = [new StorageCell(TestItem.AddressA, 0)]; @@ -99,7 +99,7 @@ public void ReportAccess_AddressAIsSetToOptmizedAndHasStorageCell_AddressAAndBIs } [Test] - public void ReportAccess_AddressAIsSetToOptmizedAndHasStorageCell_AccesslistHasCorrectStorageCell() + public void ReportAccess_AddressAIsSetToOptimizedAndHasStorageCell_AccessListHasCorrectStorageCell() { JournalSet
accessedAddresses = [TestItem.AddressA, TestItem.AddressB]; JournalSet accessedStorageCells = [new StorageCell(TestItem.AddressA, 1)]; diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/DebugTracerTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/DebugTracerTests.cs index e53fd52c78a..c4bfadd34fa 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/DebugTracerTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/DebugTracerTests.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only #if DEBUG +using System; using Nethermind.Blockchain.Tracing.GethStyle; using NUnit.Framework; using System.Threading; @@ -9,11 +10,13 @@ using Nethermind.Core.Test.Builders; using Nethermind.Evm.Tracing.Debugger; using Nethermind.Core; +using Nethermind.Evm.GasPolicy; namespace Nethermind.Evm.Test; public class DebugTracerTests : VirtualMachineTestsBase { + private static readonly TimeSpan ThreadJoinTimeout = TimeSpan.FromSeconds(5); private GethLikeTxMemoryTracer GethLikeTxTracer => new(Build.A.Transaction.TestObject, GethTraceOptions.Default); [SetUp] @@ -22,6 +25,18 @@ public override void Setup() base.Setup(); } + private void ExecuteSafe(DebugTracer tracer, byte[] bytecode) + { + try + { + Execute(tracer, bytecode); + } + catch (ThreadInterruptedException) + { + // Expected when test aborts the thread + } + } + [TestCase("0x5b601760005600")] public void Debugger_Halts_Execution_On_Breakpoint(string bytecodeHex) { @@ -39,7 +54,7 @@ public void Debugger_Halts_Execution_On_Breakpoint(string bytecodeHex) tracer.SetBreakPoint(JUMP_OPCODE_PTR_BREAK_POINT); // we set the break point to BREAK_POINT - Thread vmThread = new Thread(() => Execute(tracer, bytecode)); + Thread vmThread = new Thread(() => ExecuteSafe(tracer, bytecode)); vmThread.Start(); // we run the bytecode for iteration and check how many times we stopped at BREAK_POINT @@ -69,6 +84,7 @@ public void Debugger_Halts_Execution_On_Breakpoint(string bytecodeHex) } + vmThread.Join(ThreadJoinTimeout); Assert.That(TestFailed, Is.False); } @@ -91,7 +107,7 @@ public void Debugger_Halts_Execution_On_Breakpoint_If_Condition_Is_True(string b return state.DataStackHead == 23; }); - Thread vmThread = new(() => Execute(tracer, bytecode)); + Thread vmThread = new(() => ExecuteSafe(tracer, bytecode)); vmThread.Start(); // we run the bytecode for iteration and check how many times we stopped at BREAK_POINT @@ -121,13 +137,14 @@ public void Debugger_Halts_Execution_On_Breakpoint_If_Condition_Is_True(string b } } + vmThread.Join(ThreadJoinTimeout); Assert.That(TestFailed, Is.False); } [TestCase("0x5b5b5b5b5b5b5b5b5b5b00")] - public void Debugger_Halts_Execution_On_Eeach_Iteration_using_StepByStepMode(string bytecodeHex) + public void Debugger_Halts_Execution_On_Each_Iteration_using_StepByStepMode(string bytecodeHex) { - // this bytecode is just a bunch of NOP/JUMPDEST, the idea is it will take as much bytes in the bytecode as steps to go throught it + // this bytecode is just a bunch of NOP/JUMPDEST; the idea is it will take as many bytes in the bytecode as steps to go through it byte[] bytecode = Bytes.FromHexString(bytecodeHex); using DebugTracer tracer = new DebugTracer(GethLikeTxTracer) @@ -136,7 +153,7 @@ public void Debugger_Halts_Execution_On_Eeach_Iteration_using_StepByStepMode(str IsStepByStepModeOn = true, }; - Thread vmThread = new Thread(() => Execute(tracer, bytecode)); + Thread vmThread = new Thread(() => ExecuteSafe(tracer, bytecode)); vmThread.Start(); int countBreaks = 0; @@ -152,6 +169,7 @@ public void Debugger_Halts_Execution_On_Eeach_Iteration_using_StepByStepMode(str } } + vmThread.Join(ThreadJoinTimeout); countBreaks--; //Pre-run break // we check that it matches the number of opcodes in the bytecode @@ -161,7 +179,7 @@ public void Debugger_Halts_Execution_On_Eeach_Iteration_using_StepByStepMode(str [TestCase("0x5b5b5b5b5b5b5b5b5b5b00")] public void Debugger_Skips_Single_Step_Breakpoints_When_MoveNext_Uses_Override(string bytecodeHex) { - // this bytecode is just a bunch of NOP/JUMPDEST, the idea is it will take as much bytes in the bytecode as steps to go throught it + // this bytecode is just a bunch of NOP/JUMPDEST; the idea is it will take as many bytes in the bytecode as steps to go through it byte[] bytecode = Bytes.FromHexString(bytecodeHex); using DebugTracer tracer = new DebugTracer(GethLikeTxTracer) @@ -170,7 +188,7 @@ public void Debugger_Skips_Single_Step_Breakpoints_When_MoveNext_Uses_Override(s IsStepByStepModeOn = true, }; - Thread vmThread = new Thread(() => Execute(tracer, bytecode)); + Thread vmThread = new Thread(() => ExecuteSafe(tracer, bytecode)); vmThread.Start(); int countBreaks = 0; @@ -186,6 +204,8 @@ public void Debugger_Skips_Single_Step_Breakpoints_When_MoveNext_Uses_Override(s } } + vmThread.Join(ThreadJoinTimeout); + // we check that it matches the number of opcodes in the bytecode Assert.That(countBreaks, Is.EqualTo(1)); } @@ -193,7 +213,7 @@ public void Debugger_Skips_Single_Step_Breakpoints_When_MoveNext_Uses_Override(s [TestCase("0x5b5b5b5b5b5b5b5b5b5b00")] public void Debugger_Switches_To_Single_Steps_After_First_Breakpoint(string bytecodeHex) { - // this bytecode is just a bunch of NOP/JUMPDEST, the idea is it will take as much bytes in the bytecode as steps to go throught it + // this bytecode is just a bunch of NOP/JUMPDEST; the idea is it will take as many bytes in the bytecode as steps to go through it byte[] bytecode = Bytes.FromHexString(bytecodeHex); (int depth, int pc) BREAKPOINT = (0, 5); @@ -205,7 +225,7 @@ public void Debugger_Switches_To_Single_Steps_After_First_Breakpoint(string byte tracer.SetBreakPoint(BREAKPOINT); - Thread vmThread = new Thread(() => Execute(tracer, bytecode)); + Thread vmThread = new Thread(() => ExecuteSafe(tracer, bytecode)); vmThread.Start(); int countBreaks = -1; // not counting the post-run stop @@ -221,6 +241,8 @@ public void Debugger_Switches_To_Single_Steps_After_First_Breakpoint(string byte } } + vmThread.Join(ThreadJoinTimeout); + // we check that it matches the number of opcodes in the bytecode Assert.That(countBreaks, Is.EqualTo(bytecode.Length - BREAKPOINT.pc)); } @@ -239,7 +261,7 @@ public void Debugger_Can_Alter_Program_Counter(string bytecodeHex) tracer.SetBreakPoint(JUMP_OPCODE_PTR_BREAK_POINT); - Thread vmThread = new Thread(() => Execute(tracer, bytecode)); + Thread vmThread = new Thread(() => ExecuteSafe(tracer, bytecode)); vmThread.Start(); while (vmThread.IsAlive) @@ -251,6 +273,8 @@ public void Debugger_Can_Alter_Program_Counter(string bytecodeHex) } } + vmThread.Join(ThreadJoinTimeout); + // we check if bytecode execution failed var resultTraces = (tracer.InnerTracer as GethLikeTxMemoryTracer).BuildResult(); Assert.That(resultTraces.Failed, Is.False); @@ -270,7 +294,7 @@ public void Debugger_Can_Alter_Data_Stack(string bytecodeHex) tracer.SetBreakPoint(JUMP_OPCODE_PTR_BREAK_POINT); - Thread vmThread = new Thread(() => Execute(tracer, bytecode)); + Thread vmThread = new Thread(() => ExecuteSafe(tracer, bytecode)); vmThread.Start(); while (vmThread.IsAlive) @@ -286,6 +310,8 @@ public void Debugger_Can_Alter_Data_Stack(string bytecodeHex) } } + vmThread.Join(ThreadJoinTimeout); + // we check if bytecode execution failed var resultTraces = (tracer.InnerTracer as GethLikeTxMemoryTracer).BuildResult(); Assert.That(resultTraces.Failed, Is.False); @@ -305,7 +331,7 @@ public void Debugger_Can_Alter_Memory(string bytecodeHex) tracer.SetBreakPoint(MSTORE_OPCODE_PTR_BREAK_POINT); - Thread vmThread = new Thread(() => Execute(tracer, bytecode)); + Thread vmThread = new Thread(() => ExecuteSafe(tracer, bytecode)); vmThread.Start(); while (vmThread.IsAlive) @@ -313,12 +339,14 @@ public void Debugger_Can_Alter_Memory(string bytecodeHex) if (tracer.CanReadState) { // we alter the value stored in memory to force EQ check at the end to fail - tracer.CurrentState.Memory.SaveByte(31, 0x0A); + tracer.CurrentState.Memory.TrySaveByte(31, 0x0A); tracer.MoveNext(); } } + vmThread.Join(ThreadJoinTimeout); + // we check if bytecode execution failed var resultTraces = (tracer.InnerTracer as GethLikeTxMemoryTracer).BuildResult(); Assert.That(resultTraces.ReturnValue[31] == 0, Is.True); @@ -344,7 +372,7 @@ public void Use_Debug_Tracer_To_Check_Assertion_Live(string bytecodeHex) tracer.SetBreakPoint(MSTORE_OPCODE_PTR_BREAK_POINT); tracer.SetBreakPoint(POST_MSTORE_OPCODE_PTR_BREAK_POINT); - Thread vmThread = new Thread(() => Execute(tracer, bytecode)); + Thread vmThread = new Thread(() => ExecuteSafe(tracer, bytecode)); vmThread.Start(); long? gasAvailable_pre_MSTORE = null; @@ -353,15 +381,17 @@ public void Use_Debug_Tracer_To_Check_Assertion_Live(string bytecodeHex) if (tracer.CanReadState) { // we alter the value stored in memory to force EQ check at the end to fail - if (gasAvailable_pre_MSTORE is null) gasAvailable_pre_MSTORE = tracer.CurrentState.GasAvailable; + if (gasAvailable_pre_MSTORE is null) gasAvailable_pre_MSTORE = EthereumGasPolicy.GetRemainingGas(tracer.CurrentState.Gas); else { - long gasAvailable_post_MSTORE = tracer.CurrentState.GasAvailable; + long gasAvailable_post_MSTORE = EthereumGasPolicy.GetRemainingGas(tracer.CurrentState.Gas); Assert.That(gasAvailable_pre_MSTORE - gasAvailable_post_MSTORE, Is.EqualTo(GasCostOf.VeryLow)); } tracer.MoveNext(); } } + + vmThread.Join(ThreadJoinTimeout); } [TestCase("ef601700")] @@ -375,7 +405,7 @@ public void Use_Debug_Tracer_To_Check_failure_status(string bytecodeHex) IsStepByStepModeOn = true, }; - Thread vmThread = new Thread(() => Execute(tracer, bytecode)); + Thread vmThread = new Thread(() => ExecuteSafe(tracer, bytecode)); vmThread.Start(); while (vmThread.IsAlive) @@ -386,6 +416,8 @@ public void Use_Debug_Tracer_To_Check_failure_status(string bytecodeHex) } } + vmThread.Join(ThreadJoinTimeout); + // we check if bytecode execution failed var resultTraces = (tracer.InnerTracer as GethLikeTxMemoryTracer).BuildResult(); Assert.That(resultTraces.Failed, Is.True); @@ -403,7 +435,7 @@ public void Debugger_stops_at_correct_breakpoint_depth(string bytecodeHex) tracer.SetBreakPoint(TARGET_OPCODE_PTR_BREAK_POINT); - Thread vmThread = new Thread(() => Execute(tracer, bytecode)); + Thread vmThread = new Thread(() => ExecuteSafe(tracer, bytecode)); vmThread.Start(); bool stoppedAtCorrectBreakpoint = false; @@ -423,6 +455,7 @@ public void Debugger_stops_at_correct_breakpoint_depth(string bytecodeHex) } } + vmThread.Join(ThreadJoinTimeout); Assert.That(stoppedAtCorrectBreakpoint, Is.True); } @@ -435,9 +468,9 @@ public void Debugger_Halts_Execution_When_Global_Condition_Is_Met(string bytecod const int DATA_STACK_HEIGHT = 10; using DebugTracer tracer = new DebugTracer(GethLikeTxTracer); - tracer.SetCondtion(state => state.DataStackHead == DATA_STACK_HEIGHT); + tracer.SetCondition(state => state.DataStackHead == DATA_STACK_HEIGHT); - Thread vmThread = new Thread(() => Execute(tracer, bytecode)); + Thread vmThread = new Thread(() => ExecuteSafe(tracer, bytecode)); vmThread.Start(); bool stoppedAtLeastOneTime = false; @@ -451,11 +484,12 @@ public void Debugger_Halts_Execution_When_Global_Condition_Is_Met(string bytecod } } + vmThread.Join(ThreadJoinTimeout); Assert.That(stoppedAtLeastOneTime, Is.True); } [TestCase("0x5b601760005600")] - public void Debugger_Wont_Halt_If_Breakpoint_Is_Unreacheable(string bytecodeHex) + public void Debugger_Wont_Halt_If_Breakpoint_Is_Unreachable(string bytecodeHex) { byte[] bytecode = Bytes.FromHexString(bytecodeHex); @@ -464,7 +498,7 @@ public void Debugger_Wont_Halt_If_Breakpoint_Is_Unreacheable(string bytecodeHex) tracer.SetBreakPoint(TARGET_OPCODE_PTR_BREAK_POINT); - Thread vmThread = new Thread(() => Execute(tracer, bytecode)); + Thread vmThread = new Thread(() => ExecuteSafe(tracer, bytecode)); vmThread.Start(); bool stoppedAtCorrectBreakpoint = false; @@ -477,6 +511,7 @@ public void Debugger_Wont_Halt_If_Breakpoint_Is_Unreacheable(string bytecodeHex) } } + vmThread.Join(ThreadJoinTimeout); Assert.That(stoppedAtCorrectBreakpoint, Is.False); } } diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs index 5552059366d..b7ea5200d44 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/GasEstimationTests.cs @@ -506,6 +506,7 @@ public void Should_succeed_when_address_zero_has_no_value_transfer() TestEnvironment testEnvironment = new(); Transaction tx = Build.A.Transaction .WithGasLimit(100000) + .WithGasPrice(0) .WithSenderAddress(Address.Zero) .WithValue(0) // No value transfer - should work even with zero balance .TestObject; @@ -604,7 +605,8 @@ public void Should_estimate_gas_for_explicit_gas_check_and_revert(long gasLimit, if (shouldSucceed) { - result.Should().BeGreaterThan(1_000_000, "Gas estimation should account for the gas threshold in the contract"); + result.Should().BeGreaterThan(1_000_000, + "Gas estimation should account for the gas threshold in the contract"); err.Should().BeNull(); } else @@ -613,11 +615,66 @@ public void Should_estimate_gas_for_explicit_gas_check_and_revert(long gasLimit, } } + [Test] + public void Should_succeed_with_internal_revert() + { + using TestEnvironment testEnvironment = new(); + long gasLimit = 100_000; + Transaction tx = Build.A.Transaction.WithGasLimit(gasLimit).TestObject; + Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).WithGasLimit(gasLimit).TestObject; + + long gasLeft = gasLimit - 22000; + testEnvironment.tracer.ReportAction(gasLeft, 0, Address.Zero, Address.Zero, Array.Empty(), + ExecutionType.TRANSACTION, false); + + gasLeft = 63 * gasLeft / 64; + testEnvironment.tracer.ReportAction(gasLeft, 0, Address.Zero, Address.Zero, Array.Empty(), + ExecutionType.CALL, false); + + gasLeft = 63 * gasLeft / 64; + testEnvironment.tracer.ReportAction(gasLeft, 0, Address.Zero, Address.Zero, Array.Empty(), + ExecutionType.CALL, false); + + testEnvironment.tracer.ReportActionRevert(gasLeft - 1000, Array.Empty()); + testEnvironment.tracer.ReportActionEnd(gasLeft - 500, Array.Empty()); + testEnvironment.tracer.ReportActionEnd(gasLeft, Array.Empty()); + testEnvironment.tracer.MarkAsSuccess(Address.Zero, 25000, Array.Empty(), Array.Empty()); + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().BeGreaterThan(0); + err.Should().BeNull(); + testEnvironment.tracer.TopLevelRevert.Should().BeFalse(); + testEnvironment.tracer.OutOfGas.Should().BeFalse(); + } + + [Test] + public void Should_fail_with_top_level_revert() + { + using TestEnvironment testEnvironment = new(); + long gasLimit = 100_000; + Transaction tx = Build.A.Transaction.WithGasLimit(gasLimit).TestObject; + Block block = Build.A.Block.WithNumber(1).WithTransactions(tx).WithGasLimit(gasLimit).TestObject; + + long gasLeft = gasLimit - 22000; + testEnvironment.tracer.ReportAction(gasLeft, 0, Address.Zero, Address.Zero, Array.Empty(), + ExecutionType.TRANSACTION, false); + + testEnvironment.tracer.ReportActionRevert(gasLeft - 1000, Array.Empty()); + testEnvironment.tracer.MarkAsFailed(Address.Zero, 25000, Array.Empty(), "execution reverted"); + + long result = testEnvironment.estimator.Estimate(tx, block.Header, testEnvironment.tracer, out string? err); + + result.Should().Be(0); + err.Should().Be("execution reverted"); + testEnvironment.tracer.TopLevelRevert.Should().BeTrue(); + } + private class TestEnvironment : IDisposable { public ISpecProvider _specProvider; public IEthereumEcdsa _ethereumEcdsa; - public TransactionProcessor _transactionProcessor; + public EthereumTransactionProcessor _transactionProcessor; public IWorldState _stateProvider; public EstimateGasTracer tracer; public GasEstimator estimator; @@ -633,8 +690,8 @@ public TestEnvironment() _stateProvider.CommitTree(0); EthereumCodeInfoRepository codeInfoRepository = new(_stateProvider); - VirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); - _transactionProcessor = new TransactionProcessor(BlobBaseFeeCalculator.Instance, _specProvider, _stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); + EthereumVirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); + _transactionProcessor = new EthereumTransactionProcessor(BlobBaseFeeCalculator.Instance, _specProvider, _stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); _ethereumEcdsa = new EthereumEcdsa(_specProvider.ChainId); tracer = new(); diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/GethLikeTxTraceCollectionConverterTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/GethLikeTxTraceCollectionConverterTests.cs index b7d0959b0cf..289ca3592cc 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/GethLikeTxTraceCollectionConverterTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/GethLikeTxTraceCollectionConverterTests.cs @@ -32,7 +32,7 @@ public void Write_empty() Assert.That(result, Is.EqualTo("[]")); } - [TestCaseSource(nameof(TracesAndJsonsSource))] + [TestCaseSource(nameof(TraceAndJsonSource))] public void Write_with_traces_with_tx_hash(GethLikeTxTrace trace, string json) { var expected = $"""[{json}]"""; @@ -65,7 +65,7 @@ public void Read_empty() } - [TestCaseSource(nameof(TracesAndJsonsSource))] + [TestCaseSource(nameof(TraceAndJsonSource))] public void Read_with_traces(GethLikeTxTrace expectedTrace, string json) { var result = _serializer.Deserialize($"""[{json}]"""); @@ -79,7 +79,7 @@ public void Read_with_traces(GethLikeTxTrace expectedTrace, string json) }); } - private static IEnumerable TracesAndJsonsSource() + private static IEnumerable TraceAndJsonSource() { yield return [ new GethLikeTxTrace { Gas = 1, ReturnValue = [0x01], TxHash = null }, diff --git a/src/Nethermind/Nethermind.Evm.Test/Tracing/ProofTxTracerTests.cs b/src/Nethermind/Nethermind.Evm.Test/Tracing/ProofTxTracerTests.cs index 93f0e39762f..60ba34a5f03 100644 --- a/src/Nethermind/Nethermind.Evm.Test/Tracing/ProofTxTracerTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/Tracing/ProofTxTracerTests.cs @@ -5,6 +5,7 @@ using Nethermind.Core; using Nethermind.Core.Test.Builders; using Nethermind.Blockchain.Tracing.Proofs; +using Nethermind.Evm.State; using Nethermind.Evm.TransactionProcessing; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip4844Tests.cs b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip4844Tests.cs index 16d1dba46bd..d0fc8e2ac58 100644 --- a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip4844Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip4844Tests.cs @@ -38,8 +38,8 @@ public void Setup() _stateProvider = TestWorldStateFactory.CreateForTest(); _worldStateCloser = _stateProvider.BeginScope(IWorldState.PreGenesis); EthereumCodeInfoRepository codeInfoRepository = new(_stateProvider); - VirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); - _transactionProcessor = new TransactionProcessor(BlobBaseFeeCalculator.Instance, _specProvider, _stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); + EthereumVirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); + _transactionProcessor = new EthereumTransactionProcessor(BlobBaseFeeCalculator.Instance, _specProvider, _stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); _ethereumEcdsa = new EthereumEcdsa(_specProvider.ChainId); } diff --git a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip7623Tests.cs b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip7623Tests.cs index 69ed6870c84..1865ecfc8a2 100644 --- a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip7623Tests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorEip7623Tests.cs @@ -36,8 +36,8 @@ public void Setup() _stateProvider = TestWorldStateFactory.CreateForTest(); _worldStateCloser = _stateProvider.BeginScope(IWorldState.PreGenesis); EthereumCodeInfoRepository codeInfoRepository = new(_stateProvider); - VirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); - _transactionProcessor = new TransactionProcessor(BlobBaseFeeCalculator.Instance, _specProvider, _stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); + EthereumVirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); + _transactionProcessor = new EthereumTransactionProcessor(BlobBaseFeeCalculator.Instance, _specProvider, _stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); _ethereumEcdsa = new EthereumEcdsa(_specProvider.ChainId); } diff --git a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs index 50f593d378b..adf9866fb05 100644 --- a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs +++ b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorFeeTests.cs @@ -48,8 +48,8 @@ public void Setup() _stateProvider.CommitTree(0); EthereumCodeInfoRepository codeInfoRepository = new(_stateProvider); - VirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); - _transactionProcessor = new TransactionProcessor(BlobBaseFeeCalculator.Instance, _specProvider, _stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); + EthereumVirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); + _transactionProcessor = new EthereumTransactionProcessor(BlobBaseFeeCalculator.Instance, _specProvider, _stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); _ethereumEcdsa = new EthereumEcdsa(_specProvider.ChainId); } diff --git a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorTraceTest.cs b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorTraceTest.cs index d139a885c33..81f14bde867 100644 --- a/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorTraceTest.cs +++ b/src/Nethermind/Nethermind.Evm.Test/TransactionProcessorTraceTest.cs @@ -20,7 +20,7 @@ public class TransactionProcessorTraceTest : VirtualMachineTestsBase [TestCase(50000)] public void Trace_should_not_charge_gas(long gasLimit) { - (Block block, Transaction transaction) = PrepareTx(BlockNumber, gasLimit); + (Block block, Transaction transaction) = PrepareTx(BlockNumber, gasLimit, gasPrice: 0); ParityLikeTxTracer tracer = new(block, transaction, ParityTraceTypes.All); _processor.Trace(transaction, new BlockExecutionContext(block.Header, Spec), tracer); var senderBalance = tracer.BuildResult().StateChanges[TestItem.AddressA].Balance; diff --git a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs index a686d51fdef..b45550ea705 100644 --- a/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs +++ b/src/Nethermind/Nethermind.Evm.Test/VirtualMachineTestsBase.cs @@ -37,7 +37,7 @@ public abstract class VirtualMachineTestsBase private IDb _stateDb; private IDisposable _worldStateCloser; - protected VirtualMachine Machine { get; private set; } + protected EthereumVirtualMachine Machine { get; private set; } protected CodeInfoRepository CodeInfoRepository { get; private set; } protected IWorldState TestState { get; private set; } protected static Address Contract { get; } = new("0xd75a3a95360e44a3874e691fb48d77855f127069"); @@ -72,8 +72,8 @@ public virtual void Setup() _ethereumEcdsa = new EthereumEcdsa(SpecProvider.ChainId); IBlockhashProvider blockhashProvider = new TestBlockhashProvider(SpecProvider); CodeInfoRepository = new EthereumCodeInfoRepository(TestState); - Machine = new VirtualMachine(blockhashProvider, SpecProvider, logManager); - _processor = new TransactionProcessor(BlobBaseFeeCalculator.Instance, SpecProvider, TestState, Machine, CodeInfoRepository, logManager); + Machine = new EthereumVirtualMachine(blockhashProvider, SpecProvider, logManager); + _processor = new EthereumTransactionProcessor(BlobBaseFeeCalculator.Instance, SpecProvider, TestState, Machine, CodeInfoRepository, logManager); } [TearDown] @@ -197,9 +197,10 @@ protected TestAllTracerWithOutput Execute(ForkActivation activation, long gasLim int value = 1, long blockGasLimit = DefaultBlockGasLimit, byte[][]? blobVersionedHashes = null, - ulong excessBlobGas = 0) + ulong excessBlobGas = 0, + ulong gasPrice = 1) { - return PrepareTx((blockNumber, Timestamp), gasLimit, code, senderRecipientAndMiner, value, blockGasLimit, blobVersionedHashes, excessBlobGas); + return PrepareTx((blockNumber, Timestamp), gasLimit, code, senderRecipientAndMiner, value, blockGasLimit, blobVersionedHashes, excessBlobGas, gasPrice: gasPrice); } protected (Block block, Transaction transaction) PrepareTx( @@ -211,7 +212,8 @@ protected TestAllTracerWithOutput Execute(ForkActivation activation, long gasLim long blockGasLimit = DefaultBlockGasLimit, byte[][]? blobVersionedHashes = null, ulong excessBlobGas = 0, - Transaction transaction = null) + Transaction transaction = null, + ulong gasPrice = 1) { senderRecipientAndMiner ??= SenderRecipientAndMiner.Default; @@ -243,7 +245,7 @@ protected TestAllTracerWithOutput Execute(ForkActivation activation, long gasLim transaction ??= Build.A.Transaction .WithGasLimit(gasLimit) - .WithGasPrice(1) + .WithGasPrice(gasPrice) .WithValue(value) .WithBlobVersionedHashes(blobVersionedHashes) .WithNonce(TestState.GetNonce(senderRecipientAndMiner.Sender)) @@ -261,7 +263,7 @@ protected TestAllTracerWithOutput Execute(ForkActivation activation, long gasLim /// deprecated. Please use activation instead of blockNumber. ///
protected (Block block, Transaction transaction) PrepareTx(long blockNumber, long gasLimit, byte[] code, - byte[] input, UInt256 value, SenderRecipientAndMiner senderRecipientAndMiner = null) + byte[] input, UInt256 value, SenderRecipientAndMiner senderRecipientAndMiner = null, ulong gasPrice = 1) { return PrepareTx((blockNumber, Timestamp), gasLimit, code, input, value, senderRecipientAndMiner); } diff --git a/src/Nethermind/Nethermind.Evm.Test/VmStateTests.cs b/src/Nethermind/Nethermind.Evm.Test/VmStateTests.cs new file mode 100644 index 00000000000..8fc3e9a8b58 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm.Test/VmStateTests.cs @@ -0,0 +1,244 @@ +// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Core.Test.Builders; +using Nethermind.Evm.GasPolicy; +using Nethermind.Evm.State; +using NUnit.Framework; + +namespace Nethermind.Evm.Test +{ + public class VmStateTests + { + [Test] + public void Things_are_cold_to_start_with() + { + VmState vmState = CreateEvmState(); + StorageCell storageCell = new(TestItem.AddressA, 1); + vmState.AccessTracker.IsCold(TestItem.AddressA).Should().BeTrue(); + vmState.AccessTracker.IsCold(storageCell).Should().BeTrue(); + } + + [Test] + public void Can_warm_address_up_twice() + { + VmState vmState = CreateEvmState(); + Address address = TestItem.AddressA; + vmState.AccessTracker.WarmUp(address); + vmState.AccessTracker.WarmUp(address); + vmState.AccessTracker.IsCold(address).Should().BeFalse(); + } + + [Test] + public void Can_warm_up_many() + { + VmState vmState = CreateEvmState(); + for (int i = 0; i < TestItem.Addresses.Length; i++) + { + vmState.AccessTracker.WarmUp(TestItem.Addresses[i]); + vmState.AccessTracker.WarmUp(new StorageCell(TestItem.Addresses[i], 1)); + } + + for (int i = 0; i < TestItem.Addresses.Length; i++) + { + vmState.AccessTracker.IsCold(TestItem.Addresses[i]).Should().BeFalse(); + vmState.AccessTracker.IsCold(new StorageCell(TestItem.Addresses[i], 1)).Should().BeFalse(); + } + } + + [Test] + public void Can_warm_storage_up_twice() + { + VmState vmState = CreateEvmState(); + Address address = TestItem.AddressA; + StorageCell storageCell = new(address, 1); + vmState.AccessTracker.WarmUp(storageCell); + vmState.AccessTracker.WarmUp(storageCell); + vmState.AccessTracker.IsCold(storageCell).Should().BeFalse(); + } + + [Test] + public void Nothing_to_commit() + { + VmState parentVmState = CreateEvmState(); + using (VmState vmState = CreateEvmState(parentVmState)) + { + vmState.CommitToParent(parentVmState); + } + } + + [Test] + public void Nothing_to_restore() + { + VmState parentVmState = CreateEvmState(); + using VmState vmState = CreateEvmState(parentVmState); + } + + [Test] + public void Address_to_commit_keeps_it_warm() + { + VmState parentVmState = CreateEvmState(); + using (VmState vmState = CreateEvmState(parentVmState)) + { + vmState.AccessTracker.WarmUp(TestItem.AddressA); + vmState.CommitToParent(parentVmState); + } + + parentVmState.AccessTracker.IsCold(TestItem.AddressA).Should().BeFalse(); + } + + [Test] + public void Address_to_restore_keeps_it_cold() + { + VmState parentVmState = CreateEvmState(); + using (VmState vmState = CreateEvmState(parentVmState)) + { + vmState.AccessTracker.WarmUp(TestItem.AddressA); + } + + parentVmState.AccessTracker.IsCold(TestItem.AddressA).Should().BeTrue(); + } + + [Test] + public void Storage_to_commit_keeps_it_warm() + { + VmState parentVmState = CreateEvmState(); + StorageCell storageCell = new(TestItem.AddressA, 1); + using (VmState vmState = CreateEvmState(parentVmState)) + { + vmState.AccessTracker.WarmUp(storageCell); + vmState.CommitToParent(parentVmState); + } + + parentVmState.AccessTracker.IsCold(storageCell).Should().BeFalse(); + } + + [Test] + public void Storage_to_restore_keeps_it_cold() + { + VmState parentVmState = CreateEvmState(); + StorageCell storageCell = new(TestItem.AddressA, 1); + using (VmState vmState = CreateEvmState(parentVmState)) + { + vmState.AccessTracker.WarmUp(storageCell); + } + + parentVmState.AccessTracker.IsCold(storageCell).Should().BeTrue(); + } + + [Test] + public void Logs_are_committed() + { + VmState parentVmState = CreateEvmState(); + LogEntry logEntry = new(Address.Zero, Bytes.Empty, []); + using (VmState vmState = CreateEvmState(parentVmState)) + { + vmState.AccessTracker.Logs.Add(logEntry); + vmState.CommitToParent(parentVmState); + } + + parentVmState.AccessTracker.Logs.Contains(logEntry).Should().BeTrue(); + } + + [Test] + public void Logs_are_restored() + { + VmState parentVmState = CreateEvmState(); + LogEntry logEntry = new(Address.Zero, Bytes.Empty, []); + using (VmState vmState = CreateEvmState(parentVmState)) + { + vmState.AccessTracker.Logs.Add(logEntry); + } + + parentVmState.AccessTracker.Logs.Contains(logEntry).Should().BeFalse(); + } + + [Test] + public void Destroy_list_is_committed() + { + VmState parentVmState = CreateEvmState(); + using (VmState vmState = CreateEvmState(parentVmState)) + { + vmState.AccessTracker.ToBeDestroyed(Address.Zero); + vmState.CommitToParent(parentVmState); + } + + parentVmState.AccessTracker.DestroyList.Contains(Address.Zero).Should().BeTrue(); + } + + [Test] + public void Destroy_list_is_restored() + { + VmState parentVmState = CreateEvmState(); + using (VmState vmState = CreateEvmState(parentVmState)) + { + vmState.AccessTracker.ToBeDestroyed(Address.Zero); + } + + parentVmState.AccessTracker.DestroyList.Contains(Address.Zero).Should().BeFalse(); + } + + [Test] + public void Commit_adds_refunds() + { + VmState parentVmState = CreateEvmState(); + using (VmState vmState = CreateEvmState(parentVmState)) + { + vmState.Refund = 333; + vmState.CommitToParent(parentVmState); + } + + parentVmState.Refund.Should().Be(333); + } + + [Test] + public void Restore_does_not_add_refunds() + { + VmState parentVmState = CreateEvmState(); + using (VmState vmState = CreateEvmState(parentVmState)) + { + vmState.Refund = 333; + } + + parentVmState.Refund.Should().Be(0); + } + + [Test] + public void Can_dispose_without_init() + { + VmState vmState = CreateEvmState(); + vmState.Dispose(); + } + + [Test] + public void Can_dispose_after_init() + { + VmState vmState = CreateEvmState(); + vmState.InitializeStacks(); + vmState.Dispose(); + } + + private static VmState CreateEvmState(VmState parentVmState = null, bool isContinuation = false) => + parentVmState is null + ? VmState.RentTopLevel(EthereumGasPolicy.FromLong(10000), + ExecutionType.CALL, + RentExecutionEnvironment(), + new StackAccessTracker(), + Snapshot.Empty) + : VmState.RentFrame(EthereumGasPolicy.FromLong(10000), + 0, + 0, + ExecutionType.CALL, + false, + false, + RentExecutionEnvironment(), + parentVmState.AccessTracker, + Snapshot.Empty); + + private static ExecutionEnvironment RentExecutionEnvironment() => + ExecutionEnvironment.Rent(null, null, null, null, 0, default, default, default); + } +} diff --git a/src/Nethermind/Nethermind.Evm/BlobGasCalculator.cs b/src/Nethermind/Nethermind.Evm/BlobGasCalculator.cs index d6c18253126..11728698918 100644 --- a/src/Nethermind/Nethermind.Evm/BlobGasCalculator.cs +++ b/src/Nethermind/Nethermind.Evm/BlobGasCalculator.cs @@ -84,10 +84,10 @@ static bool FakeExponentialOverflow(UInt256 factor, UInt256 num, UInt256 denomin return true; } - accumulator = updatedAccumulator / multipliedDenominator; + accumulator = multipliedDenominator.IsZero ? default : updatedAccumulator / multipliedDenominator; } - feePerBlobGas = output / denominator; + feePerBlobGas = denominator.IsZero ? default : output / denominator; return false; } diff --git a/src/Nethermind/Nethermind.Evm/CallResult.cs b/src/Nethermind/Nethermind.Evm/CallResult.cs index 8c1e4b9b1af..08d88379284 100644 --- a/src/Nethermind/Nethermind.Evm/CallResult.cs +++ b/src/Nethermind/Nethermind.Evm/CallResult.cs @@ -3,10 +3,12 @@ using System; using Nethermind.Evm.CodeAnalysis; +using Nethermind.Evm.GasPolicy; namespace Nethermind.Evm; -public unsafe partial class VirtualMachine +public partial class VirtualMachine + where TGasPolicy : struct, IGasPolicy { protected readonly ref struct CallResult { @@ -23,7 +25,7 @@ protected readonly ref struct CallResult public static CallResult InvalidAddressRange => new(EvmExceptionType.AddressOutOfRange); public static CallResult Empty(int fromVersion) => new(container: null, output: default, precompileSuccess: null, fromVersion); - public CallResult(EvmState stateToExecute) + public CallResult(VmState stateToExecute) { StateToExecute = stateToExecute; Output = (null, Array.Empty()); @@ -61,7 +63,7 @@ private CallResult(EvmExceptionType exceptionType) ExceptionType = exceptionType; } - public EvmState? StateToExecute { get; } + public VmState? StateToExecute { get; } public (ICodeInfo Container, ReadOnlyMemory Bytes) Output { get; } public EvmExceptionType ExceptionType { get; } public bool ShouldRevert { get; } diff --git a/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs b/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs index f5fb43269de..2ceca978a1b 100644 --- a/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs +++ b/src/Nethermind/Nethermind.Evm/CodeAnalysis/JumpDestinationAnalyzer.cs @@ -108,14 +108,14 @@ private long[] CreateJumpDestinationBitmap() Metrics.IncrementContractsAnalysed(); ReadOnlySpan code = MachineCode.Span; - // If code is empty or starts with STOP, then we don't need to analyse + // If code is empty or starts with STOP, then we don't need to analyze if ((uint)code.Length < (uint)1 || code[0] == (byte)Instruction.STOP) return _emptyJumpDestinationBitmap; long[] bitmap = CreateBitmap(code.Length); - return Vector512.IsSupported && code.Length >= Vector512.Count ? + return Vector512.IsHardwareAccelerated && code.Length >= Vector512.Count ? PopulateJumpDestinationBitmap_Vector512(bitmap, code) : - Vector128.IsSupported && code.Length >= Vector128.Count ? + Vector128.IsHardwareAccelerated && code.Length >= Vector128.Count ? PopulateJumpDestinationBitmap_Vector128(bitmap, code) : PopulateJumpDestinationBitmap_Scalar(bitmap, code); } @@ -266,7 +266,7 @@ internal static long[] PopulateJumpDestinationBitmap_Vector128(long[] bitmap, Re int move = 1; // We use 128bit rather than Avx or Avx-512 as is optimization for stretch of code without PUSHes. // As the vector size increases the chance of there being a PUSH increases which will disable this optimization. - if (Vector128.IsSupported && + if (Vector128.IsHardwareAccelerated && // Check not going to read passed end of code. programCounter <= code.Length - Vector128.Count && // Are we on an short stride, one quarter of the long flags? @@ -305,7 +305,7 @@ internal static long[] PopulateJumpDestinationBitmap_Vector128(long[] bitmap, Re else if ((sbyte)op > PUSHx) { // Fast forward programCounter by the amount of data the push - // represents as don't need to analyse data for Jump Destinations. + // represents as don't need to analyze data for Jump Destinations. move = op - PUSH1 + 2; } diff --git a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/Handlers/EofV1.cs b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/Handlers/EofV1.cs index e71a41fc8b8..55a156b3f30 100644 --- a/src/Nethermind/Nethermind.Evm/EvmObjectFormat/Handlers/EofV1.cs +++ b/src/Nethermind/Nethermind.Evm/EvmObjectFormat/Handlers/EofV1.cs @@ -785,7 +785,7 @@ private static bool ValidateDataSection(in EofHeader header, ValidationStrategy } // When trailing bytes are not allowed, the DataSection cannot exceed the stated size data. - // Undeflow cases were checked above as they don't apply in all cases + // Underflow cases were checked above as they don't apply in all cases if (!strategy.HasFlag(ValidationStrategy.AllowTrailingBytes) && strategy.HasFlag(ValidationStrategy.ValidateFullBody) && header.DataSection.Size < dataBody.Length) @@ -1083,7 +1083,7 @@ private static bool ValidateInstructions( return false; } - // JUMPF is only returnig when the target is returning + // JUMPF is only returning when the target is returning if (!isTargetSectionNonReturning) { hasRequiredSectionExit = true; @@ -1473,7 +1473,7 @@ private static bool ValidateEofCreate( if (eofContainer.Header.ContainerSections is null || initCodeSectionId >= eofContainer.Header.ContainerSections.Value.Count) { if (Logger.IsTrace) - Logger.Trace($"EOF: Eof{VERSION}, {Instruction.EOFCREATE}'s immediate must fall within the Containers' range available, i.e.: {eofContainer.Header.CodeSections.Count}"); + Logger.Trace($"EOF: Eof{VERSION}, {Instruction.EOFCREATE}'s immediate must fall within the Containers' range available, i.e.: {eofContainer.Header.ContainerSections?.Count}"); return false; } diff --git a/src/Nethermind/Nethermind.Evm/EvmPooledMemory.cs b/src/Nethermind/Nethermind.Evm/EvmPooledMemory.cs index e466680673c..043a12513fa 100644 --- a/src/Nethermind/Nethermind.Evm/EvmPooledMemory.cs +++ b/src/Nethermind/Nethermind.Evm/EvmPooledMemory.cs @@ -23,11 +23,13 @@ public struct EvmPooledMemory : IEvmMemory public ulong Length { get; private set; } public ulong Size { get; private set; } - public void SaveWord(in UInt256 location, Span word) + public bool TrySaveWord(in UInt256 location, Span word) { if (word.Length != WordSize) ThrowArgumentOutOfRangeException(); - CheckMemoryAccessViolation(in location, WordSize, out ulong newLength); + CheckMemoryAccessViolation(in location, WordSize, out ulong newLength, out bool outOfGas); + if (outOfGas) return false; + UpdateSize(newLength); int offset = (int)location; @@ -37,113 +39,107 @@ public void SaveWord(in UInt256 location, Span word) ref Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_memory), offset), Unsafe.As>(ref MemoryMarshal.GetReference(word)) ); + + return true; } - public void SaveByte(in UInt256 location, byte value) + public bool TrySaveByte(in UInt256 location, byte value) { - CheckMemoryAccessViolation(in location, WordSize, out _); - UpdateSize(in location, in UInt256.One); + CheckMemoryAccessViolation(in location, 1, out ulong newLength, out bool isViolation); + if (isViolation) return false; + + UpdateSize(newLength); _memory![(long)location] = value; + return true; } - public void Save(in UInt256 location, Span value) + public bool TrySave(in UInt256 location, Span value) { if (value.Length == 0) { - return; + return true; } - CheckMemoryAccessViolation(in location, (ulong)value.Length, out ulong newLength); + CheckMemoryAccessViolation(in location, (ulong)value.Length, out ulong newLength, out bool isViolation); + if (isViolation) return false; + UpdateSize(newLength); value.CopyTo(_memory.AsSpan((int)location, value.Length)); + return true; } - private static void CheckMemoryAccessViolation(in UInt256 location, in UInt256 length, out ulong newLength, out bool outOfGas) + private static void CheckMemoryAccessViolation(in UInt256 location, in UInt256 length, out ulong newLength, out bool isViolation) { - if (location.IsLargerThanULong() || length.IsLargerThanULong()) + if (length.IsLargerThanULong()) { - outOfGas = true; + isViolation = true; newLength = 0; return; } - CheckMemoryAccessViolationInner(location.u0, length.u0, out newLength, out outOfGas); + CheckMemoryAccessViolation(in location, length.u0, out newLength, out isViolation); } - private static void CheckMemoryAccessViolation(in UInt256 location, in UInt256 length, out ulong newLength) + private static void CheckMemoryAccessViolation(in UInt256 location, ulong length, out ulong newLength, out bool isViolation) { - if (location.IsLargerThanULong() || length.IsLargerThanULong()) - { - ThrowOutOfGasException(); - } - - CheckMemoryAccessViolationInner(location.u0, length.u0, out newLength, out bool outOfGas); - if (outOfGas) - { - ThrowOutOfGasException(); - } - } - - private static void CheckMemoryAccessViolation(in UInt256 location, ulong length, out ulong newLength) - { - if (location.IsLargerThanULong()) - { - ThrowOutOfGasException(); - } + // Check for overflow and ensure the word-aligned size fits in int. + // Word alignment can add up to 31 bytes, so we use (int.MaxValue - WordSize + 1) as the limit. + // This ensures that after word alignment, the size still fits in int for .NET array operations. + const ulong MaxMemorySize = int.MaxValue - WordSize + 1; - CheckMemoryAccessViolationInner(location.u0, length, out newLength, out bool outOfGas); - if (outOfGas) + if (location.IsLargerThanULong() || length > MaxMemorySize) { - ThrowOutOfGasException(); + isViolation = true; + newLength = 0; + return; } - } - private static void CheckMemoryAccessViolationInner(ulong location, ulong length, out ulong newLength, out bool outOfGas) - { - ulong totalSize = location + length; - if (totalSize < location || totalSize > long.MaxValue) + ulong totalSize = location.u0 + length; + if (totalSize < location.u0 || totalSize > MaxMemorySize) { - outOfGas = true; + isViolation = true; newLength = 0; return; } - outOfGas = false; + isViolation = false; newLength = totalSize; } - public void Save(in UInt256 location, byte[] value) + public bool TrySave(in UInt256 location, byte[] value) { if (value.Length == 0) { - return; + return true; } ulong length = (ulong)value.Length; - CheckMemoryAccessViolation(in location, length, out ulong newLength); + CheckMemoryAccessViolation(in location, length, out ulong newLength, out bool isViolation); + if (isViolation) return false; + UpdateSize(newLength); Array.Copy(value, 0, _memory!, (long)location, value.Length); + return true; } - public void Save(in UInt256 location, in ZeroPaddedSpan value) + public bool TrySave(in UInt256 location, in ZeroPaddedSpan value) { if (value.Length == 0) { // Nothing to do - return; + return true; } ulong length = (ulong)value.Length; - CheckMemoryAccessViolation(in location, length, out ulong newLength); - UpdateSize(newLength); + CheckMemoryAccessViolation(in location, length, out ulong newLength, out bool isViolation); + isViolation |= location.u0 > int.MaxValue; - if (location.u0 > int.MaxValue) - { - ThrowOutOfGas(); - } + if (isViolation) return false; + + UpdateSize(newLength); int intLocation = (int)location.u0; value.Span.CopyTo(_memory.AsSpan(intLocation, value.Span.Length)); @@ -152,47 +148,66 @@ public void Save(in UInt256 location, in ZeroPaddedSpan value) ClearPadding(_memory, intLocation + value.Span.Length, value.PaddingLength); } + return true; + [MethodImpl(MethodImplOptions.NoInlining)] static void ClearPadding(byte[] memory, int offset, int length) => memory.AsSpan(offset, length).Clear(); } - public Span LoadSpan(scoped in UInt256 location) + public bool TryLoadSpan(scoped in UInt256 location, out Span data) { - CheckMemoryAccessViolation(in location, WordSize, out ulong newLength); - UpdateSize(newLength); + CheckMemoryAccessViolation(in location, WordSize, out ulong newLength, out bool isViolation); + if (isViolation) + { + data = default; + return false; + } - return _memory.AsSpan((int)location, WordSize); + UpdateSize(newLength); + data = _memory.AsSpan((int)location, WordSize); + return true; } - public Span LoadSpan(scoped in UInt256 location, scoped in UInt256 length) + public bool TryLoadSpan(scoped in UInt256 location, scoped in UInt256 length, out Span data) { if (length.IsZero) { - return []; + data = []; + return true; } - CheckMemoryAccessViolation(in location, in length, out ulong newLength); - UpdateSize(newLength); + CheckMemoryAccessViolation(in location, in length, out ulong newLength, out bool isViolation); + if (isViolation) + { + data = default; + return false; + } - return _memory.AsSpan((int)location, (int)length); + UpdateSize(newLength); + data = _memory.AsSpan((int)location, (int)length); + return true; } - public ReadOnlyMemory Load(in UInt256 location, in UInt256 length) + public bool TryLoad(in UInt256 location, in UInt256 length, out ReadOnlyMemory data) { if (length.IsZero) { - return default; + data = default; + return true; } - if (location > int.MaxValue) + CheckMemoryAccessViolation(in location, in length, out ulong newLength, out bool isViolation); + if (isViolation) { - return new byte[(long)length]; + data = default; + return false; } - UpdateSize(in location, in length); + UpdateSize(newLength); - return _memory.AsMemory((int)location, (int)length); + data = _memory.AsMemory((int)location, (int)length); + return true; } public ReadOnlyMemory Inspect(in UInt256 location, in UInt256 length) @@ -273,21 +288,6 @@ public long CalculateMemoryCost(in UInt256 location, in UInt256 length, out bool return 0L; } - public long CalculateMemoryCost(in UInt256 location, in UInt256 length) - { - long result = CalculateMemoryCost(in location, in length, out bool outOfGas); - if (outOfGas) - { - ThrowOutOfGas(); - } - - return result; - - } - - [DoesNotReturn, StackTraceHidden] - private static void ThrowOutOfGas() => throw new OutOfGasException(); - public TraceMemory GetTrace() { ulong size = Size; @@ -305,11 +305,6 @@ public void Dispose() } } - private void UpdateSize(in UInt256 location, in UInt256 length, bool rentIfNeeded = true) - { - UpdateSize((ulong)(location + length), rentIfNeeded); - } - private void UpdateSize(ulong length, bool rentIfNeeded = true) { const int MinRentSize = 1_024; @@ -359,13 +354,6 @@ private static void ThrowArgumentOutOfRangeException() Metrics.EvmExceptions++; throw new ArgumentOutOfRangeException("Word size must be 32 bytes"); } - - [DoesNotReturn, StackTraceHidden] - private static void ThrowOutOfGasException() - { - Metrics.EvmExceptions++; - throw new OutOfGasException(); - } } public static class UInt256Extensions diff --git a/src/Nethermind/Nethermind.Evm/EvmStack.cs b/src/Nethermind/Nethermind.Evm/EvmStack.cs index a86286434ca..6f9aaa10940 100644 --- a/src/Nethermind/Nethermind.Evm/EvmStack.cs +++ b/src/Nethermind/Nethermind.Evm/EvmStack.cs @@ -319,7 +319,7 @@ public void PushUInt256(in UInt256 value) Word data = Unsafe.As(ref Unsafe.AsRef(in value)); head = Avx512Vbmi.VL.PermuteVar32x8(data, shuffle); } - else if (Avx2.IsSupported) + else { Vector256 permute = Unsafe.As>(ref Unsafe.AsRef(in value)); Vector256 convert = Avx2.Permute4x64(permute, 0b_01_00_11_10); diff --git a/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs b/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs index a186862e72f..0f396d82bd4 100644 --- a/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs +++ b/src/Nethermind/Nethermind.Evm/ExecutionEnvironment.cs @@ -2,60 +2,123 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Concurrent; +using System.Diagnostics; using Nethermind.Core; using Nethermind.Evm.CodeAnalysis; using Nethermind.Int256; namespace Nethermind.Evm { - public readonly struct ExecutionEnvironment( - ICodeInfo codeInfo, - Address executingAccount, - Address caller, - Address? codeSource, - int callDepth, - in UInt256 transferValue, - in UInt256 value, - in ReadOnlyMemory inputData) + /// + /// Execution environment for EVM calls. Pooled to avoid allocation and GC write barrier overhead. + /// + public sealed class ExecutionEnvironment : IDisposable { + private static readonly ConcurrentQueue _pool = new(); + private UInt256 _value; + private UInt256 _transferValue; + /// /// Parsed bytecode for the current call. /// - public readonly ICodeInfo CodeInfo = codeInfo; + public ICodeInfo CodeInfo { get; private set; } = null!; /// /// Currently executing account (in DELEGATECALL this will be equal to caller). /// - public readonly Address ExecutingAccount = executingAccount; + public Address ExecutingAccount { get; private set; } = null!; /// /// Caller /// - public readonly Address Caller = caller; + public Address Caller { get; private set; } = null!; /// /// Bytecode source (account address). /// - public readonly Address? CodeSource = codeSource; + public Address? CodeSource { get; private set; } /// If we call TX -> DELEGATECALL -> CALL -> STATICCALL then the call depth would be 3. - public readonly int CallDepth = callDepth; + public int CallDepth { get; private set; } /// /// ETH value transferred in this call. /// - public readonly UInt256 TransferValue = transferValue; + public ref readonly UInt256 TransferValue => ref _transferValue; /// /// Value information passed (it is different from transfer value in DELEGATECALL. - /// DELEGATECALL behaves like a library call and it uses the value information from the caller even + /// DELEGATECALL behaves like a library call, and it uses the value information from the caller even /// as no transfer happens. /// - public readonly UInt256 Value = value; + public ref readonly UInt256 Value => ref _value; /// /// Parameters / arguments of the current call. /// - public readonly ReadOnlyMemory InputData = inputData; + public ReadOnlyMemory InputData { get; private set; } + + private ExecutionEnvironment() { } + + /// + /// Rents an ExecutionEnvironment from the pool and initializes it with the provided values. + /// + public static ExecutionEnvironment Rent( + ICodeInfo codeInfo, + Address executingAccount, + Address caller, + Address? codeSource, + int callDepth, + in UInt256 transferValue, + in UInt256 value, + in ReadOnlyMemory inputData) + { + ExecutionEnvironment env = _pool.TryDequeue(out ExecutionEnvironment pooled) ? pooled : new ExecutionEnvironment(); + env.CodeInfo = codeInfo; + env.ExecutingAccount = executingAccount; + env.Caller = caller; + env.CodeSource = codeSource; + env.CallDepth = callDepth; + env._transferValue = transferValue; + env._value = value; + env.InputData = inputData; + return env; + } + + /// + /// Returns the ExecutionEnvironment to the pool for reuse. + /// + public void Dispose() + { + if (ExecutingAccount is not null) + { + CodeInfo = null!; + ExecutingAccount = null!; + Caller = null!; + CodeSource = null; + CallDepth = 0; + _transferValue = default; + _value = default; + InputData = default; + _pool.Enqueue(this); + } +#if DEBUG + GC.SuppressFinalize(this); +#endif + } + +#if DEBUG + + private readonly StackTrace _creationStackTrace = new(); + + ~ExecutionEnvironment() + { + if (ExecutingAccount is null) + { + Console.Error.WriteLine($"Warning: {nameof(ExecutionEnvironment)} was not disposed. Created at: {_creationStackTrace}"); + } + } +#endif } } diff --git a/src/Nethermind/Nethermind.Evm/ExecutionEnvironmentExtensions.cs b/src/Nethermind/Nethermind.Evm/ExecutionEnvironmentExtensions.cs index 03f48b11ffb..68c6e5f97f2 100644 --- a/src/Nethermind/Nethermind.Evm/ExecutionEnvironmentExtensions.cs +++ b/src/Nethermind/Nethermind.Evm/ExecutionEnvironmentExtensions.cs @@ -5,5 +5,5 @@ namespace Nethermind.Evm; public static class ExecutionEnvironmentExtensions { - public static int GetGethTraceDepth(in this ExecutionEnvironment env) => env.CallDepth + 1; + public static int GetGethTraceDepth(this ExecutionEnvironment env) => env.CallDepth + 1; } diff --git a/src/Nethermind/Nethermind.Evm/GasPolicy/EthereumGasPolicy.cs b/src/Nethermind/Nethermind.Evm/GasPolicy/EthereumGasPolicy.cs new file mode 100644 index 00000000000..fddd0f0bd54 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/GasPolicy/EthereumGasPolicy.cs @@ -0,0 +1,234 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Runtime.CompilerServices; +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Core.Specs; +using Nethermind.Int256; + +namespace Nethermind.Evm.GasPolicy; + +/// +/// Standard Ethereum single-dimensional gas.Value policy. +/// +public struct EthereumGasPolicy : IGasPolicy +{ + public long Value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EthereumGasPolicy FromLong(long value) => new() { Value = value }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long GetRemainingGas(in EthereumGasPolicy gas) => gas.Value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Consume(ref EthereumGasPolicy gas, long cost) => + gas.Value -= cost; + + public static void ConsumeSelfDestructGas(ref EthereumGasPolicy gas) + => Consume(ref gas, GasCostOf.SelfDestructEip150); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Refund(ref EthereumGasPolicy gas, in EthereumGasPolicy childGas) => + gas.Value += childGas.Value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SetOutOfGas(ref EthereumGasPolicy gas) => gas.Value = 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ConsumeAccountAccessGasWithDelegation(ref EthereumGasPolicy gas, + IReleaseSpec spec, + ref readonly StackAccessTracker accessTracker, + bool isTracingAccess, + Address address, + Address? delegated, + bool chargeForWarm = true) + { + if (!spec.UseHotAndColdStorage) + return true; + + bool notOutOfGas = ConsumeAccountAccessGas(ref gas, spec, in accessTracker, isTracingAccess, address, chargeForWarm); + return notOutOfGas + && (delegated is null + || ConsumeAccountAccessGas(ref gas, spec, in accessTracker, isTracingAccess, delegated, chargeForWarm)); + } + + public static bool ConsumeAccountAccessGas(ref EthereumGasPolicy gas, + IReleaseSpec spec, + ref readonly StackAccessTracker accessTracker, + bool isTracingAccess, + Address address, + bool chargeForWarm = true) + { + bool result = true; + if (spec.UseHotAndColdStorage) + { + if (isTracingAccess) + { + // Ensure that tracing simulates access-list behavior. + accessTracker.WarmUp(address); + } + + // If the account is cold (and not a precompile), charge the cold access cost. + if (!spec.IsPrecompile(address) && accessTracker.WarmUp(address)) + { + result = UpdateGas(ref gas, GasCostOf.ColdAccountAccess); + } + else if (chargeForWarm) + { + // Otherwise, if warm access should be charged, apply the warm read cost. + result = UpdateGas(ref gas, GasCostOf.WarmStateRead); + } + } + + return result; + } + + public static bool ConsumeStorageAccessGas(ref EthereumGasPolicy gas, + ref readonly StackAccessTracker accessTracker, + bool isTracingAccess, + in StorageCell storageCell, + StorageAccessType storageAccessType, + IReleaseSpec spec) + { + // If the spec requires hot/cold storage tracking, determine if extra gas should be charged. + if (!spec.UseHotAndColdStorage) + return true; + // When tracing access, ensure the storage cell is marked as warm to simulate inclusion in the access list. + if (isTracingAccess) + { + accessTracker.WarmUp(in storageCell); + } + + // If the storage cell is still cold, apply the higher cold access cost and mark it as warm. + if (accessTracker.WarmUp(in storageCell)) + return UpdateGas(ref gas, GasCostOf.ColdSLoad); + // For SLOAD operations on already warmed-up storage, apply a lower warm-read cost. + if (storageAccessType == StorageAccessType.SLOAD) + return UpdateGas(ref gas, GasCostOf.WarmStateRead); + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool UpdateMemoryCost(ref EthereumGasPolicy gas, + in UInt256 position, + in UInt256 length, VmState vmState) + { + long memoryCost = vmState.Memory.CalculateMemoryCost(in position, length, out bool outOfGas); + if (memoryCost == 0L) + return !outOfGas; + return UpdateGas(ref gas, memoryCost); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool UpdateGas(ref EthereumGasPolicy gas, + long gasCost) + { + if (GetRemainingGas(gas) < gasCost) + return false; + + Consume(ref gas, gasCost); + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void UpdateGasUp(ref EthereumGasPolicy gas, + long refund) + { + gas.Value += refund; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ConsumeStorageWrite(ref EthereumGasPolicy gas, bool isSlotCreation, IReleaseSpec spec) + { + long cost = isSlotCreation ? GasCostOf.SSet : spec.GetSStoreResetCost(); + return UpdateGas(ref gas, cost); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ConsumeCallValueTransfer(ref EthereumGasPolicy gas) + => UpdateGas(ref gas, GasCostOf.CallValue); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ConsumeNewAccountCreation(ref EthereumGasPolicy gas) + => UpdateGas(ref gas, GasCostOf.NewAccount); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool ConsumeLogEmission(ref EthereumGasPolicy gas, long topicCount, long dataSize) + { + long cost = GasCostOf.Log + topicCount * GasCostOf.LogTopic + dataSize * GasCostOf.LogData; + return UpdateGas(ref gas, cost); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ConsumeDataCopyGas(ref EthereumGasPolicy gas, bool isExternalCode, long baseCost, long dataCost) + => Consume(ref gas, baseCost + dataCost); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void OnBeforeInstructionTrace(in EthereumGasPolicy gas, int pc, Instruction instruction, int depth) { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void OnAfterInstructionTrace(in EthereumGasPolicy gas) { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EthereumGasPolicy Max(in EthereumGasPolicy a, in EthereumGasPolicy b) => + a.Value >= b.Value ? a : b; + + public static EthereumGasPolicy CalculateIntrinsicGas(Transaction tx, IReleaseSpec spec) + { + long gas = GasCostOf.Transaction + + DataCost(tx, spec) + + CreateCost(tx, spec) + + IntrinsicGasCalculator.AccessListCost(tx, spec) + + AuthorizationListCost(tx, spec); + return new() { Value = gas }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static EthereumGasPolicy CreateAvailableFromIntrinsic(long gasLimit, in EthereumGasPolicy intrinsicGas) + => new() { Value = gasLimit - intrinsicGas.Value }; + + private static long CreateCost(Transaction tx, IReleaseSpec spec) => + tx.IsContractCreation && spec.IsEip2Enabled ? GasCostOf.TxCreate : 0; + + private static long DataCost(Transaction tx, IReleaseSpec spec) + { + long baseDataCost = tx.IsContractCreation && spec.IsEip3860Enabled + ? EvmCalculations.Div32Ceiling((UInt256)tx.Data.Length) * GasCostOf.InitCodeWord + : 0; + + long tokensInCallData = CalculateTokensInCallData(tx, spec); + return baseDataCost + tokensInCallData * GasCostOf.TxDataZero; + } + + private static long CalculateTokensInCallData(Transaction tx, IReleaseSpec spec) + { + long txDataNonZeroMultiplier = spec.IsEip2028Enabled + ? GasCostOf.TxDataNonZeroMultiplierEip2028 + : GasCostOf.TxDataNonZeroMultiplier; + ReadOnlySpan data = tx.Data.Span; + int totalZeros = data.CountZeros(); + return totalZeros + (data.Length - totalZeros) * txDataNonZeroMultiplier; + } + + private static long AuthorizationListCost(Transaction tx, IReleaseSpec spec) + { + AuthorizationTuple[]? authList = tx.AuthorizationList; + if (authList is not null) + { + if (!spec.IsAuthorizationListEnabled) + ThrowAuthorizationListNotEnabled(spec); + return authList.Length * GasCostOf.NewAccount; + } + return 0; + } + + [DoesNotReturn, StackTraceHidden] + private static void ThrowAuthorizationListNotEnabled(IReleaseSpec spec) => + throw new InvalidDataException($"Transaction with an authorization list received within the context of {spec.Name}. EIP-7702 is not enabled."); +} diff --git a/src/Nethermind/Nethermind.Evm/GasPolicy/IGasPolicy.cs b/src/Nethermind/Nethermind.Evm/GasPolicy/IGasPolicy.cs new file mode 100644 index 00000000000..c1a1801d7d9 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/GasPolicy/IGasPolicy.cs @@ -0,0 +1,235 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Int256; + +namespace Nethermind.Evm.GasPolicy; + +/// +/// Defines a gas policy for EVM execution. +/// +/// The implementing type +public interface IGasPolicy where TSelf : struct, IGasPolicy +{ + /// + /// Creates a new gas instance from a long value. + /// This is primarily used for warmup/testing scenarios. + /// Main execution flow should pass TGasPolicy directly through EvmState. + /// + /// The initial gas value + /// A new gas instance + static abstract TSelf FromLong(long value); + + /// + /// Get the remaining single-dimensional gas available for execution. + /// This is what's checked against zero to detect out-of-gas conditions. + /// + /// The gas state to query. + /// Remaining gas (negative values indicate out-of-gas) + static abstract long GetRemainingGas(in TSelf gas); + + /// + /// Consume gas for an EVM operation. + /// + /// The gas state to update. + /// The gas cost to consume. + static abstract void Consume(ref TSelf gas, long cost); + + /// + /// Consume gas for SelfDestruct operation. + /// + /// The gas state to update. + static abstract void ConsumeSelfDestructGas(ref TSelf gas); + + /// + /// Refund gas from a child call frame. + /// Merges the child gas state back into the parent, preserving any tracking data. + /// + /// The parent gas state to refund into. + /// The child gas state to merge from. + static abstract void Refund(ref TSelf gas, in TSelf childGas); + + /// + /// Mark the gas state as out of gas. + /// Called when execution exhausts all gas. + /// + /// The gas state to update. + static abstract void SetOutOfGas(ref TSelf gasState); + + /// + /// Charges gas for accessing an account, including potential delegation lookups. + /// This method ensures that both the requested account and its delegated account (if any) are properly charged. + /// + /// The gas state to update. + /// The release specification governing gas costs. + /// The access tracker for cold/warm state. + /// Whether access tracing is enabled. + /// The target account address. + /// The delegated account address, if any. + /// If true, charge even if the account is already warm. + /// True if gas was successfully charged; otherwise false. + static abstract bool ConsumeAccountAccessGasWithDelegation(ref TSelf gas, + IReleaseSpec spec, + ref readonly StackAccessTracker accessTracker, + bool isTracingAccess, + Address address, + Address? delegated, + bool chargeForWarm = true); + + /// + /// Charges gas for accessing an account based on its storage state (cold vs. warm). + /// Precompiles are treated as exceptions to the cold/warm gas charge. + /// + /// The gas state to update. + /// The release specification governing gas costs. + /// The access tracker for cold/warm state. + /// Whether access tracing is enabled. + /// The target account address. + /// If true, applies the warm read gas cost even if the account is warm. + /// True if the gas charge was successful; otherwise false. + static abstract bool ConsumeAccountAccessGas(ref TSelf gas, + IReleaseSpec spec, + ref readonly StackAccessTracker accessTracker, + bool isTracingAccess, + Address address, + bool chargeForWarm = true); + + /// + /// Charges the appropriate gas cost for accessing a storage cell, taking into account whether the access is cold or warm. + /// + /// For cold storage accesses (or if not previously warmed up), a higher gas cost is applied. For warm access during SLOAD, + /// a lower cost is deducted. + /// + /// + /// The gas state to update. + /// The access tracker for cold/warm state. + /// Whether access tracing is enabled. + /// The target storage cell being accessed. + /// Indicates whether the access is for a load (SLOAD) or store (SSTORE) operation. + /// The release specification which governs gas metering and storage access rules. + /// true if the gas charge was successfully applied; otherwise, false indicating an out-of-gas condition. + static abstract bool ConsumeStorageAccessGas(ref TSelf gas, + ref readonly StackAccessTracker accessTracker, + bool isTracingAccess, + in StorageCell storageCell, + StorageAccessType storageAccessType, + IReleaseSpec spec); + + /// + /// Calculates and deducts the gas cost for accessing a specific memory region. + /// + /// The gas state to update. + /// The starting position in memory. + /// The length of the memory region. + /// The current EVM state. + /// true if sufficient gas was available and deducted; otherwise, false. + static abstract bool UpdateMemoryCost(ref TSelf gas, + in UInt256 position, + in UInt256 length, VmState vmState); + + /// + /// Deducts a specified gas cost from the available gas. + /// + /// The gas state to update. + /// The gas cost to deduct. + /// true if there was enough gas; otherwise, false. + static abstract bool UpdateGas(ref TSelf gas, long gasCost); + + /// + /// Refunds gas by adding the specified amount back to the available gas. + /// + /// The gas state to update. + /// The gas amount to refund. + static abstract void UpdateGasUp(ref TSelf gas, long refund); + + /// + /// Charges gas for SSTORE write operation (after cold/warm access cost). + /// Cost is calculated internally based on whether it's a slot creation or update. + /// + /// The gas state to update. + /// True if creating a new slot (original was zero). + /// The release specification for determining reset cost. + /// True if sufficient gas available + static abstract bool ConsumeStorageWrite(ref TSelf gas, bool isSlotCreation, IReleaseSpec spec); + + /// + /// Charges gas for CALL value transfer. + /// + /// The gas state to update. + /// True if sufficient gas available + static abstract bool ConsumeCallValueTransfer(ref TSelf gas); + + /// + /// Charges gas for new account creation (25000 gas). + /// + /// The gas state to update. + /// True if sufficient gas available + static abstract bool ConsumeNewAccountCreation(ref TSelf gas); + + /// + /// Charges gas for LOG emission with topic and data costs. + /// Cost is calculated internally: GasCostOf.Log + topicCount * GasCostOf.LogTopic + dataSize * GasCostOf.LogData + /// + /// The gas state to update. + /// Number of topics. + /// Size of log data in bytes. + /// True if sufficient gas available + static abstract bool ConsumeLogEmission(ref TSelf gas, long topicCount, long dataSize); + + /// + /// Returns the maximum of two gas values. + /// Used for MinimalGas calculation in IntrinsicGas. + /// + /// First gas value. + /// Second gas value. + /// The gas value with greater remaining gas. + static abstract TSelf Max(in TSelf a, in TSelf b); + + /// + /// Calculates intrinsic gas for a transaction. + /// Returns TGasPolicy allowing implementations to track gas breakdown by category. + /// + /// The transaction to calculate intrinsic gas for. + /// The release specification governing gas costs. + /// The intrinsic gas as TGasPolicy. + static abstract TSelf CalculateIntrinsicGas(Transaction tx, IReleaseSpec spec); + + /// + /// Creates available gas from gas limit minus intrinsic gas, preserving any tracking data. + /// For simple implementations, this is a subtraction. For multi-dimensional gas tracking, + /// this preserves the breakdown categories from intrinsic gas. + /// + /// The transaction gas limit. + /// The intrinsic gas to subtract. + /// Available gas with preserved tracking data. + static abstract TSelf CreateAvailableFromIntrinsic(long gasLimit, in TSelf intrinsicGas); + + /// + /// Consumes gas for code copy operations (CODECOPY, CALLDATACOPY, EXTCODECOPY, etc.). + /// Allows policies to categorize external code copy differently (state trie access). + /// + /// The gas state to update. + /// True for EXTCODECOPY (external account code). + /// Fixed opcode cost. + /// Per-word copy cost. + static abstract void ConsumeDataCopyGas(ref TSelf gas, bool isExternalCode, long baseCost, long dataCost); + + /// + /// Hook called before instruction execution when tracing is active. + /// Allows gas policies to capture pre-execution state. + /// + /// The current gas state. + /// The program counter before incrementing. + /// The instruction about to be executed. + /// The current call depth. + static abstract void OnBeforeInstructionTrace(in TSelf gas, int pc, Instruction instruction, int depth); + + /// + /// Hook called after instruction execution when tracing is active. + /// Allows gas policies to capture post-execution state. + /// + /// The current gas state after execution. + static abstract void OnAfterInstructionTrace(in TSelf gas); +} diff --git a/src/Nethermind/Nethermind.Evm/ICodeInfo.cs b/src/Nethermind/Nethermind.Evm/ICodeInfo.cs index 40909e8abdd..07156260530 100644 --- a/src/Nethermind/Nethermind.Evm/ICodeInfo.cs +++ b/src/Nethermind/Nethermind.Evm/ICodeInfo.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using Nethermind.Evm.Precompiles; namespace Nethermind.Evm.CodeAnalysis; @@ -34,6 +35,7 @@ public interface ICodeInfo /// By default, this returns false. /// bool IsPrecompile => false; + IPrecompile? Precompile => null; /// /// Gets the code section. diff --git a/src/Nethermind/Nethermind.Evm/IEvmMemory.cs b/src/Nethermind/Nethermind.Evm/IEvmMemory.cs index 4cd68736f31..5f443910984 100644 --- a/src/Nethermind/Nethermind.Evm/IEvmMemory.cs +++ b/src/Nethermind/Nethermind.Evm/IEvmMemory.cs @@ -10,13 +10,13 @@ namespace Nethermind.Evm; public interface IEvmMemory : IDisposable { ulong Size { get; } - void SaveWord(in UInt256 location, Span word); - void SaveByte(in UInt256 location, byte value); - void Save(in UInt256 location, Span value); - void Save(in UInt256 location, byte[] value); - Span LoadSpan(in UInt256 location); - Span LoadSpan(in UInt256 location, in UInt256 length); - ReadOnlyMemory Load(in UInt256 location, in UInt256 length); - long CalculateMemoryCost(in UInt256 location, in UInt256 length); + bool TrySaveWord(in UInt256 location, Span word); + bool TrySaveByte(in UInt256 location, byte value); + bool TrySave(in UInt256 location, Span value); + bool TrySave(in UInt256 location, byte[] value); + bool TryLoadSpan(in UInt256 location, out Span data); + bool TryLoadSpan(in UInt256 location, in UInt256 length, out Span data); + bool TryLoad(in UInt256 location, in UInt256 length, out ReadOnlyMemory data); + long CalculateMemoryCost(in UInt256 location, in UInt256 length, out bool outOfGas); TraceMemory GetTrace(); } diff --git a/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs b/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs index f2bc60da5c8..c13306a47fe 100644 --- a/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/IVirtualMachine.cs @@ -1,21 +1,28 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using Nethermind.Core; +using Nethermind.Evm.GasPolicy; using Nethermind.Evm.State; using Nethermind.Evm.Tracing; -namespace Nethermind.Evm +namespace Nethermind.Evm; + +public interface IVirtualMachine + where TGasPolicy : struct, IGasPolicy +{ + TransactionSubstate ExecuteTransaction(VmState state, IWorldState worldState, ITxTracer txTracer) + where TTracingInst : struct, IFlag; + ref readonly BlockExecutionContext BlockExecutionContext { get; } + ref readonly TxExecutionContext TxExecutionContext { get; } + void SetBlockExecutionContext(in BlockExecutionContext blockExecutionContext); + void SetTxExecutionContext(in TxExecutionContext txExecutionContext); + int OpCodeCount { get; } +} + +/// +/// Non-generic IVirtualMachine for backward compatibility with EthereumGasPolicy. +/// +public interface IVirtualMachine : IVirtualMachine { - public interface IVirtualMachine - { - TransactionSubstate ExecuteTransaction(EvmState state, IWorldState worldState, ITxTracer txTracer) - where TTracingInst : struct, IFlag; - ref readonly BlockExecutionContext BlockExecutionContext { get; } - ref readonly TxExecutionContext TxExecutionContext { get; } - void SetBlockExecutionContext(in BlockExecutionContext blockExecutionContext); - void SetTxExecutionContext(in TxExecutionContext txExecutionContext); - int OpCodeCount { get; } - } } diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmCalculations.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmCalculations.cs index 79de1c3389f..3f52dd37c43 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmCalculations.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmCalculations.cs @@ -4,176 +4,15 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using Nethermind.Core; -using Nethermind.Core.Specs; using Nethermind.Int256; namespace Nethermind.Evm; +/// +/// Utility calculations for EVM operations. +/// public static class EvmCalculations { - /// - /// Charges gas for accessing an account, including potential delegation lookups. - /// This method ensures that both the requested account and its delegated account (if any) are properly charged. - /// - /// Reference to the available gas which will be updated. - /// The virtual machine instance. - /// The target account address. - /// If true, charge even if the account is already warm. - /// True if gas was successfully charged; otherwise false. - public static bool ChargeAccountAccessGasWithDelegation(ref long gasAvailable, VirtualMachine vm, Address address, bool chargeForWarm = true) - { - IReleaseSpec spec = vm.Spec; - if (!spec.UseHotAndColdStorage) - { - // No extra cost if hot/cold storage is not used. - return true; - } - bool notOutOfGas = ChargeAccountAccessGas(ref gasAvailable, vm, address, chargeForWarm); - return notOutOfGas - && (!vm.TxExecutionContext.CodeInfoRepository.TryGetDelegation(address, spec, out Address delegated) - // Charge additional gas for the delegated account if it exists. - || ChargeAccountAccessGas(ref gasAvailable, vm, delegated, chargeForWarm)); - } - - /// - /// Charges gas for accessing an account based on its storage state (cold vs. warm). - /// Precompiles are treated as exceptions to the cold/warm gas charge. - /// - /// Reference to the available gas which will be updated. - /// The virtual machine instance. - /// The target account address. - /// If true, applies the warm read gas cost even if the account is warm. - /// True if the gas charge was successful; otherwise false. - public static bool ChargeAccountAccessGas(ref long gasAvailable, VirtualMachine vm, Address address, bool chargeForWarm = true) - { - bool result = true; - IReleaseSpec spec = vm.Spec; - if (spec.UseHotAndColdStorage) - { - EvmState vmState = vm.EvmState; - if (vm.TxTracer.IsTracingAccess) - { - // Ensure that tracing simulates access-list behavior. - vmState.AccessTracker.WarmUp(address); - } - - // If the account is cold (and not a precompile), charge the cold access cost. - if (!spec.IsPrecompile(address) && vmState.AccessTracker.WarmUp(address)) - { - result = UpdateGas(GasCostOf.ColdAccountAccess, ref gasAvailable); - } - else if (chargeForWarm) - { - // Otherwise, if warm access should be charged, apply the warm read cost. - result = UpdateGas(GasCostOf.WarmStateRead, ref gasAvailable); - } - } - - return result; - } - - /// - /// Charges the appropriate gas cost for accessing a storage cell, taking into account whether the access is cold or warm. - /// - /// For cold storage accesses (or if not previously warmed up), a higher gas cost is applied. For warm accesses during SLOAD, - /// a lower cost is deducted. - /// - /// - /// The remaining gas, passed by reference and reduced by the access cost. - /// The virtual machine instance. - /// The target storage cell being accessed. - /// Indicates whether the access is for a load (SLOAD) or store (SSTORE) operation. - /// The release specification which governs gas metering and storage access rules. - /// true if the gas charge was successfully applied; otherwise, false indicating an out-of-gas condition. - public static bool ChargeStorageAccessGas( - ref long gasAvailable, - VirtualMachine vm, - in StorageCell storageCell, - StorageAccessType storageAccessType, - IReleaseSpec spec) - { - EvmState vmState = vm.EvmState; - bool result = true; - - // If the spec requires hot/cold storage tracking, determine if extra gas should be charged. - if (spec.UseHotAndColdStorage) - { - // When tracing access, ensure the storage cell is marked as warm to simulate inclusion in the access list. - ref readonly StackAccessTracker accessTracker = ref vmState.AccessTracker; - if (vm.TxTracer.IsTracingAccess) - { - accessTracker.WarmUp(in storageCell); - } - - // If the storage cell is still cold, apply the higher cold access cost and mark it as warm. - if (accessTracker.WarmUp(in storageCell)) - { - result = UpdateGas(GasCostOf.ColdSLoad, ref gasAvailable); - } - // For SLOAD operations on already warmed-up storage, apply a lower warm-read cost. - else if (storageAccessType == StorageAccessType.SLOAD) - { - result = UpdateGas(GasCostOf.WarmStateRead, ref gasAvailable); - } - } - - return result; - } - - /// - /// Calculates and deducts the gas cost for accessing a specific memory region. - /// - /// The current EVM state. - /// The remaining gas available. - /// The starting position in memory. - /// The length of the memory region. - /// true if sufficient gas was available and deducted; otherwise, false. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool UpdateMemoryCost(EvmState vmState, ref long gasAvailable, in UInt256 position, in UInt256 length) - { - // Calculate additional gas cost for any memory expansion. - long memoryCost = vmState.Memory.CalculateMemoryCost(in position, length); - if (memoryCost != 0L) - { - if (!UpdateGas(memoryCost, ref gasAvailable)) - { - return false; - } - } - - return true; - } - - /// - /// Deducts a specified gas cost from the available gas. - /// - /// The gas cost to deduct. - /// The remaining gas available. - /// true if there was sufficient gas; otherwise, false. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool UpdateGas(long gasCost, ref long gasAvailable) - { - if (gasAvailable < gasCost) - { - return false; - } - - gasAvailable -= gasCost; - return true; - } - - /// - /// Refunds gas by adding the specified amount back to the available gas. - /// - /// The gas amount to refund. - /// The current gas available. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void UpdateGasUp(long refund, ref long gasAvailable) - { - gasAvailable += refund; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long Div32Ceiling(in UInt256 length, out bool outOfGas) { diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Bitwise.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Bitwise.cs index aaeec10e0bf..7bffb4e634a 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Bitwise.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Bitwise.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using System.Runtime.Intrinsics; +using Nethermind.Evm.GasPolicy; using static System.Runtime.CompilerServices.Unsafe; namespace Nethermind.Evm; @@ -34,18 +35,20 @@ public interface IOpBitwise /// Executes a bitwise operation defined by on the top two stack elements. /// This method reads the operands as 256-bit vectors from unaligned memory and writes the result back directly. /// + /// The gas policy used for gas accounting. /// The specific bitwise operation to execute. /// An unused virtual machine instance parameter. /// The EVM stack from which operands are retrieved and where the result is stored. - /// The remaining gas, reduced by the operation’s cost. + /// The gas which is updated by the operation's cost. /// The program counter (unused in this operation). /// An indicating success or a stack underflow error. [SkipLocalsInit] - public static EvmExceptionType InstructionBitwise(VirtualMachine _, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionBitwise(VirtualMachine _, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TOpBitwise : struct, IOpBitwise { // Deduct the operation's gas cost. - gasAvailable -= TOpBitwise.GasCost; + TGasPolicy.Consume(ref gas, TOpBitwise.GasCost); // Pop the first operand from the stack by reference to minimize copying. ref byte bytesRef = ref stack.PopBytesByRef(); diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs index d23ccb0eef3..27a82b9f5d4 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Call.cs @@ -6,10 +6,10 @@ using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Evm.CodeAnalysis; +using Nethermind.Evm.GasPolicy; using Nethermind.Int256; using Nethermind.Evm.State; - -using static Nethermind.Evm.VirtualMachine; +using static Nethermind.Evm.VirtualMachineStatics; namespace Nethermind.Evm; @@ -83,17 +83,17 @@ public struct OpStaticCall : IOpCall /// /// The current virtual machine instance containing execution state. /// The EVM stack for retrieving call parameters and pushing results. - /// Reference to the available gas, which is deducted according to various call costs. + /// The gas which is updated by the operation's cost. /// Reference to the current program counter (not modified by this method). /// /// An value indicating success or the type of error encountered. /// [SkipLocalsInit] - public static EvmExceptionType InstructionCall( - VirtualMachine vm, + public static EvmExceptionType InstructionCall(VirtualMachine vm, ref EvmStack stack, - ref long gasAvailable, + ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TOpCall : struct, IOpCall where TTracingInst : struct, IFlag { @@ -109,7 +109,7 @@ public static EvmExceptionType InstructionCall( Address codeSource = stack.PopAddress(); if (codeSource is null) goto StackUnderflow; - ref readonly ExecutionEnvironment env = ref vm.EvmState.Env; + ExecutionEnvironment env = vm.VmState.Env; // Determine the call value based on the call type. UInt256 callValue; if (typeof(TOpCall) == typeof(OpStaticCall)) @@ -136,10 +136,16 @@ public static EvmExceptionType InstructionCall( goto StackUnderflow; } + // Charge gas for accessing the account's code (including delegation logic if applicable). + bool _ = vm.TxExecutionContext.CodeInfoRepository + .TryGetDelegation(codeSource, vm.Spec, out Address delegated); + if (!TGasPolicy.ConsumeAccountAccessGasWithDelegation(ref gas, vm.Spec, in vm.VmState.AccessTracker, + vm.TxTracer.IsTracingAccess, codeSource, delegated)) goto OutOfGas; + // For non-delegate calls, the transfer value is the call value. UInt256 transferValue = typeof(TOpCall) == typeof(OpDelegateCall) ? UInt256.Zero : callValue; // Enforce static call restrictions: no value transfer allowed unless it's a CALLCODE. - if (vm.EvmState.IsStatic && !transferValue.IsZero && typeof(TOpCall) != typeof(OpCallCode)) + if (vm.VmState.IsStatic && !transferValue.IsZero && typeof(TOpCall) != typeof(OpCallCode)) return EvmExceptionType.StaticCallViolation; // Determine caller and target based on the call type. @@ -148,43 +154,30 @@ public static EvmExceptionType InstructionCall( ? codeSource : env.ExecutingAccount; - long gasExtra = 0L; - // Add extra gas cost if value is transferred. if (!transferValue.IsZero) { - gasExtra += GasCostOf.CallValue; + if (!TGasPolicy.ConsumeCallValueTransfer(ref gas)) goto OutOfGas; } IReleaseSpec spec = vm.Spec; - // Update gas: call cost, memory expansion for input and output, and extra gas. - // Charge gas for accessing the account's code (including delegation logic if applicable). - if (!EvmCalculations.UpdateGas(spec.GetCallCost(), ref gasAvailable) || - !EvmCalculations.UpdateMemoryCost(vm.EvmState, ref gasAvailable, in dataOffset, dataLength) || - !EvmCalculations.UpdateMemoryCost(vm.EvmState, ref gasAvailable, in outputOffset, outputLength) || - !EvmCalculations.UpdateGas(gasExtra, ref gasAvailable) || - !EvmCalculations.ChargeAccountAccessGasWithDelegation(ref gasAvailable, vm, codeSource)) - { - goto OutOfGas; - } - - gasExtra = 0L; IWorldState state = vm.WorldState; // Charge additional gas if the target account is new or considered empty. if (!spec.ClearEmptyAccountWhenTouched && !state.AccountExists(target)) { - gasExtra += GasCostOf.NewAccount; + if (!TGasPolicy.ConsumeNewAccountCreation(ref gas)) goto OutOfGas; } else if (spec.ClearEmptyAccountWhenTouched && transferValue != 0 && state.IsDeadAccount(target)) { - gasExtra += GasCostOf.NewAccount; + if (!TGasPolicy.ConsumeNewAccountCreation(ref gas)) goto OutOfGas; } - if (!EvmCalculations.UpdateGas(gasExtra, ref gasAvailable)) - { + // Update gas: call cost and memory expansion for input and output. + if (!TGasPolicy.UpdateGas(ref gas, spec.GetCallCost()) || + !TGasPolicy.UpdateMemoryCost(ref gas, in dataOffset, dataLength, vm.VmState) || + !TGasPolicy.UpdateMemoryCost(ref gas, in outputOffset, outputLength, vm.VmState)) goto OutOfGas; - } // Retrieve code information for the call and schedule background analysis if needed. ICodeInfo codeInfo = vm.CodeInfoRepository.GetCachedCodeInfo(codeSource, spec); @@ -193,9 +186,13 @@ public static EvmExceptionType InstructionCall( if (spec.IsEip7907Enabled) { uint excessContractSize = (uint)Math.Max(0, codeInfo.CodeSpan.Length - CodeSizeConstants.MaxCodeSizeEip170); - if (excessContractSize > 0 && !ChargeForLargeContractAccess(excessContractSize, codeSource, in vm.EvmState.AccessTracker, ref gasAvailable)) + if (excessContractSize > 0 && !ChargeForLargeContractAccess(excessContractSize, codeSource, in vm.VmState.AccessTracker, ref gas)) goto OutOfGas; } + + // Get remaining gas for 63/64 calculation + long gasAvailable = TGasPolicy.GetRemainingGas(in gas); + // Apply the 63/64 gas rule if enabled. if (spec.Use63Over64Rule) { @@ -206,7 +203,7 @@ public static EvmExceptionType InstructionCall( if (gasLimit >= long.MaxValue) goto OutOfGas; long gasLimitUl = (long)gasLimit; - if (!EvmCalculations.UpdateGas(gasLimitUl, ref gasAvailable)) goto OutOfGas; + if (!TGasPolicy.UpdateGas(ref gas, gasLimitUl)) goto OutOfGas; // Add call stipend if value is being transferred. if (!transferValue.IsZero) @@ -228,21 +225,21 @@ public static EvmExceptionType InstructionCall( if (vm.TxTracer.IsTracingRefunds) { // Specific to Parity tracing: inspect 32 bytes from data offset. - ReadOnlyMemory? memoryTrace = vm.EvmState.Memory.Inspect(in dataOffset, 32); + ReadOnlyMemory? memoryTrace = vm.VmState.Memory.Inspect(in dataOffset, 32); vm.TxTracer.ReportMemoryChange(dataOffset, memoryTrace is null ? default : memoryTrace.Value.Span); } if (TTracingInst.IsActive) { - vm.TxTracer.ReportOperationRemainingGas(gasAvailable); + vm.TxTracer.ReportOperationRemainingGas(TGasPolicy.GetRemainingGas(in gas)); vm.TxTracer.ReportOperationError(EvmExceptionType.NotEnoughBalance); } // Refund the remaining gas to the caller. - EvmCalculations.UpdateGasUp(gasLimitUl, ref gasAvailable); + TGasPolicy.UpdateGasUp(ref gas, gasLimitUl); if (TTracingInst.IsActive) { - vm.TxTracer.ReportGasUpdateForVmTrace(gasLimitUl, gasAvailable); + vm.TxTracer.ReportGasUpdateForVmTrace(gasLimitUl, TGasPolicy.GetRemainingGas(in gas)); } return EvmExceptionType.None; } @@ -257,14 +254,15 @@ public static EvmExceptionType InstructionCall( { vm.ReturnDataBuffer = default; stack.PushBytes(StatusCode.SuccessBytes.Span); - EvmCalculations.UpdateGasUp(gasLimitUl, ref gasAvailable); + TGasPolicy.UpdateGasUp(ref gas, gasLimitUl); return FastCall(vm, spec, in transferValue, target); } // Load call data from memory. - ReadOnlyMemory callData = vm.EvmState.Memory.Load(in dataOffset, dataLength); + if (!vm.VmState.Memory.TryLoad(in dataOffset, dataLength, out ReadOnlyMemory callData)) + goto OutOfGas; // Construct the execution environment for the call. - ExecutionEnvironment callEnv = new( + ExecutionEnvironment callEnv = ExecutionEnvironment.Rent( codeInfo: codeInfo, executingAccount: target, caller: caller, @@ -282,22 +280,22 @@ public static EvmExceptionType InstructionCall( } // Rent a new call frame for executing the call. - vm.ReturnData = EvmState.RentFrame( - gasAvailable: gasLimitUl, + vm.ReturnData = VmState.RentFrame( + gas: TGasPolicy.FromLong(gasLimitUl), outputDestination: outputOffset.ToLong(), outputLength: outputLength.ToLong(), executionType: TOpCall.ExecutionType, - isStatic: TOpCall.IsStatic || vm.EvmState.IsStatic, + isStatic: TOpCall.IsStatic || vm.VmState.IsStatic, isCreateOnPreExistingAccount: false, - env: in callEnv, - stateForAccessLists: in vm.EvmState.AccessTracker, + env: callEnv, + stateForAccessLists: in vm.VmState.AccessTracker, snapshot: in snapshot); return EvmExceptionType.None; // Fast-call path for non-contract calls: // Directly credit the target account and avoid constructing a full call frame. - static EvmExceptionType FastCall(VirtualMachine vm, IReleaseSpec spec, in UInt256 transferValue, Address target) + static EvmExceptionType FastCall(VirtualMachine vm, IReleaseSpec spec, in UInt256 transferValue, Address target) { IWorldState state = vm.WorldState; state.AddToBalanceAndCreateIfNotExists(target, transferValue, spec); @@ -314,12 +312,13 @@ static EvmExceptionType FastCall(VirtualMachine vm, IReleaseSpec spec, in UInt25 return EvmExceptionType.OutOfGas; } - private static bool ChargeForLargeContractAccess(uint excessContractSize, Address codeAddress, in StackAccessTracker accessTracer, ref long gasAvailable) + private static bool ChargeForLargeContractAccess(uint excessContractSize, Address codeAddress, in StackAccessTracker accessTracer, ref TGasPolicy gas) + where TGasPolicy : struct, IGasPolicy { if (accessTracer.WarmUpLargeContract(codeAddress)) { long largeContractCost = GasCostOf.InitCodeWord * EvmCalculations.Div32Ceiling(excessContractSize, out bool outOfGas); - if (outOfGas || !EvmCalculations.UpdateGas(largeContractCost, ref gasAvailable)) return false; + if (outOfGas || !TGasPolicy.UpdateGas(ref gas, largeContractCost)) return false; } return true; @@ -332,21 +331,21 @@ private static bool ChargeForLargeContractAccess(uint excessContractSize, Addres /// /// The current virtual machine instance. /// The EVM stack from which the offset and length are popped. - /// Reference to the available gas, adjusted by memory expansion cost. + /// The gas which is updated by the operation's cost. /// Reference to the program counter (unused in this operation). /// /// on success; otherwise, an error such as , /// , or . /// [SkipLocalsInit] - public static EvmExceptionType InstructionReturn( - VirtualMachine vm, + public static EvmExceptionType InstructionReturn(VirtualMachine vm, ref EvmStack stack, - ref long gasAvailable, + ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { // RETURN is not allowed during contract creation. - if (vm.EvmState.ExecutionType is ExecutionType.EOFCREATE or ExecutionType.TXCREATE) + if (vm.VmState.ExecutionType is ExecutionType.EOFCREATE or ExecutionType.TXCREATE) { goto BadInstruction; } @@ -357,15 +356,13 @@ public static EvmExceptionType InstructionReturn( goto StackUnderflow; // Update the memory cost for the region being returned. - if (!EvmCalculations.UpdateMemoryCost(vm.EvmState, ref gasAvailable, in position, in length)) + if (!TGasPolicy.UpdateMemoryCost(ref gas, in position, in length, vm.VmState) || + !vm.VmState.Memory.TryLoad(in position, in length, out ReadOnlyMemory returnData)) { goto OutOfGas; } - // Load the return data from memory and copy it to an array, - // so the return value isn't referencing live memory, - // which is being unwound in return. - vm.ReturnData = vm.EvmState.Memory.Load(in position, in length).ToArray(); + vm.ReturnData = returnData.ToArray(); return EvmExceptionType.None; // Jump forward to be unpredicted by the branch predictor. diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs index 79684a619a7..aee900e5ca1 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.CodeCopy.cs @@ -7,6 +7,7 @@ using Nethermind.Core.Specs; using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.EvmObjectFormat; +using Nethermind.Evm.GasPolicy; namespace Nethermind.Evm; @@ -19,14 +20,16 @@ internal static partial class EvmInstructions /// Provides a mechanism to retrieve a code segment for code copy operations. /// Implementers return a ReadOnlySpan of bytes representing the code to copy. /// - public interface IOpCodeCopy + /// The gas policy type parameter. + public interface IOpCodeCopy + where TGasPolicy : struct, IGasPolicy { /// /// Gets the code to be copied. /// /// The virtual machine instance providing execution context. /// A read-only span of bytes containing the code. - abstract static ReadOnlySpan GetCode(VirtualMachine vm); + abstract static ReadOnlySpan GetCode(VirtualMachine vm); } /// @@ -34,6 +37,7 @@ public interface IOpCodeCopy /// Pops three parameters from the stack: destination memory offset, source offset, and length. /// It then deducts gas based on the memory expansion and performs the copy using the provided code source. /// + /// The gas policy used for gas accounting. /// /// A struct implementing that defines the code source to copy from. /// @@ -42,18 +46,19 @@ public interface IOpCodeCopy /// /// The current virtual machine instance. /// The EVM stack used for operand retrieval and result storage. - /// Reference to the available gas; reduced by the operation’s cost. + /// The gas which is updated by the operation's cost. /// Reference to the current program counter (unused in this operation). /// /// on success, or an appropriate error code if an error occurs. /// [SkipLocalsInit] - public static EvmExceptionType InstructionCodeCopy( - VirtualMachine vm, + public static EvmExceptionType InstructionCodeCopy( + VirtualMachine vm, ref EvmStack stack, - ref long gasAvailable, + ref TGasPolicy gas, ref int programCounter) - where TOpCodeCopy : struct, IOpCodeCopy + where TGasPolicy : struct, IGasPolicy + where TOpCodeCopy : struct, IOpCodeCopy where TTracingInst : struct, IFlag { // Pop destination offset, source offset, and copy length. @@ -64,20 +69,20 @@ public static EvmExceptionType InstructionCodeCopy( // Deduct gas for the operation plus the cost for memory expansion. // Gas cost is calculated as a fixed "VeryLow" cost plus a per-32-bytes cost. - gasAvailable -= GasCostOf.VeryLow + GasCostOf.Memory * EvmCalculations.Div32Ceiling(in result, out bool outOfGas); + TGasPolicy.ConsumeDataCopyGas(ref gas, isExternalCode: false, GasCostOf.VeryLow, GasCostOf.Memory * EvmCalculations.Div32Ceiling(in result, out bool outOfGas)); if (outOfGas) goto OutOfGas; // Only perform the copy if length (result) is non-zero. if (!result.IsZero) { // Check and update memory expansion cost. - if (!EvmCalculations.UpdateMemoryCost(vm.EvmState, ref gasAvailable, in a, result)) + if (!TGasPolicy.UpdateMemoryCost(ref gas, in a, result, vm.VmState)) goto OutOfGas; // Obtain the code slice with zero-padding if needed. ZeroPaddedSpan slice = TOpCodeCopy.GetCode(vm).SliceWithZeroPadding(in b, (int)result); // Save the slice into memory at the destination offset. - vm.EvmState.Memory.Save(in a, in slice); + if (!vm.VmState.Memory.TrySave(in a, in slice)) goto OutOfGas; // If tracing is enabled, report the memory change. if (TTracingInst.IsActive) @@ -97,19 +102,21 @@ public static EvmExceptionType InstructionCodeCopy( /// /// Retrieves call data as the source for a code copy. /// - public struct OpCallDataCopy : IOpCodeCopy + public struct OpCallDataCopy : IOpCodeCopy + where TGasPolicy : struct, IGasPolicy { - public static ReadOnlySpan GetCode(VirtualMachine vm) - => vm.EvmState.Env.InputData.Span; + public static ReadOnlySpan GetCode(VirtualMachine vm) + => vm.VmState.Env.InputData.Span; } /// /// Retrieves the executing code as the source for a code copy. /// - public struct OpCodeCopy : IOpCodeCopy + public struct OpCodeCopy : IOpCodeCopy + where TGasPolicy : struct, IGasPolicy { - public static ReadOnlySpan GetCode(VirtualMachine vm) - => vm.EvmState.Env.CodeInfo.CodeSpan; + public static ReadOnlySpan GetCode(VirtualMachine vm) + => vm.VmState.Env.CodeInfo.CodeSpan; } /// @@ -117,22 +124,23 @@ public static ReadOnlySpan GetCode(VirtualMachine vm) /// Pops an address and three parameters (destination offset, source offset, and length) from the stack. /// Validates account access and memory expansion, then copies the external code into memory. /// + /// The gas policy used for gas accounting. /// /// A struct implementing that indicates whether tracing is active. /// /// The current virtual machine instance. /// The EVM stack for operand retrieval and memory copy operations. - /// Reference to the available gas; reduced by both external code access and memory costs. + /// The gas which is updated by the operation's cost. /// Reference to the program counter (unused in this operation). /// /// on success, or an appropriate error code on failure. /// [SkipLocalsInit] - public static EvmExceptionType InstructionExtCodeCopy( - VirtualMachine vm, + public static EvmExceptionType InstructionExtCodeCopy(VirtualMachine vm, ref EvmStack stack, - ref long gasAvailable, + ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { IReleaseSpec spec = vm.Spec; @@ -146,17 +154,17 @@ public static EvmExceptionType InstructionExtCodeCopy( goto StackUnderflow; // Deduct gas cost: cost for external code access plus memory expansion cost. - gasAvailable -= spec.GetExtCodeCost() + GasCostOf.Memory * EvmCalculations.Div32Ceiling(in result, out bool outOfGas); + TGasPolicy.ConsumeDataCopyGas(ref gas, isExternalCode: true, spec.GetExtCodeCost(), GasCostOf.Memory * EvmCalculations.Div32Ceiling(in result, out bool outOfGas)); if (outOfGas) goto OutOfGas; // Charge gas for account access (considering hot/cold storage costs). - if (!EvmCalculations.ChargeAccountAccessGas(ref gasAvailable, vm, address)) + if (!TGasPolicy.ConsumeAccountAccessGas(ref gas, spec, in vm.VmState.AccessTracker, vm.TxTracer.IsTracingAccess, address)) goto OutOfGas; if (!result.IsZero) { // Update memory cost if the destination region requires expansion. - if (!EvmCalculations.UpdateMemoryCost(vm.EvmState, ref gasAvailable, in a, result)) + if (!TGasPolicy.UpdateMemoryCost(ref gas, in a, result, vm.VmState)) goto OutOfGas; (vm.WorldState as TracedAccessWorldState)?.AddAccountRead(address); @@ -170,7 +178,7 @@ public static EvmExceptionType InstructionExtCodeCopy( if (spec.IsEip7907Enabled) { uint excessContractSize = (uint)Math.Max(0, externalCode.Length - CodeSizeConstants.MaxCodeSizeEip170); - if (excessContractSize > 0 && !ChargeForLargeContractAccess(excessContractSize, address, in vm.EvmState.AccessTracker, ref gasAvailable)) + if (excessContractSize > 0 && !ChargeForLargeContractAccess(excessContractSize, address, in vm.VmState.AccessTracker, ref gas)) goto OutOfGas; } @@ -183,7 +191,7 @@ public static EvmExceptionType InstructionExtCodeCopy( // Slice the external code starting at the source offset with appropriate zero-padding. ZeroPaddedSpan slice = externalCode.SliceWithZeroPadding(in b, (int)result); // Save the slice into memory at the destination offset. - vm.EvmState.Memory.Save(in a, in slice); + if (!vm.VmState.Memory.TrySave(in a, in slice)) goto OutOfGas; // Report memory changes if tracing is enabled. if (TTracingInst.IsActive) @@ -209,40 +217,41 @@ public static EvmExceptionType InstructionExtCodeCopy( /// Pops an account address from the stack, validates access, and pushes the code size onto the stack. /// Additionally, applies peephole optimizations for common contract checks. /// + /// The gas policy used for gas accounting. /// /// A struct implementing indicating if instruction tracing is active. /// /// The virtual machine instance. /// The EVM stack from which the account address is popped and where the code size is pushed. - /// Reference to the available gas; reduced by external code cost. + /// The gas which is updated by the operation's cost. /// Reference to the program counter, which may be adjusted during optimization. /// /// on success, or an appropriate error code if an error occurs. /// [SkipLocalsInit] - public static EvmExceptionType InstructionExtCodeSize( - VirtualMachine vm, + public static EvmExceptionType InstructionExtCodeSize(VirtualMachine vm, ref EvmStack stack, - ref long gasAvailable, + ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { IReleaseSpec spec = vm.Spec; // Deduct the gas cost for external code access. - gasAvailable -= spec.GetExtCodeCost(); + TGasPolicy.Consume(ref gas, spec.GetExtCodeCost()); // Pop the account address from the stack. Address address = stack.PopAddress(); if (address is null) goto StackUnderflow; // Charge gas for accessing the account's state. - if (!EvmCalculations.ChargeAccountAccessGas(ref gasAvailable, vm, address)) + if (!TGasPolicy.ConsumeAccountAccessGas(ref gas, spec, in vm.VmState.AccessTracker, vm.TxTracer.IsTracingAccess, address)) goto OutOfGas; (vm.WorldState as TracedAccessWorldState)?.AddAccountRead(address); // Attempt a peephole optimization when tracing is not active and code is available. - ReadOnlySpan codeSection = vm.EvmState.Env.CodeInfo.CodeSpan; + ReadOnlySpan codeSection = vm.VmState.Env.CodeInfo.CodeSpan; if (!TTracingInst.IsActive && programCounter < codeSection.Length) { bool optimizeAccess = false; @@ -270,7 +279,7 @@ public static EvmExceptionType InstructionExtCodeSize( vm.OpCodeCount++; programCounter++; // Deduct very-low gas cost for the next operation (ISZERO, GT, or EQ). - gasAvailable -= GasCostOf.VeryLow; + TGasPolicy.Consume(ref gas, GasCostOf.VeryLow); // Determine if the account is a contract by checking the loaded CodeHash. bool isCodeLengthNotZero = vm.WorldState.IsContract(address); diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.ControlFlow.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.ControlFlow.cs index 067ce786d6d..e8d9e1adbab 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.ControlFlow.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.ControlFlow.cs @@ -6,6 +6,7 @@ using System.Runtime.Intrinsics; using Nethermind.Core.Specs; using Nethermind.Core; +using Nethermind.Evm.GasPolicy; using Nethermind.Evm.State; namespace Nethermind.Evm; @@ -20,17 +21,18 @@ internal static partial class EvmInstructions /// /// The virtual machine instance. /// The execution stack where the program counter is pushed. - /// Reference to the remaining gas; reduced by the gas cost. + /// The gas which is updated by the operation's cost. /// The current program counter. /// /// on success. /// [SkipLocalsInit] - public static EvmExceptionType InstructionProgramCounter(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionProgramCounter(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { // Deduct the base gas cost for reading the program counter. - gasAvailable -= GasCostOf.Base; + TGasPolicy.Consume(ref gas, GasCostOf.Base); // The program counter pushed is adjusted by -1 to reflect the correct opcode location. stack.PushUInt32((uint)(programCounter - 1)); @@ -43,16 +45,17 @@ public static EvmExceptionType InstructionProgramCounter(VirtualMa /// /// The virtual machine instance. /// The execution stack. - /// Reference to the remaining gas; reduced by the jump destination cost. + /// The gas which is updated by the operation's cost. /// The current program counter. /// /// on success. /// [SkipLocalsInit] - public static EvmExceptionType InstructionJumpDest(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionJumpDest(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { // Deduct the gas cost specific for a jump destination marker. - gasAvailable -= GasCostOf.JumpDest; + TGasPolicy.Consume(ref gas, GasCostOf.JumpDest); return EvmExceptionType.None; } @@ -64,21 +67,22 @@ public static EvmExceptionType InstructionJumpDest(VirtualMachine vm, ref EvmSta /// /// The virtual machine instance. /// The execution stack from which the jump destination is popped. - /// Reference to the remaining gas; reduced by the gas cost for jumping. + /// Reference to the gas state; reduced by the gas cost for jumping. /// Reference to the program counter that may be updated with the jump destination. /// /// on success; or /// on failure. /// [SkipLocalsInit] - public static EvmExceptionType InstructionJump(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionJump(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { // Deduct the gas cost for performing a jump. - gasAvailable -= GasCostOf.Jump; + TGasPolicy.Consume(ref gas, GasCostOf.Jump); // Pop the jump destination from the stack. if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; // Validate the jump destination and update the program counter if valid. - if (!Jump(result, ref programCounter, in vm.EvmState.Env)) goto InvalidJumpDestination; + if (!Jump(result, ref programCounter, vm.VmState.Env)) goto InvalidJumpDestination; return EvmExceptionType.None; // Jump forward to be unpredicted by the branch predictor. @@ -95,7 +99,7 @@ public static EvmExceptionType InstructionJump(VirtualMachine vm, ref EvmStack s /// /// The virtual machine instance. /// The execution stack from which the jump destination and condition are popped. - /// Reference to the remaining gas; reduced by the cost for conditional jump. + /// Reference to the gas state; reduced by the cost for conditional jump. /// Reference to the program counter that may be updated on a jump. /// /// on success; returns @@ -103,10 +107,11 @@ public static EvmExceptionType InstructionJump(VirtualMachine vm, ref EvmStack s /// [SkipLocalsInit] [MethodImpl(MethodImplOptions.NoInlining)] - public static EvmExceptionType InstructionJumpIf(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionJumpIf(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { // Deduct the high gas cost for a conditional jump. - gasAvailable -= GasCostOf.JumpI; + TGasPolicy.Consume(ref gas, GasCostOf.JumpI); // Pop the jump destination. if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; @@ -114,7 +119,7 @@ public static EvmExceptionType InstructionJumpIf(VirtualMachine vm, ref EvmStack if (isOverflow) goto StackUnderflow; if (shouldJump) { - if (!Jump(result, ref programCounter, in vm.EvmState.Env)) goto InvalidJumpDestination; + if (!Jump(result, ref programCounter, vm.VmState.Env)) goto InvalidJumpDestination; } return EvmExceptionType.None; @@ -146,10 +151,11 @@ private static bool TestJumpCondition(ref EvmStack stack, out bool isOverflow) /// In EOFCREATE or TXCREATE executions, the STOP opcode is considered illegal. /// [SkipLocalsInit] - public static EvmExceptionType InstructionStop(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionStop(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { // In contract creation contexts, a STOP is not permitted. - if (vm.EvmState.ExecutionType is ExecutionType.EOFCREATE or ExecutionType.TXCREATE) + if (vm.VmState.ExecutionType is ExecutionType.EOFCREATE or ExecutionType.TXCREATE) { return EvmExceptionType.BadInstruction; } @@ -163,7 +169,8 @@ public static EvmExceptionType InstructionStop(VirtualMachine vm, ref EvmStack s /// and returns a revert exception. /// [SkipLocalsInit] - public static EvmExceptionType InstructionRevert(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionRevert(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { // Attempt to pop memory offset and length; if either fails, signal a stack underflow. if (!stack.PopUInt256(out UInt256 position) || @@ -173,13 +180,13 @@ public static EvmExceptionType InstructionRevert(VirtualMachine vm, ref EvmStack } // Ensure sufficient gas for any required memory expansion. - if (!EvmCalculations.UpdateMemoryCost(vm.EvmState, ref gasAvailable, in position, in length)) + if (!TGasPolicy.UpdateMemoryCost(ref gas, in position, in length, vm.VmState) || + !vm.VmState.Memory.TryLoad(in position, in length, out ReadOnlyMemory returnData)) { goto OutOfGas; } - // Copy the specified memory region as return data. - vm.ReturnData = vm.EvmState.Memory.Load(in position, in length).ToArray(); + vm.ReturnData = returnData.ToArray(); return EvmExceptionType.Revert; // Jump forward to be unpredicted by the branch predictor. @@ -195,12 +202,13 @@ public static EvmExceptionType InstructionRevert(VirtualMachine vm, ref EvmStack /// and marks the executing account for destruction. /// [SkipLocalsInit] - private static EvmExceptionType InstructionSelfDestruct(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + private static EvmExceptionType InstructionSelfDestruct(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { // Increment metrics for self-destruct operations. Metrics.IncrementSelfDestructs(); - EvmState vmState = vm.EvmState; + VmState vmState = vm.VmState; IReleaseSpec spec = vm.Spec; IWorldState state = vm.WorldState; @@ -211,8 +219,7 @@ private static EvmExceptionType InstructionSelfDestruct(VirtualMachine vm, ref E // If Shanghai DDoS protection is active, charge the appropriate gas cost. if (spec.UseShanghaiDDosProtection) { - if (!EvmCalculations.UpdateGas(GasCostOf.SelfDestructEip150, ref gasAvailable)) - goto OutOfGas; + TGasPolicy.ConsumeSelfDestructGas(ref gas); } // Pop the inheritor address from the stack; signal underflow if missing. @@ -221,7 +228,7 @@ private static EvmExceptionType InstructionSelfDestruct(VirtualMachine vm, ref E goto StackUnderflow; // Charge gas for account access; if insufficient, signal out-of-gas. - if (!EvmCalculations.ChargeAccountAccessGas(ref gasAvailable, vm, inheritor, chargeForWarm: false)) + if (!TGasPolicy.ConsumeAccountAccessGas(ref gas, spec, in vmState.AccessTracker, vm.TxTracer.IsTracingAccess, inheritor, false)) goto OutOfGas; Address executingAccount = vmState.Env.ExecutingAccount; @@ -238,7 +245,7 @@ private static EvmExceptionType InstructionSelfDestruct(VirtualMachine vm, ref E // For certain specs, charge gas if transferring to a dead account. if (spec.ClearEmptyAccountWhenTouched && !result.IsZero && state.IsDeadAccount(inheritor)) { - if (!EvmCalculations.UpdateGas(GasCostOf.NewAccount, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.NewAccount)) goto OutOfGas; } @@ -246,7 +253,7 @@ private static EvmExceptionType InstructionSelfDestruct(VirtualMachine vm, ref E bool inheritorAccountExists = state.AccountExists(inheritor); if (!spec.ClearEmptyAccountWhenTouched && !inheritorAccountExists && spec.UseShanghaiDDosProtection) { - if (!EvmCalculations.UpdateGas(GasCostOf.NewAccount, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.NewAccount)) goto OutOfGas; } @@ -281,16 +288,18 @@ private static EvmExceptionType InstructionSelfDestruct(VirtualMachine vm, ref E /// /// Handles invalid opcodes by deducting a high gas cost and returning a BadInstruction error. /// - public static EvmExceptionType InstructionInvalid(VirtualMachine _, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionInvalid(VirtualMachine _, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { - gasAvailable -= GasCostOf.High; + TGasPolicy.Consume(ref gas, GasCostOf.High); return EvmExceptionType.BadInstruction; } /// /// Default handler for undefined opcodes, always returning a BadInstruction error. /// - public static EvmExceptionType InstructionBadInstruction(VirtualMachine _, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionBadInstruction(VirtualMachine _, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy => EvmExceptionType.BadInstruction; /// @@ -304,7 +313,7 @@ public static EvmExceptionType InstructionBadInstruction(VirtualMachine _, ref E /// true if the destination is valid and the program counter is updated; otherwise, false. /// [SkipLocalsInit] - private static bool Jump(in UInt256 jumpDestination, ref int programCounter, in ExecutionEnvironment env) + private static bool Jump(in UInt256 jumpDestination, ref int programCounter, ExecutionEnvironment env) { // Check if the jump destination exceeds the maximum allowed integer value. if (jumpDestination > int.MaxValue) @@ -313,10 +322,10 @@ private static bool Jump(in UInt256 jumpDestination, ref int programCounter, in } // Extract the jump destination from the lowest limb of the UInt256. - return Jump((int)jumpDestination.u0, ref programCounter, in env); + return Jump((int)jumpDestination.u0, ref programCounter, env); } - private static bool Jump(int jumpDestination, ref int programCounter, in ExecutionEnvironment env) + private static bool Jump(int jumpDestination, ref int programCounter, ExecutionEnvironment env) { // Validate that the jump destination corresponds to a valid jump marker in the code. if (!env.CodeInfo.ValidateJump(jumpDestination)) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs index 06618582ea9..66f79084b2c 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Create.cs @@ -7,10 +7,10 @@ using Nethermind.Core.Specs; using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.EvmObjectFormat; +using Nethermind.Evm.GasPolicy; using Nethermind.Int256; using Nethermind.Evm.State; - -using static Nethermind.Evm.VirtualMachine; +using static Nethermind.Evm.VirtualMachineStatics; namespace Nethermind.Evm; @@ -59,19 +59,21 @@ public struct OpCreate2 : IOpCreate /// This method performs validation, gas and memory cost calculations, state updates, /// and delegates execution to a new call frame for the contract's initialization code. /// + /// The gas policy implementation. /// The type of create operation (either or ). /// Tracing instructions type used for instrumentation if active. /// The current virtual machine instance. /// Reference to the EVM stack. - /// Reference to the gas counter available for execution. + /// Reference to the gas state. /// Reference to the program counter. /// An indicating success or the type of exception encountered. [SkipLocalsInit] - public static EvmExceptionType InstructionCreate( - VirtualMachine vm, + public static EvmExceptionType InstructionCreate( + VirtualMachine vm, ref EvmStack stack, - ref long gasAvailable, + ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TOpCreate : struct, IOpCreate where TTracingInst : struct, IFlag { @@ -80,12 +82,12 @@ public static EvmExceptionType InstructionCreate( // Obtain the current EVM specification and check if the call is static (static calls cannot create contracts). IReleaseSpec spec = vm.Spec; - if (vm.EvmState.IsStatic) + if (vm.VmState.IsStatic) goto StaticCallViolation; // Reset the return data buffer as contract creation does not use previous return data. vm.ReturnData = null; - ref readonly ExecutionEnvironment env = ref vm.EvmState.Env; + ExecutionEnvironment env = vm.VmState.Env; IWorldState state = vm.WorldState; // Pop parameters off the stack: value to transfer, memory position for the initialization code, @@ -119,11 +121,11 @@ public static EvmExceptionType InstructionCreate( : 0); // Check gas sufficiency: if outOfGas flag was set during gas division or if gas update fails. - if (outOfGas || !EvmCalculations.UpdateGas(gasCost, ref gasAvailable)) + if (outOfGas || !TGasPolicy.UpdateGas(ref gas, gasCost)) goto OutOfGas; // Update memory gas cost based on the required memory expansion for the init code. - if (!EvmCalculations.UpdateMemoryCost(vm.EvmState, ref gasAvailable, in memoryPositionOfInitCode, in initCodeLength)) + if (!TGasPolicy.UpdateMemoryCost(ref gas, in memoryPositionOfInitCode, in initCodeLength, vm.VmState)) goto OutOfGas; // Verify call depth does not exceed the maximum allowed. If exceeded, return early with empty data. @@ -136,7 +138,8 @@ public static EvmExceptionType InstructionCreate( } // Load the initialization code from memory based on the specified position and length. - ReadOnlyMemory initCode = vm.EvmState.Memory.Load(in memoryPositionOfInitCode, in initCodeLength); + if (!vm.VmState.Memory.TryLoad(in memoryPositionOfInitCode, in initCodeLength, out ReadOnlyMemory initCode)) + goto OutOfGas; // Check that the executing account has sufficient balance to transfer the specified value. UInt256 balance = state.GetBalance(env.ExecutingAccount); @@ -157,6 +160,9 @@ public static EvmExceptionType InstructionCreate( goto None; } + // Get remaining gas for the create operation. + long gasAvailable = TGasPolicy.GetRemainingGas(in gas); + // End tracing if enabled, prior to switching to the new call frame. if (TTracingInst.IsActive) vm.EndInstructionTrace(gasAvailable); @@ -164,7 +170,7 @@ public static EvmExceptionType InstructionCreate( // Calculate gas available for the contract creation call. // Use the 63/64 gas rule if specified in the current EVM specification. long callGas = spec.Use63Over64Rule ? gasAvailable - gasAvailable / 64L : gasAvailable; - if (!EvmCalculations.UpdateGas(callGas, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, callGas)) goto OutOfGas; // Compute the contract address: @@ -177,7 +183,7 @@ public static EvmExceptionType InstructionCreate( // For EIP-2929 support, pre-warm the contract address in the access tracker to account for hot/cold storage costs. if (spec.UseHotAndColdStorage) { - vm.EvmState.AccessTracker.WarmUp(contractAddress); + vm.VmState.AccessTracker.WarmUp(contractAddress); } // Special case: if EOF code format is enabled and the init code starts with the EOF marker, @@ -186,7 +192,7 @@ public static EvmExceptionType InstructionCreate( { vm.ReturnDataBuffer = Array.Empty(); stack.PushZero(); - EvmCalculations.UpdateGasUp(callGas, ref gasAvailable); + TGasPolicy.UpdateGasUp(ref gas, callGas); goto None; } @@ -194,7 +200,7 @@ public static EvmExceptionType InstructionCreate( state.IncrementNonce(env.ExecutingAccount); // Analyze and compile the initialization code. - CodeInfoFactory.CreateInitCodeInfo(initCode.ToArray(), spec, out ICodeInfo? codeInfo, out _); + CodeInfoFactory.CreateInitCodeInfo(initCode, spec, out ICodeInfo? codeInfo, out _); // Take a snapshot of the current state. This allows the state to be reverted if contract creation fails. Snapshot snapshot = state.TakeSnapshot(); @@ -221,7 +227,7 @@ public static EvmExceptionType InstructionCreate( // Construct a new execution environment for the contract creation call. // This environment sets up the call frame for executing the contract's initialization code. - ExecutionEnvironment callEnv = new( + ExecutionEnvironment callEnv = ExecutionEnvironment.Rent( codeInfo: codeInfo, executingAccount: contractAddress, caller: env.ExecutingAccount, @@ -232,15 +238,15 @@ public static EvmExceptionType InstructionCreate( inputData: in _emptyMemory); // Rent a new frame to run the initialization code in the new execution environment. - vm.ReturnData = EvmState.RentFrame( - gasAvailable: callGas, + vm.ReturnData = VmState.RentFrame( + gas: TGasPolicy.FromLong(callGas), outputDestination: 0, outputLength: 0, executionType: TOpCreate.ExecutionType, - isStatic: vm.EvmState.IsStatic, + isStatic: vm.VmState.IsStatic, isCreateOnPreExistingAccount: accountExists, - env: in callEnv, - stateForAccessLists: in vm.EvmState.AccessTracker, + env: callEnv, + stateForAccessLists: in vm.VmState.AccessTracker, snapshot: in snapshot); None: return EvmExceptionType.None; diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Crypto.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Crypto.cs index 374a6914851..f0015a84489 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Crypto.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Crypto.cs @@ -5,6 +5,7 @@ using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Evm.GasPolicy; namespace Nethermind.Evm; @@ -18,7 +19,8 @@ internal static partial class EvmInstructions /// and pushes the resulting 256-bit hash onto the stack. /// [SkipLocalsInit] - public static EvmExceptionType InstructionKeccak256(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionKeccak256(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { // Ensure two 256-bit words are available (memory offset and length). @@ -26,17 +28,17 @@ public static EvmExceptionType InstructionKeccak256(VirtualMachine goto StackUnderflow; // Deduct gas: base cost plus additional cost per 32-byte word. - gasAvailable -= GasCostOf.Sha3 + GasCostOf.Sha3Word * EvmCalculations.Div32Ceiling(in b, out bool outOfGas); - if (outOfGas) - goto OutOfGas; + TGasPolicy.Consume(ref gas, GasCostOf.Sha3 + GasCostOf.Sha3Word * EvmCalculations.Div32Ceiling(in b, out bool outOfGas)); + if (outOfGas) goto OutOfGas; - EvmState vmState = vm.EvmState; + VmState vmState = vm.VmState; // Charge gas for any required memory expansion. - if (!EvmCalculations.UpdateMemoryCost(vmState, ref gasAvailable, in a, b)) + if (!TGasPolicy.UpdateMemoryCost(ref gas, in a, b, vmState) || + !vmState.Memory.TryLoadSpan(in a, b, out Span bytes)) + { goto OutOfGas; + } - // Load the target memory region. - Span bytes = vmState.Memory.LoadSpan(in a, b); // Compute the Keccak-256 hash. KeccakCache.ComputeTo(bytes, out ValueHash256 keccak); // Push the 256-bit hash result onto the stack. diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs index 1e81b3cee4d..928373b196e 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Environment.cs @@ -7,9 +7,9 @@ using Nethermind.Core.Specs; using Nethermind.Core.Crypto; using Nethermind.Evm.EvmObjectFormat; +using Nethermind.Evm.GasPolicy; using Nethermind.Evm.State; - -using static Nethermind.Evm.VirtualMachine; +using static Nethermind.Evm.VirtualMachineStatics; namespace Nethermind.Evm; @@ -21,7 +21,9 @@ internal static partial class EvmInstructions /// Defines an environment introspection operation that returns a byte span. /// Implementations should provide a static gas cost and a static Operation method. /// - public interface IOpBlkAddress + /// The gas policy type parameter. + public interface IOpBlkAddress + where TGasPolicy : struct, IGasPolicy { /// /// The gas cost for the operation. @@ -30,15 +32,17 @@ public interface IOpBlkAddress /// /// Executes the operation and returns the result as address. /// - /// The current virtual machine state. - abstract static Address Operation(VirtualMachine vm); + /// The current virtual machine instance. + abstract static Address Operation(VirtualMachine vm); } /// /// Defines an environment introspection operation that returns a big endian word. /// Implementations should provide a static gas cost and a static Operation method. /// - public interface IOpEnv32Bytes + /// The gas policy type parameter. + public interface IOpEnv32Bytes + where TGasPolicy : struct, IGasPolicy { /// /// The gas cost for the operation. @@ -47,15 +51,17 @@ public interface IOpEnv32Bytes /// /// Executes the operation and returns the result as ref to big endian word. /// - /// The current virtual machine state. - abstract static ref readonly ValueHash256 Operation(VirtualMachine vm); + /// The current virtual machine instance. + abstract static ref readonly ValueHash256 Operation(VirtualMachine vm); } /// /// Defines an environment introspection operation that returns an Address. /// Implementations should provide a static gas cost and a static Operation method. /// - public interface IOpEnvAddress + /// The gas policy type parameter. + public interface IOpEnvAddress + where TGasPolicy : struct, IGasPolicy { /// /// The gas cost for the operation. @@ -65,13 +71,15 @@ public interface IOpEnvAddress /// Executes the operation and returns the result as address. /// /// The current virtual machine state. - abstract static Address Operation(EvmState vmState); + abstract static Address Operation(VmState vmState); } /// /// Defines an environment introspection operation that returns a 256-bit unsigned integer. /// - public interface IOpEnvUInt256 + /// The gas policy type parameter. + public interface IOpEnvUInt256 + where TGasPolicy : struct, IGasPolicy { virtual static long GasCost => GasCostOf.Base; /// @@ -79,82 +87,92 @@ public interface IOpEnvUInt256 /// /// The current virtual machine state. /// The resulting 256-bit unsigned integer. - abstract static ref readonly UInt256 Operation(EvmState vmState); + abstract static ref readonly UInt256 Operation(VmState vmState); } /// /// Defines an environment introspection operation that returns a 256-bit unsigned integer. /// - public interface IOpBlkUInt256 + /// The gas policy type parameter. + public interface IOpBlkUInt256 + where TGasPolicy : struct, IGasPolicy { virtual static long GasCost => GasCostOf.Base; /// /// Executes the operation and returns the result as a UInt256. /// - /// The current virtual machine state. + /// The current virtual machine instance. /// The resulting 256-bit unsigned integer. - abstract static ref readonly UInt256 Operation(VirtualMachine vm); + abstract static ref readonly UInt256 Operation(VirtualMachine vm); } /// /// Defines an environment introspection operation that returns a 32-bit unsigned integer. /// - public interface IOpEnvUInt32 + /// The gas policy type parameter. + public interface IOpEnvUInt32 + where TGasPolicy : struct, IGasPolicy { virtual static long GasCost => GasCostOf.Base; /// /// Executes the operation and returns the result as a UInt32. /// /// The current virtual machine state. - abstract static uint Operation(EvmState vmState); + abstract static uint Operation(VmState vmState); } /// /// Defines an environment introspection operation that returns a 64-bit unsigned integer. /// - public interface IOpEnvUInt64 + /// The gas policy type parameter. + public interface IOpEnvUInt64 + where TGasPolicy : struct, IGasPolicy { virtual static long GasCost => GasCostOf.Base; /// /// Executes the operation and returns the result as a UInt64. /// /// The current virtual machine state. - abstract static ulong Operation(EvmState vmState); + abstract static ulong Operation(VmState vmState); } /// /// Defines an environment introspection operation that returns a 64-bit unsigned integer. /// - public interface IOpBlkUInt64 + /// The gas policy type parameter. + public interface IOpBlkUInt64 + where TGasPolicy : struct, IGasPolicy { virtual static long GasCost => GasCostOf.Base; /// /// Executes the operation and returns the result as a UInt64. /// - /// The current virtual machine state. - abstract static ulong Operation(VirtualMachine vm); + /// The current virtual machine instance. + abstract static ulong Operation(VirtualMachine vm); } /// /// Executes an environment introspection opcode that returns an Address. /// Generic parameter TOpEnv defines the concrete operation. /// + /// The gas policy used for gas accounting. /// The specific operation implementation. /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// Reference to the gas state, updated by the operation's cost. /// The program counter. /// An EVM exception type if an error occurs. [SkipLocalsInit] - public static EvmExceptionType InstructionEnvAddress(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) - where TOpEnv : struct, IOpEnvAddress + public static EvmExceptionType InstructionEnvAddress(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy + where TOpEnv : struct, IOpEnvAddress where TTracingInst : struct, IFlag { // Deduct the gas cost as defined by the operation implementation. - gasAvailable -= TOpEnv.GasCost; + TGasPolicy.Consume(ref gas, TOpEnv.GasCost); // Execute the operation and retrieve the result. - Address result = TOpEnv.Operation(vm.EvmState); + Address result = TOpEnv.Operation(vm.VmState); // Push the resulting bytes onto the EVM stack. stack.PushAddress(result); @@ -166,19 +184,21 @@ public static EvmExceptionType InstructionEnvAddress(Virtu /// Executes an block introspection opcode that returns an Address. /// Generic parameter TOpEnv defines the concrete operation. /// + /// The gas policy used for gas accounting. /// The specific operation implementation. /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// Reference to the gas state, updated by the operation's cost. /// The program counter. /// An EVM exception type if an error occurs. [SkipLocalsInit] - public static EvmExceptionType InstructionBlkAddress(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) - where TOpEnv : struct, IOpBlkAddress + public static EvmExceptionType InstructionBlkAddress(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy + where TOpEnv : struct, IOpBlkAddress where TTracingInst : struct, IFlag { // Deduct the gas cost as defined by the operation implementation. - gasAvailable -= TOpEnv.GasCost; + TGasPolicy.Consume(ref gas, TOpEnv.GasCost); // Execute the operation and retrieve the result. Address result = TOpEnv.Operation(vm); @@ -192,20 +212,22 @@ public static EvmExceptionType InstructionBlkAddress(Virtu /// /// Executes an environment introspection opcode that returns a UInt256 value. /// + /// The gas policy used for gas accounting. /// The specific operation implementation. /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// Reference to the gas state, updated by the operation's cost. /// The program counter. /// An EVM exception type if an error occurs. [SkipLocalsInit] - public static EvmExceptionType InstructionEnvUInt256(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) - where TOpEnv : struct, IOpEnvUInt256 + public static EvmExceptionType InstructionEnvUInt256(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy + where TOpEnv : struct, IOpEnvUInt256 where TTracingInst : struct, IFlag { - gasAvailable -= TOpEnv.GasCost; + TGasPolicy.Consume(ref gas, TOpEnv.GasCost); - ref readonly UInt256 result = ref TOpEnv.Operation(vm.EvmState); + ref readonly UInt256 result = ref TOpEnv.Operation(vm.VmState); stack.PushUInt256(in result); @@ -215,18 +237,20 @@ public static EvmExceptionType InstructionEnvUInt256(Virtu /// /// Executes an environment introspection opcode that returns a UInt256 value. /// + /// The gas policy used for gas accounting. /// The specific operation implementation. /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// Reference to the gas state, updated by the operation's cost. /// The program counter. /// An EVM exception type if an error occurs. [SkipLocalsInit] - public static EvmExceptionType InstructionBlkUInt256(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) - where TOpEnv : struct, IOpBlkUInt256 + public static EvmExceptionType InstructionBlkUInt256(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy + where TOpEnv : struct, IOpBlkUInt256 where TTracingInst : struct, IFlag { - gasAvailable -= TOpEnv.GasCost; + TGasPolicy.Consume(ref gas, TOpEnv.GasCost); ref readonly UInt256 result = ref TOpEnv.Operation(vm); @@ -238,20 +262,22 @@ public static EvmExceptionType InstructionBlkUInt256(Virtu /// /// Executes an environment introspection opcode that returns a UInt32 value. /// + /// The gas policy used for gas accounting. /// The specific operation implementation. /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// Reference to the gas state, updated by the operation's cost. /// The program counter. /// An EVM exception type if an error occurs. [SkipLocalsInit] - public static EvmExceptionType InstructionEnvUInt32(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) - where TOpEnv : struct, IOpEnvUInt32 + public static EvmExceptionType InstructionEnvUInt32(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy + where TOpEnv : struct, IOpEnvUInt32 where TTracingInst : struct, IFlag { - gasAvailable -= TOpEnv.GasCost; + TGasPolicy.Consume(ref gas, TOpEnv.GasCost); - uint result = TOpEnv.Operation(vm.EvmState); + uint result = TOpEnv.Operation(vm.VmState); stack.PushUInt32(result); @@ -261,20 +287,22 @@ public static EvmExceptionType InstructionEnvUInt32(Virtua /// /// Executes an environment introspection opcode that returns a UInt64 value. /// + /// The gas policy used for gas accounting. /// The specific operation implementation. /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// Reference to the gas state, updated by the operation's cost. /// The program counter. /// An EVM exception type if an error occurs. [SkipLocalsInit] - public static EvmExceptionType InstructionEnvUInt64(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) - where TOpEnv : struct, IOpEnvUInt64 + public static EvmExceptionType InstructionEnvUInt64(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy + where TOpEnv : struct, IOpEnvUInt64 where TTracingInst : struct, IFlag { - gasAvailable -= TOpEnv.GasCost; + TGasPolicy.Consume(ref gas, TOpEnv.GasCost); - ulong result = TOpEnv.Operation(vm.EvmState); + ulong result = TOpEnv.Operation(vm.VmState); stack.PushUInt64(result); @@ -284,18 +312,20 @@ public static EvmExceptionType InstructionEnvUInt64(Virtua /// /// Executes an environment introspection opcode that returns a UInt64 value. /// + /// The gas policy used for gas accounting. /// The specific operation implementation. /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// Reference to the gas state, updated by the operation's cost. /// The program counter. /// An EVM exception type if an error occurs. [SkipLocalsInit] - public static EvmExceptionType InstructionBlkUInt64(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) - where TOpEnv : struct, IOpBlkUInt64 + public static EvmExceptionType InstructionBlkUInt64(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy + where TOpEnv : struct, IOpBlkUInt64 where TTracingInst : struct, IFlag { - gasAvailable -= TOpEnv.GasCost; + TGasPolicy.Consume(ref gas, TOpEnv.GasCost); ulong result = TOpEnv.Operation(vm); @@ -307,18 +337,20 @@ public static EvmExceptionType InstructionBlkUInt64(Virtua /// /// Executes an environment introspection opcode that returns a UInt64 value. /// + /// The gas policy used for gas accounting. /// The specific operation implementation. /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// Reference to the gas state, updated by the operation's cost. /// The program counter. /// An EVM exception type if an error occurs. [SkipLocalsInit] - public static EvmExceptionType InstructionEnv32Bytes(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) - where TOpEnv : struct, IOpEnv32Bytes + public static EvmExceptionType InstructionEnv32Bytes(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy + where TOpEnv : struct, IOpEnv32Bytes where TTracingInst : struct, IFlag { - gasAvailable -= TOpEnv.GasCost; + TGasPolicy.Consume(ref gas, TOpEnv.GasCost); ref readonly ValueHash256 result = ref TOpEnv.Operation(vm); @@ -330,63 +362,71 @@ public static EvmExceptionType InstructionEnv32Bytes(Virtu /// /// Returns the size of the transaction call data. /// - public struct OpCallDataSize : IOpEnvUInt32 + public struct OpCallDataSize : IOpEnvUInt32 + where TGasPolicy : struct, IGasPolicy { - public static uint Operation(EvmState vmState) + public static uint Operation(VmState vmState) => (uint)vmState.Env.InputData.Length; } /// /// Returns the size of the executing code. /// - public struct OpCodeSize : IOpEnvUInt32 + public struct OpCodeSize : IOpEnvUInt32 + where TGasPolicy : struct, IGasPolicy { - public static uint Operation(EvmState vmState) + public static uint Operation(VmState vmState) => (uint)vmState.Env.CodeInfo.CodeSpan.Length; } /// /// Returns the timestamp of the current block. /// - public struct OpTimestamp : IOpBlkUInt64 + public struct OpTimestamp : IOpBlkUInt64 + where TGasPolicy : struct, IGasPolicy { - public static ulong Operation(VirtualMachine vm) + public static ulong Operation(VirtualMachine vm) => vm.BlockExecutionContext.Header.Timestamp; } /// /// Returns the block number of the current block. /// - public struct OpNumber : IOpBlkUInt64 + public struct OpNumber : IOpBlkUInt64 + where TGasPolicy : struct, IGasPolicy { - public static ulong Operation(VirtualMachine vm) + public static ulong Operation(VirtualMachine vm) => vm.BlockExecutionContext.Number; } /// /// Returns the gas limit of the current block. /// - public struct OpGasLimit : IOpBlkUInt64 + public struct OpGasLimit : IOpBlkUInt64 + where TGasPolicy : struct, IGasPolicy { - public static ulong Operation(VirtualMachine vm) + public static ulong Operation(VirtualMachine vm) => vm.BlockExecutionContext.GasLimit; } /// /// Returns the current size of the EVM memory. /// - public struct OpMSize : IOpEnvUInt64 + public struct OpMSize : IOpEnvUInt64 + where TGasPolicy : struct, IGasPolicy { - public static ulong Operation(EvmState vmState) + public static ulong Operation(VmState vmState) => vmState.Memory.Size; } /// /// Returns the base fee per gas for the current block. /// - public struct OpBaseFee : IOpBlkUInt256 + public struct OpBaseFee : IOpBlkUInt256 + where TGasPolicy : struct, IGasPolicy { - public static ref readonly UInt256 Operation(VirtualMachine vm) + + public static ref readonly UInt256 Operation(VirtualMachine vm) => ref vm.BlockExecutionContext.Header.BaseFeePerGas; } @@ -394,14 +434,16 @@ public static ref readonly UInt256 Operation(VirtualMachine vm) /// Implements the BLOBBASEFEE opcode. /// Returns the blob base fee from the block header. /// + /// The gas policy used for gas accounting. /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// Reference to the gas state, updated by the operation's cost. /// The program counter. /// /// , or if blob base fee not set. /// - public static EvmExceptionType InstructionBlobBaseFee(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionBlobBaseFee(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { ref readonly BlockExecutionContext context = ref vm.BlockExecutionContext; @@ -409,7 +451,7 @@ public static EvmExceptionType InstructionBlobBaseFee(VirtualMachi if (!context.Header.ExcessBlobGas.HasValue) goto BadInstruction; // Charge the base gas cost for this opcode. - gasAvailable -= GasCostOf.Base; + TGasPolicy.Consume(ref gas, GasCostOf.Base); stack.Push32Bytes(in context.BlobBaseFee); return EvmExceptionType.None; @@ -421,63 +463,70 @@ public static EvmExceptionType InstructionBlobBaseFee(VirtualMachi /// /// Returns the gas price for the transaction. /// - public struct OpGasPrice : IOpBlkUInt256 + public struct OpGasPrice : IOpBlkUInt256 + where TGasPolicy : struct, IGasPolicy { - public static ref readonly UInt256 Operation(VirtualMachine vm) + public static ref readonly UInt256 Operation(VirtualMachine vm) => ref vm.TxExecutionContext.GasPrice; } /// /// Returns the value transferred with the current call. /// - public struct OpCallValue : IOpEnvUInt256 + public struct OpCallValue : IOpEnvUInt256 + where TGasPolicy : struct, IGasPolicy { - public static ref readonly UInt256 Operation(EvmState vmState) + public static ref readonly UInt256 Operation(VmState vmState) => ref vmState.Env.Value; } /// /// Returns the address of the currently executing account. /// - public struct OpAddress : IOpEnvAddress + public struct OpAddress : IOpEnvAddress + where TGasPolicy : struct, IGasPolicy { - public static Address Operation(EvmState vmState) + public static Address Operation(VmState vmState) => vmState.Env.ExecutingAccount; } /// /// Returns the address of the caller of the current execution context. /// - public struct OpCaller : IOpEnvAddress + public struct OpCaller : IOpEnvAddress + where TGasPolicy : struct, IGasPolicy { - public static Address Operation(EvmState vmState) + public static Address Operation(VmState vmState) => vmState.Env.Caller; } /// /// Returns the origin address of the transaction. /// - public struct OpOrigin : IOpEnv32Bytes + public struct OpOrigin : IOpEnv32Bytes + where TGasPolicy : struct, IGasPolicy { - public static ref readonly ValueHash256 Operation(VirtualMachine vm) + public static ref readonly ValueHash256 Operation(VirtualMachine vm) => ref vm.TxExecutionContext.Origin; } /// /// Returns the coinbase (beneficiary) address for the current block. /// - public struct OpCoinbase : IOpBlkAddress + public struct OpCoinbase : IOpBlkAddress + where TGasPolicy : struct, IGasPolicy { - public static Address Operation(VirtualMachine vm) + public static Address Operation(VirtualMachine vm) => vm.BlockExecutionContext.Coinbase; } /// /// Returns the chain identifier. /// - public struct OpChainId : IOpEnv32Bytes + public struct OpChainId : IOpEnv32Bytes + where TGasPolicy : struct, IGasPolicy { - public static ref readonly ValueHash256 Operation(VirtualMachine vm) + public static ref readonly ValueHash256 Operation(VirtualMachine vm) => ref vm.ChainId; } @@ -485,9 +534,10 @@ public static ref readonly ValueHash256 Operation(VirtualMachine vm) /// Retrieves and pushes the balance of an account. /// The address is popped from the stack. /// + /// The gas policy used for gas accounting. /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// Reference to the gas state, updated by the operation's cost. /// The program counter. /// /// if gas is available, @@ -495,18 +545,19 @@ public static ref readonly ValueHash256 Operation(VirtualMachine vm) /// or if not enough items on stack. /// [SkipLocalsInit] - public static EvmExceptionType InstructionBalance(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionBalance(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { IReleaseSpec spec = vm.Spec; // Deduct gas cost for balance operation as per specification. - gasAvailable -= spec.GetBalanceCost(); + TGasPolicy.Consume(ref gas, spec.GetBalanceCost()); Address address = stack.PopAddress(); if (address is null) goto StackUnderflow; // Charge gas for account access. If insufficient gas remains, abort. - if (!EvmCalculations.ChargeAccountAccessGas(ref gasAvailable, vm, address)) goto OutOfGas; + if (!TGasPolicy.ConsumeAccountAccessGas(ref gas, spec, in vm.VmState.AccessTracker, vm.TxTracer.IsTracingAccess, address)) goto OutOfGas; UInt256 result = vm.WorldState.GetBalance(address); stack.PushUInt256(in result); @@ -522,21 +573,23 @@ public static EvmExceptionType InstructionBalance(VirtualMachine v /// /// Pushes the balance of the executing account onto the stack. /// + /// The gas policy used for gas accounting. /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// Reference to the gas state, updated by the operation's cost. /// The program counter. /// /// /// [SkipLocalsInit] - public static EvmExceptionType InstructionSelfBalance(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionSelfBalance(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - gasAvailable -= GasCostOf.SelfBalance; + TGasPolicy.Consume(ref gas, GasCostOf.SelfBalance); // Get balance for currently executing account. - UInt256 result = vm.WorldState.GetBalance(vm.EvmState.Env.ExecutingAccount); + UInt256 result = vm.WorldState.GetBalance(vm.VmState.Env.ExecutingAccount); stack.PushUInt256(in result); return EvmExceptionType.None; @@ -546,9 +599,10 @@ public static EvmExceptionType InstructionSelfBalance(VirtualMachi /// Retrieves the code hash of an external account. /// Returns zero if the account does not exist or is considered dead. /// + /// The gas policy used for gas accounting. /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// Reference to the gas state, updated by the operation's cost. /// The program counter. /// /// if gas is available, @@ -556,16 +610,17 @@ public static EvmExceptionType InstructionSelfBalance(VirtualMachi /// or if not enough items on stack. /// [SkipLocalsInit] - public static EvmExceptionType InstructionExtCodeHash(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionExtCodeHash(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { IReleaseSpec spec = vm.Spec; - gasAvailable -= spec.GetExtCodeHashCost(); + TGasPolicy.Consume(ref gas, spec.GetExtCodeHashCost()); Address address = stack.PopAddress(); if (address is null) goto StackUnderflow; // Check if enough gas for account access and charge accordingly. - if (!EvmCalculations.ChargeAccountAccessGas(ref gasAvailable, vm, address)) goto OutOfGas; + if (!TGasPolicy.ConsumeAccountAccessGas(ref gas, spec, in vm.VmState.AccessTracker, vm.TxTracer.IsTracingAccess, address)) goto OutOfGas; IWorldState state = vm.WorldState; // For dead accounts, the specification requires pushing zero. @@ -592,9 +647,10 @@ public static EvmExceptionType InstructionExtCodeHash(VirtualMachi /// Retrieves the code hash of an external account, considering the possibility of an EOF-validated contract. /// If the code is an EOF contract, a predefined EOF hash is pushed. /// + /// The gas policy used for gas accounting. /// The virtual machine instance. /// The execution stack where the gas value will be pushed. - /// Reference to the current available gas, which is modified by this operation. + /// Reference to the gas state, updated by the operation's cost. /// The current program counter. /// /// if gas is available, @@ -602,15 +658,16 @@ public static EvmExceptionType InstructionExtCodeHash(VirtualMachi /// or if not enough items on stack. /// [SkipLocalsInit] - public static EvmExceptionType InstructionExtCodeHashEof(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionExtCodeHashEof(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { IReleaseSpec spec = vm.Spec; - gasAvailable -= spec.GetExtCodeHashCost(); + TGasPolicy.Consume(ref gas, spec.GetExtCodeHashCost()); Address address = stack.PopAddress(); if (address is null) goto StackUnderflow; - if (!EvmCalculations.ChargeAccountAccessGas(ref gasAvailable, vm, address)) goto OutOfGas; + if (!TGasPolicy.ConsumeAccountAccessGas(ref gas, spec, in vm.VmState.AccessTracker, vm.TxTracer.IsTracingAccess, address)) goto OutOfGas; IWorldState state = vm.WorldState; if (state.IsDeadAccount(address)) @@ -644,19 +701,21 @@ public static EvmExceptionType InstructionExtCodeHashEof(VirtualMa /// Implements the PREVRANDAO opcode. /// Pushes the previous random value (post-merge) or block difficulty (pre-merge) onto the stack. /// + /// The gas policy used for gas accounting. /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// Reference to the gas state, updated by the operation's cost. /// The program counter. /// /// /// [SkipLocalsInit] - public static EvmExceptionType InstructionPrevRandao(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionPrevRandao(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { // Charge the base gas cost for this opcode. - gasAvailable -= GasCostOf.Base; + TGasPolicy.Consume(ref gas, GasCostOf.Base); stack.Push32Bytes(in vm.BlockExecutionContext.PrevRandao); return EvmExceptionType.None; } @@ -665,25 +724,27 @@ public static EvmExceptionType InstructionPrevRandao(VirtualMachin /// Pushes the remaining gas onto the stack. /// The gas available is decremented by the base cost, and if negative, an OutOfGas error is returned. /// + /// The gas policy used for gas accounting. /// The virtual machine instance. /// The execution stack where the gas value will be pushed. - /// Reference to the current available gas, which is modified by this operation. + /// Reference to the gas state, updated by the operation's cost. /// The current program counter. /// /// if gas is available, or if the gas becomes negative. /// [SkipLocalsInit] - public static EvmExceptionType InstructionGas(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionGas(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { // Deduct the base gas cost for reading gas. - gasAvailable -= GasCostOf.Base; + TGasPolicy.Consume(ref gas, GasCostOf.Base); // If gas falls below zero after cost deduction, signal out-of-gas error. - if (gasAvailable < 0) goto OutOfGas; + if (TGasPolicy.GetRemainingGas(in gas) < 0) goto OutOfGas; // Push the remaining gas (as unsigned 64-bit) onto the stack. - stack.PushUInt64((ulong)gasAvailable); + stack.PushUInt64((ulong)TGasPolicy.GetRemainingGas(in gas)); return EvmExceptionType.None; // Jump forward to be unpredicted by the branch predictor. @@ -696,20 +757,22 @@ public static EvmExceptionType InstructionGas(VirtualMachine vm, r /// Pops an index from the stack and uses it to select a blob hash from the versioned hashes array. /// If the index is invalid, pushes zero. /// + /// The gas policy used for gas accounting. /// The virtual machine instance. /// The execution stack from which the index is popped and where the blob hash is pushed. - /// Reference to the available gas; reduced by the blob hash cost. + /// Reference to the gas state, updated by the operation's cost. /// The program counter. /// /// on success; otherwise, /// if there are insufficient elements on the stack. /// [SkipLocalsInit] - public static EvmExceptionType InstructionBlobHash(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionBlobHash(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { // Deduct the gas cost for blob hash operation. - gasAvailable -= GasCostOf.BlobHash; + TGasPolicy.Consume(ref gas, GasCostOf.BlobHash); // Pop the blob index from the stack. if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; @@ -740,20 +803,22 @@ public static EvmExceptionType InstructionBlobHash(VirtualMachine /// If no valid block hash exists, pushes a zero value. /// Additionally, reports the block hash if block hash tracing is enabled. /// + /// The gas policy used for gas accounting. /// The virtual machine instance. /// The execution stack from which the block number is popped and where the block hash is pushed. - /// Reference to the available gas; reduced by the block hash operation cost. + /// Reference to the gas state, updated by the operation's cost. /// The program counter. /// /// if the operation completes successfully; /// otherwise, if there are insufficient stack elements. /// [SkipLocalsInit] - public static EvmExceptionType InstructionBlockHash(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionBlockHash(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { // Deduct the gas cost for block hash operation. - gasAvailable -= GasCostOf.BlockHash; + TGasPolicy.Consume(ref gas, GasCostOf.BlockHash); // Pop the block number from the stack. if (!stack.PopUInt256(out UInt256 a)) goto StackUnderflow; diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Eof.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Eof.cs index 3ec3bd0b93e..3be4898dfb1 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Eof.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Eof.cs @@ -9,10 +9,10 @@ using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.EvmObjectFormat; using Nethermind.Evm.EvmObjectFormat.Handlers; +using Nethermind.Evm.GasPolicy; using Nethermind.Evm.Tracing; using Nethermind.Evm.State; - -using static Nethermind.Evm.VirtualMachine; +using static Nethermind.Evm.VirtualMachineStatics; namespace Nethermind.Evm; @@ -60,17 +60,20 @@ public struct OpEofStaticCall : IOpEofCall /// Retrieves the length of the return data buffer and pushes it onto the stack. /// Deducts the base gas cost from the available gas. /// + /// The gas policy used for gas accounting. + /// Tracing flag type. /// The current virtual machine instance. /// Reference to the operand stack. - /// Reference to the remaining gas counter. + /// Reference to the gas state. /// Reference to the current program counter. /// An indicating the outcome. [SkipLocalsInit] - public static EvmExceptionType InstructionReturnDataSize(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionReturnDataSize(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { // Deduct base gas cost for this instruction. - gasAvailable -= GasCostOf.Base; + TGasPolicy.Consume(ref gas, GasCostOf.Base); // Push the length of the return data buffer (as a 32-bit unsigned integer) onto the stack. stack.PushUInt32((uint)vm.ReturnDataBuffer.Length); @@ -92,7 +95,8 @@ public static EvmExceptionType InstructionReturnDataSize(VirtualMa /// Reference to the current program counter. /// An representing success or the type of failure. [SkipLocalsInit] - public static EvmExceptionType InstructionReturnDataCopy(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionReturnDataCopy(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { // Pop the required parameters: destination memory offset, source offset in return data, and number of bytes to copy. @@ -104,12 +108,12 @@ public static EvmExceptionType InstructionReturnDataCopy(VirtualMa } // Deduct the fixed gas cost and the memory cost based on the size (rounded up to 32-byte words). - gasAvailable -= GasCostOf.VeryLow + GasCostOf.Memory * EvmCalculations.Div32Ceiling(in size, out bool outOfGas); + TGasPolicy.Consume(ref gas, GasCostOf.VeryLow + GasCostOf.Memory * EvmCalculations.Div32Ceiling(in size, out bool outOfGas)); if (outOfGas) goto OutOfGas; ReadOnlyMemory returnDataBuffer = vm.ReturnDataBuffer; // For legacy (non-EOF) code, ensure that the copy does not exceed the available return data. - if (vm.EvmState.Env.CodeInfo.Version == 0 && + if (vm.VmState.Env.CodeInfo.Version == 0 && (UInt256.AddOverflow(size, sourceOffset, out UInt256 result) || result > returnDataBuffer.Length)) { goto AccessViolation; @@ -119,12 +123,12 @@ public static EvmExceptionType InstructionReturnDataCopy(VirtualMa if (!size.IsZero) { // Update memory cost for expanding memory to accommodate the destination slice. - if (!EvmCalculations.UpdateMemoryCost(vm.EvmState, ref gasAvailable, in destOffset, size)) + if (!TGasPolicy.UpdateMemoryCost(ref gas, in destOffset, size, vm.VmState)) return EvmExceptionType.OutOfGas; // Get the source slice; if the requested range exceeds the buffer length, it is zero-padded. ZeroPaddedSpan slice = returnDataBuffer.Span.SliceWithZeroPadding(sourceOffset, (int)size); - vm.EvmState.Memory.Save(in destOffset, in slice); + if (!vm.VmState.Memory.TrySave(in destOffset, in slice)) goto OutOfGas; // Report the memory change if tracing is active. if (TTracingInst.IsActive) @@ -153,16 +157,17 @@ public static EvmExceptionType InstructionReturnDataCopy(VirtualMa /// Reference to the program counter. /// An representing success or an error. [SkipLocalsInit] - public static EvmExceptionType InstructionDataLoad(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionDataLoad(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.EvmState.Env.CodeInfo; + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; // Ensure the instruction is only valid for non-legacy (EOF) code. if (codeInfo.Version == 0) goto BadInstruction; // Deduct gas required for data loading. - if (!EvmCalculations.UpdateGas(GasCostOf.DataLoad, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.DataLoad)) goto OutOfGas; // Pop the offset from the stack. @@ -184,14 +189,15 @@ public static EvmExceptionType InstructionDataLoad(VirtualMachine /// Advances the program counter accordingly. /// [SkipLocalsInit] - public static EvmExceptionType InstructionDataLoadN(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionDataLoadN(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.EvmState.Env.CodeInfo; + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; if (codeInfo.Version == 0) goto BadInstruction; - if (!EvmCalculations.UpdateGas(GasCostOf.DataLoadN, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.DataLoadN)) goto OutOfGas; // Read a 16-bit immediate operand from the code. @@ -215,14 +221,15 @@ public static EvmExceptionType InstructionDataLoadN(VirtualMachine /// Pushes the size of the code's data section onto the stack. /// [SkipLocalsInit] - public static EvmExceptionType InstructionDataSize(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionDataSize(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.EvmState.Env.CodeInfo; + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; if (codeInfo.Version == 0) goto BadInstruction; - if (!EvmCalculations.UpdateGas(GasCostOf.DataSize, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.DataSize)) goto OutOfGas; stack.PushUInt32((uint)codeInfo.DataSection.Length); @@ -240,10 +247,11 @@ public static EvmExceptionType InstructionDataSize(VirtualMachine /// The source offset, destination memory offset, and number of bytes are specified on the stack. /// [SkipLocalsInit] - public static EvmExceptionType InstructionDataCopy(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionDataCopy(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.EvmState.Env.CodeInfo; + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; if (codeInfo.Version == 0) goto BadInstruction; @@ -256,7 +264,7 @@ public static EvmExceptionType InstructionDataCopy(VirtualMachine } // Calculate memory expansion gas cost and deduct overall gas for data copy. - if (!EvmCalculations.UpdateGas(GasCostOf.DataCopy + GasCostOf.Memory * EvmCalculations.Div32Ceiling(in size, out bool outOfGas), ref gasAvailable) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.DataCopy + GasCostOf.Memory * EvmCalculations.Div32Ceiling(in size, out bool outOfGas)) || outOfGas) { goto OutOfGas; @@ -265,11 +273,11 @@ public static EvmExceptionType InstructionDataCopy(VirtualMachine if (!size.IsZero) { // Update memory cost for the destination region. - if (!EvmCalculations.UpdateMemoryCost(vm.EvmState, ref gasAvailable, in memOffset, size)) + if (!TGasPolicy.UpdateMemoryCost(ref gas, in memOffset, size, vm.VmState)) goto OutOfGas; // Retrieve the slice from the data section with zero padding if necessary. ZeroPaddedSpan dataSectionSlice = codeInfo.DataSection.SliceWithZeroPadding(offset, (int)size); - vm.EvmState.Memory.Save(in memOffset, dataSectionSlice); + if (!vm.VmState.Memory.TrySave(in memOffset, in dataSectionSlice)) goto OutOfGas; if (TTracingInst.IsActive) { @@ -292,13 +300,14 @@ public static EvmExceptionType InstructionDataCopy(VirtualMachine /// Reads a two-byte signed offset from the code section and adjusts the program counter accordingly. /// [SkipLocalsInit] - public static EvmExceptionType InstructionRelativeJump(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionRelativeJump(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { - ICodeInfo codeInfo = vm.EvmState.Env.CodeInfo; + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; if (codeInfo.Version == 0) goto BadInstruction; - if (!EvmCalculations.UpdateGas(GasCostOf.RJump, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.RJump)) goto OutOfGas; // Read a signed 16-bit offset and adjust the program counter. @@ -318,13 +327,14 @@ public static EvmExceptionType InstructionRelativeJump(VirtualMachine vm, ref Ev /// Pops a condition value; if non-zero, jumps by the signed offset embedded in the code. /// [SkipLocalsInit] - public static EvmExceptionType InstructionRelativeJumpIf(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionRelativeJumpIf(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { - ICodeInfo codeInfo = vm.EvmState.Env.CodeInfo; + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; if (codeInfo.Version == 0) goto BadInstruction; - if (!EvmCalculations.UpdateGas(GasCostOf.RJumpi, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.RJumpi)) goto OutOfGas; // Pop the condition word. @@ -352,13 +362,14 @@ public static EvmExceptionType InstructionRelativeJumpIf(VirtualMachine vm, ref /// Uses the top-of-stack value as an index into a list of jump offsets, then jumps accordingly. /// [SkipLocalsInit] - public static EvmExceptionType InstructionJumpTable(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionJumpTable(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { - ICodeInfo codeInfo = vm.EvmState.Env.CodeInfo; + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; if (codeInfo.Version == 0) goto BadInstruction; - if (!EvmCalculations.UpdateGas(GasCostOf.RJumpv, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.RJumpv)) goto OutOfGas; // Pop the table index from the stack. @@ -392,15 +403,16 @@ public static EvmExceptionType InstructionJumpTable(VirtualMachine vm, ref EvmSt /// Sets up the return state and verifies stack and call depth constraints before transferring control. /// [SkipLocalsInit] - public static EvmExceptionType InstructionCallFunction(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionCallFunction(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { - ICodeInfo iCodeInfo = vm.EvmState.Env.CodeInfo; + ICodeInfo iCodeInfo = vm.VmState.Env.CodeInfo; if (iCodeInfo.Version == 0) goto BadInstruction; EofCodeInfo codeInfo = (EofCodeInfo)iCodeInfo; - if (!EvmCalculations.UpdateGas(GasCostOf.Callf, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Callf)) goto OutOfGas; ReadOnlySpan codeSection = codeInfo.CodeSection.Span; @@ -416,11 +428,11 @@ public static EvmExceptionType InstructionCallFunction(VirtualMachine vm, ref Ev } // Ensure there is room on the return stack. - if (vm.EvmState.ReturnStackHead == Eof1.RETURN_STACK_MAX_HEIGHT) + if (vm.VmState.ReturnStackHead == Eof1.RETURN_STACK_MAX_HEIGHT) goto InvalidSubroutineEntry; // Push current state onto the return stack. - vm.EvmState.ReturnStack[vm.EvmState.ReturnStackHead++] = new EvmState.ReturnState + vm.VmState.ReturnStack[vm.VmState.ReturnStackHead++] = new ReturnState { Index = vm.SectionIndex, Height = stack.Head - inputCount, @@ -447,17 +459,18 @@ public static EvmExceptionType InstructionCallFunction(VirtualMachine vm, ref Ev /// Returns from a subroutine call by restoring the state from the return stack. /// [SkipLocalsInit] - public static EvmExceptionType InstructionReturnFunction(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionReturnFunction(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { - ICodeInfo codeInfo = vm.EvmState.Env.CodeInfo; + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; if (codeInfo.Version == 0) goto BadInstruction; - if (!EvmCalculations.UpdateGas(GasCostOf.Retf, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Retf)) goto OutOfGas; // Pop the return state from the return stack. - EvmState.ReturnState stackFrame = vm.EvmState.ReturnStack[--vm.EvmState.ReturnStackHead]; + ReturnState stackFrame = vm.VmState.ReturnStack[--vm.VmState.ReturnStackHead]; vm.SectionIndex = stackFrame.Index; programCounter = stackFrame.Offset; @@ -474,15 +487,16 @@ public static EvmExceptionType InstructionReturnFunction(VirtualMachine vm, ref /// Verifies that the target section does not cause a stack overflow. /// [SkipLocalsInit] - public static EvmExceptionType InstructionJumpFunction(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionJumpFunction(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { - ICodeInfo iCodeInfo = vm.EvmState.Env.CodeInfo; + ICodeInfo iCodeInfo = vm.VmState.Env.CodeInfo; if (iCodeInfo.Version == 0) goto BadInstruction; EofCodeInfo codeInfo = (EofCodeInfo)iCodeInfo; - if (!EvmCalculations.UpdateGas(GasCostOf.Jumpf, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Jumpf)) goto OutOfGas; // Read the target section index from the code. @@ -512,14 +526,15 @@ public static EvmExceptionType InstructionJumpFunction(VirtualMachine vm, ref Ev /// The immediate value (n) specifies that the (n+1)th element from the top is duplicated. /// [SkipLocalsInit] - public static EvmExceptionType InstructionDupN(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionDupN(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.EvmState.Env.CodeInfo; + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; if (codeInfo.Version == 0) goto BadInstruction; - if (!EvmCalculations.UpdateGas(GasCostOf.Dupn, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Dupn)) goto OutOfGas; // Read the immediate operand. @@ -542,14 +557,15 @@ public static EvmExceptionType InstructionDupN(VirtualMachine vm, /// Swaps the top-of-stack with the (n+1)th element. /// [SkipLocalsInit] - public static EvmExceptionType InstructionSwapN(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionSwapN(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.EvmState.Env.CodeInfo; + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; if (codeInfo.Version == 0) goto BadInstruction; - if (!EvmCalculations.UpdateGas(GasCostOf.Swapn, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Swapn)) goto OutOfGas; // Immediate operand determines the swap index. @@ -571,14 +587,15 @@ public static EvmExceptionType InstructionSwapN(VirtualMachine vm, /// The high nibble and low nibble of the operand specify the two swap distances. /// [SkipLocalsInit] - public static EvmExceptionType InstructionExchange(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionExchange(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - ICodeInfo codeInfo = vm.EvmState.Env.CodeInfo; + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; if (codeInfo.Version == 0) goto BadInstruction; - if (!EvmCalculations.UpdateGas(GasCostOf.Swapn, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.Swapn)) goto OutOfGas; ReadOnlySpan codeSection = codeInfo.CodeSection.Span; @@ -608,18 +625,19 @@ public static EvmExceptionType InstructionExchange(VirtualMachine /// A tracing flag type to conditionally report events. /// [SkipLocalsInit] - public static EvmExceptionType InstructionEofCreate(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionEofCreate(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { Metrics.IncrementCreates(); vm.ReturnData = null; IReleaseSpec spec = vm.Spec; - ref readonly ExecutionEnvironment env = ref vm.EvmState.Env; + ExecutionEnvironment env = vm.VmState.Env; if (env.CodeInfo.Version == 0) goto BadInstruction; - if (vm.EvmState.IsStatic) + if (vm.VmState.IsStatic) goto StaticCallViolation; // Cast the current code info to EOF-specific container type. @@ -627,7 +645,7 @@ public static EvmExceptionType InstructionEofCreate(VirtualMachine ExecutionType currentContext = ExecutionType.EOFCREATE; // 1. Deduct the creation gas cost. - if (!EvmCalculations.UpdateGas(GasCostOf.TxCreate, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.TxCreate)) goto OutOfGas; ReadOnlySpan codeSection = container.CodeSection.Span; @@ -644,11 +662,11 @@ public static EvmExceptionType InstructionEofCreate(VirtualMachine } // 4. Charge for memory expansion for the input data. - if (!EvmCalculations.UpdateMemoryCost(vm.EvmState, ref gasAvailable, in dataOffset, dataSize)) + if (!TGasPolicy.UpdateMemoryCost(ref gas, in dataOffset, dataSize, vm.VmState)) goto OutOfGas; // 5. Load the init code (EOF subContainer) from the container using the given index. - ReadOnlySpan initContainer = container.ContainerSection.Span[(Range)container.ContainerSectionOffset(initContainerIndex).Value]; + ReadOnlyMemory initContainer = container.ContainerSection[(Range)container.ContainerSectionOffset(initContainerIndex)!.Value]; // EIP-3860: Check that the init code size does not exceed the maximum allowed. if (spec.IsEip3860Enabled) { @@ -659,7 +677,7 @@ public static EvmExceptionType InstructionEofCreate(VirtualMachine // 6. Deduct gas for keccak256 hashing of the init code. long numberOfWordsInInitCode = EvmCalculations.Div32Ceiling((UInt256)initContainer.Length, out bool outOfGas); long hashCost = GasCostOf.Sha3Word * numberOfWordsInInitCode; - if (outOfGas || !EvmCalculations.UpdateGas(hashCost, ref gasAvailable)) + if (outOfGas || !TGasPolicy.UpdateGas(ref gas, hashCost)) goto OutOfGas; IWorldState state = vm.WorldState; @@ -674,8 +692,9 @@ public static EvmExceptionType InstructionEofCreate(VirtualMachine } // 9. Determine gas available for the new contract execution, applying the 63/64 rule if enabled. + long gasAvailable = TGasPolicy.GetRemainingGas(in gas); long callGas = spec.Use63Over64Rule ? gasAvailable - gasAvailable / 64L : gasAvailable; - if (!EvmCalculations.UpdateGas(callGas, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, callGas)) goto OutOfGas; // 10. Increment the nonce of the sender account. @@ -690,15 +709,15 @@ public static EvmExceptionType InstructionEofCreate(VirtualMachine state.IncrementNonce(env.ExecutingAccount); // 11. Calculate the new contract address. - Address contractAddress = ContractAddress.From(env.ExecutingAccount, salt, initContainer); + Address contractAddress = ContractAddress.From(env.ExecutingAccount, salt, initContainer.Span); if (spec.UseHotAndColdStorage) { // Warm up the target address for subsequent storage accesses. - vm.EvmState.AccessTracker.WarmUp(contractAddress); + vm.VmState.AccessTracker.WarmUp(contractAddress); } if (TTracingInst.IsActive) - vm.EndInstructionTrace(gasAvailable); + vm.EndInstructionTrace(TGasPolicy.GetRemainingGas(in gas)); // Take a snapshot before modifying state for the new contract. Snapshot snapshot = state.TakeSnapshot(); @@ -723,13 +742,14 @@ public static EvmExceptionType InstructionEofCreate(VirtualMachine state.SubtractFromBalance(env.ExecutingAccount, value, spec); // Create new code info for the init code. - ICodeInfo codeInfo = CodeInfoFactory.CreateCodeInfo(initContainer.ToArray(), spec, ValidationStrategy.ExtractHeader); + ICodeInfo codeInfo = CodeInfoFactory.CreateCodeInfo(initContainer, spec, ValidationStrategy.ExtractHeader); // 8. Prepare the callData from the caller’s memory slice. - ReadOnlyMemory callData = vm.EvmState.Memory.Load(dataOffset, dataSize); + if (!vm.VmState.Memory.TryLoad(dataOffset, dataSize, out ReadOnlyMemory callData)) + goto OutOfGas; // Set up the execution environment for the new contract. - ExecutionEnvironment callEnv = new( + ExecutionEnvironment callEnv = ExecutionEnvironment.Rent( codeInfo: codeInfo, executingAccount: contractAddress, caller: env.ExecutingAccount, @@ -739,15 +759,15 @@ public static EvmExceptionType InstructionEofCreate(VirtualMachine value: in value, inputData: in callData); - vm.ReturnData = EvmState.RentFrame( - gasAvailable: callGas, + vm.ReturnData = VmState.RentFrame( + gas: TGasPolicy.FromLong(callGas), outputDestination: 0, outputLength: 0, executionType: currentContext, - isStatic: vm.EvmState.IsStatic, + isStatic: vm.VmState.IsStatic, isCreateOnPreExistingAccount: accountExists, - env: in callEnv, - stateForAccessLists: in vm.EvmState.AccessTracker, + env: callEnv, + stateForAccessLists: in vm.VmState.AccessTracker, snapshot: in snapshot); return EvmExceptionType.None; @@ -765,17 +785,18 @@ public static EvmExceptionType InstructionEofCreate(VirtualMachine /// Extracts the deployment code from a specified container section and prepares the return data. /// [SkipLocalsInit] - public static EvmExceptionType InstructionReturnCode(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionReturnCode(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { // This instruction is only valid in create contexts. - if (!vm.EvmState.ExecutionType.IsAnyCreateEof()) + if (!vm.VmState.ExecutionType.IsAnyCreateEof()) goto BadInstruction; - if (!EvmCalculations.UpdateGas(GasCostOf.ReturnCode, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.ReturnCode)) goto OutOfGas; IReleaseSpec spec = vm.Spec; - EofCodeInfo codeInfo = (EofCodeInfo)vm.EvmState.Env.CodeInfo; + EofCodeInfo codeInfo = (EofCodeInfo)vm.VmState.Env.CodeInfo; // Read the container section index from the code. byte sectionIdx = codeInfo.CodeSection.Span[programCounter++]; @@ -787,7 +808,7 @@ public static EvmExceptionType InstructionReturnCode(VirtualMachine vm, ref EvmS stack.PopUInt256(out UInt256 a); stack.PopUInt256(out UInt256 b); - if (!EvmCalculations.UpdateMemoryCost(vm.EvmState, ref gasAvailable, in a, b)) + if (!TGasPolicy.UpdateMemoryCost(ref gas, in a, b, vm.VmState)) goto OutOfGas; int projectedNewSize = (int)b + deployCodeInfo.DataSection.Length; @@ -798,7 +819,9 @@ public static EvmExceptionType InstructionReturnCode(VirtualMachine vm, ref EvmS } // Load the memory slice as the return data buffer. - vm.ReturnDataBuffer = vm.EvmState.Memory.Load(a, b); + if (!vm.VmState.Memory.TryLoad(a, b, out vm.ReturnDataBuffer)) + goto OutOfGas; + vm.ReturnData = deployCodeInfo; return EvmExceptionType.None; @@ -815,15 +838,16 @@ public static EvmExceptionType InstructionReturnCode(VirtualMachine vm, ref EvmS /// This instruction is only valid when EOF is enabled. /// [SkipLocalsInit] - public static EvmExceptionType InstructionReturnDataLoad(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionReturnDataLoad(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { IReleaseSpec spec = vm.Spec; - ICodeInfo codeInfo = vm.EvmState.Env.CodeInfo; + ICodeInfo codeInfo = vm.VmState.Env.CodeInfo; if (!spec.IsEofEnabled || codeInfo.Version == 0) goto BadInstruction; - gasAvailable -= GasCostOf.VeryLow; + TGasPolicy.Consume(ref gas, GasCostOf.VeryLow); if (!stack.PopUInt256(out UInt256 offset)) goto StackUnderflow; @@ -851,7 +875,8 @@ public static EvmExceptionType InstructionReturnDataLoad(VirtualMa /// A tracing flag type used to report VM state changes during the call. /// [SkipLocalsInit] - public static EvmExceptionType InstructionEofCall(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionEofCall(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TOpEofCall : struct, IOpEofCall where TTracingInst : struct, IFlag { @@ -861,7 +886,7 @@ public static EvmExceptionType InstructionEofCall(Virt IReleaseSpec spec = vm.Spec; vm.ReturnData = null; - ref readonly ExecutionEnvironment env = ref vm.EvmState.Env; + ExecutionEnvironment env = vm.VmState.Env; IWorldState state = vm.WorldState; // This instruction is only available for EOF-enabled contracts. @@ -899,10 +924,10 @@ public static EvmExceptionType InstructionEofCall(Virt } // 3. For non-static calls, ensure that a non-zero transfer value is not used in a static context. - if (vm.EvmState.IsStatic && !transferValue.IsZero) + if (vm.VmState.IsStatic && !transferValue.IsZero) goto StaticCallViolation; // 4. Charge additional gas if a value is transferred in a standard call. - if (typeof(TOpEofCall) == typeof(OpEofCall) && !transferValue.IsZero && !EvmCalculations.UpdateGas(GasCostOf.CallValue, ref gasAvailable)) + if (typeof(TOpEofCall) == typeof(OpEofCall) && !transferValue.IsZero && !TGasPolicy.UpdateGas(ref gas, GasCostOf.CallValue)) goto OutOfGas; // 5. Validate that the targetBytes represent a proper 20-byte address (high 12 bytes must be zero). @@ -917,22 +942,25 @@ public static EvmExceptionType InstructionEofCall(Virt : codeSource; // 6. Update memory cost for the call data. - if (!EvmCalculations.UpdateMemoryCost(vm.EvmState, ref gasAvailable, in dataOffset, in dataLength)) + if (!TGasPolicy.UpdateMemoryCost(ref gas, in dataOffset, in dataLength, vm.VmState)) goto OutOfGas; // 7. Account access gas: ensure target is warm or charge extra gas for cold access. - if (!EvmCalculations.ChargeAccountAccessGasWithDelegation(ref gasAvailable, vm, codeSource)) - goto OutOfGas; + bool _ = vm.TxExecutionContext.CodeInfoRepository + .TryGetDelegation(codeSource, vm.Spec, out Address delegated); + if (!TGasPolicy.ConsumeAccountAccessGasWithDelegation(ref gas, vm.Spec, in vm.VmState.AccessTracker, + vm.TxTracer.IsTracingAccess, codeSource, delegated)) goto OutOfGas; // 8. If the target does not exist or is considered a "dead" account when value is transferred, // charge for account creation. if ((!spec.ClearEmptyAccountWhenTouched && !state.AccountExists(codeSource)) || (spec.ClearEmptyAccountWhenTouched && transferValue != 0 && state.IsDeadAccount(codeSource))) { - if (!EvmCalculations.UpdateGas(GasCostOf.NewAccount, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.NewAccount)) goto OutOfGas; } // 9. Compute the gas available to the callee after reserving a minimum. + long gasAvailable = TGasPolicy.GetRemainingGas(in gas); long callGas = gasAvailable - Math.Max(gasAvailable / 64, MIN_RETAINED_GAS); // 10. Check that the call gas is sufficient, the caller has enough balance, and the call depth is within limits. @@ -948,7 +976,7 @@ public static EvmExceptionType InstructionEofCall(Virt ITxTracer txTracer = vm.TxTracer; if (TTracingInst.IsActive) { - ReadOnlyMemory memoryTrace = vm.EvmState.Memory.Inspect(in dataOffset, 32); + ReadOnlyMemory memoryTrace = vm.VmState.Memory.Inspect(in dataOffset, 32); txTracer.ReportMemoryChange(dataOffset, memoryTrace.Span); txTracer.ReportOperationRemainingGas(gasAvailable); txTracer.ReportOperationError(EvmExceptionType.NotEnoughBalance); @@ -972,10 +1000,11 @@ public static EvmExceptionType InstructionEofCall(Virt } // 12. Deduct gas for the call and prepare the call data. - if (!EvmCalculations.UpdateGas(callGas, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, callGas) || + !vm.VmState.Memory.TryLoad(in dataOffset, dataLength, out ReadOnlyMemory callData)) + { goto OutOfGas; - - ReadOnlyMemory callData = vm.EvmState.Memory.Load(in dataOffset, dataLength); + } // Snapshot the state before the call. Snapshot snapshot = state.TakeSnapshot(); @@ -983,7 +1012,7 @@ public static EvmExceptionType InstructionEofCall(Virt state.SubtractFromBalance(caller, transferValue, spec); // Set up the new execution environment for the call. - ExecutionEnvironment callEnv = new( + ExecutionEnvironment callEnv = ExecutionEnvironment.Rent( codeInfo: targetCodeInfo, executingAccount: target, caller: caller, @@ -993,15 +1022,15 @@ public static EvmExceptionType InstructionEofCall(Virt value: in callValue, inputData: in callData); - vm.ReturnData = EvmState.RentFrame( - gasAvailable: callGas, + vm.ReturnData = VmState.RentFrame( + gas: TGasPolicy.FromLong(callGas), outputDestination: 0, outputLength: 0, executionType: TOpEofCall.ExecutionType, - isStatic: TOpEofCall.IsStatic || vm.EvmState.IsStatic, + isStatic: TOpEofCall.IsStatic || vm.VmState.IsStatic, isCreateOnPreExistingAccount: false, - env: in callEnv, - stateForAccessLists: in vm.EvmState.AccessTracker, + env: callEnv, + stateForAccessLists: in vm.VmState.AccessTracker, snapshot: in snapshot); return EvmExceptionType.None; diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math1Param.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math1Param.cs index 115e1090fdd..65c2c6f2815 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math1Param.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math1Param.cs @@ -6,9 +6,10 @@ using System.Runtime.Intrinsics; using Nethermind.Core; using Nethermind.Core.Extensions; +using Nethermind.Evm.GasPolicy; using Nethermind.Int256; using static System.Runtime.CompilerServices.Unsafe; -using static Nethermind.Evm.VirtualMachine; +using static Nethermind.Evm.VirtualMachineStatics; namespace Nethermind.Evm; @@ -40,21 +41,23 @@ public interface IOpMath1Param /// The operation is defined by the generic parameter , /// which implements . /// + /// The gas policy used for gas accounting. /// A struct implementing for the specific math operation. /// An unused virtual machine instance. /// The EVM stack from which the operand is read and where the result is written. - /// Reference to the available gas, reduced by the operation's cost. + /// Reference to the gas state, updated by the operation's cost. /// Reference to the program counter (unused in this operation). /// /// if the operation completes successfully; otherwise, /// if the stack is empty. /// [SkipLocalsInit] - public static EvmExceptionType InstructionMath1Param(VirtualMachine _, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionMath1Param(VirtualMachine _, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TOpMath : struct, IOpMath1Param { // Deduct the gas cost associated with the math operation. - gasAvailable -= TOpMath.GasCost; + TGasPolicy.Consume(ref gas, TOpMath.GasCost); // Peek at the top element of the stack without removing it. // This avoids an unnecessary pop/push sequence. @@ -110,10 +113,11 @@ public struct OpCLZ : IOpMath1Param /// Extracts a byte from a 256-bit word at the position specified by the stack. /// [SkipLocalsInit] - public static EvmExceptionType InstructionByte(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionByte(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - gasAvailable -= GasCostOf.VeryLow; + TGasPolicy.Consume(ref gas, GasCostOf.VeryLow); // Pop the byte position and the 256-bit word. if (!stack.PopUInt256(out UInt256 a)) @@ -150,9 +154,10 @@ public static EvmExceptionType InstructionByte(VirtualMachine vm, /// Performs sign extension on a 256-bit integer in-place based on a specified byte index. /// [SkipLocalsInit] - public static EvmExceptionType InstructionSignExtend(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionSignExtend(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { - gasAvailable -= GasCostOf.Low; + TGasPolicy.Consume(ref gas, GasCostOf.Low); // Pop the index to determine which byte to use for sign extension. if (!stack.PopUInt256(out UInt256 a)) diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs index c5254acff00..97d43989373 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math2Param.cs @@ -5,8 +5,9 @@ using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Extensions; -using static Nethermind.Evm.VirtualMachine; +using Nethermind.Evm.GasPolicy; using static System.Runtime.CompilerServices.Unsafe; +using static Nethermind.Evm.VirtualMachineStatics; namespace Nethermind.Evm; @@ -38,22 +39,24 @@ public interface IOpMath2Param /// This method pops two UInt256 operands from the stack, applies the operation, /// and then pushes the result onto the stack. /// + /// The gas policy used for gas accounting. /// A struct implementing that defines the specific operation. /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// The gas state which is updated by the operation's cost. /// Reference to the program counter. /// /// if the operation completes successfully; /// otherwise, if insufficient stack elements are available. /// [SkipLocalsInit] - public static EvmExceptionType InstructionMath2Param(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionMath2Param(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TOpMath : struct, IOpMath2Param where TTracingInst : struct, IFlag { // Deduct the gas cost for the specific math operation. - gasAvailable -= TOpMath.GasCost; + TGasPolicy.Consume(ref gas, TOpMath.GasCost); // Pop two operands from the stack. If either pop fails, jump to the underflow handler. if (!stack.PopUInt256(out UInt256 a) || !stack.PopUInt256(out UInt256 b)) goto StackUnderflow; @@ -162,7 +165,17 @@ public struct OpMod : IOpMath2Param { public static long GasCost => GasCostOf.Low; public static void Operation(in UInt256 a, in UInt256 b, out UInt256 result) - => UInt256.Mod(in a, in b, out result); + { + if (b.IsZeroOrOne) + { + // Modulo with 0 or 1 yields zero. + result = default; + } + else + { + UInt256.Mod(in a, in b, out result); + } + } } /// @@ -253,27 +266,28 @@ public static void Operation(in UInt256 a, in UInt256 b, out UInt256 result) /// /// The virtual machine instance. /// The execution stack where the program counter is pushed. - /// Reference to the remaining gas; reduced by the gas cost. + /// Reference to the gas state; updated by the gas cost. /// The current program counter. /// /// on success; or if not enough items on stack. /// [SkipLocalsInit] - public static EvmExceptionType InstructionExp(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionExp(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { // Charge the fixed gas cost for exponentiation. - gasAvailable -= GasCostOf.Exp; + TGasPolicy.Consume(ref gas, GasCostOf.Exp); - // Pop the base value from the stack. - if (!stack.PopUInt256(out UInt256 a)) + // Pop the base value and exponent from the stack. + if (!stack.PopUInt256(out UInt256 a) || + !stack.PopUInt256(out UInt256 exponent)) + { goto StackUnderflow; - - // Pop the exponent as a 256-bit word. - ReadOnlySpan bytes = stack.PopWord256(); + } // Determine the effective byte-length of the exponent. - int leadingZeros = bytes.LeadingZerosCount(); + int leadingZeros = exponent.CountLeadingZeros() >> 3; if (leadingZeros == 32) { // Exponent is zero, so the result is 1. @@ -283,7 +297,7 @@ public static EvmExceptionType InstructionExp(VirtualMachine vm, r { int expSize = 32 - leadingZeros; // Deduct gas proportional to the number of 32-byte words needed to represent the exponent. - gasAvailable -= vm.Spec.GetExpByteCost() * expSize; + TGasPolicy.Consume(ref gas, vm.Spec.GetExpByteCost() * expSize); if (a.IsZero) { @@ -296,7 +310,7 @@ public static EvmExceptionType InstructionExp(VirtualMachine vm, r else { // Perform exponentiation and push the 256-bit result onto the stack. - UInt256.Exp(a, new UInt256(bytes, true), out UInt256 result); + UInt256.Exp(in a, in exponent, out UInt256 result); stack.PushUInt256(in result); } } diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs index e6708f6e850..f9600b8dde3 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Math3Param.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using Nethermind.Core; +using Nethermind.Evm.GasPolicy; namespace Nethermind.Evm; @@ -17,11 +18,12 @@ public interface IOpMath3Param } [SkipLocalsInit] - public static EvmExceptionType InstructionMath3Param(VirtualMachine _, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionMath3Param(VirtualMachine _, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TOpMath : struct, IOpMath3Param where TTracingInst : struct, IFlag { - gasAvailable -= TOpMath.GasCost; + TGasPolicy.Consume(ref gas, TOpMath.GasCost); if (!stack.PopUInt256(out UInt256 a) || !stack.PopUInt256(out UInt256 b) || !stack.PopUInt256(out UInt256 c)) goto StackUnderflow; diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Shifts.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Shifts.cs index 8c5bf453f80..c53d6bc1981 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Shifts.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Shifts.cs @@ -3,6 +3,7 @@ using System.Runtime.CompilerServices; using Nethermind.Core; +using Nethermind.Evm.GasPolicy; using static System.Runtime.CompilerServices.Unsafe; namespace Nethermind.Evm; @@ -38,22 +39,24 @@ public interface IOpShift /// The operation pops the shift amount and the value to shift, unless the shift amount is 256 or more. /// In that case, the value operand is discarded and zero is pushed as the result. /// + /// The gas policy used for gas accounting. /// The specific shift operation (e.g. left or right shift). /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// The gas state which is updated by the operation's cost. /// Reference to the program counter. /// /// if the operation completes successfully; /// otherwise, if there are insufficient stack elements. /// [SkipLocalsInit] - public static EvmExceptionType InstructionShift(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionShift(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TOpShift : struct, IOpShift where TTracingInst : struct, IFlag { // Deduct gas cost specific to the shift operation. - gasAvailable -= TOpShift.GasCost; + TGasPolicy.Consume(ref gas, TOpShift.GasCost); // Pop the shift amount from the stack. if (!stack.PopUInt256(out UInt256 a)) goto StackUnderflow; @@ -85,20 +88,22 @@ public static EvmExceptionType InstructionShift(VirtualM /// Pops a shift amount and a value from the stack, interprets the value as signed, /// and performs an arithmetic right shift. /// + /// The gas policy used for gas accounting. /// The virtual machine instance (unused in the operation logic). /// The EVM stack used for operands and result storage. - /// Reference to the available gas, reduced by the operation's cost. + /// The gas state which is updated by the operation's cost. /// Reference to the program counter (unused in this operation). /// /// if successful; otherwise, /// if insufficient stack elements are available. /// [SkipLocalsInit] - public static EvmExceptionType InstructionSar(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionSar(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { // Deduct the gas cost for the arithmetic shift operation. - gasAvailable -= GasCostOf.VeryLow; + TGasPolicy.Consume(ref gas, GasCostOf.VeryLow); // Pop the shift amount and the value to be shifted. if (!stack.PopUInt256(out UInt256 a) || !stack.PopUInt256(out UInt256 b)) goto StackUnderflow; diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs index b24a08cd8bc..ebe50e55f70 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Stack.cs @@ -8,10 +8,11 @@ using System.Runtime.Intrinsics; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Evm.GasPolicy; +using Nethermind.Int256; namespace Nethermind.Evm; -using Int256; using Word = Vector256; using static Unsafe; @@ -23,15 +24,16 @@ internal static partial class EvmInstructions /// /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// The gas state which is reduced by the operation's cost. /// The program counter. /// if successful; otherwise, . [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static EvmExceptionType InstructionPop(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionPop(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { // Deduct the minimal gas cost for a POP operation. - gasAvailable -= GasCostOf.Base; + TGasPolicy.Consume(ref gas, GasCostOf.Base); // Pop from the stack; if nothing to pop, signal a stack underflow. return stack.PopLimbo() ? EvmExceptionType.None : EvmExceptionType.StackUnderflow; } @@ -114,14 +116,15 @@ public struct Op2 : IOpCount { public static int Count => 2; } /// Push operation for two bytes. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static EvmExceptionType InstructionPush2(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionPush2(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { const int Size = sizeof(ushort); // Deduct a very low gas cost for the push operation. - gasAvailable -= GasCostOf.VeryLow; + TGasPolicy.Consume(ref gas, GasCostOf.VeryLow); // Retrieve the code segment containing immediate data. - ReadOnlySpan code = vm.EvmState.Env.CodeInfo.CodeSpan; + ReadOnlySpan code = vm.VmState.Env.CodeInfo.CodeSpan; ref byte bytes = ref MemoryMarshal.GetReference(code); int remainingCode = code.Length - programCounter; @@ -141,12 +144,12 @@ public static EvmExceptionType InstructionPush2(VirtualMachine vm, if (nextInstruction == Instruction.JUMP) { - gasAvailable -= GasCostOf.Jump; + TGasPolicy.Consume(ref gas, GasCostOf.Jump); vm.OpCodeCount++; } else { - gasAvailable -= GasCostOf.JumpI; + TGasPolicy.Consume(ref gas, GasCostOf.JumpI); vm.OpCodeCount++; bool shouldJump = TestJumpCondition(ref stack, out bool isOverflow); if (isOverflow) goto StackUnderflow; @@ -159,7 +162,7 @@ public static EvmExceptionType InstructionPush2(VirtualMachine vm, } // Validate the jump destination and update the program counter if valid. - if (!Jump((int)destination, ref programCounter, in vm.EvmState.Env)) + if (!Jump((int)destination, ref programCounter, vm.VmState.Env)) goto InvalidJumpDestination; goto Success; @@ -482,14 +485,15 @@ public static void Push(int length, ref EvmStack stack, int progra /// /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// The gas state which is reduced by the operation's cost. /// The program counter. /// on success. [SkipLocalsInit] - public static EvmExceptionType InstructionPush0(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionPush0(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - gasAvailable -= GasCostOf.Base; + TGasPolicy.Consume(ref gas, GasCostOf.Base); stack.PushZero(); return EvmExceptionType.None; } @@ -498,21 +502,24 @@ public static EvmExceptionType InstructionPush0(VirtualMachine vm, /// Executes a PUSH instruction. /// Reads immediate data of a fixed length from the code and pushes it onto the stack. /// + /// The gas policy implementation. /// The push operation implementation defining the byte count. + /// The tracing flag. /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// The gas state which is reduced by the operation's cost. /// Reference to the program counter, which will be advanced. /// on success. [SkipLocalsInit] - public static EvmExceptionType InstructionPush(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) - where TOpCount : IOpCount + public static EvmExceptionType InstructionPush(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy + where TOpCount : struct, IOpCount where TTracingInst : struct, IFlag { // Deduct a very low gas cost for the push operation. - gasAvailable -= GasCostOf.VeryLow; + TGasPolicy.Consume(ref gas, GasCostOf.VeryLow); // Retrieve the code segment containing immediate data. - ReadOnlySpan code = vm.EvmState.Env.CodeInfo.CodeSpan; + ReadOnlySpan code = vm.VmState.Env.CodeInfo.CodeSpan; // Use the push method defined by the specific push operation. TOpCount.Push(TOpCount.Count, ref stack, programCounter, code); // Advance the program counter by the number of bytes consumed. @@ -523,18 +530,21 @@ public static EvmExceptionType InstructionPush(VirtualMa /// /// Executes a DUP operation which duplicates the nth stack element. /// + /// The gas policy implementation. /// The duplicate operation implementation that defines which element to duplicate. + /// The tracing flag. /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// The gas state which is reduced by the operation's cost. /// Reference to the program counter. /// on success or if insufficient stack elements. [SkipLocalsInit] - public static EvmExceptionType InstructionDup(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) - where TOpCount : IOpCount + public static EvmExceptionType InstructionDup(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy + where TOpCount : struct, IOpCount where TTracingInst : struct, IFlag { - gasAvailable -= GasCostOf.VeryLow; + TGasPolicy.Consume(ref gas, GasCostOf.VeryLow); return stack.Dup(TOpCount.Count); } @@ -542,18 +552,21 @@ public static EvmExceptionType InstructionDup(VirtualMac /// /// Executes a SWAP operation which swaps the top element with the (n+1)th element. /// + /// The gas policy implementation. /// The swap operation implementation that defines the swap depth. + /// The tracing flag. /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// The gas state which is reduced by the operation's cost. /// Reference to the program counter. /// on success or if insufficient elements. [SkipLocalsInit] - public static EvmExceptionType InstructionSwap(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) - where TOpCount : IOpCount + public static EvmExceptionType InstructionSwap(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy + where TOpCount : struct, IOpCount where TTracingInst : struct, IFlag { - gasAvailable -= GasCostOf.VeryLow; + TGasPolicy.Consume(ref gas, GasCostOf.VeryLow); // Swap the top element with the (n+1)th element; ensure adequate stack depth. return stack.Swap(TOpCount.Count + 1); } @@ -563,20 +576,22 @@ public static EvmExceptionType InstructionSwap(VirtualMa /// Pops data offset and length, then pops a fixed number of topics from the stack. /// Validates memory expansion and deducts gas accordingly. /// + /// The gas policy implementation. /// Specifies the number of log topics (as defined by its Count property). /// The virtual machine instance. /// The execution stack. - /// The available gas which is reduced by the operation's cost. + /// The gas state which is reduced by the operation's cost. /// Reference to the program counter. /// /// if the log is successfully recorded; otherwise, an appropriate exception type such as /// , , or . /// [SkipLocalsInit] - public static EvmExceptionType InstructionLog(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionLog(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TOpCount : struct, IOpCount { - EvmState vmState = vm.EvmState; + VmState vmState = vm.VmState; // Logging is not permitted in static call contexts. if (vmState.IsStatic) goto StaticCallViolation; @@ -587,14 +602,15 @@ public static EvmExceptionType InstructionLog(VirtualMachine vm, ref E long topicsCount = TOpCount.Count; // Ensure that the memory expansion for the log data is accounted for. - if (!EvmCalculations.UpdateMemoryCost(vmState, ref gasAvailable, in position, length)) goto OutOfGas; + if (!TGasPolicy.UpdateMemoryCost(ref gas, in position, length, vmState)) goto OutOfGas; // Deduct gas for the log entry itself, including per-topic and per-byte data costs. - if (!EvmCalculations.UpdateGas( - GasCostOf.Log + topicsCount * GasCostOf.LogTopic + - (long)length * GasCostOf.LogData, ref gasAvailable)) goto OutOfGas; + long dataSize = (long)length; + if (!TGasPolicy.ConsumeLogEmission(ref gas, topicsCount, dataSize)) goto OutOfGas; // Load the log data from memory. - ReadOnlyMemory data = vmState.Memory.Load(in position, length); + if (!vmState.Memory.TryLoad(in position, length, out ReadOnlyMemory data)) + goto OutOfGas; + // Prepare the topics array by popping the corresponding number of words from the stack. Hash256[] topics = new Hash256[topicsCount]; for (int i = 0; i < topics.Length; i++) @@ -625,4 +641,3 @@ public static EvmExceptionType InstructionLog(VirtualMachine vm, ref E return EvmExceptionType.OutOfGas; } } - diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs index 9d174c9053c..1cd5b0f56ec 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.Storage.cs @@ -6,7 +6,8 @@ using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; -using static Nethermind.Evm.VirtualMachine; +using Nethermind.Evm.GasPolicy; +using static Nethermind.Evm.VirtualMachineStatics; namespace Nethermind.Evm; @@ -27,24 +28,25 @@ internal static partial class EvmInstructions /// /// The virtual machine instance executing the instruction. /// The EVM stack. - /// The available gas, which is reduced by the gas cost of the operation. + /// The gas state, updated by the operation's cost. /// The program counter. /// An indicating the result of the operation. [SkipLocalsInit] - public static EvmExceptionType InstructionTLoad(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionTLoad(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { // Increment the opcode metric for TLOAD. Metrics.TloadOpcode++; // Deduct the fixed gas cost for TLOAD. - gasAvailable -= GasCostOf.TLoad; + TGasPolicy.Consume(ref gas, GasCostOf.TLoad); // Attempt to pop the key (offset) from the stack; if unavailable, signal a stack underflow. if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; // Construct a transient storage cell using the executing account and the provided offset. - StorageCell storageCell = new(vm.EvmState.Env.ExecutingAccount, in result); + StorageCell storageCell = new(vm.VmState.Env.ExecutingAccount, in result); // Retrieve the value from transient storage. ReadOnlySpan value = vm.WorldState.GetTransientState(in storageCell); @@ -52,10 +54,10 @@ public static EvmExceptionType InstructionTLoad(VirtualMachine vm, // Push the retrieved value onto the stack. stack.PushBytes(value); - // If storage tracing is enabled, record the operation (ensuring gas remains non-negative). + // If storage tracing is enabled, record the operation. if (vm.TxTracer.IsTracingStorage) { - if (gasAvailable < 0) goto OutOfGas; + if (TGasPolicy.GetRemainingGas(in gas) < 0) goto OutOfGas; vm.TxTracer.LoadOperationTransientStorage(storageCell.Address, result, value); } @@ -76,22 +78,23 @@ public static EvmExceptionType InstructionTLoad(VirtualMachine vm, /// /// The virtual machine instance executing the instruction. /// The EVM stack. - /// The available gas, reduced by the cost of TSTORE. + /// The gas state, updated by the operation's cost. /// The program counter. /// An indicating success or failure. [SkipLocalsInit] - public static EvmExceptionType InstructionTStore(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionTStore(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy { // Increment the opcode metric for TSTORE. Metrics.TstoreOpcode++; - EvmState vmState = vm.EvmState; + VmState vmState = vm.VmState; // Disallow storage modification during static calls. if (vmState.IsStatic) goto StaticCallViolation; // Deduct the gas cost for TSTORE. - gasAvailable -= GasCostOf.TStore; + TGasPolicy.Consume(ref gas, GasCostOf.TStore); // Pop the key (offset) from the stack; if unavailable, signal a stack underflow. if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; @@ -108,7 +111,7 @@ public static EvmExceptionType InstructionTStore(VirtualMachine vm, ref EvmStack // If storage tracing is enabled, retrieve the current stored value and log the operation. if (vm.TxTracer.IsTracingStorage) { - if (gasAvailable < 0) goto OutOfGas; + if (TGasPolicy.GetRemainingGas(in gas) < 0) goto OutOfGas; ReadOnlySpan currentValue = vm.WorldState.GetTransientState(in storageCell); vm.TxTracer.SetOperationTransientStorage(storageCell.Address, result, bytes, currentValue); } @@ -137,10 +140,11 @@ public static EvmExceptionType InstructionTStore(VirtualMachine vm, ref EvmStack /// The program counter. /// An result. [SkipLocalsInit] - public static EvmExceptionType InstructionMStore(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionMStore(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - gasAvailable -= GasCostOf.VeryLow; + TGasPolicy.Consume(ref gas, GasCostOf.VeryLow); // Pop the memory offset; if not available, signal a stack underflow. if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; @@ -148,13 +152,13 @@ public static EvmExceptionType InstructionMStore(VirtualMachine vm // Retrieve the 32-byte word to be stored. Span bytes = stack.PopWord256(); - EvmState vmState = vm.EvmState; + VmState vmState = vm.VmState; // Update the memory cost for a 32-byte store; if insufficient gas, signal out-of-gas. - if (!EvmCalculations.UpdateMemoryCost(vmState, ref gasAvailable, in result, in BigInt32)) goto OutOfGas; - - // Write the 32-byte word into memory. - vmState.Memory.SaveWord(in result, bytes); + if (!TGasPolicy.UpdateMemoryCost(ref gas, in result, in BigInt32, vmState) || !vmState.Memory.TrySaveWord(in result, bytes)) + { + goto OutOfGas; + } // Report memory changes if tracing is active. if (TTracingInst.IsActive) @@ -182,10 +186,11 @@ public static EvmExceptionType InstructionMStore(VirtualMachine vm /// The program counter. /// An result. [SkipLocalsInit] - public static EvmExceptionType InstructionMStore8(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionMStore8(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - gasAvailable -= GasCostOf.VeryLow; + TGasPolicy.Consume(ref gas, GasCostOf.VeryLow); // Pop the memory offset from the stack; if missing, signal a stack underflow. if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; @@ -193,13 +198,14 @@ public static EvmExceptionType InstructionMStore8(VirtualMachine v // Pop a single byte from the stack. byte data = stack.PopByte(); - EvmState vmState = vm.EvmState; + VmState vmState = vm.VmState; // Update the memory cost for a single-byte extension; if insufficient, signal out-of-gas. - if (!EvmCalculations.UpdateMemoryCost(vmState, ref gasAvailable, in result, in UInt256.One)) goto OutOfGas; - - // Write the single byte to memory. - vmState.Memory.SaveByte(in result, data); + if (!TGasPolicy.UpdateMemoryCost(ref gas, in result, in UInt256.One, vmState) || + !vmState.Memory.TrySaveByte(in result, data)) + { + goto OutOfGas; + } // Report the memory change if tracing is active. if (TTracingInst.IsActive) @@ -227,21 +233,23 @@ public static EvmExceptionType InstructionMStore8(VirtualMachine v /// The program counter. /// An result. [SkipLocalsInit] - public static EvmExceptionType InstructionMLoad(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionMLoad(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - gasAvailable -= GasCostOf.VeryLow; + TGasPolicy.Consume(ref gas, GasCostOf.VeryLow); // Pop the memory offset; if missing, signal a stack underflow. if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; - EvmState vmState = vm.EvmState; + VmState vmState = vm.VmState; // Update memory cost for a 32-byte load. - if (!EvmCalculations.UpdateMemoryCost(vmState, ref gasAvailable, in result, in BigInt32)) goto OutOfGas; - - // Load the 32-byte word from memory. - Span bytes = vmState.Memory.LoadSpan(in result); + if (!TGasPolicy.UpdateMemoryCost(ref gas, in result, in BigInt32, vmState) || + !vmState.Memory.TryLoadSpan(in result, out Span bytes)) + { + goto OutOfGas; + } // Report the memory load if tracing is active. if (TTracingInst.IsActive) @@ -272,7 +280,8 @@ public static EvmExceptionType InstructionMLoad(VirtualMachine vm, /// The program counter. /// An result. [SkipLocalsInit] - public static EvmExceptionType InstructionMCopy(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionMCopy(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { // Increment the opcode metric for MCOPY. @@ -282,23 +291,24 @@ public static EvmExceptionType InstructionMCopy(VirtualMachine vm, if (!stack.PopUInt256(out UInt256 a) || !stack.PopUInt256(out UInt256 b) || !stack.PopUInt256(out UInt256 c)) goto StackUnderflow; // Calculate additional gas cost based on the length (using a division rounding-up method) and deduct the total cost. - gasAvailable -= GasCostOf.VeryLow + GasCostOf.VeryLow * EvmCalculations.Div32Ceiling(c, out bool outOfGas); + TGasPolicy.Consume(ref gas, GasCostOf.VeryLow + GasCostOf.VeryLow * EvmCalculations.Div32Ceiling(c, out bool outOfGas)); if (outOfGas) goto OutOfGas; - EvmState vmState = vm.EvmState; + VmState vmState = vm.VmState; // Update memory cost for the destination area (largest offset among source and destination) over the specified length. - if (!EvmCalculations.UpdateMemoryCost(vmState, ref gasAvailable, UInt256.Max(b, a), c)) goto OutOfGas; - - // Load the specified memory segment from the source offset. - Span bytes = vmState.Memory.LoadSpan(in b, c); + if (!TGasPolicy.UpdateMemoryCost(ref gas, UInt256.Max(b, a), c, vmState) || + !vmState.Memory.TryLoadSpan(in b, c, out Span bytes)) + { + goto OutOfGas; + } // Report the memory change at the source if tracing is active. if (TTracingInst.IsActive) vm.TxTracer.ReportMemoryChange(b, bytes); // Write the bytes into memory at the destination offset. - vmState.Memory.Save(in a, bytes); + if (!vmState.Memory.TrySave(in a, bytes)) goto OutOfGas; // Report the memory change at the destination if tracing is active. if (TTracingInst.IsActive) @@ -319,27 +329,29 @@ public static EvmExceptionType InstructionMCopy(VirtualMachine vm, /// and updates persistent storage for the executing account. This method handles legacy gas calculations. /// /// + /// The gas policy used for gas accounting. /// A flag type indicating whether detailed tracing is enabled. /// The virtual machine instance. /// The EVM stack. - /// The available gas, which is decremented by multiple cost adjustments during storage modification. + /// The gas state, updated by the operation's cost. /// The program counter. /// An indicating the outcome. [SkipLocalsInit] - internal static EvmExceptionType InstructionSStoreUnmetered(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + internal static EvmExceptionType InstructionSStoreUnmetered(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { // Increment the SSTORE opcode metric. Metrics.IncrementSStoreOpcode(); - EvmState vmState = vm.EvmState; + VmState vmState = vm.VmState; // Disallow storage modifications in static calls. if (vmState.IsStatic) goto StaticCallViolation; IReleaseSpec spec = vm.Spec; // For legacy metering: ensure there is enough gas for the SSTORE reset cost before reading storage. - if (!EvmCalculations.UpdateGas(spec.GetSStoreResetCost(), ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, spec.GetSStoreResetCost())) goto OutOfGas; // Pop the key and then the new value for storage; signal underflow if unavailable. @@ -354,7 +366,7 @@ internal static EvmExceptionType InstructionSStoreUnmetered(Virtua StorageCell storageCell = new(vmState.Env.ExecutingAccount, in result); // Charge gas based on whether this is a cold or warm storage access. - if (!EvmCalculations.ChargeStorageAccessGas(ref gasAvailable, vm, in storageCell, StorageAccessType.SSTORE, spec)) + if (!TGasPolicy.ConsumeStorageAccessGas(ref gas, in vmState.AccessTracker, vm.TxTracer.IsTracingAccess, in storageCell, StorageAccessType.SSTORE, spec)) goto OutOfGas; // Retrieve the current value from persistent storage. @@ -380,7 +392,7 @@ internal static EvmExceptionType InstructionSStoreUnmetered(Virtua // When setting a non-zero value over an existing zero, apply the difference in gas costs. else if (currentIsZero) { - if (!EvmCalculations.UpdateGas(GasCostOf.SSet - GasCostOf.SReset, ref gasAvailable)) + if (!TGasPolicy.UpdateGas(ref gas, GasCostOf.SSet - GasCostOf.SReset)) goto OutOfGas; } @@ -418,22 +430,24 @@ internal static EvmExceptionType InstructionSStoreUnmetered(Virtua /// and updates persistent storage for the executing account. This method handles net metered gas calculations. /// /// + /// The gas policy used for gas accounting. /// A flag type indicating whether detailed tracing is enabled. /// A flag type indicating whether stipend fix is enabled. /// The virtual machine instance. /// The EVM stack. - /// The available gas, which is decremented by multiple cost adjustments during storage modification. + /// The gas state, updated by the operation's cost. /// The program counter. /// An indicating the outcome. [SkipLocalsInit] - internal static EvmExceptionType InstructionSStoreMetered(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + internal static EvmExceptionType InstructionSStoreMetered(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag where TUseNetGasStipendFix : struct, IFlag { // Increment the SSTORE opcode metric. Metrics.IncrementSStoreOpcode(); - EvmState vmState = vm.EvmState; + VmState vmState = vm.VmState; // Disallow storage modifications in static calls. if (vmState.IsStatic) goto StaticCallViolation; @@ -444,7 +458,7 @@ internal static EvmExceptionType InstructionSStoreMetered bytes) + private static void TraceSstore(VirtualMachine vm, bool newIsZero, in StorageCell storageCell, ReadOnlySpan bytes) + where TGasPolicy : struct, IGasPolicy { ReadOnlySpan valueToStore = newIsZero ? BytesZero.AsSpan() : bytes; byte[] storageBytes = new byte[32]; // Allocated on the heap to avoid stack allocation. @@ -589,11 +604,12 @@ private static void TraceSstore(VirtualMachine vm, bool newIsZero, in StorageCel /// /// The virtual machine instance. /// The EVM stack. - /// The remaining gas, reduced by the SLOAD cost and any storage access gas adjustments. + /// The gas state, updated by the operation's cost. /// The program counter (unused in this instruction). /// An indicating the result of the operation. [SkipLocalsInit] - internal static EvmExceptionType InstructionSLoad(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + internal static EvmExceptionType InstructionSLoad(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { IReleaseSpec spec = vm.Spec; @@ -602,20 +618,18 @@ internal static EvmExceptionType InstructionSLoad(VirtualMachine v Metrics.IncrementSLoadOpcode(); // Deduct the gas cost for performing an SLOAD. - gasAvailable -= spec.GetSLoadCost(); + TGasPolicy.Consume(ref gas, spec.GetSLoadCost()); // Pop the key from the stack; if unavailable, signal a stack underflow. if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; // Construct the storage cell for the executing account. - Address executingAccount = vm.EvmState.Env.ExecutingAccount; + Address executingAccount = vm.VmState.Env.ExecutingAccount; StorageCell storageCell = new(executingAccount, in result); // Charge additional gas based on whether the storage cell is hot or cold. - if (!EvmCalculations.ChargeStorageAccessGas(ref gasAvailable, vm, in storageCell, StorageAccessType.SLOAD, spec)) - { + if (!TGasPolicy.ConsumeStorageAccessGas(ref gas, in vm.VmState.AccessTracker, vm.TxTracer.IsTracingAccess, in storageCell, StorageAccessType.SLOAD, spec)) goto OutOfGas; - } // Retrieve the persistent storage value and push it onto the stack. ReadOnlySpan value = vm.WorldState.Get(in storageCell); @@ -641,16 +655,17 @@ internal static EvmExceptionType InstructionSLoad(VirtualMachine v /// zero-padding if necessary. /// [SkipLocalsInit] - public static EvmExceptionType InstructionCallDataLoad(VirtualMachine vm, ref EvmStack stack, ref long gasAvailable, ref int programCounter) + public static EvmExceptionType InstructionCallDataLoad(VirtualMachine vm, ref EvmStack stack, ref TGasPolicy gas, ref int programCounter) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { - gasAvailable -= GasCostOf.VeryLow; + TGasPolicy.Consume(ref gas, GasCostOf.VeryLow); // Pop the offset from which to load call data. if (!stack.PopUInt256(out UInt256 result)) goto StackUnderflow; // Load 32 bytes from input data, applying zero padding as needed. - stack.PushBytes(vm.EvmState.Env.InputData.SliceWithZeroPadding(result, 32)); + stack.PushBytes(vm.VmState.Env.InputData.SliceWithZeroPadding(result, 32)); return EvmExceptionType.None; // Jump forward to be unpredicted by the branch predictor. diff --git a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.cs b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.cs index e3b6646cda9..cd1d55436d8 100644 --- a/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.cs +++ b/src/Nethermind/Nethermind.Evm/Instructions/EvmInstructions.cs @@ -1,15 +1,15 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Runtime.CompilerServices; using Nethermind.Core; using Nethermind.Core.Specs; +using Nethermind.Evm.GasPolicy; [assembly: InternalsVisibleTo("Nethermind.Evm.Precompiles")] namespace Nethermind.Evm; -using unsafe OpCode = delegate*; - internal static unsafe partial class EvmInstructions { /// @@ -17,14 +17,16 @@ internal static unsafe partial class EvmInstructions /// Each of the 256 entries in the returned array corresponds to an EVM instruction, /// with unassigned opcodes defaulting to a bad instruction handler. /// + /// The gas policy type used for gas accounting. /// A struct implementing IFlag used for tracing purposes. /// The release specification containing enabled features and opcode flags. /// An array of function pointers (opcode handlers) indexed by opcode value. - public static OpCode[] GenerateOpCodes(IReleaseSpec spec) + public static delegate*, ref EvmStack, ref TGasPolicy, ref int, EvmExceptionType>[] GenerateOpCodes(IReleaseSpec spec) + where TGasPolicy : struct, IGasPolicy where TTracingInst : struct, IFlag { // Allocate lookup table for all possible opcodes. - OpCode[] lookup = new OpCode[byte.MaxValue + 1]; + var lookup = new delegate*, ref EvmStack, ref TGasPolicy, ref int, EvmExceptionType>[byte.MaxValue + 1]; for (int i = 0; i < lookup.Length; i++) { @@ -33,275 +35,277 @@ public static OpCode[] GenerateOpCodes(IReleaseSpec spec) // Set basic control and arithmetic opcodes. lookup[(int)Instruction.STOP] = &InstructionStop; - lookup[(int)Instruction.ADD] = &InstructionMath2Param; - lookup[(int)Instruction.MUL] = &InstructionMath2Param; - lookup[(int)Instruction.SUB] = &InstructionMath2Param; - lookup[(int)Instruction.DIV] = &InstructionMath2Param; - lookup[(int)Instruction.SDIV] = &InstructionMath2Param; - lookup[(int)Instruction.MOD] = &InstructionMath2Param; - lookup[(int)Instruction.SMOD] = &InstructionMath2Param; - lookup[(int)Instruction.ADDMOD] = &InstructionMath3Param; - lookup[(int)Instruction.MULMOD] = &InstructionMath3Param; - lookup[(int)Instruction.EXP] = &InstructionExp; - lookup[(int)Instruction.SIGNEXTEND] = &InstructionSignExtend; + lookup[(int)Instruction.ADD] = &InstructionMath2Param; + lookup[(int)Instruction.MUL] = &InstructionMath2Param; + lookup[(int)Instruction.SUB] = &InstructionMath2Param; + lookup[(int)Instruction.DIV] = &InstructionMath2Param; + lookup[(int)Instruction.SDIV] = &InstructionMath2Param; + lookup[(int)Instruction.MOD] = &InstructionMath2Param; + lookup[(int)Instruction.SMOD] = &InstructionMath2Param; + lookup[(int)Instruction.ADDMOD] = &InstructionMath3Param; + lookup[(int)Instruction.MULMOD] = &InstructionMath3Param; + lookup[(int)Instruction.EXP] = &InstructionExp; + lookup[(int)Instruction.SIGNEXTEND] = &InstructionSignExtend; // Comparison and bitwise opcodes. - lookup[(int)Instruction.LT] = &InstructionMath2Param; - lookup[(int)Instruction.GT] = &InstructionMath2Param; - lookup[(int)Instruction.SLT] = &InstructionMath2Param; - lookup[(int)Instruction.SGT] = &InstructionMath2Param; - lookup[(int)Instruction.EQ] = &InstructionBitwise; - lookup[(int)Instruction.ISZERO] = &InstructionMath1Param; - lookup[(int)Instruction.AND] = &InstructionBitwise; - lookup[(int)Instruction.OR] = &InstructionBitwise; - lookup[(int)Instruction.XOR] = &InstructionBitwise; - lookup[(int)Instruction.NOT] = &InstructionMath1Param; - lookup[(int)Instruction.BYTE] = &InstructionByte; + lookup[(int)Instruction.LT] = &InstructionMath2Param; + lookup[(int)Instruction.GT] = &InstructionMath2Param; + lookup[(int)Instruction.SLT] = &InstructionMath2Param; + lookup[(int)Instruction.SGT] = &InstructionMath2Param; + lookup[(int)Instruction.EQ] = &InstructionBitwise; + lookup[(int)Instruction.ISZERO] = &InstructionMath1Param; + lookup[(int)Instruction.AND] = &InstructionBitwise; + lookup[(int)Instruction.OR] = &InstructionBitwise; + lookup[(int)Instruction.XOR] = &InstructionBitwise; + lookup[(int)Instruction.NOT] = &InstructionMath1Param; + lookup[(int)Instruction.BYTE] = &InstructionByte; // Conditional: enable shift opcodes if the spec allows. if (spec.ShiftOpcodesEnabled) { - lookup[(int)Instruction.SHL] = &InstructionShift; - lookup[(int)Instruction.SHR] = &InstructionShift; - lookup[(int)Instruction.SAR] = &InstructionSar; + lookup[(int)Instruction.SHL] = &InstructionShift; + lookup[(int)Instruction.SHR] = &InstructionShift; + lookup[(int)Instruction.SAR] = &InstructionSar; } if (spec.CLZEnabled) { - lookup[(int)Instruction.CLZ] = &InstructionMath1Param; + lookup[(int)Instruction.CLZ] = &InstructionMath1Param; } // Cryptographic hash opcode. - lookup[(int)Instruction.KECCAK256] = &InstructionKeccak256; + lookup[(int)Instruction.KECCAK256] = &InstructionKeccak256; // Environment opcodes. - lookup[(int)Instruction.ADDRESS] = &InstructionEnvAddress; - lookup[(int)Instruction.BALANCE] = &InstructionBalance; - lookup[(int)Instruction.ORIGIN] = &InstructionEnv32Bytes; - lookup[(int)Instruction.CALLER] = &InstructionEnvAddress; - lookup[(int)Instruction.CALLVALUE] = &InstructionEnvUInt256; - lookup[(int)Instruction.CALLDATALOAD] = &InstructionCallDataLoad; - lookup[(int)Instruction.CALLDATASIZE] = &InstructionEnvUInt32; - lookup[(int)Instruction.CALLDATACOPY] = &InstructionCodeCopy; - lookup[(int)Instruction.CODESIZE] = &InstructionEnvUInt32; - lookup[(int)Instruction.CODECOPY] = &InstructionCodeCopy; - lookup[(int)Instruction.GASPRICE] = &InstructionBlkUInt256; - - lookup[(int)Instruction.EXTCODESIZE] = &InstructionExtCodeSize; - lookup[(int)Instruction.EXTCODECOPY] = &InstructionExtCodeCopy; + lookup[(int)Instruction.ADDRESS] = &InstructionEnvAddress, TTracingInst>; + lookup[(int)Instruction.BALANCE] = &InstructionBalance; + lookup[(int)Instruction.ORIGIN] = &InstructionEnv32Bytes, TTracingInst>; + lookup[(int)Instruction.CALLER] = &InstructionEnvAddress, TTracingInst>; + lookup[(int)Instruction.CALLVALUE] = &InstructionEnvUInt256, TTracingInst>; + lookup[(int)Instruction.CALLDATALOAD] = &InstructionCallDataLoad; + lookup[(int)Instruction.CALLDATASIZE] = &InstructionEnvUInt32, TTracingInst>; + lookup[(int)Instruction.CALLDATACOPY] = + &InstructionCodeCopy, TTracingInst>; + lookup[(int)Instruction.CODESIZE] = &InstructionEnvUInt32, TTracingInst>; + lookup[(int)Instruction.CODECOPY] = &InstructionCodeCopy, TTracingInst>; + lookup[(int)Instruction.GASPRICE] = &InstructionBlkUInt256, TTracingInst>; + + lookup[(int)Instruction.EXTCODESIZE] = &InstructionExtCodeSize; + lookup[(int)Instruction.EXTCODECOPY] = &InstructionExtCodeCopy; // Return data opcodes (if enabled). if (spec.ReturnDataOpcodesEnabled) { - lookup[(int)Instruction.RETURNDATASIZE] = &InstructionReturnDataSize; - lookup[(int)Instruction.RETURNDATACOPY] = &InstructionReturnDataCopy; + lookup[(int)Instruction.RETURNDATASIZE] = &InstructionReturnDataSize; + lookup[(int)Instruction.RETURNDATACOPY] = &InstructionReturnDataCopy; } // Extended code hash opcode handling. if (spec.ExtCodeHashOpcodeEnabled) { lookup[(int)Instruction.EXTCODEHASH] = spec.IsEofEnabled ? - &InstructionExtCodeHashEof : - &InstructionExtCodeHash; + &InstructionExtCodeHashEof : + &InstructionExtCodeHash; } - lookup[(int)Instruction.BLOCKHASH] = &InstructionBlockHash; + lookup[(int)Instruction.BLOCKHASH] = &InstructionBlockHash; // More environment opcodes. - lookup[(int)Instruction.COINBASE] = &InstructionBlkAddress; - lookup[(int)Instruction.TIMESTAMP] = &InstructionBlkUInt64; - lookup[(int)Instruction.NUMBER] = &InstructionBlkUInt64; - lookup[(int)Instruction.PREVRANDAO] = &InstructionPrevRandao; - lookup[(int)Instruction.GASLIMIT] = &InstructionBlkUInt64; + lookup[(int)Instruction.COINBASE] = &InstructionBlkAddress, TTracingInst>; + lookup[(int)Instruction.TIMESTAMP] = &InstructionBlkUInt64, TTracingInst>; + lookup[(int)Instruction.NUMBER] = &InstructionBlkUInt64, TTracingInst>; + lookup[(int)Instruction.PREVRANDAO] = &InstructionPrevRandao; + lookup[(int)Instruction.GASLIMIT] = &InstructionBlkUInt64, TTracingInst>; if (spec.ChainIdOpcodeEnabled) { - lookup[(int)Instruction.CHAINID] = &InstructionEnv32Bytes; + lookup[(int)Instruction.CHAINID] = &InstructionEnv32Bytes, TTracingInst>; } if (spec.SelfBalanceOpcodeEnabled) { - lookup[(int)Instruction.SELFBALANCE] = &InstructionSelfBalance; + lookup[(int)Instruction.SELFBALANCE] = &InstructionSelfBalance; } if (spec.BaseFeeEnabled) { - lookup[(int)Instruction.BASEFEE] = &InstructionBlkUInt256; + lookup[(int)Instruction.BASEFEE] = &InstructionBlkUInt256, TTracingInst>; } if (spec.IsEip4844Enabled) { - lookup[(int)Instruction.BLOBHASH] = &InstructionBlobHash; + lookup[(int)Instruction.BLOBHASH] = &InstructionBlobHash; } if (spec.BlobBaseFeeEnabled) { - lookup[(int)Instruction.BLOBBASEFEE] = &InstructionBlobBaseFee; + lookup[(int)Instruction.BLOBBASEFEE] = &InstructionBlobBaseFee; } // Gap: opcodes 0x4b to 0x4f are unassigned. // Memory and storage instructions. lookup[(int)Instruction.POP] = &InstructionPop; - lookup[(int)Instruction.MLOAD] = &InstructionMLoad; - lookup[(int)Instruction.MSTORE] = &InstructionMStore; - lookup[(int)Instruction.MSTORE8] = &InstructionMStore8; - lookup[(int)Instruction.SLOAD] = &InstructionSLoad; + lookup[(int)Instruction.MLOAD] = &InstructionMLoad; + lookup[(int)Instruction.MSTORE] = &InstructionMStore; + lookup[(int)Instruction.MSTORE8] = &InstructionMStore8; + lookup[(int)Instruction.SLOAD] = &InstructionSLoad; lookup[(int)Instruction.SSTORE] = spec.UseNetGasMetering ? (spec.UseNetGasMeteringWithAStipendFix ? - &InstructionSStoreMetered : - &InstructionSStoreMetered + &InstructionSStoreMetered : + &InstructionSStoreMetered ) : - &InstructionSStoreUnmetered; + &InstructionSStoreUnmetered; // Jump instructions. lookup[(int)Instruction.JUMP] = &InstructionJump; lookup[(int)Instruction.JUMPI] = &InstructionJumpIf; - lookup[(int)Instruction.PC] = &InstructionProgramCounter; - lookup[(int)Instruction.MSIZE] = &InstructionEnvUInt64; - lookup[(int)Instruction.GAS] = &InstructionGas; + lookup[(int)Instruction.PC] = &InstructionProgramCounter; + lookup[(int)Instruction.MSIZE] = &InstructionEnvUInt64, TTracingInst>; + lookup[(int)Instruction.GAS] = &InstructionGas; lookup[(int)Instruction.JUMPDEST] = &InstructionJumpDest; // Transient storage opcodes. if (spec.TransientStorageEnabled) { - lookup[(int)Instruction.TLOAD] = &InstructionTLoad; + lookup[(int)Instruction.TLOAD] = &InstructionTLoad; lookup[(int)Instruction.TSTORE] = &InstructionTStore; } if (spec.MCopyIncluded) { - lookup[(int)Instruction.MCOPY] = &InstructionMCopy; + lookup[(int)Instruction.MCOPY] = &InstructionMCopy; } // Optional PUSH0 instruction. if (spec.IncludePush0Instruction) { - lookup[(int)Instruction.PUSH0] = &InstructionPush0; + lookup[(int)Instruction.PUSH0] = &InstructionPush0; } // PUSH opcodes (PUSH1 to PUSH32). - lookup[(int)Instruction.PUSH1] = &InstructionPush; - lookup[(int)Instruction.PUSH2] = &InstructionPush2; - lookup[(int)Instruction.PUSH3] = &InstructionPush; - lookup[(int)Instruction.PUSH4] = &InstructionPush; - lookup[(int)Instruction.PUSH5] = &InstructionPush; - lookup[(int)Instruction.PUSH6] = &InstructionPush; - lookup[(int)Instruction.PUSH7] = &InstructionPush; - lookup[(int)Instruction.PUSH8] = &InstructionPush; - lookup[(int)Instruction.PUSH9] = &InstructionPush; - lookup[(int)Instruction.PUSH10] = &InstructionPush; - lookup[(int)Instruction.PUSH11] = &InstructionPush; - lookup[(int)Instruction.PUSH12] = &InstructionPush; - lookup[(int)Instruction.PUSH13] = &InstructionPush; - lookup[(int)Instruction.PUSH14] = &InstructionPush; - lookup[(int)Instruction.PUSH15] = &InstructionPush; - lookup[(int)Instruction.PUSH16] = &InstructionPush; - lookup[(int)Instruction.PUSH17] = &InstructionPush; - lookup[(int)Instruction.PUSH18] = &InstructionPush; - lookup[(int)Instruction.PUSH19] = &InstructionPush; - lookup[(int)Instruction.PUSH20] = &InstructionPush; - lookup[(int)Instruction.PUSH21] = &InstructionPush; - lookup[(int)Instruction.PUSH22] = &InstructionPush; - lookup[(int)Instruction.PUSH23] = &InstructionPush; - lookup[(int)Instruction.PUSH24] = &InstructionPush; - lookup[(int)Instruction.PUSH25] = &InstructionPush; - lookup[(int)Instruction.PUSH26] = &InstructionPush; - lookup[(int)Instruction.PUSH27] = &InstructionPush; - lookup[(int)Instruction.PUSH28] = &InstructionPush; - lookup[(int)Instruction.PUSH29] = &InstructionPush; - lookup[(int)Instruction.PUSH30] = &InstructionPush; - lookup[(int)Instruction.PUSH31] = &InstructionPush; - lookup[(int)Instruction.PUSH32] = &InstructionPush; + lookup[(int)Instruction.PUSH1] = &InstructionPush; + lookup[(int)Instruction.PUSH2] = &InstructionPush2; + lookup[(int)Instruction.PUSH3] = &InstructionPush; + lookup[(int)Instruction.PUSH4] = &InstructionPush; + lookup[(int)Instruction.PUSH5] = &InstructionPush; + lookup[(int)Instruction.PUSH6] = &InstructionPush; + lookup[(int)Instruction.PUSH7] = &InstructionPush; + lookup[(int)Instruction.PUSH8] = &InstructionPush; + lookup[(int)Instruction.PUSH9] = &InstructionPush; + lookup[(int)Instruction.PUSH10] = &InstructionPush; + lookup[(int)Instruction.PUSH11] = &InstructionPush; + lookup[(int)Instruction.PUSH12] = &InstructionPush; + lookup[(int)Instruction.PUSH13] = &InstructionPush; + lookup[(int)Instruction.PUSH14] = &InstructionPush; + lookup[(int)Instruction.PUSH15] = &InstructionPush; + lookup[(int)Instruction.PUSH16] = &InstructionPush; + lookup[(int)Instruction.PUSH17] = &InstructionPush; + lookup[(int)Instruction.PUSH18] = &InstructionPush; + lookup[(int)Instruction.PUSH19] = &InstructionPush; + lookup[(int)Instruction.PUSH20] = &InstructionPush; + lookup[(int)Instruction.PUSH21] = &InstructionPush; + lookup[(int)Instruction.PUSH22] = &InstructionPush; + lookup[(int)Instruction.PUSH23] = &InstructionPush; + lookup[(int)Instruction.PUSH24] = &InstructionPush; + lookup[(int)Instruction.PUSH25] = &InstructionPush; + lookup[(int)Instruction.PUSH26] = &InstructionPush; + lookup[(int)Instruction.PUSH27] = &InstructionPush; + lookup[(int)Instruction.PUSH28] = &InstructionPush; + lookup[(int)Instruction.PUSH29] = &InstructionPush; + lookup[(int)Instruction.PUSH30] = &InstructionPush; + lookup[(int)Instruction.PUSH31] = &InstructionPush; + lookup[(int)Instruction.PUSH32] = &InstructionPush; // DUP opcodes (DUP1 to DUP16). - lookup[(int)Instruction.DUP1] = &InstructionDup; - lookup[(int)Instruction.DUP2] = &InstructionDup; - lookup[(int)Instruction.DUP3] = &InstructionDup; - lookup[(int)Instruction.DUP4] = &InstructionDup; - lookup[(int)Instruction.DUP5] = &InstructionDup; - lookup[(int)Instruction.DUP6] = &InstructionDup; - lookup[(int)Instruction.DUP7] = &InstructionDup; - lookup[(int)Instruction.DUP8] = &InstructionDup; - lookup[(int)Instruction.DUP9] = &InstructionDup; - lookup[(int)Instruction.DUP10] = &InstructionDup; - lookup[(int)Instruction.DUP11] = &InstructionDup; - lookup[(int)Instruction.DUP12] = &InstructionDup; - lookup[(int)Instruction.DUP13] = &InstructionDup; - lookup[(int)Instruction.DUP14] = &InstructionDup; - lookup[(int)Instruction.DUP15] = &InstructionDup; - lookup[(int)Instruction.DUP16] = &InstructionDup; + lookup[(int)Instruction.DUP1] = &InstructionDup; + lookup[(int)Instruction.DUP2] = &InstructionDup; + lookup[(int)Instruction.DUP3] = &InstructionDup; + lookup[(int)Instruction.DUP4] = &InstructionDup; + lookup[(int)Instruction.DUP5] = &InstructionDup; + lookup[(int)Instruction.DUP6] = &InstructionDup; + lookup[(int)Instruction.DUP7] = &InstructionDup; + lookup[(int)Instruction.DUP8] = &InstructionDup; + lookup[(int)Instruction.DUP9] = &InstructionDup; + lookup[(int)Instruction.DUP10] = &InstructionDup; + lookup[(int)Instruction.DUP11] = &InstructionDup; + lookup[(int)Instruction.DUP12] = &InstructionDup; + lookup[(int)Instruction.DUP13] = &InstructionDup; + lookup[(int)Instruction.DUP14] = &InstructionDup; + lookup[(int)Instruction.DUP15] = &InstructionDup; + lookup[(int)Instruction.DUP16] = &InstructionDup; // SWAP opcodes (SWAP1 to SWAP16). - lookup[(int)Instruction.SWAP1] = &InstructionSwap; - lookup[(int)Instruction.SWAP2] = &InstructionSwap; - lookup[(int)Instruction.SWAP3] = &InstructionSwap; - lookup[(int)Instruction.SWAP4] = &InstructionSwap; - lookup[(int)Instruction.SWAP5] = &InstructionSwap; - lookup[(int)Instruction.SWAP6] = &InstructionSwap; - lookup[(int)Instruction.SWAP7] = &InstructionSwap; - lookup[(int)Instruction.SWAP8] = &InstructionSwap; - lookup[(int)Instruction.SWAP9] = &InstructionSwap; - lookup[(int)Instruction.SWAP10] = &InstructionSwap; - lookup[(int)Instruction.SWAP11] = &InstructionSwap; - lookup[(int)Instruction.SWAP12] = &InstructionSwap; - lookup[(int)Instruction.SWAP13] = &InstructionSwap; - lookup[(int)Instruction.SWAP14] = &InstructionSwap; - lookup[(int)Instruction.SWAP15] = &InstructionSwap; - lookup[(int)Instruction.SWAP16] = &InstructionSwap; + lookup[(int)Instruction.SWAP1] = &InstructionSwap; + lookup[(int)Instruction.SWAP2] = &InstructionSwap; + lookup[(int)Instruction.SWAP3] = &InstructionSwap; + lookup[(int)Instruction.SWAP4] = &InstructionSwap; + lookup[(int)Instruction.SWAP5] = &InstructionSwap; + lookup[(int)Instruction.SWAP6] = &InstructionSwap; + lookup[(int)Instruction.SWAP7] = &InstructionSwap; + lookup[(int)Instruction.SWAP8] = &InstructionSwap; + lookup[(int)Instruction.SWAP9] = &InstructionSwap; + lookup[(int)Instruction.SWAP10] = &InstructionSwap; + lookup[(int)Instruction.SWAP11] = &InstructionSwap; + lookup[(int)Instruction.SWAP12] = &InstructionSwap; + lookup[(int)Instruction.SWAP13] = &InstructionSwap; + lookup[(int)Instruction.SWAP14] = &InstructionSwap; + lookup[(int)Instruction.SWAP15] = &InstructionSwap; + lookup[(int)Instruction.SWAP16] = &InstructionSwap; // LOG opcodes. - lookup[(int)Instruction.LOG0] = &InstructionLog; - lookup[(int)Instruction.LOG1] = &InstructionLog; - lookup[(int)Instruction.LOG2] = &InstructionLog; - lookup[(int)Instruction.LOG3] = &InstructionLog; - lookup[(int)Instruction.LOG4] = &InstructionLog; + lookup[(int)Instruction.LOG0] = &InstructionLog; + lookup[(int)Instruction.LOG1] = &InstructionLog; + lookup[(int)Instruction.LOG2] = &InstructionLog; + lookup[(int)Instruction.LOG3] = &InstructionLog; + lookup[(int)Instruction.LOG4] = &InstructionLog; // Extended opcodes for EO (EoF) mode. if (spec.IsEofEnabled) { - lookup[(int)Instruction.DATALOAD] = &InstructionDataLoad; - lookup[(int)Instruction.DATALOADN] = &InstructionDataLoadN; - lookup[(int)Instruction.DATASIZE] = &InstructionDataSize; - lookup[(int)Instruction.DATACOPY] = &InstructionDataCopy; + lookup[(int)Instruction.DATALOAD] = &InstructionDataLoad; + lookup[(int)Instruction.DATALOADN] = &InstructionDataLoadN; + lookup[(int)Instruction.DATASIZE] = &InstructionDataSize; + lookup[(int)Instruction.DATACOPY] = &InstructionDataCopy; lookup[(int)Instruction.RJUMP] = &InstructionRelativeJump; lookup[(int)Instruction.RJUMPI] = &InstructionRelativeJumpIf; lookup[(int)Instruction.RJUMPV] = &InstructionJumpTable; lookup[(int)Instruction.CALLF] = &InstructionCallFunction; lookup[(int)Instruction.RETF] = &InstructionReturnFunction; lookup[(int)Instruction.JUMPF] = &InstructionJumpFunction; - lookup[(int)Instruction.DUPN] = &InstructionDupN; - lookup[(int)Instruction.SWAPN] = &InstructionSwapN; - lookup[(int)Instruction.EXCHANGE] = &InstructionExchange; - lookup[(int)Instruction.EOFCREATE] = &InstructionEofCreate; + lookup[(int)Instruction.DUPN] = &InstructionDupN; + lookup[(int)Instruction.SWAPN] = &InstructionSwapN; + lookup[(int)Instruction.EXCHANGE] = &InstructionExchange; + lookup[(int)Instruction.EOFCREATE] = &InstructionEofCreate; lookup[(int)Instruction.RETURNCODE] = &InstructionReturnCode; } // Contract creation and call opcodes. - lookup[(int)Instruction.CREATE] = &InstructionCreate; - lookup[(int)Instruction.CALL] = &InstructionCall; - lookup[(int)Instruction.CALLCODE] = &InstructionCall; + lookup[(int)Instruction.CREATE] = &InstructionCreate; + lookup[(int)Instruction.CALL] = &InstructionCall; + lookup[(int)Instruction.CALLCODE] = &InstructionCall; lookup[(int)Instruction.RETURN] = &InstructionReturn; if (spec.DelegateCallEnabled) { - lookup[(int)Instruction.DELEGATECALL] = &InstructionCall; + lookup[(int)Instruction.DELEGATECALL] = &InstructionCall; } if (spec.Create2OpcodeEnabled) { - lookup[(int)Instruction.CREATE2] = &InstructionCreate; + lookup[(int)Instruction.CREATE2] = &InstructionCreate; } - lookup[(int)Instruction.RETURNDATALOAD] = &InstructionReturnDataLoad; + lookup[(int)Instruction.RETURNDATALOAD] = &InstructionReturnDataLoad; if (spec.StaticCallEnabled) { - lookup[(int)Instruction.STATICCALL] = &InstructionCall; + lookup[(int)Instruction.STATICCALL] = &InstructionCall; } // Extended call opcodes in EO mode. if (spec.IsEofEnabled) { - lookup[(int)Instruction.EXTCALL] = &InstructionEofCall; + lookup[(int)Instruction.EXTCALL] = &InstructionEofCall; if (spec.DelegateCallEnabled) { - lookup[(int)Instruction.EXTDELEGATECALL] = &InstructionEofCall; + lookup[(int)Instruction.EXTDELEGATECALL] = + &InstructionEofCall; } if (spec.StaticCallEnabled) { - lookup[(int)Instruction.EXTSTATICCALL] = &InstructionEofCall; + lookup[(int)Instruction.EXTSTATICCALL] = &InstructionEofCall; } } diff --git a/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs b/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs index bdb4935fed1..5f483bb2c8c 100644 --- a/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs +++ b/src/Nethermind/Nethermind.Evm/IntrinsicGasCalculator.cs @@ -9,28 +9,56 @@ using Nethermind.Core.Eip2930; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; +using Nethermind.Evm.GasPolicy; using Nethermind.Int256; namespace Nethermind.Evm; +/// +/// Generic intrinsic gas result with TGasPolicy-typed Standard and FloorGas. +/// +public readonly record struct IntrinsicGas(TGasPolicy Standard, TGasPolicy FloorGas) + where TGasPolicy : struct, IGasPolicy +{ + public TGasPolicy MinimalGas { get; } = TGasPolicy.Max(Standard, FloorGas); + public static explicit operator TGasPolicy(IntrinsicGas gas) => gas.MinimalGas; +} -public readonly record struct IntrinsicGas(long Standard, long FloorGas) +/// +/// Non-generic intrinsic gas result for backward compatibility. +/// +public readonly record struct EthereumIntrinsicGas(long Standard, long FloorGas) { public long MinimalGas { get; } = Math.Max(Standard, FloorGas); - public static explicit operator long(IntrinsicGas gas) => gas.MinimalGas; + public static explicit operator long(EthereumIntrinsicGas gas) => gas.MinimalGas; } public static class IntrinsicGasCalculator { - public static IntrinsicGas Calculate(Transaction transaction, IReleaseSpec releaseSpec) + /// + /// Calculates intrinsic gas with TGasPolicy type, allowing MultiGas breakdown for Arbitrum. + /// + public static IntrinsicGas Calculate(Transaction transaction, IReleaseSpec releaseSpec) + where TGasPolicy : struct, IGasPolicy + { + TGasPolicy standard = TGasPolicy.CalculateIntrinsicGas(transaction, releaseSpec); + long floorCost = CalculateFloorCost(transaction, releaseSpec); + TGasPolicy floorGas = TGasPolicy.FromLong(floorCost); + return new IntrinsicGas(standard, floorGas); + } + + /// + /// Non-generic backward-compatible Calculate method. + /// + public static EthereumIntrinsicGas Calculate(Transaction transaction, IReleaseSpec releaseSpec) { - var intrinsicGas = GasCostOf.Transaction + long intrinsicGas = GasCostOf.Transaction + DataCost(transaction, releaseSpec) + CreateCost(transaction, releaseSpec) + AccessListCost(transaction, releaseSpec) + AuthorizationListCost(transaction, releaseSpec); - var floorGas = CalculateFloorCost(transaction, releaseSpec); - return new IntrinsicGas(intrinsicGas, floorGas); + long floorGas = CalculateFloorCost(transaction, releaseSpec); + return new EthereumIntrinsicGas(intrinsicGas, floorGas); } private static long CreateCost(Transaction transaction, IReleaseSpec releaseSpec) => diff --git a/src/Nethermind/Nethermind.Evm/StackPool.cs b/src/Nethermind/Nethermind.Evm/StackPool.cs index a51014738ee..3c5a1309388 100644 --- a/src/Nethermind/Nethermind.Evm/StackPool.cs +++ b/src/Nethermind/Nethermind.Evm/StackPool.cs @@ -4,15 +4,16 @@ using System; using System.Collections.Concurrent; using System.Runtime.Intrinsics; +using System.Threading; -using static Nethermind.Evm.EvmState; +using static Nethermind.Evm.VirtualMachineStatics; namespace Nethermind.Evm; internal sealed class StackPool { // Also have parallel prewarming and Rpc calls - private const int MaxStacksPooled = VirtualMachine.MaxCallDepth * 2; + private const int MaxStacksPooled = MaxCallDepth * 2; private readonly struct StackItem(byte[] dataStack, ReturnState[] returnStack) { public readonly byte[] DataStack = dataStack; @@ -29,24 +30,35 @@ private readonly struct StackItem(byte[] dataStack, ReturnState[] returnStack) /// public void ReturnStacks(byte[] dataStack, ReturnState[] returnStack) { - if (_stackPool.Count <= MaxStacksPooled) + // Reserve a slot first - O(1) bound without touching ConcurrentQueue.Count. + if (Interlocked.Increment(ref _poolCount) > MaxStacksPooled) { - _stackPool.Enqueue(new(dataStack, returnStack)); + // Cap hit - roll back the reservation and drop the item. + Interlocked.Decrement(ref _poolCount); + return; } + + _stackPool.Enqueue(new StackItem(dataStack, returnStack)); } + // Manual reservation count - upper bound on items actually in the queue. + private int _poolCount; + public const int StackLength = (EvmStack.MaxStackSize + EvmStack.RegisterLength) * 32; public (byte[], ReturnState[]) RentStacks() { - if (_stackPool.TryDequeue(out StackItem result)) + if (Volatile.Read(ref _poolCount) > 0 && _stackPool.TryDequeue(out StackItem result)) { + Interlocked.Decrement(ref _poolCount); return (result.DataStack, result.ReturnStack); } + // Count was positive but we lost the race or the enqueuer has not published yet. + // Include extra Vector256.Count and pin so we can align to 32 bytes. + // This ensures the stack is properly aligned for SIMD operations. return ( - // Include extra Vector256.Count and pin so we can align to 32 bytes GC.AllocateUninitializedArray(StackLength + Vector256.Count, pinned: true), new ReturnState[EvmStack.ReturnStackSize] ); diff --git a/src/Nethermind/Nethermind.Evm/State/IPreBlockCaches.cs b/src/Nethermind/Nethermind.Evm/State/IPreBlockCaches.cs index 2e29278e9c6..7bb0793dcfe 100644 --- a/src/Nethermind/Nethermind.Evm/State/IPreBlockCaches.cs +++ b/src/Nethermind/Nethermind.Evm/State/IPreBlockCaches.cs @@ -5,6 +5,6 @@ namespace Nethermind.Evm.State; public interface IPreBlockCaches { - PreBlockCaches Caches { get; } + PreBlockCaches? Caches { get; } bool IsWarmWorldState { get; } } diff --git a/src/Nethermind/Nethermind.Evm/State/IReadOnlyStateProviderExtensions.cs b/src/Nethermind/Nethermind.Evm/State/IReadOnlyStateProviderExtensions.cs index 3e12df463d5..119d43ef09b 100644 --- a/src/Nethermind/Nethermind.Evm/State/IReadOnlyStateProviderExtensions.cs +++ b/src/Nethermind/Nethermind.Evm/State/IReadOnlyStateProviderExtensions.cs @@ -9,11 +9,6 @@ namespace Nethermind.Evm.State { public static class IReadOnlyStateProviderExtensions { - public static byte[] GetCode(this IReadOnlyStateProvider stateProvider, Address address) - { - stateProvider.TryGetAccount(address, out AccountStruct account); - return !account.HasCode ? [] : stateProvider.GetCode(in account.CodeHash) ?? []; - } /// /// Checks if has code that is not a delegation, according to the rules of eip-3607 and eip-7702. /// Where possible a cache for code lookup should be used, since the fallback will read from . @@ -31,7 +26,7 @@ public static bool IsInvalidContractSender( spec.IsEip3607Enabled && stateProvider.HasCode(sender) && (!spec.IsEip7702Enabled - || (!isDelegatedCode?.Invoke(sender) ?? !Eip7702Constants.IsDelegatedCode(GetCode(stateProvider, sender)))); + || (!isDelegatedCode?.Invoke(sender) ?? !Eip7702Constants.IsDelegatedCode(stateProvider.GetCode(sender)))); /// /// Checks if has code that is not a delegation, according to the rules of eip-3607 and eip-7702. diff --git a/src/Nethermind/Nethermind.Evm/State/IWorldState.cs b/src/Nethermind/Nethermind.Evm/State/IWorldState.cs index 29f3afb96ab..a5460c1007b 100644 --- a/src/Nethermind/Nethermind.Evm/State/IWorldState.cs +++ b/src/Nethermind/Nethermind.Evm/State/IWorldState.cs @@ -24,6 +24,7 @@ public interface IWorldState : IJournal, IReadOnlyStateProvider IDisposable BeginScope(BlockHeader? baseBlock); bool IsInScope { get; } + IWorldStateScopeProvider ScopeProvider { get; } new UInt256 GetBalance(Address address); new ValueHash256 GetCodeHash(Address address); bool HasStateForBlock(BlockHeader? baseBlock); @@ -136,9 +137,6 @@ public interface IWorldState : IJournal, IReadOnlyStateProvider void SetNonce(Address address, in UInt256 nonce); /* snapshots */ - - void Commit(IReleaseSpec releaseSpec, bool isGenesis = false, bool commitRoots = true); - void Commit(IReleaseSpec releaseSpec, IWorldStateTracer tracer, bool isGenesis = false, bool commitRoots = true); /// diff --git a/src/Nethermind/Nethermind.Evm/State/IWorldStateExtensions.cs b/src/Nethermind/Nethermind.Evm/State/IWorldStateExtensions.cs index a0749cce8ab..c0e73a6a311 100644 --- a/src/Nethermind/Nethermind.Evm/State/IWorldStateExtensions.cs +++ b/src/Nethermind/Nethermind.Evm/State/IWorldStateExtensions.cs @@ -5,6 +5,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Specs; +using Nethermind.Evm.Tracing.State; namespace Nethermind.Evm.State; @@ -16,4 +17,9 @@ public static void InsertCode(this IWorldState worldState, Address address, Read ValueHash256 codeHash = code.Length == 0 ? ValueKeccak.OfAnEmptyString : ValueKeccak.Compute(code.Span); worldState.InsertCode(address, codeHash, code, spec, isGenesis); } + + public static void Commit(this IWorldState worldState, IReleaseSpec releaseSpec, bool isGenesis = false, bool commitRoots = true) + { + worldState.Commit(releaseSpec, NullStateTracer.Instance, isGenesis, commitRoots); + } } diff --git a/src/Nethermind/Nethermind.Evm/State/IWorldStateScopeProvider.cs b/src/Nethermind/Nethermind.Evm/State/IWorldStateScopeProvider.cs new file mode 100644 index 00000000000..96f1e1d7de8 --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/State/IWorldStateScopeProvider.cs @@ -0,0 +1,132 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Int256; + +namespace Nethermind.Evm.State; + +/// +/// An interface for storage backend for . This interface does not have +/// logic for snapshot/rollback and code. Operations must be done using . +/// +public interface IWorldStateScopeProvider +{ + bool HasRoot(BlockHeader? baseBlock); + IScope BeginScope(BlockHeader? baseBlock); + + public interface IScope : IDisposable + { + Hash256 RootHash { get; } + + void UpdateRootHash(); + + /// + /// Get the account information for the following address. + /// Note: Do not rely on as it may be modified after write. Instead use . + /// + /// + /// + Account? Get(Address address); + + /// + /// Call when top level application read an account without going through this scope to reduce time during commit later. + /// + /// + /// + void HintGet(Address address, Account? account); + + /// + /// The code db + /// + ICodeDb CodeDb { get; } + + /// + /// Create a per address storage tree. Multiple call to the same + /// address yield the same . The returned object + /// must not be used after , and should be re-created. + /// + /// + /// + IStorageTree CreateStorageTree(Address address); + + /// + /// Begin a write batch to update the world state. + /// + /// For optimization, estimated account number. + /// + IWorldStateWriteBatch StartWriteBatch(int estimatedAccountNum); + + /// + /// A commit will traverse the dirty nodes in the tree, calculate the hash and save + /// the tree to underlying store. + /// That said, will always call + /// first. + /// + void Commit(long blockNumber); + } + + public interface ICodeDb + { + byte[]? GetCode(in ValueHash256 codeHash); + + ICodeSetter BeginCodeWrite(); + } + + public interface IStorageTree + { + Hash256 RootHash { get; } + + byte[] Get(in UInt256 index); + + void HintGet(in UInt256 index, byte[]? value); + + /// + /// Used by JS tracer. May not work on some database layout. + /// + /// + /// + byte[] Get(in ValueHash256 hash); + } + + public interface IWorldStateWriteBatch : IDisposable + { + public event EventHandler OnAccountUpdated; + + // Note: Null account imply removal and clearing of storage. + void Set(Address key, Account? account); + + IStorageWriteBatch CreateStorageWriteBatch(Address key, int estimatedEntries); + } + + public class AccountUpdated(Address Address, Account? Account) : EventArgs + { + public Address Address { get; init; } = Address; + public Account? Account { get; init; } = Account; + + public void Deconstruct(out Address Address, out Account? Account) + { + Address = this.Address; + Account = this.Account; + } + } + + public interface IStorageWriteBatch : IDisposable + { + void Set(in UInt256 index, byte[] value); + + /// + /// Self-destruct. Maybe costly. Must be called first. + /// Note: Is called on new account or dead account at start of tx also. + /// Note: May not get called if the account is removed at the end of the time of commit. + /// + void Clear(); + } + + public interface ICodeSetter : IDisposable + { + void Set(in ValueHash256 codeHash, ReadOnlySpan code); + } +} diff --git a/src/Nethermind/Nethermind.Evm/State/PreBlockCaches.cs b/src/Nethermind/Nethermind.Evm/State/PreBlockCaches.cs index 1af7e330e67..d3fb098612b 100644 --- a/src/Nethermind/Nethermind.Evm/State/PreBlockCaches.cs +++ b/src/Nethermind/Nethermind.Evm/State/PreBlockCaches.cs @@ -30,7 +30,6 @@ public PreBlockCaches() [ () => _storageCache.NoResizeClear() ? CacheType.Storage : CacheType.None, () => _stateCache.NoResizeClear() ? CacheType.State : CacheType.None, - () => _rlpCache.NoResizeClear() ? CacheType.Rlp : CacheType.None, () => _precompileCache.NoResizeClear() ? CacheType.Precompile : CacheType.None ]; } diff --git a/src/Nethermind/Nethermind.Evm/State/TracedAccessWorldState.cs b/src/Nethermind/Nethermind.Evm/State/TracedAccessWorldState.cs index 17c10cb9a8a..b20b13c22ac 100644 --- a/src/Nethermind/Nethermind.Evm/State/TracedAccessWorldState.cs +++ b/src/Nethermind/Nethermind.Evm/State/TracedAccessWorldState.cs @@ -15,9 +15,9 @@ public class TracedAccessWorldState(IWorldState innerWorldState) : WrappedWorldS public bool Enabled { get; set; } = false; public BlockAccessList BlockAccessList = new(); - public PreBlockCaches Caches => (_innerWorldState as IPreBlockCaches).Caches; + public PreBlockCaches? Caches => (_innerWorldState as IPreBlockCaches)?.Caches; - public bool IsWarmWorldState => (_innerWorldState as IPreBlockCaches).IsWarmWorldState; + public bool IsWarmWorldState => (_innerWorldState as IPreBlockCaches)?.IsWarmWorldState ?? false; public override void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec) => AddToBalance(address, balanceChange, spec, out _); diff --git a/src/Nethermind/Nethermind.Evm/State/TracedAccessWorldStateScopeProvider.cs b/src/Nethermind/Nethermind.Evm/State/TracedAccessWorldStateScopeProvider.cs new file mode 100644 index 00000000000..5b2c9ffbadf --- /dev/null +++ b/src/Nethermind/Nethermind.Evm/State/TracedAccessWorldStateScopeProvider.cs @@ -0,0 +1,17 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Nethermind.Evm.State; + +/// +/// A decorator for that enables block access list tracing +/// when is used. +/// +public class TracedAccessWorldStateScopeProvider(IWorldStateScopeProvider innerProvider) : IWorldStateScopeProvider +{ + public bool HasRoot(BlockHeader? baseBlock) => innerProvider.HasRoot(baseBlock); + + public IWorldStateScopeProvider.IScope BeginScope(BlockHeader? baseBlock) => innerProvider.BeginScope(baseBlock); +} diff --git a/src/Nethermind/Nethermind.Evm/State/WrappedWorldState.cs b/src/Nethermind/Nethermind.Evm/State/WrappedWorldState.cs index d07e28624e8..17516549b38 100644 --- a/src/Nethermind/Nethermind.Evm/State/WrappedWorldState.cs +++ b/src/Nethermind/Nethermind.Evm/State/WrappedWorldState.cs @@ -16,6 +16,7 @@ public class WrappedWorldState(IWorldState innerWorldState) : IWorldState { protected IWorldState _innerWorldState = innerWorldState; public bool IsInScope => _innerWorldState.IsInScope; + public IWorldStateScopeProvider ScopeProvider => _innerWorldState.ScopeProvider; public Hash256 StateRoot => _innerWorldState.StateRoot; @@ -40,9 +41,6 @@ public virtual IDisposable BeginScope(BlockHeader? baseBlock) public virtual void ClearStorage(Address address) => _innerWorldState.ClearStorage(address); - public virtual void Commit(IReleaseSpec releaseSpec, bool isGenesis = false, bool commitRoots = true) - => _innerWorldState.Commit(releaseSpec, isGenesis, commitRoots); - public virtual void Commit(IReleaseSpec releaseSpec, IWorldStateTracer tracer, bool isGenesis = false, bool commitRoots = true) => _innerWorldState.Commit(releaseSpec, tracer, isGenesis, commitRoots); diff --git a/src/Nethermind/Nethermind.Evm/Tracing/CancellationTxTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/CancellationTxTracer.cs index 7a2db1770ee..206f7729156 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/CancellationTxTracer.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/CancellationTxTracer.cs @@ -310,7 +310,7 @@ public void ReportMemoryChange(UInt256 offset, byte data) public void ReportStorageChange(in ReadOnlySpan key, in ReadOnlySpan value) { token.ThrowIfCancellationRequested(); - if (innerTracer.IsTracingInstructions) + if (innerTracer.IsTracingStorage) { innerTracer.ReportStorageChange(key, value); } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/CompositeTxTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/CompositeTxTracer.cs index 0a1d8100b02..d341d6c6c4a 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/CompositeTxTracer.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/CompositeTxTracer.cs @@ -313,7 +313,7 @@ public void ReportStorageChange(in ReadOnlySpan key, in ReadOnlySpan for (int index = 0; index < _txTracers.Count; index++) { ITxTracer innerTracer = _txTracers[index]; - if (innerTracer.IsTracingInstructions) + if (innerTracer.IsTracingStorage) { innerTracer.ReportStorageChange(key, value); } diff --git a/src/Nethermind/Nethermind.Evm/Tracing/Debugger/DebugTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/Debugger/DebugTracer.cs index e2d791ec8c9..a8b35aca16c 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/Debugger/DebugTracer.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/Debugger/DebugTracer.cs @@ -7,18 +7,20 @@ using System.Threading; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Evm.GasPolicy; using Nethermind.Evm.TransactionProcessing; using Nethermind.Int256; namespace Nethermind.Evm.Tracing.Debugger; -public class DebugTracer : ITxTracer, ITxTracerWrapper, IDisposable +public class DebugTracer : ITxTracer, ITxTracerWrapper, IDisposable + where TGasPolicy : struct, IGasPolicy { public enum DebugPhase { Starting, Blocked, Running, Aborted } private readonly AutoResetEvent _autoResetEvent = new(false); - private readonly Dictionary<(int depth, int pc), Func> _breakPoints = new(); - private Func? _globalBreakCondition; + private readonly Dictionary<(int depth, int pc), Func, bool>> _breakPoints = new(); + private Func, bool>? _globalBreakCondition; private readonly object _lock = new(); public DebugTracer(ITxTracer tracer) @@ -32,7 +34,7 @@ public DebugTracer(ITxTracer tracer) public DebugPhase CurrentPhase { get; private set; } = DebugPhase.Starting; public bool CanReadState => CurrentPhase is DebugPhase.Blocked; public bool IsStepByStepModeOn { get; set; } - public EvmState? CurrentState { get; set; } + public VmState? CurrentState { get; set; } public bool IsTracingReceipt => InnerTracer.IsTracingReceipt; @@ -62,9 +64,9 @@ public DebugTracer(ITxTracer tracer) public bool IsTracingLogs => InnerTracer.IsTracingLogs; - public bool IsBreakpoitnSet(int depth, int programCounter) => _breakPoints.ContainsKey((depth, programCounter)); + public bool IsBreakpointSet(int depth, int programCounter) => _breakPoints.ContainsKey((depth, programCounter)); - public void SetBreakPoint((int depth, int pc) point, Func condition = null) + public void SetBreakPoint((int depth, int pc) point, Func, bool> condition = null) { if (CurrentPhase is DebugPhase.Blocked or DebugPhase.Starting) { @@ -79,12 +81,12 @@ public void UnsetBreakPoint(int depth, int programCounter) } } - public void SetCondtion(Func? condition = null) + public void SetCondition(Func, bool>? condition = null) { if (CurrentPhase is DebugPhase.Blocked or DebugPhase.Starting) _globalBreakCondition = condition; } - public void TryWait(ref EvmState evmState, ref int programCounter, ref long gasAvailable, ref int stackHead) + public void TryWait(ref VmState vmState, ref int programCounter, ref TGasPolicy gas, ref int stackHead) { if (CurrentPhase is DebugPhase.Aborted) { @@ -93,10 +95,10 @@ public void TryWait(ref EvmState evmState, ref int programCounter, ref long gasA lock (_lock) { - evmState.ProgramCounter = programCounter; - evmState.GasAvailable = gasAvailable; - evmState.DataStackHead = stackHead; - CurrentState = evmState; + vmState.ProgramCounter = programCounter; + vmState.Gas = gas; + vmState.DataStackHead = stackHead; + CurrentState = vmState; } if (IsStepByStepModeOn) @@ -112,7 +114,7 @@ public void TryWait(ref EvmState evmState, ref int programCounter, ref long gasA lock (_lock) { stackHead = CurrentState.DataStackHead; - gasAvailable = CurrentState.GasAvailable; + gas = CurrentState.Gas; programCounter = CurrentState.ProgramCounter; } } @@ -163,7 +165,7 @@ public void CheckBreakPoint() { (int CallDepth, int ProgramCounter) breakpoint = (CurrentState!.Env.CallDepth, CurrentState.ProgramCounter); - if (_breakPoints.TryGetValue(breakpoint, out Func? point)) + if (_breakPoints.TryGetValue(breakpoint, out Func, bool>? point)) { bool conditionResults = point?.Invoke(CurrentState) ?? true; if (conditionResults) @@ -292,4 +294,12 @@ public void Dispose() _autoResetEvent.Dispose(); } } + +/// +/// Non-generic DebugTracer for backward compatibility with EthereumGasPolicy. +/// +public class DebugTracer : DebugTracer +{ + public DebugTracer(ITxTracer tracer) : base(tracer) { } +} #endif diff --git a/src/Nethermind/Nethermind.Evm/Tracing/TxTracer.cs b/src/Nethermind/Nethermind.Evm/Tracing/TxTracer.cs index d8500f9220b..9bc615cafd6 100644 --- a/src/Nethermind/Nethermind.Evm/Tracing/TxTracer.cs +++ b/src/Nethermind/Nethermind.Evm/Tracing/TxTracer.cs @@ -13,23 +13,25 @@ namespace Nethermind.Evm.Tracing; public abstract class TxTracer : ITxTracer { - [SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")] - protected TxTracer() + private bool? _isTracing; + + public bool IsTracing { - IsTracing = IsTracingReceipt - || IsTracingActions - || IsTracingOpLevelStorage - || IsTracingMemory - || IsTracingInstructions - || IsTracingRefunds - || IsTracingCode - || IsTracingStack - || IsTracingBlockHash - || IsTracingAccess - || IsTracingFees - || IsTracingLogs; + get => _isTracing ??= IsTracingReceipt + || IsTracingActions + || IsTracingOpLevelStorage + || IsTracingMemory + || IsTracingInstructions + || IsTracingRefunds + || IsTracingCode + || IsTracingStack + || IsTracingBlockHash + || IsTracingAccess + || IsTracingFees + || IsTracingLogs; + protected set => _isTracing = value; } - public bool IsTracing { get; protected set; } + public virtual bool IsTracingState { get; protected set; } public virtual bool IsTracingReceipt { get; protected set; } public virtual bool IsTracingActions { get; protected set; } diff --git a/src/Nethermind/Nethermind.Evm/TransactionExtensions.cs b/src/Nethermind/Nethermind.Evm/TransactionExtensions.cs index 8ab7cb6ace8..811d29510f6 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionExtensions.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionExtensions.cs @@ -10,9 +10,9 @@ namespace Nethermind.Evm { public static class TransactionExtensions { - public static Address? GetRecipient(this Transaction tx, in UInt256 nonce) => + public static Address GetRecipient(this Transaction tx, in UInt256 nonce) => tx.To ?? (tx.IsSystem() - ? tx.SenderAddress + ? tx.SenderAddress! : ContractAddress.From(tx.SenderAddress, nonce > 0 ? nonce - 1 : nonce)); public static TxGasInfo GetGasInfo(this Transaction tx, IReleaseSpec spec, BlockHeader header) diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/SystemTransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/SystemTransactionProcessor.cs index 24de0b64ba7..cac2907385e 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/SystemTransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/SystemTransactionProcessor.cs @@ -3,6 +3,7 @@ using Nethermind.Core; using Nethermind.Core.Specs; +using Nethermind.Evm.GasPolicy; using Nethermind.Evm.Tracing; using Nethermind.Int256; using Nethermind.Logging; @@ -11,7 +12,8 @@ namespace Nethermind.Evm.TransactionProcessing; -public sealed class SystemTransactionProcessor : TransactionProcessorBase +public sealed class SystemTransactionProcessor : TransactionProcessorBase + where TGasPolicy : struct, IGasPolicy { private readonly bool _isAura; @@ -25,7 +27,7 @@ public SystemTransactionProcessor( ITransactionProcessor.IBlobBaseFeeCalculator blobBaseFeeCalculator, ISpecProvider? specProvider, IWorldState? worldState, - IVirtualMachine? virtualMachine, + IVirtualMachine? virtualMachine, ICodeInfoRepository? codeInfoRepository, ILogManager? logManager) : base(blobBaseFeeCalculator, specProvider, worldState, virtualMachine, codeInfoRepository, logManager) @@ -45,9 +47,19 @@ protected override TransactionResult Execute(Transaction tx, ITxTracer tracer, E : opts); } + protected override TransactionResult BuyGas(Transaction tx, IReleaseSpec spec, ITxTracer tracer, ExecutionOptions opts, + in UInt256 effectiveGasPrice, out UInt256 premiumPerGas, out UInt256 senderReservedGasPayment, + out UInt256 blobBaseFee) + { + premiumPerGas = 0; + senderReservedGasPayment = 0; + blobBaseFee = 0; + return TransactionResult.Ok; + } + protected override IReleaseSpec GetSpec(BlockHeader header) => SystemTransactionReleaseSpec.GetReleaseSpec(base.GetSpec(header), _isAura, header.IsGenesis); - protected override TransactionResult ValidateGas(Transaction tx, BlockHeader header, long minGasRequired, bool validate) => TransactionResult.Ok; + protected override TransactionResult ValidateGas(Transaction tx, BlockHeader header, long minGasRequired) => TransactionResult.Ok; protected override TransactionResult IncrementNonce(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer, ExecutionOptions opts) => TransactionResult.Ok; @@ -63,7 +75,7 @@ protected override void PayValue(Transaction tx, IReleaseSpec spec, ExecutionOpt } } - protected override IntrinsicGas CalculateIntrinsicGas(Transaction tx, IReleaseSpec spec) => tx is SystemCall ? default : base.CalculateIntrinsicGas(tx, spec); + protected override IntrinsicGas CalculateIntrinsicGas(Transaction tx, IReleaseSpec spec) => tx is SystemCall ? default : base.CalculateIntrinsicGas(tx, spec); protected override bool RecoverSenderIfNeeded(Transaction tx, IReleaseSpec spec, ExecutionOptions opts, in UInt256 effectiveGasPrice) { @@ -71,4 +83,6 @@ protected override bool RecoverSenderIfNeeded(Transaction tx, IReleaseSpec spec, return (sender is null || (spec.IsEip158IgnoredAccount(sender) && !WorldState.AccountExists(sender))) && base.RecoverSenderIfNeeded(tx, spec, opts, in effectiveGasPrice); } + + protected override void PayRefund(Transaction tx, UInt256 refundAmount, IReleaseSpec spec) { } } diff --git a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs index ab148a9b784..4d6291e447b 100644 --- a/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Evm/TransactionProcessing/TransactionProcessor.cs @@ -13,6 +13,7 @@ using Nethermind.Crypto; using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.EvmObjectFormat.Handlers; +using Nethermind.Evm.GasPolicy; using Nethermind.Evm.Tracing; using Nethermind.Int256; using Nethermind.Logging; @@ -22,14 +23,27 @@ namespace Nethermind.Evm.TransactionProcessing { - public sealed class TransactionProcessor( + public sealed class TransactionProcessor( + ITransactionProcessor.IBlobBaseFeeCalculator blobBaseFeeCalculator, + ISpecProvider? specProvider, + IWorldState? worldState, + IVirtualMachine? virtualMachine, + ICodeInfoRepository? codeInfoRepository, + ILogManager? logManager) + : TransactionProcessorBase(blobBaseFeeCalculator, specProvider, worldState, virtualMachine, codeInfoRepository, logManager) + where TGasPolicy : struct, IGasPolicy; + + /// + /// Non-generic TransactionProcessor for backward compatibility with EthereumGasPolicy. + /// + public sealed class EthereumTransactionProcessor( ITransactionProcessor.IBlobBaseFeeCalculator blobBaseFeeCalculator, ISpecProvider? specProvider, IWorldState? worldState, IVirtualMachine? virtualMachine, ICodeInfoRepository? codeInfoRepository, ILogManager? logManager) - : TransactionProcessorBase(blobBaseFeeCalculator, specProvider, worldState, virtualMachine, codeInfoRepository, logManager); + : EthereumTransactionProcessorBase(blobBaseFeeCalculator, specProvider, worldState, virtualMachine, codeInfoRepository, logManager); public class BlobBaseFeeCalculator : ITransactionProcessor.IBlobBaseFeeCalculator { @@ -40,15 +54,16 @@ public bool TryCalculateBlobBaseFee(BlockHeader header, Transaction transaction, BlobGasCalculator.TryCalculateBlobBaseFee(header, transaction, blobGasPriceUpdateFraction, out blobBaseFee); } - public abstract class TransactionProcessorBase : ITransactionProcessor + public abstract class TransactionProcessorBase : ITransactionProcessor + where TGasPolicy : struct, IGasPolicy { protected EthereumEcdsa Ecdsa { get; } protected ILogger Logger { get; } protected ISpecProvider SpecProvider { get; } protected IWorldState WorldState { get; } - protected IVirtualMachine VirtualMachine { get; } + protected IVirtualMachine VirtualMachine { get; } private readonly ICodeInfoRepository _codeInfoRepository; - private SystemTransactionProcessor? _systemTransactionProcessor; + private SystemTransactionProcessor? _systemTransactionProcessor; private readonly ITransactionProcessor.IBlobBaseFeeCalculator _blobBaseFeeCalculator; private readonly ILogManager _logManager; private readonly TracedAccessWorldState? _tracedAccessWorldState; @@ -91,7 +106,7 @@ protected TransactionProcessorBase( ITransactionProcessor.IBlobBaseFeeCalculator? blobBaseFeeCalculator, ISpecProvider? specProvider, IWorldState? worldState, - IVirtualMachine? virtualMachine, + IVirtualMachine? virtualMachine, ICodeInfoRepository? codeInfoRepository, ILogManager? logManager) { @@ -149,7 +164,7 @@ private TransactionResult ExecuteCore(Transaction tx, ITxTracer tracer, Executio if (Logger.IsTrace) Logger.Trace($"Executing tx {tx.Hash}"); if (tx.IsSystem() || opts == ExecutionOptions.SkipValidation) { - _systemTransactionProcessor ??= new SystemTransactionProcessor(_blobBaseFeeCalculator, SpecProvider, WorldState, VirtualMachine, _codeInfoRepository, _logManager); + _systemTransactionProcessor ??= new SystemTransactionProcessor(_blobBaseFeeCalculator, SpecProvider, WorldState, VirtualMachine, _codeInfoRepository, _logManager); return _systemTransactionProcessor.Execute(tx, tracer, opts); } @@ -171,7 +186,7 @@ protected virtual TransactionResult Execute(Transaction tx, ITxTracer tracer, Ex bool commit = opts.HasFlag(ExecutionOptions.Commit) || (!opts.HasFlag(ExecutionOptions.SkipValidation) && !spec.IsEip658Enabled); TransactionResult result; - IntrinsicGas intrinsicGas = CalculateIntrinsicGas(tx, spec); + IntrinsicGas intrinsicGas = CalculateIntrinsicGas(tx, spec); if (!(result = ValidateStatic(tx, header, spec, opts, in intrinsicGas))) return result; UInt256 effectiveGasPrice = CalculateEffectiveGasPrice(tx, spec.IsEip1559Enabled, header.BaseFeePerGas, out UInt256 opcodeGasPrice); @@ -182,9 +197,16 @@ protected virtual TransactionResult Execute(Transaction tx, ITxTracer tracer, Ex bool deleteCallerAccount = RecoverSenderIfNeeded(tx, spec, opts, effectiveGasPrice); - if (!(result = ValidateSender(tx, header, spec, tracer, opts))) return result; - if (!(result = BuyGas(tx, spec, tracer, opts, effectiveGasPrice, out UInt256 premiumPerGas, out UInt256 senderReservedGasPayment, out UInt256 blobBaseFee))) return result; - if (!(result = IncrementNonce(tx, header, spec, tracer, opts))) return result; + if (!(result = ValidateSender(tx, header, spec, tracer, opts)) || + !(result = BuyGas(tx, spec, tracer, opts, effectiveGasPrice, out UInt256 premiumPerGas, out UInt256 senderReservedGasPayment, out UInt256 blobBaseFee)) || + !(result = IncrementNonce(tx, header, spec, tracer, opts))) + { + if (restore) + { + WorldState.Reset(resetBlockChanges: false); + } + return result; + } if (commit) WorldState.Commit(spec, tracer.IsTracingState ? tracer : NullTxTracer.Instance, commitRoots: false); @@ -193,8 +215,9 @@ protected virtual TransactionResult Execute(Transaction tx, ITxTracer tracer, Ex int delegationRefunds = (!spec.IsEip7702Enabled || !tx.HasAuthorizationList) ? 0 : ProcessDelegations(tx, spec, accessTracker); - if (!(result = CalculateAvailableGas(tx, intrinsicGas, out long gasAvailable))) return result; - if (!(result = BuildExecutionEnvironment(tx, spec, _codeInfoRepository, accessTracker, out ExecutionEnvironment env))) return result; + if (!(result = CalculateAvailableGas(tx, in intrinsicGas, out TGasPolicy gasAvailable))) return result; + if (!(result = BuildExecutionEnvironment(tx, spec, _codeInfoRepository, accessTracker, out ExecutionEnvironment e))) return result; + using ExecutionEnvironment env = e; int statusCode = !tracer.IsTracingInstructions ? ExecuteEvmCall(tx, header, spec, tracer, opts, delegationRefunds, intrinsicGas, accessTracker, gasAvailable, env, out TransactionSubstate substate, out GasConsumed spentGas) : @@ -213,7 +236,7 @@ protected virtual TransactionResult Execute(Transaction tx, ITxTracer tracer, Ex } else { - if (!opts.HasFlag(ExecutionOptions.SkipValidation)) + if (!senderReservedGasPayment.IsZero) WorldState.AddToBalance(tx.SenderAddress!, senderReservedGasPayment, spec); DecrementNonce(tx); @@ -255,9 +278,9 @@ protected virtual TransactionResult Execute(Transaction tx, ITxTracer tracer, Ex : TransactionResult.Ok; } - protected virtual TransactionResult CalculateAvailableGas(Transaction tx, IntrinsicGas intrinsicGas, out long gasAvailable) + protected virtual TransactionResult CalculateAvailableGas(Transaction tx, in IntrinsicGas intrinsicGas, out TGasPolicy gasAvailable) { - gasAvailable = tx.GasLimit - intrinsicGas.Standard; + gasAvailable = TGasPolicy.CreateAvailableFromIntrinsic(tx.GasLimit, intrinsicGas.Standard); return TransactionResult.Ok; } @@ -378,7 +401,7 @@ private static void UpdateMetrics(ExecutionOptions opts, UInt256 effectiveGasPri } /// - /// Validates the transaction, in a static manner (i.e. without accesing state/storage). + /// Validates the transaction, in a static manner (i.e. without accessing state/storage). /// It basically ensures the transaction is well formed (i.e. no null values where not allowed, no overflows, etc). /// As a part of validating the transaction the premium per gas will be calculated, to save computation this /// is returned in an out parameter. @@ -394,7 +417,7 @@ protected virtual TransactionResult ValidateStatic( BlockHeader header, IReleaseSpec spec, ExecutionOptions opts, - in IntrinsicGas intrinsicGas) + in IntrinsicGas intrinsicGas) { bool validate = !opts.HasFlag(ExecutionOptions.SkipValidation); @@ -422,10 +445,10 @@ protected virtual TransactionResult ValidateStatic( return TransactionResult.TransactionSizeOverMaxInitCodeSize; } - return ValidateGas(tx, header, intrinsicGas.MinimalGas, validate); + return ValidateGas(tx, header, TGasPolicy.GetRemainingGas(intrinsicGas.MinimalGas)); } - protected virtual TransactionResult ValidateGas(Transaction tx, BlockHeader header, long minGasRequired, bool validate) + protected virtual TransactionResult ValidateGas(Transaction tx, BlockHeader header, long minGasRequired) { if (tx.GasLimit < minGasRequired) { @@ -482,8 +505,8 @@ protected virtual bool RecoverSenderIfNeeded(Transaction tx, IReleaseSpec spec, return deleteCallerAccount; } - protected virtual IntrinsicGas CalculateIntrinsicGas(Transaction tx, IReleaseSpec spec) - => IntrinsicGasCalculator.Calculate(tx, spec); + protected virtual IntrinsicGas CalculateIntrinsicGas(Transaction tx, IReleaseSpec spec) + => IntrinsicGasCalculator.Calculate(tx, spec); protected virtual UInt256 CalculateEffectiveGasPrice(Transaction tx, bool eip1559Enabled, in UInt256 baseFee, out UInt256 opcodeGasPrice) { @@ -507,69 +530,69 @@ protected virtual TransactionResult ValidateSender(Transaction tx, BlockHeader h return TransactionResult.Ok; } + protected static bool ShouldValidateGas(Transaction tx, ExecutionOptions opts) + => !opts.HasFlag(ExecutionOptions.SkipValidation) || !tx.MaxFeePerGas.IsZero || !tx.MaxPriorityFeePerGas.IsZero; + protected virtual TransactionResult BuyGas(Transaction tx, IReleaseSpec spec, ITxTracer tracer, ExecutionOptions opts, in UInt256 effectiveGasPrice, out UInt256 premiumPerGas, out UInt256 senderReservedGasPayment, out UInt256 blobBaseFee) { premiumPerGas = UInt256.Zero; senderReservedGasPayment = UInt256.Zero; blobBaseFee = UInt256.Zero; - bool validate = !opts.HasFlag(ExecutionOptions.SkipValidation); + bool validate = ShouldValidateGas(tx, opts); - if (validate) + BlockHeader header = VirtualMachine.BlockExecutionContext.Header; + if (validate && !TryCalculatePremiumPerGas(tx, header.BaseFeePerGas, out premiumPerGas)) { - BlockHeader header = VirtualMachine.BlockExecutionContext.Header; - if (!TryCalculatePremiumPerGas(tx, header.BaseFeePerGas, out premiumPerGas)) - { - TraceLogInvalidTx(tx, "MINER_PREMIUM_IS_NEGATIVE"); - return TransactionResult.MinerPremiumNegative; - } + TraceLogInvalidTx(tx, "MINER_PREMIUM_IS_NEGATIVE"); + return TransactionResult.MinerPremiumNegative; + } - UInt256 senderBalance = WorldState.GetBalance(tx.SenderAddress!); - if (UInt256.SubtractUnderflow(in senderBalance, in tx.ValueRef, out UInt256 balanceLeft)) - { - TraceLogInvalidTx(tx, $"INSUFFICIENT_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}"); - return TransactionResult.InsufficientSenderBalance; - } + UInt256 senderBalance = WorldState.GetBalance(tx.SenderAddress!); + if (UInt256.SubtractUnderflow(in senderBalance, in tx.ValueRef, out UInt256 balanceLeft)) + { + TraceLogInvalidTx(tx, $"INSUFFICIENT_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}"); + return TransactionResult.InsufficientSenderBalance; + } - bool overflows; - if (spec.IsEip1559Enabled && !tx.IsFree()) + bool overflows; + if (spec.IsEip1559Enabled && !tx.IsFree()) + { + overflows = UInt256.MultiplyOverflow((UInt256)tx.GasLimit, tx.MaxFeePerGas, out UInt256 maxGasFee); + if (overflows || balanceLeft < maxGasFee) { - overflows = UInt256.MultiplyOverflow((UInt256)tx.GasLimit, tx.MaxFeePerGas, out UInt256 maxGasFee); - if (overflows || balanceLeft < maxGasFee) - { - TraceLogInvalidTx(tx, $"INSUFFICIENT_MAX_FEE_PER_GAS_FOR_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}, MAX_FEE_PER_GAS: {tx.MaxFeePerGas}"); - return TransactionResult.InsufficientMaxFeePerGasForSenderBalance; - } - - if (tx.SupportsBlobs) - { - overflows = UInt256.MultiplyOverflow(BlobGasCalculator.CalculateBlobGas(tx), (UInt256)tx.MaxFeePerBlobGas!, out UInt256 maxBlobGasFee); - if (overflows || UInt256.AddOverflow(maxGasFee, maxBlobGasFee, out UInt256 multidimGasFee) || multidimGasFee > balanceLeft) - { - TraceLogInvalidTx(tx, $"INSUFFICIENT_MAX_FEE_PER_BLOB_GAS_FOR_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}"); - return TransactionResult.InsufficientSenderBalance; - } - } + TraceLogInvalidTx(tx, $"INSUFFICIENT_MAX_FEE_PER_GAS_FOR_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}, MAX_FEE_PER_GAS: {tx.MaxFeePerGas}"); + return TransactionResult.InsufficientMaxFeePerGasForSenderBalance; } - overflows = UInt256.MultiplyOverflow((UInt256)tx.GasLimit, effectiveGasPrice, out senderReservedGasPayment); - if (!overflows && tx.SupportsBlobs) + if (tx.SupportsBlobs) { - overflows = !_blobBaseFeeCalculator.TryCalculateBlobBaseFee(header, tx, spec.BlobBaseFeeUpdateFraction, out blobBaseFee); - if (!overflows) + overflows = UInt256.MultiplyOverflow(BlobGasCalculator.CalculateBlobGas(tx), (UInt256)tx.MaxFeePerBlobGas!, out UInt256 maxBlobGasFee); + if (overflows || UInt256.AddOverflow(maxGasFee, maxBlobGasFee, out UInt256 multidimGasFee) || multidimGasFee > balanceLeft) { - overflows = UInt256.AddOverflow(senderReservedGasPayment, blobBaseFee, out senderReservedGasPayment); + TraceLogInvalidTx(tx, $"INSUFFICIENT_MAX_FEE_PER_BLOB_GAS_FOR_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}"); + return TransactionResult.InsufficientSenderBalance; } } + } - if (overflows || senderReservedGasPayment > balanceLeft) + overflows = UInt256.MultiplyOverflow((UInt256)tx.GasLimit, effectiveGasPrice, out senderReservedGasPayment); + if (!overflows && tx.SupportsBlobs) + { + overflows = !_blobBaseFeeCalculator.TryCalculateBlobBaseFee(header, tx, spec.BlobBaseFeeUpdateFraction, out blobBaseFee); + if (!overflows) { - TraceLogInvalidTx(tx, $"INSUFFICIENT_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}"); - return TransactionResult.InsufficientSenderBalance; + overflows = UInt256.AddOverflow(senderReservedGasPayment, blobBaseFee, out senderReservedGasPayment); } } - if (validate) WorldState.SubtractFromBalance(tx.SenderAddress, senderReservedGasPayment, spec); + if (overflows || senderReservedGasPayment > balanceLeft) + { + TraceLogInvalidTx(tx, $"INSUFFICIENT_SENDER_BALANCE: ({tx.SenderAddress})_BALANCE = {senderBalance}"); + return TransactionResult.InsufficientSenderBalance; + } + + if (!senderReservedGasPayment.IsZero) WorldState.SubtractFromBalance(tx.SenderAddress, senderReservedGasPayment, spec); return TransactionResult.Ok; } @@ -580,8 +603,8 @@ protected virtual TransactionResult IncrementNonce(Transaction tx, BlockHeader h UInt256 nonce = WorldState.GetNonce(tx.SenderAddress!); if (validate && tx.Nonce != nonce) { - TraceLogInvalidTx(tx, $"WRONG_TRANSACTION_NONCE: {tx.Nonce} (expected {WorldState.GetNonce(tx.SenderAddress)})"); - return TransactionResult.WrongTransactionNonce; + TraceLogInvalidTx(tx, $"WRONG_TRANSACTION_NONCE: {tx.Nonce} (expected {nonce})"); + return tx.Nonce > nonce ? TransactionResult.TransactionNonceTooHigh : TransactionResult.TransactionNonceTooLow; } UInt256 newNonce = validate || nonce < ulong.MaxValue ? nonce + 1 : 0; @@ -605,7 +628,6 @@ private TransactionResult BuildExecutionEnvironment( { Address recipient = tx.GetRecipient(tx.IsContractCreation ? WorldState.GetNonce(tx.SenderAddress!) : 0); if (recipient is null) ThrowInvalidDataException("Recipient has not been resolved properly before tx execution"); - ICodeInfo? codeInfo; ReadOnlyMemory inputData = tx.IsMessageCall ? tx.Data : default; if (tx.IsContractCreation) @@ -636,7 +658,7 @@ private TransactionResult BuildExecutionEnvironment( accessTracker.WarmUp(tx.SenderAddress!); } - env = new ExecutionEnvironment( + env = ExecutionEnvironment.Rent( codeInfo: codeInfo, executingAccount: recipient, caller: tx.SenderAddress!, @@ -658,10 +680,10 @@ private int ExecuteEvmCall( ITxTracer tracer, ExecutionOptions opts, int delegationRefunds, - IntrinsicGas gas, + IntrinsicGas gas, in StackAccessTracker accessedItems, - long gasAvailable, - in ExecutionEnvironment env, + TGasPolicy gasAvailable, + ExecutionEnvironment env, out TransactionSubstate substate, out GasConsumed gasConsumed) where TTracingInst : struct, IFlag @@ -689,65 +711,66 @@ private int ExecuteEvmCall( { // If EOF header parsing or full container validation fails, transaction is considered valid and failing. // Gas for initcode execution is not consumed, only intrinsic creation transaction costs are charged. - gasConsumed = gas.MinimalGas; + long minimalGasLong = TGasPolicy.GetRemainingGas(gas.MinimalGas); + gasConsumed = minimalGasLong; // If noValidation we didn't charge for gas, so do not refund; otherwise return unspent gas if (!opts.HasFlag(ExecutionOptions.SkipValidation)) - WorldState.AddToBalance(tx.SenderAddress!, (ulong)(tx.GasLimit - gas.MinimalGas) * VirtualMachine.TxExecutionContext.GasPrice, spec); + WorldState.AddToBalance(tx.SenderAddress!, (ulong)(tx.GasLimit - minimalGasLong) * VirtualMachine.TxExecutionContext.GasPrice, spec); goto Complete; } ExecutionType executionType = tx.IsContractCreation ? ((spec.IsEofEnabled && tx.IsEofContractCreation) ? ExecutionType.TXCREATE : ExecutionType.CREATE) : ExecutionType.TRANSACTION; - using (EvmState state = EvmState.RentTopLevel(gasAvailable, executionType, in env, in accessedItems, in snapshot)) + using (VmState state = VmState.RentTopLevel(gasAvailable, executionType, env, in accessedItems, in snapshot)) { substate = VirtualMachine.ExecuteTransaction(state, WorldState, tracer); Metrics.IncrementOpCodes(VirtualMachine.OpCodeCount); - gasAvailable = state.GasAvailable; - } + gasAvailable = state.Gas; - if (tracer.IsTracingAccess) - { - tracer.ReportAccess(accessedItems.AccessedAddresses, accessedItems.AccessedStorageCells); - } + if (tracer.IsTracingAccess) + { + tracer.ReportAccess(accessedItems.AccessedAddresses, accessedItems.AccessedStorageCells); + } - if (substate.ShouldRevert || substate.IsError) - { - if (Logger.IsTrace) Logger.Trace("Restoring state from before transaction"); - WorldState.Restore(snapshot); - } - else - { - if (tx.IsContractCreation) + if (substate.ShouldRevert || substate.IsError) + { + if (Logger.IsTrace) Logger.Trace("Restoring state from before transaction"); + WorldState.Restore(snapshot); + } + else { - if (!spec.IsEofEnabled || tx.IsLegacyContractCreation) + if (tx.IsContractCreation) { - if (!DeployLegacyContract(spec, env.ExecutingAccount, in substate, in accessedItems, ref gasAvailable)) + if (!spec.IsEofEnabled || tx.IsLegacyContractCreation) { - goto FailContractCreate; + if (!DeployLegacyContract(spec, env.ExecutingAccount, in substate, in accessedItems, ref gasAvailable)) + { + goto FailContractCreate; + } } - } - else - { - if (!DeployEofContract(spec, env.ExecutingAccount, in substate, in accessedItems, ref gasAvailable)) + else { - goto FailContractCreate; + if (!DeployEofContract(spec, env.ExecutingAccount, in substate, in accessedItems, ref gasAvailable)) + { + goto FailContractCreate; + } } } - } - foreach (Address toBeDestroyed in substate.DestroyList) - { - if (Logger.IsTrace) - Logger.Trace($"Destroying account {toBeDestroyed}"); + foreach (Address toBeDestroyed in substate.DestroyList) + { + if (Logger.IsTrace) + Logger.Trace($"Destroying account {toBeDestroyed}"); - WorldState.ClearStorage(toBeDestroyed); - WorldState.DeleteAccount(toBeDestroyed); + WorldState.ClearStorage(toBeDestroyed); + WorldState.DeleteAccount(toBeDestroyed); - if (tracer.IsTracingRefunds) - tracer.ReportRefund(RefundOf.Destroy(spec.IsEip3529Enabled)); - } + if (tracer.IsTracingRefunds) + tracer.ReportRefund(RefundOf.Destroy(spec.IsEip3529Enabled)); + } - statusCode = StatusCode.Success; + statusCode = StatusCode.Success; + } } gasConsumed = Refund(tx, header, spec, opts, in substate, gasAvailable, @@ -770,10 +793,10 @@ protected virtual GasConsumed RefundOnFailContractCreation(Transaction tx, Block return tx.GasLimit; } - protected virtual bool DeployLegacyContract(IReleaseSpec spec, Address codeOwner, in TransactionSubstate substate, in StackAccessTracker accessedItems, ref long unspentGas) + protected virtual bool DeployLegacyContract(IReleaseSpec spec, Address codeOwner, in TransactionSubstate substate, in StackAccessTracker accessedItems, ref TGasPolicy unspentGas) { long codeDepositGasCost = CodeDepositHandler.CalculateCost(spec, substate.Output.Bytes.Length); - if (unspentGas < codeDepositGasCost && spec.ChargeForTopLevelCreate) + if (TGasPolicy.GetRemainingGas(unspentGas) < codeDepositGasCost && spec.ChargeForTopLevelCreate) { return false; } @@ -783,7 +806,7 @@ protected virtual bool DeployLegacyContract(IReleaseSpec spec, Address codeOwner return false; } - if (unspentGas >= codeDepositGasCost) + if (TGasPolicy.GetRemainingGas(unspentGas) >= codeDepositGasCost) { // Copy the bytes so it's not live memory that will be used in another tx byte[] code = substate.Output.Bytes.ToArray(); @@ -793,20 +816,20 @@ protected virtual bool DeployLegacyContract(IReleaseSpec spec, Address codeOwner accessedItems.WarmUpLargeContract(codeOwner); } - unspentGas -= codeDepositGasCost; + TGasPolicy.Consume(ref unspentGas, codeDepositGasCost); } return true; } - private bool DeployEofContract(IReleaseSpec spec, Address codeOwner, in TransactionSubstate substate, in StackAccessTracker accessedItems, ref long unspentGas) + private bool DeployEofContract(IReleaseSpec spec, Address codeOwner, in TransactionSubstate substate, in StackAccessTracker accessedItems, ref TGasPolicy unspentGas) { // 1 - load deploy EOF subContainer at deploy_container_index in the container from which RETURNCODE is executed ReadOnlySpan auxExtraData = substate.Output.Bytes.Span; EofCodeInfo deployCodeInfo = (EofCodeInfo)substate.Output.DeployCode; long codeDepositGasCost = CodeDepositHandler.CalculateCost(spec, deployCodeInfo.Code.Length + auxExtraData.Length); - if (unspentGas < codeDepositGasCost && spec.ChargeForTopLevelCreate) + if (TGasPolicy.GetRemainingGas(unspentGas) < codeDepositGasCost && spec.ChargeForTopLevelCreate) { return false; } @@ -835,7 +858,7 @@ private bool DeployEofContract(IReleaseSpec spec, Address codeOwner, in Transact bytecodeResult[dataSubHeaderSectionStart + 1] = (byte)(dataSize >> 8); bytecodeResult[dataSubHeaderSectionStart + 2] = (byte)(dataSize & 0xFF); - if (unspentGas >= codeDepositGasCost) + if (TGasPolicy.GetRemainingGas(unspentGas) >= codeDepositGasCost) { // 4 - set state[new_address].code to the updated deploy container // push new_address onto the stack (already done before the ifs) @@ -844,7 +867,7 @@ private bool DeployEofContract(IReleaseSpec spec, Address codeOwner, in Transact { accessedItems.WarmUpLargeContract(codeOwner); } - unspentGas -= codeDepositGasCost; + TGasPolicy.Consume(ref unspentGas, codeDepositGasCost); } return true; @@ -904,14 +927,14 @@ protected void TraceLogInvalidTx(Transaction transaction, string reason) } protected virtual GasConsumed Refund(Transaction tx, BlockHeader header, IReleaseSpec spec, ExecutionOptions opts, - in TransactionSubstate substate, in long unspentGas, in UInt256 gasPrice, int codeInsertRefunds, long floorGas) + in TransactionSubstate substate, in TGasPolicy unspentGas, in UInt256 gasPrice, int codeInsertRefunds, TGasPolicy floorGas) { long spentGas = tx.GasLimit; var codeInsertRefund = (GasCostOf.NewAccount - GasCostOf.PerAuthBaseCost) * codeInsertRefunds; if (!substate.IsError) { - spentGas -= unspentGas; + spentGas -= TGasPolicy.GetRemainingGas(unspentGas); long totalToRefund = codeInsertRefund; if (!substate.ShouldRevert) @@ -919,7 +942,7 @@ protected virtual GasConsumed Refund(Transaction tx, BlockHeader header, IReleas long actualRefund = CalculateClaimableRefund(spentGas, totalToRefund, spec); if (Logger.IsTrace) - Logger.Trace("Refunding unused gas of " + unspentGas + " and refund of " + actualRefund); + Logger.Trace("Refunding unused gas of " + TGasPolicy.GetRemainingGas(unspentGas) + " and refund of " + actualRefund); spentGas -= actualRefund; } else if (codeInsertRefund > 0) @@ -932,16 +955,21 @@ protected virtual GasConsumed Refund(Transaction tx, BlockHeader header, IReleas } long operationGas = spentGas; - spentGas = Math.Max(spentGas, floorGas); + spentGas = Math.Max(spentGas, TGasPolicy.GetRemainingGas(floorGas)); // If noValidation we didn't charge for gas, so do not refund - // report to tracer?? - if (!opts.HasFlag(ExecutionOptions.SkipValidation)) - WorldState.AddToBalance(tx.SenderAddress!, (ulong)(tx.GasLimit - spentGas) * gasPrice, spec); + UInt256 refundAmount = (ulong)(tx.GasLimit - spentGas) * gasPrice; + PayRefund(tx, refundAmount, spec); return new GasConsumed(spentGas, operationGas); } + protected virtual void PayRefund(Transaction tx, UInt256 refundAmount, IReleaseSpec spec) + { + if (!refundAmount.IsZero) + WorldState.AddToBalance(tx.SenderAddress!, refundAmount, spec); + } + protected virtual long CalculateClaimableRefund(long spentGas, long totalRefund, IReleaseSpec spec) => RefundHelper.CalculateClaimableRefund(spentGas, totalRefund, spec); @@ -949,6 +977,18 @@ protected virtual long CalculateClaimableRefund(long spentGas, long totalRefund, private static void ThrowInvalidDataException(string message) => throw new InvalidDataException(message); } + /// + /// Non-generic TransactionProcessorBase for backward compatibility with EthereumGasPolicy. + /// + public abstract class EthereumTransactionProcessorBase( + ITransactionProcessor.IBlobBaseFeeCalculator? blobBaseFeeCalculator, + ISpecProvider? specProvider, + IWorldState? worldState, + IVirtualMachine? virtualMachine, + ICodeInfoRepository? codeInfoRepository, + ILogManager? logManager) + : TransactionProcessorBase(blobBaseFeeCalculator, specProvider, worldState, virtualMachine, codeInfoRepository, logManager); + public readonly struct TransactionResult : IEquatable { private TransactionResult(ErrorType error = ErrorType.None, EvmExceptionType evmException = EvmExceptionType.None) @@ -973,7 +1013,8 @@ private TransactionResult(ErrorType error = ErrorType.None, EvmExceptionType evm ErrorType.SenderHasDeployedCode => "sender has deployed code", ErrorType.SenderNotSpecified => "sender not specified", ErrorType.TransactionSizeOverMaxInitCodeSize => "EIP-3860 - transaction size over max init code size", - ErrorType.WrongTransactionNonce => "wrong transaction nonce", + ErrorType.TransactionNonceTooHigh => "transaction nonce is too high", + ErrorType.TransactionNonceTooLow => "transaction nonce is too low", _ => "" }; public static implicit operator TransactionResult(ErrorType error) => new(error); @@ -1003,7 +1044,8 @@ public static TransactionResult EvmException(EvmExceptionType evmExceptionType, public static readonly TransactionResult SenderHasDeployedCode = ErrorType.SenderHasDeployedCode; public static readonly TransactionResult SenderNotSpecified = ErrorType.SenderNotSpecified; public static readonly TransactionResult TransactionSizeOverMaxInitCodeSize = ErrorType.TransactionSizeOverMaxInitCodeSize; - public static readonly TransactionResult WrongTransactionNonce = ErrorType.WrongTransactionNonce; + public static readonly TransactionResult TransactionNonceTooHigh = ErrorType.TransactionNonceTooHigh; + public static readonly TransactionResult TransactionNonceTooLow = ErrorType.TransactionNonceTooLow; public enum ErrorType { @@ -1018,7 +1060,8 @@ public enum ErrorType SenderHasDeployedCode, SenderNotSpecified, TransactionSizeOverMaxInitCodeSize, - WrongTransactionNonce, + TransactionNonceTooHigh, + TransactionNonceTooLow, } } } diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.Warmup.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.Warmup.cs index c332d8fc63e..8d50611b3f7 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.Warmup.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.Warmup.cs @@ -10,6 +10,7 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Evm.CodeAnalysis; +using Nethermind.Evm.GasPolicy; using Nethermind.Evm.State; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; @@ -20,15 +21,14 @@ namespace Nethermind.Evm; -using unsafe OpCode = delegate*; - -public unsafe partial class VirtualMachine +public unsafe partial class VirtualMachine + where TGasPolicy : struct, IGasPolicy { public static void WarmUpEvmInstructions(IWorldState state, ICodeInfoRepository codeInfoRepository) { IReleaseSpec spec = Fork.GetLatest(); IBlockhashProvider hashProvider = new WarmupBlockhashProvider(MainnetSpecProvider.Instance); - VirtualMachine vm = new(hashProvider, MainnetSpecProvider.Instance, LimboLogs.Instance); + VirtualMachine vm = new(hashProvider, MainnetSpecProvider.Instance, LimboLogs.Instance); ILogManager lm = new OneLoggerLogManager(NullLogger.Instance); byte[] bytecode = new byte[64]; @@ -39,39 +39,39 @@ public static void WarmUpEvmInstructions(IWorldState state, ICodeInfoRepository state.CreateAccount(addressOne, 1000.Ether()); state.Commit(spec); - BlockHeader _header = new(Keccak.Zero, Keccak.Zero, addressOne, UInt256.One, MainnetSpecProvider.PragueActivation.BlockNumber, Int64.MaxValue, 1UL, Bytes.Empty, 0, 0); + BlockHeader header = new(Keccak.Zero, Keccak.Zero, addressOne, UInt256.One, MainnetSpecProvider.PragueActivation.BlockNumber, Int64.MaxValue, 1UL, Bytes.Empty, 0, 0); - vm.SetBlockExecutionContext(new BlockExecutionContext(_header, spec)); + vm.SetBlockExecutionContext(new BlockExecutionContext(header, spec)); vm.SetTxExecutionContext(new TxExecutionContext(addressOne, codeInfoRepository, null, 0)); - ExecutionEnvironment env = new( + using ExecutionEnvironment env = ExecutionEnvironment.Rent( + codeInfo: new CodeInfo(bytecode), executingAccount: addressOne, - codeSource: addressOne, caller: addressOne, - codeInfo: new CodeInfo(bytecode), - value: 0, + codeSource: addressOne, + callDepth: 0, transferValue: 0, - inputData: default, - callDepth: 0); + value: 0, + inputData: default); - using (var evmState = EvmState.RentTopLevel(long.MaxValue, ExecutionType.TRANSACTION, in env, new StackAccessTracker(), state.TakeSnapshot())) + using (VmState vmState = VmState.RentTopLevel(TGasPolicy.FromLong(long.MaxValue), ExecutionType.TRANSACTION, env, new StackAccessTracker(), state.TakeSnapshot())) { - vm.EvmState = evmState; + vm.VmState = vmState; vm._worldState = state; vm._codeInfoRepository = codeInfoRepository; - evmState.InitializeStacks(); + vmState.InitializeStacks(); - RunOpCodes(vm, state, evmState, spec); - RunOpCodes(vm, state, evmState, spec); + RunOpCodes(vm, state, vmState, spec); + RunOpCodes(vm, state, vmState, spec); } - TransactionProcessor processor = new(BlobBaseFeeCalculator.Instance, MainnetSpecProvider.Instance, state, vm, codeInfoRepository, lm); - processor.SetBlockExecutionContext(new BlockExecutionContext(_header, spec)); + TransactionProcessor processor = new(BlobBaseFeeCalculator.Instance, MainnetSpecProvider.Instance, state, vm, codeInfoRepository, lm); + processor.SetBlockExecutionContext(new BlockExecutionContext(header, spec)); RunTransactions(processor, state, spec); } - private static void RunTransactions(TransactionProcessor processor, IWorldState state, IReleaseSpec spec) + private static void RunTransactions(TransactionProcessor processor, IWorldState state, IReleaseSpec spec) { const int WarmUpIterations = 40; @@ -147,16 +147,16 @@ static void AddPrecompileCall(List codeToDeploy) codeToDeploy.Add((byte)Instruction.POP); } - private static void RunOpCodes(VirtualMachine vm, IWorldState state, EvmState evmState, IReleaseSpec spec) + private static void RunOpCodes(VirtualMachine vm, IWorldState state, VmState vmState, IReleaseSpec spec) where TTracingInst : struct, IFlag { const int WarmUpIterations = 40; - OpCode[] opcodes = vm.GenerateOpCodes(spec); + var opcodes = vm.GenerateOpCodes(spec); ITxTracer txTracer = new FeesTracer(); vm._txTracer = txTracer; - EvmStack stack = new(0, txTracer, evmState.DataStack); - long gas = long.MaxValue; + EvmStack stack = new(0, txTracer, vmState.DataStack); + TGasPolicy gas = TGasPolicy.FromLong(long.MaxValue); int pc = 0; for (int repeat = 0; repeat < WarmUpIterations; repeat++) @@ -172,15 +172,15 @@ private static void RunOpCodes(VirtualMachine vm, IWorldState stat stack.PushOne(); opcodes[i](vm, ref stack, ref gas, ref pc); - if (vm.ReturnData is EvmState returnState) + if (vm.ReturnData is VmState returnState) { returnState.Dispose(); vm.ReturnData = null!; } state.Reset(resetBlockChanges: true); - stack = new(0, txTracer, evmState.DataStack); - gas = long.MaxValue; + stack = new(0, txTracer, vmState.DataStack); + gas = TGasPolicy.FromLong(long.MaxValue); pc = 0; } } diff --git a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs index ee271825afb..127bfb731d2 100644 --- a/src/Nethermind/Nethermind.Evm/VirtualMachine.cs +++ b/src/Nethermind/Nethermind.Evm/VirtualMachine.cs @@ -8,18 +8,21 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; +using Nethermind.Config; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Evm.CodeAnalysis; using Nethermind.Evm.EvmObjectFormat.Handlers; +using Nethermind.Evm.GasPolicy; using Nethermind.Evm.Precompiles; using Nethermind.Evm.Tracing; using Nethermind.Logging; using Nethermind.Evm.State; using static Nethermind.Evm.EvmObjectFormat.EofValidator; +using static Nethermind.Evm.VirtualMachineStatics; #if DEBUG using Nethermind.Evm.Tracing.Debugger; @@ -28,32 +31,33 @@ [assembly: InternalsVisibleTo("Nethermind.Evm.Test")] namespace Nethermind.Evm; -using unsafe OpCode = delegate*; using Int256; public sealed class EthereumVirtualMachine( IBlockhashProvider? blockHashProvider, ISpecProvider? specProvider, ILogManager? logManager -) : VirtualMachine(blockHashProvider, specProvider, logManager) +) : VirtualMachine(blockHashProvider, specProvider, logManager), IVirtualMachine { } -public unsafe partial class VirtualMachine( - IBlockhashProvider? blockHashProvider, - ISpecProvider? specProvider, - ILogManager? logManager) : IVirtualMachine +/// +/// Static fields shared across all VirtualMachine generic instantiations. +/// Moved out of the generic class to avoid duplication per type parameter. +/// +public static class VirtualMachineStatics { public const int MaxCallDepth = Eof1.RETURN_STACK_MAX_HEIGHT; - private static readonly UInt256 P255Int = (UInt256)BigInteger.Pow(2, 255); - internal static readonly byte[] EofHash256 = KeccakHash.ComputeHashBytes(MAGIC); - internal static ref readonly UInt256 P255 => ref P255Int; - internal static readonly UInt256 BigInt256 = 256; - internal static readonly UInt256 BigInt32 = 32; - internal static readonly byte[] BytesZero = [0]; + public static readonly UInt256 P255Int = (UInt256)BigInteger.Pow(2, 255); + public static readonly byte[] EofHash256 = KeccakHash.ComputeHashBytes(EvmObjectFormat.EofValidator.MAGIC); + public static ref readonly UInt256 P255 => ref P255Int; + public static readonly UInt256 BigInt256 = 256; + public static readonly UInt256 BigInt32 = 32; - internal static readonly byte[] BytesZero32 = + public static readonly byte[] BytesZero = [0]; + + public static readonly byte[] BytesZero32 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -61,7 +65,7 @@ public unsafe partial class VirtualMachine( 0, 0, 0, 0, 0, 0, 0, 0 }; - internal static readonly byte[] BytesMax32 = + public static readonly byte[] BytesMax32 = { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, @@ -69,15 +73,22 @@ public unsafe partial class VirtualMachine( 255, 255, 255, 255, 255, 255, 255, 255 }; - internal static readonly PrecompileExecutionFailureException PrecompileExecutionFailureException = new(); - internal static readonly OutOfGasException PrecompileOutOfGasException = new(); + public static readonly PrecompileExecutionFailureException PrecompileExecutionFailureException = new(); + public static readonly OutOfGasException PrecompileOutOfGasException = new(); +} +public unsafe partial class VirtualMachine( + IBlockhashProvider? blockHashProvider, + ISpecProvider? specProvider, + ILogManager? logManager) : IVirtualMachine + where TGasPolicy : struct, IGasPolicy +{ private readonly ValueHash256 _chainId = ((UInt256)specProvider.ChainId).ToValueHash(); private readonly IBlockhashProvider _blockHashProvider = blockHashProvider ?? throw new ArgumentNullException(nameof(blockHashProvider)); protected readonly ISpecProvider _specProvider = specProvider ?? throw new ArgumentNullException(nameof(specProvider)); protected readonly ILogger _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - protected readonly Stack _stateStack = new(); + protected readonly Stack> _stateStack = new(); protected IWorldState _worldState; private (Address Address, bool ShouldDelete) _parityTouchBugAccount = (Address.FromNumber(3), false); @@ -86,10 +97,11 @@ public unsafe partial class VirtualMachine( private ICodeInfoRepository _codeInfoRepository; - private OpCode[] _opcodeMethods; + private delegate*, ref EvmStack, ref TGasPolicy, ref int, EvmExceptionType>[] _opcodeMethods; private static long _txCount; - protected EvmState _currentState; + private ReadOnlyMemory _returnDataBuffer = Array.Empty(); + protected VmState _currentState; protected ReadOnlyMemory? _previousCallResult; protected UInt256 _previousCallOutputDestination; @@ -99,10 +111,10 @@ public unsafe partial class VirtualMachine( public ITxTracer TxTracer => _txTracer; public IWorldState WorldState => _worldState; public ref readonly ValueHash256 ChainId => ref _chainId; - public ReadOnlyMemory ReturnDataBuffer { get; set; } = Array.Empty(); + public ref ReadOnlyMemory ReturnDataBuffer => ref _returnDataBuffer; public object ReturnData { get; set; } public IBlockhashProvider BlockHashProvider => _blockHashProvider; - protected Stack StateStack => _stateStack; + protected Stack> StateStack => _stateStack; private BlockExecutionContext _blockExecutionContext; public void SetBlockExecutionContext(in BlockExecutionContext blockExecutionContext) => _blockExecutionContext = blockExecutionContext; @@ -115,7 +127,7 @@ public unsafe partial class VirtualMachine( /// public void SetTxExecutionContext(in TxExecutionContext txExecutionContext) => _txExecutionContext = txExecutionContext; - public EvmState EvmState { get => _currentState; protected set => _currentState = value; } + public VmState VmState { get => _currentState; protected set => _currentState = value; } public int SectionIndex { get; set; } public int OpCodeCount { get; set; } @@ -127,7 +139,7 @@ public unsafe partial class VirtualMachine( /// /// The type of tracing instructions flag used to conditionally trace execution actions. /// - /// The initial EVM state to begin transaction execution. + /// The initial EVM state to begin transaction execution. /// The current world state that may be modified during execution. /// An object used to record execution details and trace transaction actions. /// @@ -137,7 +149,7 @@ public unsafe partial class VirtualMachine( /// Thrown when an EVM-specific error occurs during execution. /// public virtual TransactionSubstate ExecuteTransaction( - EvmState evmState, + VmState vmState, IWorldState worldState, ITxTracer txTracer) where TTracingInst : struct, IFlag @@ -155,7 +167,7 @@ public virtual TransactionSubstate ExecuteTransaction( OpCodeCount = 0; // Initialize the code repository and set up the initial execution state. _codeInfoRepository = TxExecutionContext.CodeInfoRepository; - _currentState = evmState; + _currentState = vmState; _previousCallResult = null; _previousCallOutputDestination = UInt256.Zero; ZeroPaddedSpan previousCallOutput = ZeroPaddedSpan.Empty; @@ -240,18 +252,18 @@ public virtual TransactionSubstate ExecuteTransaction( } // For nested call frames, merge the results and restore the previous execution state. - using (EvmState previousState = _currentState) + using (VmState previousState = _currentState) { // Restore the previous state from the stack and mark it as a continuation. _currentState = _stateStack.Pop(); _currentState.IsContinuation = true; // Refund the remaining gas from the completed call frame. - _currentState.GasAvailable += previousState.GasAvailable; + TGasPolicy.Refund(ref _currentState.Gas, in previousState.Gas); bool previousStateSucceeded = true; if (!callResult.ShouldRevert) { - long gasAvailableForCodeDeposit = previousState.GasAvailable; + long gasAvailableForCodeDeposit = TGasPolicy.GetRemainingGas(previousState.Gas); // Process contract creation calls differently from regular calls. if (previousState.ExecutionType.IsAnyCreate()) @@ -315,7 +327,7 @@ public virtual TransactionSubstate ExecuteTransaction( } } - protected void PrepareCreateData(EvmState previousState, ref ZeroPaddedSpan previousCallOutput) + protected void PrepareCreateData(VmState previousState, ref ZeroPaddedSpan previousCallOutput) { _previousCallResult = previousState.Env.ExecutingAccount.Bytes; _previousCallOutputDestination = UInt256.Zero; @@ -323,7 +335,7 @@ protected void PrepareCreateData(EvmState previousState, ref ZeroPaddedSpan prev previousCallOutput = ZeroPaddedSpan.Empty; } - protected ZeroPaddedSpan HandleRegularReturn(scoped in CallResult callResult, EvmState previousState) + protected ZeroPaddedSpan HandleRegularReturn(scoped in CallResult callResult, VmState previousState) where TTracingInst : struct, IFlag { ZeroPaddedSpan previousCallOutput; @@ -345,13 +357,13 @@ protected ZeroPaddedSpan HandleRegularReturn(scoped in CallResult if (_txTracer.IsTracingActions) { - _txTracer.ReportActionEnd(previousState.GasAvailable, ReturnDataBuffer); + _txTracer.ReportActionEnd(TGasPolicy.GetRemainingGas(previousState.Gas), ReturnDataBuffer); } return previousCallOutput; } - protected void HandleEofCreate(in CallResult callResult, EvmState previousState, long gasAvailableForCodeDeposit, ref bool previousStateSucceeded) + protected void HandleEofCreate(in CallResult callResult, VmState previousState, long gasAvailableForCodeDeposit, ref bool previousStateSucceeded) { Address callCodeOwner = previousState.Env.ExecutingAccount; // ReturnCode was called with a container index and auxdata @@ -400,16 +412,16 @@ protected void HandleEofCreate(in CallResult callResult, EvmState previousState, // 4 - set state[new_address].code to the updated deploy container // push new_address onto the stack (already done before the ifs) _codeInfoRepository.InsertCode(bytecodeResultArray, callCodeOwner, spec); - _currentState.GasAvailable -= codeDepositGasCost; + TGasPolicy.Consume(ref _currentState.Gas, codeDepositGasCost); if (_txTracer.IsTracingActions) { - _txTracer.ReportActionEnd(previousState.GasAvailable - codeDepositGasCost, callCodeOwner, bytecodeResultArray); + _txTracer.ReportActionEnd(TGasPolicy.GetRemainingGas(previousState.Gas) - codeDepositGasCost, callCodeOwner, bytecodeResultArray); } } else if (spec.FailOnOutOfGasCodeDeposit || invalidCode) { - _currentState.GasAvailable -= gasAvailableForCodeDeposit; + TGasPolicy.Consume(ref _currentState.Gas, gasAvailableForCodeDeposit); _worldState.Restore(previousState.Snapshot); if (!previousState.IsCreateOnPreExistingAccount) { @@ -453,7 +465,7 @@ protected void HandleEofCreate(in CallResult callResult, EvmState previousState, /// protected void HandleLegacyCreate( in CallResult callResult, - EvmState previousState, + VmState previousState, long gasAvailableForCodeDeposit, ref bool previousStateSucceeded) { @@ -478,19 +490,19 @@ protected void HandleLegacyCreate( _codeInfoRepository.InsertCode(code, callCodeOwner, spec); // Deduct the gas cost for the code deposit from the current state's available gas. - _currentState.GasAvailable -= codeDepositGasCost; + TGasPolicy.Consume(ref _currentState.Gas, codeDepositGasCost); // If tracing is enabled, report the successful code deposit operation. if (isTracing) { - _txTracer.ReportActionEnd(previousState.GasAvailable - codeDepositGasCost, callCodeOwner, callResult.Output.Bytes); + _txTracer.ReportActionEnd(TGasPolicy.GetRemainingGas(previousState.Gas) - codeDepositGasCost, callCodeOwner, callResult.Output.Bytes); } } // If the code deposit should fail due to out-of-gas or invalid code conditions... else if (spec.FailOnOutOfGasCodeDeposit || invalidCode) { // Consume all remaining gas allocated for the code deposit. - _currentState.GasAvailable -= gasAvailableForCodeDeposit; + TGasPolicy.Consume(ref _currentState.Gas, gasAvailableForCodeDeposit); // Roll back the world state to its snapshot from before the creation attempt. _worldState.Restore(previousState.Snapshot); @@ -517,7 +529,7 @@ protected void HandleLegacyCreate( // report the end of the action if tracing is enabled. else if (isTracing) { - _txTracer.ReportActionEnd(previousState.GasAvailable - codeDepositGasCost, callCodeOwner, callResult.Output.Bytes); + _txTracer.ReportActionEnd(TGasPolicy.GetRemainingGas(previousState.Gas) - codeDepositGasCost, callCodeOwner, callResult.Output.Bytes); } } @@ -551,7 +563,7 @@ protected TransactionSubstate PrepareTopLevelSubstate(scoped in CallResult callR /// A reference to the output data buffer that will be updated with the reverted call's output, /// padded to match the expected length. /// - protected void HandleRevert(EvmState previousState, in CallResult callResult, ref ZeroPaddedSpan previousCallOutput) + protected void HandleRevert(VmState previousState, in CallResult callResult, ref ZeroPaddedSpan previousCallOutput) { // Restore the world state to the snapshot taken before the execution of the call. _worldState.Restore(previousState.Snapshot); @@ -581,7 +593,7 @@ protected void HandleRevert(EvmState previousState, in CallResult callResult, re // If transaction tracing is enabled, report the revert action along with the available gas and output bytes. if (_txTracer.IsTracingActions) { - _txTracer.ReportActionRevert(previousState.GasAvailable, outputBytes); + _txTracer.ReportActionRevert(TGasPolicy.GetRemainingGas(previousState.Gas), outputBytes); } } @@ -771,13 +783,13 @@ protected TransactionSubstate HandleException(scoped in CallResult callResult, s /// A containing the results of the precompile execution. In case of a failure, /// returns the default value of . /// - protected virtual CallResult ExecutePrecompile(EvmState currentState, bool isTracingActions, out Exception? failure, out string? substateError) + protected virtual CallResult ExecutePrecompile(VmState currentState, bool isTracingActions, out Exception? failure, out string? substateError) { // Report the precompile action if tracing is enabled. if (isTracingActions) { _txTracer.ReportAction( - currentState.GasAvailable, + TGasPolicy.GetRemainingGas(currentState.Gas), currentState.Env.Value, currentState.From, currentState.To, @@ -804,12 +816,12 @@ protected virtual CallResult ExecutePrecompile(EvmState currentState, bool isTra // If running a precompile on a top-level call frame, and it fails, assign a general execution failure. if (currentState.IsPrecompile && currentState.IsTopLevel) { - failure = PrecompileExecutionFailureException; + failure = VirtualMachineStatics.PrecompileExecutionFailureException; goto Failure; } // Otherwise, if no exception but precompile did not succeed, exhaust the remaining gas. - currentState.GasAvailable = 0; + TGasPolicy.SetOutOfGas(ref currentState.Gas); } // If execution reaches here, the precompile operation is considered successful. @@ -823,9 +835,9 @@ protected virtual CallResult ExecutePrecompile(EvmState currentState, bool isTra } - protected void TraceTransactionActionStart(EvmState currentState) + protected void TraceTransactionActionStart(VmState currentState) { - _txTracer.ReportAction(currentState.GasAvailable, + _txTracer.ReportAction(TGasPolicy.GetRemainingGas(currentState.Gas), currentState.Env.Value, currentState.From, currentState.To, @@ -869,18 +881,18 @@ private void PrepareOpcodes(IReleaseSpec spec) spec.EvmInstructionsNoTrace = GenerateOpCodes(spec); } // Ensure the non-traced opcode set is generated and assign it to the _opcodeMethods field. - _opcodeMethods = (OpCode[])(spec.EvmInstructionsNoTrace ??= GenerateOpCodes(spec)); + _opcodeMethods = (delegate*, ref EvmStack, ref TGasPolicy, ref int, EvmExceptionType>[])(spec.EvmInstructionsNoTrace ??= GenerateOpCodes(spec)); } else { // For tracing-enabled execution, generate (if necessary) and cache the traced opcode set. - _opcodeMethods = (OpCode[])(spec.EvmInstructionsTraced ??= GenerateOpCodes(spec)); + _opcodeMethods = (delegate*, ref EvmStack, ref TGasPolicy, ref int, EvmExceptionType>[])(spec.EvmInstructionsTraced ??= GenerateOpCodes(spec)); } } - protected virtual OpCode[] GenerateOpCodes(IReleaseSpec spec) + protected virtual delegate*, ref EvmStack, ref TGasPolicy, ref int, EvmExceptionType>[] GenerateOpCodes(IReleaseSpec spec) where TTracingInst : struct, IFlag - => EvmInstructions.GenerateOpCodes(spec); + => EvmInstructions.GenerateOpCodes(spec); /// /// Reports the final outcome of a transaction action to the transaction tracer, taking into account @@ -896,7 +908,7 @@ protected virtual OpCode[] GenerateOpCodes(IReleaseSpec spec) /// /// The result of the executed call, including output bytes, exception and revert flags, and additional metadata. /// - protected void TraceTransactionActionEnd(EvmState currentState, in CallResult callResult) + protected void TraceTransactionActionEnd(VmState currentState, in CallResult callResult) { IReleaseSpec spec = BlockExecutionContext.Spec; // Calculate the gas cost required for depositing the contract code based on the length of the output. @@ -914,14 +926,16 @@ protected void TraceTransactionActionEnd(EvmState currentState, in CallResult ca else if (callResult.ShouldRevert) { // For creation operations, subtract the code deposit cost from the available gas; otherwise, use full gas. - long reportedGas = currentState.ExecutionType.IsAnyCreate() ? currentState.GasAvailable - codeDepositGasCost : currentState.GasAvailable; + long gasAvailable = TGasPolicy.GetRemainingGas(currentState.Gas); + long reportedGas = currentState.ExecutionType.IsAnyCreate() ? gasAvailable - codeDepositGasCost : gasAvailable; _txTracer.ReportActionRevert(reportedGas, outputBytes); } // Process contract creation flows. else if (currentState.ExecutionType.IsAnyCreate()) { + long gasAvailable = TGasPolicy.GetRemainingGas(currentState.Gas); // If available gas is insufficient to cover the code deposit cost... - if (currentState.GasAvailable < codeDepositGasCost) + if (gasAvailable < codeDepositGasCost) { // When the spec mandates charging for top-level creation, report an out-of-gas error. if (spec.ChargeForTopLevelCreate) @@ -931,7 +945,7 @@ protected void TraceTransactionActionEnd(EvmState currentState, in CallResult ca // Otherwise, report a successful action end with the remaining gas. else { - _txTracer.ReportActionEnd(currentState.GasAvailable, currentState.To, outputBytes); + _txTracer.ReportActionEnd(gasAvailable, currentState.To, outputBytes); } } // If the generated code is invalid (e.g., violates EIP-3541 by starting with 0xEF), report an invalid code error. @@ -942,13 +956,13 @@ protected void TraceTransactionActionEnd(EvmState currentState, in CallResult ca // In the successful contract creation case, deduct the code deposit gas cost and report a normal action end. else { - _txTracer.ReportActionEnd(currentState.GasAvailable - codeDepositGasCost, currentState.To, outputBytes); + _txTracer.ReportActionEnd(gasAvailable - codeDepositGasCost, currentState.To, outputBytes); } } // For non-creation calls, report the action end using the current available gas and the standard return data. else { - _txTracer.ReportActionEnd(currentState.GasAvailable, ReturnDataBuffer); + _txTracer.ReportActionEnd(TGasPolicy.GetRemainingGas(currentState.Gas), ReturnDataBuffer); } } @@ -965,53 +979,43 @@ private void RevertParityTouchBugAccount() } } - protected static bool UpdateGas(long gasCost, ref long gasAvailable) - { - if (gasAvailable < gasCost) - { - return false; - } - - gasAvailable -= gasCost; - return true; - } - - private CallResult RunPrecompile(EvmState state) + private CallResult RunPrecompile(VmState state) { ReadOnlyMemory callData = state.Env.InputData; UInt256 transferValue = state.Env.TransferValue; - long gasAvailable = state.GasAvailable; + TGasPolicy gas = state.Gas; - IPrecompile precompile = ((PrecompileInfo)state.Env.CodeInfo).Precompile!; + IPrecompile precompile = state.Env.CodeInfo.Precompile!; IReleaseSpec spec = BlockExecutionContext.Spec; long baseGasCost = precompile.BaseGasCost(spec); long dataGasCost = precompile.DataGasCost(callData, spec); - bool wasCreated = _worldState.AddToBalanceAndCreateIfNotExists(state.Env.ExecutingAccount, transferValue, spec); + bool wasCreated = _worldState.AddToBalanceAndCreateIfNotExists(state.Env.ExecutingAccount, in transferValue, spec); // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-161.md // An additional issue was found in Parity, // where the Parity client incorrectly failed // to revert empty account deletions in a more limited set of contexts // involving out-of-gas calls to precompiled contracts; - // the new Geth behavior matches Parity’s, + // the new Geth behavior matches Parity's, // and empty accounts will cease to be a source of concern in general // in about one week once the state clearing process finishes. - if (state.Env.ExecutingAccount.Equals(_parityTouchBugAccount.Address) - && !wasCreated - && transferValue.IsZero - && spec.ClearEmptyAccountWhenTouched) + if (!wasCreated && + transferValue.IsZero && + spec.ClearEmptyAccountWhenTouched && + state.Env.ExecutingAccount.Equals(_parityTouchBugAccount.Address)) { _parityTouchBugAccount.ShouldDelete = true; } - if (!UpdateGas(checked(baseGasCost + dataGasCost), ref gasAvailable)) + if ((ulong)baseGasCost + (ulong)dataGasCost > (ulong)long.MaxValue || + !TGasPolicy.UpdateGas(ref gas, baseGasCost + dataGasCost)) { - return new(default, false, 0, true, EvmExceptionType.OutOfGas); + return new(output: default, precompileSuccess: false, fromVersion: 0, shouldRevert: true, EvmExceptionType.OutOfGas); } - state.GasAvailable = gasAvailable; + state.Gas = gas; try { @@ -1025,19 +1029,32 @@ private CallResult RunPrecompile(EvmState state) exceptionType: !success ? EvmExceptionType.PrecompileFailure : EvmExceptionType.None ) { - SubstateError = $"Precompile {precompile.GetStaticName()} failed with error: {output.Error}" + SubstateError = success ? null : GetErrorString(precompile, output.Error) }; } - catch (DllNotFoundException exception) + catch (Exception exception) when (exception is DllNotFoundException or { InnerException: DllNotFoundException }) { - if (_logger.IsError) _logger.Error($"Failed to load one of the dependencies of {precompile.GetType()} precompile", exception); - throw; + if (_logger.IsError) LogMissingDependency(precompile, exception as DllNotFoundException ?? exception.InnerException as DllNotFoundException); + Environment.Exit(ExitCodes.MissingPrecompile); + throw; // Unreachable } catch (Exception exception) { - if (_logger.IsError) _logger.Error($"Precompiled contract ({precompile.GetType()}) execution exception", exception); + if (_logger.IsError) LogExecutionException(precompile, exception); return new(output: default, precompileSuccess: false, fromVersion: 0, shouldRevert: true); } + + [MethodImpl(MethodImplOptions.NoInlining)] + void LogExecutionException(IPrecompile precompile, Exception exception) + => _logger.Error($"Precompiled contract ({precompile.GetType()}) execution exception", exception); + + [MethodImpl(MethodImplOptions.NoInlining)] + void LogMissingDependency(IPrecompile precompile, DllNotFoundException exception) + => _logger.Error($"Failed to load one of the dependencies of {precompile.GetType()} precompile", exception); + + [MethodImpl(MethodImplOptions.NoInlining)] + static string GetErrorString(IPrecompile precompile, string? error) + => $"Precompile {precompile.GetStaticName()} failed with error: {error}"; } /// @@ -1077,9 +1094,9 @@ protected CallResult ExecuteCall( scoped in UInt256 previousCallOutputDestination) where TTracingInst : struct, IFlag { - EvmState vmState = _currentState; + VmState vmState = _currentState; // Obtain a reference to the execution environment for convenience. - ref readonly ExecutionEnvironment env = ref vmState.Env; + ExecutionEnvironment env = vmState.Env; // If this is the first call frame (not a continuation), adjust account balances and nonces. if (!vmState.IsContinuation) @@ -1113,7 +1130,7 @@ protected CallResult ExecuteCall( EvmStack stack = new(vmState.DataStackHead, _txTracer, AsAlignedSpan(vmState.DataStack, alignment: EvmStack.WordSize, size: StackPool.StackLength)); // Cache the available gas from the state for local use. - long gasAvailable = vmState.GasAvailable; + TGasPolicy gas = vmState.Gas; // If a previous call result exists, push its bytes onto the stack. if (previousCallResult is not null) @@ -1123,7 +1140,7 @@ protected CallResult ExecuteCall( // Report the remaining gas if tracing instructions are enabled. if (TTracingInst.IsActive) { - _txTracer.ReportOperationRemainingGas(vmState.GasAvailable); + _txTracer.ReportOperationRemainingGas(TGasPolicy.GetRemainingGas(vmState.Gas)); } } @@ -1134,24 +1151,24 @@ protected CallResult ExecuteCall( UInt256 localPreviousDest = previousCallOutputDestination; // Attempt to update the memory cost; if insufficient gas is available, jump to the out-of-gas handler. - if (!UpdateMemoryCost(vmState, ref gasAvailable, in localPreviousDest, (ulong)previousCallOutput.Length)) + if (!TGasPolicy.UpdateMemoryCost(ref gas, in localPreviousDest, (ulong)previousCallOutput.Length, vmState)) { goto OutOfGas; } // Save the previous call's output into the VM state's memory. - vmState.Memory.Save(in localPreviousDest, previousCallOutput); + if (!vmState.Memory.TrySave(in localPreviousDest, previousCallOutput)) goto OutOfGas; } // Dispatch the bytecode interpreter. // The second generic parameter is selected based on whether the transaction tracer is cancelable: - // - OffFlag is used when cancelation is not needed. - // - OnFlag is used when cancelation is enabled. + // - OffFlag is used when cancellation is not needed. + // - OnFlag is used when cancellation is enabled. // This leverages the compile-time evaluation of TTracingInst to optimize away runtime checks. return _txTracer.IsCancelable switch { - false => RunByteCode(ref stack, gasAvailable), - true => RunByteCode(ref stack, gasAvailable), + false => RunByteCode(ref stack, ref gas), + true => RunByteCode(ref stack, ref gas), }; Empty: @@ -1176,10 +1193,10 @@ protected CallResult ExecuteCall( /// A struct implementing that indicates at compile time whether cancellation support is enabled. /// /// - /// A reference to the current EVM stack used for execution. + /// A reference to the current EVM stack used for execution. /// - /// - /// The amount of gas available for executing the bytecode. + /// + /// The gas state to update /// /// /// A that encapsulates the outcome of the execution, which can be a successful result, @@ -1192,32 +1209,33 @@ protected CallResult ExecuteCall( [SkipLocalsInit] protected virtual unsafe CallResult RunByteCode( scoped ref EvmStack stack, - long gasAvailable) + scoped ref TGasPolicy gas) where TTracingInst : struct, IFlag where TCancelable : struct, IFlag { // Reset return data and set the current section index from the VM state. ReturnData = null; - SectionIndex = EvmState.FunctionIndex; + SectionIndex = VmState.FunctionIndex; // Retrieve the code information and create a read-only span of instructions. - ICodeInfo codeInfo = EvmState.Env.CodeInfo; + ICodeInfo codeInfo = VmState.Env.CodeInfo; ReadOnlySpan codeSection = GetInstructions(codeInfo); // Initialize the exception type to "None". EvmExceptionType exceptionType = EvmExceptionType.None; #if DEBUG // In debug mode, retrieve a tracer for interactive debugging. - DebugTracer? debugger = _txTracer.GetTracer(); + DebugTracer? debugger = _txTracer.GetTracer>(); #endif // Set the program counter from the current VM state; it may not be zero if resuming after a call. - int programCounter = EvmState.ProgramCounter; + int programCounter = VmState.ProgramCounter; - // Pin the opcode methods array to obtain a fixed pointer, avoiding repeated bounds checks and casts. - // If we don't use a pointer we have to cast for each call (delegate*<...> can't be used as a generic arg) - // Or have bounds checks (however only 256 opcodes and opcode is a byte so know always in bounds). - fixed (OpCode* opcodeMethods = &_opcodeMethods[0]) + // Pin the opcode methods array to obtain a fixed pointer, avoiding repeated bounds checks. + // If we don't use a pointer we have bounds checks (however only 256 opcodes and opcode is a byte so know always in bounds). + var opcodeArray = _opcodeMethods; + fixed (delegate*, ref EvmStack, ref TGasPolicy, ref int, EvmExceptionType>* + opcodeMethods = &opcodeArray[0]) { int opCodeCount = 0; ref Instruction code = ref MemoryMarshal.GetReference(codeSection); @@ -1226,7 +1244,7 @@ protected virtual unsafe CallResult RunByteCode( { #if DEBUG // Allow the debugger to inspect and possibly pause execution for debugging purposes. - debugger?.TryWait(ref _currentState, ref programCounter, ref gasAvailable, ref stack.Head); + debugger?.TryWait(ref _currentState, ref programCounter, ref gas, ref stack.Head); #endif // Fetch the current instruction from the code section. Instruction instruction = Unsafe.Add(ref code, programCounter); @@ -1235,9 +1253,12 @@ protected virtual unsafe CallResult RunByteCode( if (TCancelable.IsActive && _txTracer.IsCancelled) ThrowOperationCanceledException(); + // Call gas policy hook before instruction execution. + TGasPolicy.OnBeforeInstructionTrace(in gas, programCounter, instruction, VmState.Env.CallDepth); + // If tracing is enabled, start an instruction trace. if (TTracingInst.IsActive) - StartInstructionTrace(instruction, gasAvailable, programCounter, in stack); + StartInstructionTrace(instruction, TGasPolicy.GetRemainingGas(in gas), programCounter, in stack); // Advance the program counter to point to the next instruction. programCounter++; @@ -1246,30 +1267,34 @@ protected virtual unsafe CallResult RunByteCode( // For the very common POP opcode, use an inlined implementation to reduce overhead. if (Instruction.POP == instruction) { - exceptionType = EvmInstructions.InstructionPop(this, ref stack, ref gasAvailable, ref programCounter); + exceptionType = EvmInstructions.InstructionPop(this, ref stack, ref gas, ref programCounter); } else { // Retrieve the opcode function pointer corresponding to the current instruction. - OpCode opcodeMethod = opcodeMethods[(int)instruction]; + var opcodeMethod = opcodeMethods[(int)instruction]; // Invoke the opcode method, which may modify the stack, gas, and program counter. // Is executed using fast delegate* via calli (see: C# function pointers https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code#function-pointers) - exceptionType = opcodeMethod(this, ref stack, ref gasAvailable, ref programCounter); + exceptionType = opcodeMethod(this, ref stack, ref gas, ref programCounter); } // If gas is exhausted, jump to the out-of-gas handler. - if (gasAvailable < 0) + if (TGasPolicy.GetRemainingGas(in gas) < 0) { OpCodeCount += opCodeCount; goto OutOfGas; } + + // Call gas policy hook after instruction execution. + TGasPolicy.OnAfterInstructionTrace(in gas); + // If an exception occurred, exit the loop. if (exceptionType != EvmExceptionType.None) break; // If tracing is enabled, complete the trace for the current instruction. if (TTracingInst.IsActive) - EndInstructionTrace(gasAvailable); + EndInstructionTrace(TGasPolicy.GetRemainingGas(in gas)); // If return data has been set, exit the loop to process the returned value. if (ReturnData is not null) @@ -1283,8 +1308,8 @@ protected virtual unsafe CallResult RunByteCode( { // If tracing is enabled, complete the trace for the current instruction. if (TTracingInst.IsActive) - EndInstructionTrace(gasAvailable); - UpdateCurrentState(programCounter, gasAvailable, stack.Head); + EndInstructionTrace(TGasPolicy.GetRemainingGas(in gas)); + UpdateCurrentState(programCounter, in gas, stack.Head); } else { @@ -1305,10 +1330,10 @@ protected virtual unsafe CallResult RunByteCode( DataReturn: #if DEBUG // Allow debugging before processing the return data. - debugger?.TryWait(ref _currentState, ref programCounter, ref gasAvailable, ref stack.Head); + debugger?.TryWait(ref _currentState, ref programCounter, ref gas, ref stack.Head); #endif // Process the return data based on its runtime type. - if (ReturnData is EvmState state) + if (ReturnData is VmState state) { return new CallResult(state); } @@ -1324,12 +1349,12 @@ protected virtual unsafe CallResult RunByteCode( return new CallResult(null, (byte[])ReturnData, null, codeInfo.Version, shouldRevert: true, exceptionType); OutOfGas: - gasAvailable = 0; + TGasPolicy.SetOutOfGas(ref gas); // Set the exception type to OutOfGas if gas has been exhausted. exceptionType = EvmExceptionType.OutOfGas; ReturnFailure: // Return a failure CallResult based on the remaining gas and the exception type. - return GetFailureReturn(gasAvailable, exceptionType); + return GetFailureReturn(TGasPolicy.GetRemainingGas(in gas), exceptionType); // Converts the code section bytes into a read-only span of instructions. // Lightest weight conversion as mostly just helpful when debugging to see what the opcodes are. @@ -1365,27 +1390,20 @@ private CallResult GetFailureReturn(long gasAvailable, EvmExceptionType exceptio }; } - private void UpdateCurrentState(int pc, long gas, int stackHead) + private void UpdateCurrentState(int pc, in TGasPolicy gas, int stackHead) { - EvmState state = EvmState; + VmState state = VmState; state.ProgramCounter = pc; - state.GasAvailable = gas; + state.Gas = gas; state.DataStackHead = stackHead; state.FunctionIndex = SectionIndex; } - private static bool UpdateMemoryCost(EvmState vmState, ref long gasAvailable, in UInt256 position, in UInt256 length) - { - long memoryCost = vmState.Memory.CalculateMemoryCost(in position, length, out bool outOfGas); - if (outOfGas) return false; - return memoryCost == 0L || UpdateGas(memoryCost, ref gasAvailable); - } - [MethodImpl(MethodImplOptions.NoInlining)] private void StartInstructionTrace(Instruction instruction, long gasAvailable, int programCounter, in EvmStack stackValue) { - EvmState vmState = EvmState; + VmState vmState = VmState; int sectionIndex = SectionIndex; bool isEofFrame = vmState.Env.CodeInfo.Version > 0; @@ -1445,3 +1463,11 @@ private void EndInstructionTraceError(long gasAvailable, EvmExceptionType evmExc _txTracer.ReportOperationError(evmExceptionType); } } + +/// +/// Non-generic VirtualMachine for backward compatibility with EthereumGasPolicy. +/// +public sealed class VirtualMachine( + IBlockhashProvider? blockHashProvider, + ISpecProvider? specProvider, + ILogManager? logManager) : VirtualMachine(blockHashProvider, specProvider, logManager), IVirtualMachine; diff --git a/src/Nethermind/Nethermind.Evm/EvmState.cs b/src/Nethermind/Nethermind.Evm/VmState.cs similarity index 83% rename from src/Nethermind/Nethermind.Evm/EvmState.cs rename to src/Nethermind/Nethermind.Evm/VmState.cs index 872db9b4ea8..0636d957ba7 100644 --- a/src/Nethermind/Nethermind.Evm/EvmState.cs +++ b/src/Nethermind/Nethermind.Evm/VmState.cs @@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Nethermind.Core; +using Nethermind.Evm.GasPolicy; using Nethermind.Evm.State; namespace Nethermind.Evm; @@ -15,14 +16,15 @@ namespace Nethermind.Evm; /// State for EVM Calls /// [DebuggerDisplay("{ExecutionType} to {Env.ExecutingAccount}, G {GasAvailable} R {Refund} PC {ProgramCounter} OUT {OutputDestination}:{OutputLength}")] -public sealed class EvmState : IDisposable // TODO: rename to CallState +public class VmState : IDisposable + where TGasPolicy : struct, IGasPolicy { - private static readonly ConcurrentQueue _statePool = new(); + private static readonly ConcurrentQueue> _statePool = new(); private static readonly StackPool _stackPool = new(); /* Type layout for 'EvmState' - Size: 264 bytes. Paddings: 9 bytes (%3 of empty space) + Size: 176 bytes. Paddings: 5 bytes (%3 of empty space) |=======================================================================| | Object Header (8 bytes) | |-----------------------------------------------------------------------| @@ -66,19 +68,19 @@ public sealed class EvmState : IDisposable // TODO: rename to CallState |-----------------------------------------------------------------------| | 72-103: EvmPooledMemory _memory (32 bytes) | |-----------------------------------------------------------------------| - | 104-223: ExecutionEnvironment _env (120 bytes) | + | 104-111: ExecutionEnvironment _env (8 bytes) | |-----------------------------------------------------------------------| - | 224-247: StackAccessTracker _accessTracker (24 bytes) | + | 112-143: StackAccessTracker _accessTracker (32 bytes) | |-----------------------------------------------------------------------| - | 248-259: Snapshot _snapshot (12 bytes) | + | 144-155: Snapshot _snapshot (12 bytes) | |-----------------------------------------------------------------------| - | 260-263: padding (4 bytes) | + | 156-159: padding (4 bytes) | |=======================================================================| */ public byte[]? DataStack; public ReturnState[]? ReturnStack; - public long GasAvailable { get; set; } + public TGasPolicy Gas; internal long OutputDestination { get; private set; } // TODO: move to CallEnv internal long OutputLength { get; private set; } // TODO: move to CallEnv public long Refund { get; set; } @@ -96,27 +98,23 @@ public sealed class EvmState : IDisposable // TODO: rename to CallState private bool _isDisposed = true; private EvmPooledMemory _memory; - private ExecutionEnvironment _env; + private ExecutionEnvironment? _env; private StackAccessTracker _accessTracker; private Snapshot _snapshot; -#if DEBUG - private StackTrace? _creationStackTrace; -#endif - /// - /// Rent a top level . + /// Rent a top level . /// - public static EvmState RentTopLevel( - long gasAvailable, + public static VmState RentTopLevel( + TGasPolicy gas, ExecutionType executionType, - in ExecutionEnvironment env, + ExecutionEnvironment env, in StackAccessTracker accessedItems, in Snapshot snapshot) { - EvmState state = Rent(); + VmState state = Rent(); state.Initialize( - gasAvailable, + gas, outputDestination: 0L, outputLength: 0L, executionType: executionType, @@ -130,23 +128,23 @@ public static EvmState RentTopLevel( } /// - /// Constructor for a frame beneath top level. + /// Constructor for a frame beneath top level. /// - public static EvmState RentFrame( - long gasAvailable, + public static VmState RentFrame( + TGasPolicy gas, long outputDestination, long outputLength, ExecutionType executionType, bool isStatic, bool isCreateOnPreExistingAccount, - in ExecutionEnvironment env, + ExecutionEnvironment env, in StackAccessTracker stateForAccessLists, in Snapshot snapshot, bool isTopLevel = false) { - EvmState state = Rent(); + VmState state = Rent(); state.Initialize( - gasAvailable, + gas, outputDestination, outputLength, executionType, @@ -159,19 +157,19 @@ public static EvmState RentFrame( return state; } - private static EvmState Rent() - => _statePool.TryDequeue(out EvmState state) ? state : new EvmState(); + private static VmState Rent() + => _statePool.TryDequeue(out VmState? state) ? state : new VmState(); [SkipLocalsInit] private void Initialize( - long gasAvailable, + TGasPolicy gas, long outputDestination, long outputLength, ExecutionType executionType, bool isTopLevel, bool isStatic, bool isCreateOnPreExistingAccount, - in ExecutionEnvironment env, + ExecutionEnvironment env, in StackAccessTracker stateForAccessLists, in Snapshot snapshot) { @@ -183,7 +181,7 @@ private void Initialize( _accessTracker.WasCreated(env.ExecutingAccount); } _accessTracker.TakeSnapshot(); - GasAvailable = gasAvailable; + Gas = gas; OutputDestination = outputDestination; OutputLength = outputLength; Refund = 0; @@ -226,9 +224,9 @@ ExecutionType.STATICCALL or ExecutionType.CALL or ExecutionType.CALLCODE or Exec public bool IsPrecompile => Env.CodeInfo?.IsPrecompile ?? false; public ref readonly StackAccessTracker AccessTracker => ref _accessTracker; - public ref readonly ExecutionEnvironment Env => ref _env; - public ref EvmPooledMemory Memory => ref _memory; // TODO: move to CallEnv - public ref readonly Snapshot Snapshot => ref _snapshot; // TODO: move to CallEnv + public ExecutionEnvironment Env => _env!; + public ref EvmPooledMemory Memory => ref _memory; + public ref readonly Snapshot Snapshot => ref _snapshot; public void Dispose() { @@ -255,7 +253,8 @@ public void Dispose() _memory.Dispose(); _memory = default; _accessTracker = default; - _env = default; + if (!IsTopLevel) _env?.Dispose(); + _env = null; _snapshot = default; _statePool.Enqueue(this); @@ -266,11 +265,14 @@ public void Dispose() } #if DEBUG - ~EvmState() + + private StackTrace? _creationStackTrace; + + ~VmState() { if (!_isDisposed) { - Console.Error.WriteLine($"Warning: {nameof(EvmState)} was not disposed. Created at: {_creationStackTrace}"); + Console.Error.WriteLine($"Warning: {nameof(VmState)} was not disposed. Created at: {_creationStackTrace}"); } } #endif @@ -284,17 +286,21 @@ public void InitializeStacks() } } - public void CommitToParent(EvmState parentState) + public void CommitToParent(VmState parentState) { ObjectDisposedException.ThrowIf(_isDisposed, this); parentState.Refund += Refund; _canRestore = false; // we can't restore if we committed } +} - public struct ReturnState - { - public int Index; - public int Offset; - public int Height; - } +/// +/// Return state for EVM call stack management. +/// +public struct ReturnState +{ + public int Index; + public int Offset; + public int Height; } + diff --git a/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs b/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs index e82e8efe8b8..70f9635afe7 100644 --- a/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs +++ b/src/Nethermind/Nethermind.Facade.Test/BlockchainBridgeTests.cs @@ -1,8 +1,7 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Autofac; @@ -25,7 +24,6 @@ using Nethermind.Facade.Find; using Nethermind.Facade.Proxy.Models.Simulate; using Nethermind.Facade.Simulate; -using NSubstitute.Core; namespace Nethermind.Facade.Test; @@ -350,31 +348,59 @@ public void EstimateGas_tx_returns_MalformedTransactionError() } [Test] - public void Call_tx_returns_WrongTransactionNonceError() + public void Call_tx_returns_TransactionNonceIsToHighError() { BlockHeader header = Build.A.BlockHeader .TestObject; Transaction tx = new() { GasLimit = 456 }; _transactionProcessor.CallAndRestore(Arg.Any(), Arg.Any()) - .Returns(TransactionResult.WrongTransactionNonce); + .Returns(TransactionResult.TransactionNonceTooHigh); CallOutput callOutput = _blockchainBridge.Call(header, tx); - Assert.That(callOutput.Error, Is.EqualTo("wrong transaction nonce")); + Assert.That(callOutput.Error, Is.EqualTo("transaction nonce is too high")); } [Test] - public void EstimateGas_tx_returns_WrongTransactionNonceError() + public void Call_tx_returns_TransactionNonceIsToLowError() { BlockHeader header = Build.A.BlockHeader .TestObject; Transaction tx = new() { GasLimit = 456 }; _transactionProcessor.CallAndRestore(Arg.Any(), Arg.Any()) - .Returns(TransactionResult.WrongTransactionNonce); + .Returns(TransactionResult.TransactionNonceTooLow); + + CallOutput callOutput = _blockchainBridge.Call(header, tx); + + Assert.That(callOutput.Error, Is.EqualTo("transaction nonce is too low")); + } + + [Test] + public void EstimateGas_tx_returns_TransactionNonceIsToHighError() + { + BlockHeader header = Build.A.BlockHeader + .TestObject; + Transaction tx = new() { GasLimit = 456 }; + _transactionProcessor.CallAndRestore(Arg.Any(), Arg.Any()) + .Returns(TransactionResult.TransactionNonceTooHigh); + + CallOutput callOutput = _blockchainBridge.EstimateGas(header, tx, 1); + + Assert.That(callOutput.Error, Is.EqualTo("transaction nonce is too high")); + } + + [Test] + public void EstimateGas_tx_returns_TransactionNonceIsTooLowError() + { + BlockHeader header = Build.A.BlockHeader + .TestObject; + Transaction tx = new() { GasLimit = 456 }; + _transactionProcessor.CallAndRestore(Arg.Any(), Arg.Any()) + .Returns(TransactionResult.TransactionNonceTooLow); CallOutput callOutput = _blockchainBridge.EstimateGas(header, tx, 1); - Assert.That(callOutput.Error, Is.EqualTo("wrong transaction nonce")); + Assert.That(callOutput.Error, Is.EqualTo("transaction nonce is too low")); } [Test] diff --git a/src/Nethermind/Nethermind.Facade.Test/Eth/EthSyncingInfoTests.cs b/src/Nethermind/Nethermind.Facade.Test/Eth/EthSyncingInfoTests.cs index f5cd1c2d830..fb58a49c2e9 100644 --- a/src/Nethermind/Nethermind.Facade.Test/Eth/EthSyncingInfoTests.cs +++ b/src/Nethermind/Nethermind.Facade.Test/Eth/EthSyncingInfoTests.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System.Threading; @@ -84,7 +84,7 @@ public void IsSyncing_ReturnsExpectedResult(long bestHeader, long currentHead, b [TestCase(false, false, true)] [TestCase(true, true, false)] public void IsSyncing_AncientBarriers(bool resolverDownloadingBodies, - bool resolverDownloadingreceipts, bool expectedResult) + bool resolverDownloadingReceipts, bool expectedResult) { ISyncConfig syncConfig = new SyncConfig { @@ -95,14 +95,14 @@ public void IsSyncing_AncientBarriers(bool resolverDownloadingBodies, // AncientReceiptsBarrierCalc = Max(1, Min(Pivot, Max(BodiesBarrier, ReceiptsBarrier))) = ReceiptsBarrier = 900 DownloadBodiesInFastSync = true, DownloadReceiptsInFastSync = true, - PivotNumber = "1000" + PivotNumber = 1000 }; IBlockTree blockTree = Substitute.For(); blockTree.SyncPivot.Returns((1000, Keccak.Zero)); ISyncPointers syncPointers = Substitute.For(); ISyncProgressResolver syncProgressResolver = Substitute.For(); syncProgressResolver.IsFastBlocksBodiesFinished().Returns(resolverDownloadingBodies); - syncProgressResolver.IsFastBlocksReceiptsFinished().Returns(resolverDownloadingreceipts); + syncProgressResolver.IsFastBlocksReceiptsFinished().Returns(resolverDownloadingReceipts); blockTree.FindBestSuggestedHeader().Returns(Build.A.BlockHeader.WithNumber(6178001L).TestObject); blockTree.Head.Returns(Build.A.Block.WithHeader(Build.A.BlockHeader.WithNumber(6178000L).TestObject).TestObject); @@ -171,7 +171,7 @@ public void IsSyncing_ReturnsFalseOnFastSyncWithoutPivot(long bestHeader, long c { FastSync = true, SnapSync = true, - PivotNumber = "0", // Equivalent to not having a pivot + PivotNumber = 0, // Equivalent to not having a pivot }; EthSyncingInfo ethSyncingInfo = new(blockTree, syncPointers, syncConfig, new StaticSelector(SyncMode.All), syncProgressResolver, LimboLogs.Instance); diff --git a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs index bd319ebb868..4fc16b67da6 100644 --- a/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/BlockchainBridge.cs @@ -44,8 +44,8 @@ public class BlockchainBridge( IStateReader stateReader, ITxPool txPool, IReceiptFinder receiptStorage, - IFilterStore filterStore, - IFilterManager filterManager, + FilterStore filterStore, + FilterManager filterManager, IEthereumEcdsa ecdsa, ITimestamper timestamper, ILogFinder logFinder, @@ -109,7 +109,7 @@ private bool TryGetCanonicalTransaction( return (null, 0, null, 0); } - public (TxReceipt? Receipt, Transaction Transaction, UInt256? baseFee) GetTransaction(Hash256 txHash, bool checkTxnPool = true) => + public (TxReceipt? Receipt, Transaction? Transaction, UInt256? baseFee) GetTransaction(Hash256 txHash, bool checkTxnPool = true) => TryGetCanonicalTransaction(txHash, out Transaction? tx, out TxReceipt? txReceipt, out Block? block, out TxReceipt[]? _) ? (txReceipt, tx, block.BaseFeePerGas) : checkTxnPool && txPool.TryGetPendingTransaction(txHash, out Transaction? transaction) @@ -289,21 +289,21 @@ public ulong GetChainId() public IEnumerable GetLogs( BlockParameter fromBlock, BlockParameter toBlock, - object? address = null, - IEnumerable? topics = null, + HashSet? addresses = null, + IEnumerable? topics = null, CancellationToken cancellationToken = default) { - LogFilter filter = GetFilter(fromBlock, toBlock, address, topics); + LogFilter filter = GetFilter(fromBlock, toBlock, addresses, topics); return logFinder.FindLogs(filter, cancellationToken); } public LogFilter GetFilter( BlockParameter fromBlock, BlockParameter toBlock, - object? address = null, - IEnumerable? topics = null) + HashSet? addresses = null, + IEnumerable? topics = null) { - return filterStore.CreateLogFilter(fromBlock, toBlock, address, topics, false); + return filterStore.CreateLogFilter(fromBlock, toBlock, addresses, topics, false); } public IEnumerable GetLogs( @@ -325,10 +325,10 @@ public bool TryGetLogs(int filterId, out IEnumerable filterLogs, Canc return filter is not null; } - public int NewFilter(BlockParameter? fromBlock, BlockParameter? toBlock, - object? address = null, IEnumerable? topics = null) + public int NewFilter(BlockParameter fromBlock, BlockParameter toBlock, + HashSet? address = null, IEnumerable? topics = null) { - LogFilter filter = filterStore.CreateLogFilter(fromBlock ?? BlockParameter.Latest, toBlock ?? BlockParameter.Latest, address, topics); + LogFilter filter = filterStore.CreateLogFilter(fromBlock, toBlock, address, topics); filterStore.SaveFilter(filter); return filter.Id; } @@ -378,9 +378,9 @@ public Hash256[] GetPendingTransactionFilterChanges(int filterId) => public Address? RecoverTxSender(Transaction tx) => ecdsa.RecoverAddress(tx); - public void RunTreeVisitor(ITreeVisitor treeVisitor, Hash256 stateRoot) where TCtx : struct, INodeContext + public void RunTreeVisitor(ITreeVisitor treeVisitor, BlockHeader? baseBlock) where TCtx : struct, INodeContext { - stateReader.RunTreeVisitor(treeVisitor, stateRoot); + stateReader.RunTreeVisitor(treeVisitor, baseBlock); } public bool HasStateForBlock(BlockHeader baseBlock) diff --git a/src/Nethermind/Nethermind.Facade/Eth/BlockForRpc.cs b/src/Nethermind/Nethermind.Facade/Eth/BlockForRpc.cs index cd6ace85d7d..e9b5a0d36ad 100644 --- a/src/Nethermind/Nethermind.Facade/Eth/BlockForRpc.cs +++ b/src/Nethermind/Nethermind.Facade/Eth/BlockForRpc.cs @@ -19,14 +19,13 @@ namespace Nethermind.Facade.Eth; public class BlockForRpc { private static readonly BlockDecoder _blockDecoder = new(); - private readonly bool _isAuRaBlock; public BlockForRpc() { } [SkipLocalsInit] public BlockForRpc(Block block, bool includeFullTransactionData, ISpecProvider specProvider, bool skipTxs = false) { - _isAuRaBlock = block.Header.AuRaSignature is not null; + bool isAuRaBlock = block.Header.AuRaSignature is not null; Difficulty = block.Difficulty; ExtraData = block.ExtraData; GasLimit = block.GasLimit; @@ -34,7 +33,7 @@ public BlockForRpc(Block block, bool includeFullTransactionData, ISpecProvider s Hash = block.Hash; LogsBloom = block.Bloom; Miner = block.Beneficiary; - if (!_isAuRaBlock) + if (!isAuRaBlock) { MixHash = block.MixHash; Nonce = new byte[8]; @@ -108,29 +107,23 @@ public BlockForRpc(Block block, bool includeFullTransactionData, ISpecProvider s [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public Bloom LogsBloom { get; set; } public Address Miner { get; set; } - public Hash256 MixHash { get; set; } - - public bool ShouldSerializeMixHash() => !_isAuRaBlock && MixHash is not null; + public Hash256? MixHash { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public byte[] Nonce { get; set; } - - public bool ShouldSerializeNonce() => !_isAuRaBlock; + public byte[]? Nonce { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public long? Number { get; set; } public Hash256 ParentHash { get; set; } public Hash256 ReceiptsRoot { get; set; } public Hash256 Sha3Uncles { get; set; } - public byte[] Signature { get; set; } - public bool ShouldSerializeSignature() => _isAuRaBlock; + public byte[]? Signature { get; set; } public long Size { get; set; } public Hash256 StateRoot { get; set; } [JsonConverter(typeof(NullableRawLongConverter))] public long? Step { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public UInt256? TotalDifficulty { get; set; } - public bool ShouldSerializeStep() => _isAuRaBlock; public UInt256 Timestamp { get; set; } public UInt256? BaseFeePerGas { get; set; } diff --git a/src/Nethermind/Nethermind.Facade/Filters/AddressFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/AddressFilter.cs index 4e71a417b8e..dbbd31ae246 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/AddressFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/AddressFilter.cs @@ -7,99 +7,71 @@ namespace Nethermind.Blockchain.Filters { - public class AddressFilter + public class AddressFilter(HashSet addresses) { - public static readonly AddressFilter AnyAddress = new(addresses: new HashSet()); + public static readonly AddressFilter AnyAddress = new([]); private Bloom.BloomExtract[]? _addressesBloomIndexes; - private Bloom.BloomExtract? _addressBloomExtract; - public AddressFilter(Address address) + public AddressFilter(Address address) : this([address]) { - Address = address; } - public AddressFilter(HashSet addresses) - { - Addresses = addresses; - } - - public Address? Address { get; } - public HashSet? Addresses { get; } + public HashSet Addresses { get; } = addresses; private Bloom.BloomExtract[] AddressesBloomExtracts => _addressesBloomIndexes ??= CalculateBloomExtracts(); - private Bloom.BloomExtract AddressBloomExtract => _addressBloomExtract ??= Bloom.GetExtract(Address); - - public bool Accepts(Address address) - { - if (Addresses?.Count > 0) - { - return Addresses.Contains(address); - } - return Address is null || Address == address; - } + public bool Accepts(Address address) => Addresses.Count == 0 || Addresses.Contains(address); public bool Accepts(ref AddressStructRef address) { - if (Addresses?.Count > 0) + if (Addresses.Count == 0) { - foreach (var a in Addresses) - { - if (a == address) return true; - } + return true; + } - return false; + foreach (AddressAsKey a in Addresses) + { + if (a == address) return true; } - return Address is null || Address == address; + return false; + } public bool Matches(Bloom bloom) { - if (Addresses is not null) + if (AddressesBloomExtracts.Length == 0) { - bool result = true; - var indexes = AddressesBloomExtracts; - for (var i = 0; i < indexes.Length; i++) - { - result = bloom.Matches(indexes[i]); - if (result) - { - break; - } - } - - return result; + return true; } - if (Address is null) + + for (var i = 0; i < AddressesBloomExtracts.Length; i++) { - return true; + if (bloom.Matches(AddressesBloomExtracts[i])) + { + return true; + } } - return bloom.Matches(AddressBloomExtract); + + return false; } public bool Matches(ref BloomStructRef bloom) { - if (Addresses is not null) + if (AddressesBloomExtracts.Length == 0) { - bool result = true; - var indexes = AddressesBloomExtracts; - for (var i = 0; i < indexes.Length; i++) - { - result = bloom.Matches(indexes[i]); - if (result) - { - break; - } - } - - return result; + return true; } - if (Address is null) + + for (var i = 0; i < AddressesBloomExtracts.Length; i++) { - return true; + if (bloom.Matches(AddressesBloomExtracts[i])) + { + return true; + } } - return bloom.Matches(AddressBloomExtract); + + return false; } private Bloom.BloomExtract[] CalculateBloomExtracts() => Addresses.Select(static a => Bloom.GetExtract(a)).ToArray(); diff --git a/src/Nethermind/Nethermind.Facade/Filters/FilterManager.cs b/src/Nethermind/Nethermind.Facade/Filters/FilterManager.cs index 4cdf0466491..179c8648e19 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/FilterManager.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/FilterManager.cs @@ -15,7 +15,7 @@ namespace Nethermind.Blockchain.Filters { - public class FilterManager : IFilterManager + public sealed class FilterManager { private readonly ConcurrentDictionary> _logs = new(); @@ -27,12 +27,12 @@ public class FilterManager : IFilterManager new(); private Hash256? _lastBlockHash; - private readonly IFilterStore _filterStore; + private readonly FilterStore _filterStore; private readonly ILogger _logger; private long _logIndex; public FilterManager( - IFilterStore filterStore, + FilterStore filterStore, IMainProcessingContext mainProcessingContext, ITxPool txPool, ILogManager logManager) diff --git a/src/Nethermind/Nethermind.Facade/Filters/FilterStore.cs b/src/Nethermind/Nethermind.Facade/Filters/FilterStore.cs index 808f05d887d..1a0fe2d5cde 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/FilterStore.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/FilterStore.cs @@ -17,7 +17,7 @@ namespace Nethermind.Blockchain.Filters { - public class FilterStore : IFilterStore + public sealed class FilterStore : IDisposable { private readonly TimeSpan _timeout; private int _currentFilterId = -1; @@ -84,6 +84,16 @@ private void CleanupStaleFilters() } public IEnumerable GetFilters() where T : FilterBase + { + // Return Array.Empty() to avoid allocating enumerator + // and which has a non-allocating fast-path for + // foreach via IEnumerable + if (_filters.IsEmpty) return Array.Empty(); + + return GetFiltersEnumerate(); + } + + private IEnumerable GetFiltersEnumerate() where T : FilterBase { // Reuse the enumerator var enumerator = Interlocked.Exchange(ref _enumerator, null) ?? _filters.GetEnumerator(); @@ -113,12 +123,11 @@ public BlockFilter CreateBlockFilter(bool setId = true) => public PendingTransactionFilter CreatePendingTransactionFilter(bool setId = true) => new(GetFilterId(setId)); - public LogFilter CreateLogFilter(BlockParameter fromBlock, BlockParameter toBlock, - object? address = null, IEnumerable? topics = null, bool setId = true) => + public LogFilter CreateLogFilter(BlockParameter fromBlock, BlockParameter toBlock, HashSet? addresses = null, IEnumerable? topics = null, bool setId = true) => new(GetFilterId(setId), fromBlock, toBlock, - GetAddress(address), + GetAddress(addresses), GetTopicsFilter(topics)); public void RemoveFilter(int filterId) @@ -131,7 +140,7 @@ public void RemoveFilter(int filterId) public void SaveFilter(FilterBase filter) { - if (_filters.ContainsKey(filter.Id)) + if (!_filters.TryAdd(filter.Id, filter)) { throw new InvalidOperationException($"Filter with ID {filter.Id} already exists"); } @@ -140,8 +149,6 @@ public void SaveFilter(FilterBase filter) { _currentFilterId = Math.Max(filter.Id, _currentFilterId); } - - _filters[filter.Id] = filter; } private int GetFilterId(bool generateId) @@ -157,7 +164,7 @@ private int GetFilterId(bool generateId) return 0; } - private static TopicsFilter GetTopicsFilter(IEnumerable? topics = null) + private static TopicsFilter GetTopicsFilter(IEnumerable? topics = null) { if (topics is null) { @@ -194,48 +201,32 @@ private static TopicExpression GetTopicExpression(FilterTopic? filterTopic) return AnyTopic.Instance; } - private static AddressFilter GetAddress(object? address) => - address switch - { - null => AddressFilter.AnyAddress, - string s => new AddressFilter(new Address(s)), - IEnumerable e => new AddressFilter(e.Select(static a => new AddressAsKey(new Address(a))).ToHashSet()), - _ => throw new InvalidDataException("Invalid address filter format") - }; + private static AddressFilter GetAddress(HashSet? addresses) => addresses is null ? AddressFilter.AnyAddress : new AddressFilter(addresses); - private static FilterTopic?[]? GetFilterTopics(IEnumerable? topics) => topics?.Select(GetTopic).ToArray(); + private static FilterTopic?[]? GetFilterTopics(IEnumerable? topics) => topics?.Select(GetTopic).ToArray(); - private static FilterTopic? GetTopic(object? obj) + private static FilterTopic? GetTopic(Hash256[]? topics) { - switch (obj) + if (topics?.Length == 1) { - case null: - return null; - case string topic: - return new FilterTopic - { - Topic = new Hash256(topic) - }; - case Hash256 keccak: - return new FilterTopic - { - Topic = keccak - }; + return new FilterTopic + { + Topic = topics[0] + }; } - - return obj is not IEnumerable topics - ? null - : new FilterTopic + else + { + return new FilterTopic() { - Topics = topics.Select(static t => new Hash256(t)).ToArray() + Topics = topics }; + } } private class FilterTopic { public Hash256? Topic { get; init; } public Hash256[]? Topics { get; init; } - } public void Dispose() diff --git a/src/Nethermind/Nethermind.Facade/Filters/IFilterManager.cs b/src/Nethermind/Nethermind.Facade/Filters/IFilterManager.cs deleted file mode 100644 index 27a07e029b8..00000000000 --- a/src/Nethermind/Nethermind.Facade/Filters/IFilterManager.cs +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Core.Crypto; -using Nethermind.Facade.Filters; - -namespace Nethermind.Blockchain.Filters -{ - public interface IFilterManager - { - FilterLog[] PollLogs(int filterId); - Hash256[] PollBlockHashes(int filterId); - Hash256[] PollPendingTransactionHashes(int filterId); - } -} diff --git a/src/Nethermind/Nethermind.Facade/Filters/IFilterStore.cs b/src/Nethermind/Nethermind.Facade/Filters/IFilterStore.cs deleted file mode 100644 index 58e978f67d9..00000000000 --- a/src/Nethermind/Nethermind.Facade/Filters/IFilterStore.cs +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using Nethermind.Blockchain.Find; - -namespace Nethermind.Blockchain.Filters -{ - public interface IFilterStore : IDisposable - { - bool FilterExists(int filterId); - IEnumerable GetFilters() where T : FilterBase; - T? GetFilter(int filterId) where T : FilterBase; - BlockFilter CreateBlockFilter(bool setId = true); - PendingTransactionFilter CreatePendingTransactionFilter(bool setId = true); - - LogFilter CreateLogFilter( - BlockParameter fromBlock, - BlockParameter toBlock, - object? address = null, - IEnumerable? topics = null, - bool setId = true); - - void SaveFilter(FilterBase filter); - void RemoveFilter(int filterId); - void RefreshFilter(int filterId); - FilterType GetFilterType(int filterId); - - event EventHandler FilterRemoved; - } -} diff --git a/src/Nethermind/Nethermind.Facade/Filters/NullFilterManager.cs b/src/Nethermind/Nethermind.Facade/Filters/NullFilterManager.cs deleted file mode 100644 index 6fc634af8d2..00000000000 --- a/src/Nethermind/Nethermind.Facade/Filters/NullFilterManager.cs +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using Nethermind.Core.Crypto; -using Nethermind.Facade.Filters; - -namespace Nethermind.Blockchain.Filters -{ - public class NullFilterManager : IFilterManager - { - public static NullFilterManager Instance { get; } = new(); - - private NullFilterManager() - { - } - - public FilterLog[] GetLogs(int filterId) - { - return []; - } - - public FilterLog[] PollLogs(int filterId) - { - return []; - } - - public Hash256[] GetBlocksHashes(int filterId) - { - return []; - } - - public Hash256[] PollBlockHashes(int filterId) - { - return []; - } - - public Hash256[] PollPendingTransactionHashes(int filterId) - { - return []; - } - } -} diff --git a/src/Nethermind/Nethermind.Facade/Filters/NullFilterStore.cs b/src/Nethermind/Nethermind.Facade/Filters/NullFilterStore.cs deleted file mode 100644 index a034ba44364..00000000000 --- a/src/Nethermind/Nethermind.Facade/Filters/NullFilterStore.cs +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using System.Collections.Generic; -using Nethermind.Blockchain.Find; - -namespace Nethermind.Blockchain.Filters -{ - public class NullFilterStore : IFilterStore - { - private NullFilterStore() - { - } - - public static NullFilterStore Instance { get; } = new(); - - public bool FilterExists(int filterId) - { - return false; - } - - public IEnumerable GetFilters() where T : FilterBase - { - return Array.Empty(); - } - - public BlockFilter CreateBlockFilter(bool setId = true) - { - throw new InvalidOperationException($"{nameof(NullFilterStore)} does not support filter creation"); - } - - public PendingTransactionFilter CreatePendingTransactionFilter(bool setId = true) - { - throw new InvalidOperationException($"{nameof(NullFilterStore)} does not support filter creation"); - } - - public LogFilter CreateLogFilter(BlockParameter fromBlock, BlockParameter toBlock, object? address = null, IEnumerable? topics = null, bool setId = true) - { - throw new InvalidOperationException($"{nameof(NullFilterStore)} does not support filter creation"); - } - - public void SaveFilter(FilterBase filter) - { - throw new InvalidOperationException($"{nameof(NullFilterStore)} does not support filter creation"); - } - - public void RemoveFilter(int filterId) - { - throw new InvalidOperationException($"{nameof(NullFilterStore)} does not support filter creation"); - } - - public void RefreshFilter(int filterId) - { - throw new InvalidOperationException($"{nameof(NullFilterStore)} does not support filter refreshing"); - } - - public FilterType GetFilterType(int filterId) - { - throw new InvalidOperationException($"{nameof(NullFilterStore)} does not support filter creation"); - } - - public T? GetFilter(int filterId) where T : FilterBase - { - return null; - } - - public event EventHandler FilterRemoved - { - add { } - remove { } - } - - public void Dispose() { } - } -} diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs index 60ce1106828..7bf063bfb60 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/AnyTopicsFilter.cs @@ -8,25 +8,17 @@ namespace Nethermind.Blockchain.Filters.Topics { - public class AnyTopicsFilter : TopicsFilter + public class AnyTopicsFilter(params TopicExpression[] expressions) : TopicsFilter { - - private readonly TopicExpression[] _expressions; - - public AnyTopicsFilter(params TopicExpression[] expressions) - { - _expressions = expressions; - } - public override bool Accepts(LogEntry entry) => Accepts(entry.Topics); private bool Accepts(Hash256[] topics) { - for (int i = 0; i < _expressions.Length; i++) + for (int i = 0; i < expressions.Length; i++) { for (int j = 0; j < topics.Length; j++) { - if (_expressions[i].Accepts(topics[j])) + if (expressions[i].Accepts(topics[j])) { return true; } @@ -45,11 +37,11 @@ public override bool Accepts(ref LogEntryStructRef entry) Span buffer = stackalloc byte[32]; var iterator = new KeccaksIterator(entry.TopicsRlp, buffer); - for (int i = 0; i < _expressions.Length; i++) + for (int i = 0; i < expressions.Length; i++) { if (iterator.TryGetNext(out var keccak)) { - if (_expressions[i].Accepts(ref keccak)) + if (expressions[i].Accepts(ref keccak)) { return true; } @@ -67,9 +59,9 @@ public override bool Matches(Bloom bloom) { bool result = true; - for (int i = 0; i < _expressions.Length; i++) + for (int i = 0; i < expressions.Length; i++) { - result = _expressions[i].Matches(bloom); + result = expressions[i].Matches(bloom); if (result) { break; @@ -83,9 +75,9 @@ public override bool Matches(ref BloomStructRef bloom) { bool result = true; - for (int i = 0; i < _expressions.Length; i++) + for (int i = 0; i < expressions.Length; i++) { - result = _expressions[i].Matches(ref bloom); + result = expressions[i].Matches(ref bloom); if (result) { break; diff --git a/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs b/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs index 7386afe49ad..a3d28197ba3 100644 --- a/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs +++ b/src/Nethermind/Nethermind.Facade/Filters/Topics/SequenceTopicsFilter.cs @@ -9,16 +9,12 @@ namespace Nethermind.Blockchain.Filters.Topics { - public class SequenceTopicsFilter : TopicsFilter, IEquatable + public class SequenceTopicsFilter(params TopicExpression[] expressions) + : TopicsFilter, IEquatable { public static readonly SequenceTopicsFilter AnyTopic = new(); - private readonly TopicExpression[] _expressions; - - public SequenceTopicsFilter(params TopicExpression[] expressions) - { - _expressions = expressions; - } + private readonly TopicExpression[] _expressions = expressions; public override bool Accepts(LogEntry entry) => Accepts(entry.Topics); diff --git a/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs b/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs index d2f41be173a..6191d6bf06f 100644 --- a/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs +++ b/src/Nethermind/Nethermind.Facade/IBlockchainBridge.cs @@ -36,19 +36,19 @@ public interface IBlockchainBridge : ILogFinder int NewBlockFilter(); int NewPendingTransactionFilter(); - int NewFilter(BlockParameter? fromBlock, BlockParameter? toBlock, object? address = null, IEnumerable? topics = null); + int NewFilter(BlockParameter fromBlock, BlockParameter toBlock, HashSet? address = null, IEnumerable? topics = null); void UninstallFilter(int filterId); bool FilterExists(int filterId); Hash256[] GetBlockFilterChanges(int filterId); Hash256[] GetPendingTransactionFilterChanges(int filterId); FilterLog[] GetLogFilterChanges(int filterId); FilterType GetFilterType(int filterId); - LogFilter GetFilter(BlockParameter fromBlock, BlockParameter toBlock, object? address = null, IEnumerable? topics = null); + LogFilter GetFilter(BlockParameter fromBlock, BlockParameter toBlock, HashSet? addresses = null, IEnumerable? topics = null); IEnumerable GetLogs(LogFilter filter, BlockHeader fromBlock, BlockHeader toBlock, CancellationToken cancellationToken = default); - IEnumerable GetLogs(BlockParameter fromBlock, BlockParameter toBlock, object? address = null, IEnumerable? topics = null, CancellationToken cancellationToken = default); + IEnumerable GetLogs(BlockParameter fromBlock, BlockParameter toBlock, HashSet? addresses = null, IEnumerable? topics = null, CancellationToken cancellationToken = default); bool TryGetLogs(int filterId, out IEnumerable filterLogs, CancellationToken cancellationToken = default); - void RunTreeVisitor(ITreeVisitor treeVisitor, Hash256 stateRoot) where TCtx : struct, INodeContext; + void RunTreeVisitor(ITreeVisitor treeVisitor, BlockHeader? baseBlock) where TCtx : struct, INodeContext; bool HasStateForBlock(BlockHeader? baseBlock); } } diff --git a/src/Nethermind/Nethermind.Facade/Proxy/DefaultHttpClient.cs b/src/Nethermind/Nethermind.Facade/Proxy/DefaultHttpClient.cs index 53765e36759..0f9e168c9f8 100644 --- a/src/Nethermind/Nethermind.Facade/Proxy/DefaultHttpClient.cs +++ b/src/Nethermind/Nethermind.Facade/Proxy/DefaultHttpClient.cs @@ -12,7 +12,7 @@ namespace Nethermind.Facade.Proxy { - public class DefaultHttpClient : IHttpClient + public class DefaultHttpClient : IHttpClient, IDisposable { private readonly HttpClient _client; private readonly IJsonSerializer _jsonSerializer; @@ -25,7 +25,7 @@ public DefaultHttpClient( IJsonSerializer jsonSerializer, ILogManager logManager, int retries = 3, - int retryDelayMilliseconds = 1000) + int retryDelayMilliseconds = 100) { _client = client ?? throw new ArgumentNullException(nameof(client)); _jsonSerializer = jsonSerializer ?? throw new ArgumentNullException(nameof(jsonSerializer)); @@ -110,5 +110,10 @@ private enum Method Get, Post } + + public void Dispose() + { + _client?.Dispose(); + } } } diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryBlockStore.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryBlockStore.cs index 600332daf7d..7c16e0f7331 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryBlockStore.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryBlockStore.cs @@ -53,16 +53,10 @@ public void Delete(long blockNumber, Hash256 blockHash) return readonlyBaseBlockStore.GetRlp(blockNumber, blockHash); } - public ReceiptRecoveryBlock? GetReceiptRecoveryBlock(long blockNumber, Hash256 blockHash) - { - if (_blockNumDict.TryGetValue(blockNumber, out Block block)) - { - using NettyRlpStream newRlp = _blockDecoder.EncodeToNewNettyStream(block); - using var memoryManager = new CappedArrayMemoryManager(newRlp.Data); - return _blockDecoder.DecodeToReceiptRecoveryBlock(memoryManager, memoryManager.Memory, RlpBehaviors.None); - } - return readonlyBaseBlockStore.GetReceiptRecoveryBlock(blockNumber, blockHash); - } + public ReceiptRecoveryBlock? GetReceiptRecoveryBlock(long blockNumber, Hash256 blockHash) => + _blockNumDict.TryGetValue(blockNumber, out Block block) + ? new ReceiptRecoveryBlock(block) + : readonlyBaseBlockStore.GetReceiptRecoveryBlock(blockNumber, blockHash); public void Cache(Block block) => Insert(block); diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryHeaderStore.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryHeaderStore.cs index 0876b9d3084..b68e7a21707 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryHeaderStore.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateDictionaryHeaderStore.cs @@ -36,17 +36,13 @@ public void BulkInsert(IReadOnlyList headers) public BlockHeader? Get(Hash256 blockHash, bool shouldCache = false, long? blockNumber = null) { - blockNumber ??= GetBlockNumber(blockHash); - - if (blockNumber.HasValue && _headerDict.TryGetValue(blockHash, out BlockHeader? header)) + if (_headerDict.TryGetValue(blockHash, out BlockHeader? header)) { - if (shouldCache) - { - Cache(header!); - } return header; } + blockNumber ??= GetBlockNumber(blockHash); + header = readonlyBaseHeaderStore.Get(blockHash, false, blockNumber); if (header is not null && shouldCache) { diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnv.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnv.cs index e779fa4dd8d..ba3e5d28c08 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnv.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnv.cs @@ -15,7 +15,7 @@ namespace Nethermind.Facade.Simulate; /// -/// This is an env for eth simulater. It is constructed by . +/// This is an env for eth simulator. It is constructed by . /// It is not thread safe and is meant to be reused. must be called and the returned /// must be disposed once done or there may be some memory leak. /// @@ -62,6 +62,6 @@ IDisposable overridableWorldStateCloser public void Dispose() { overridableWorldStateCloser.Dispose(); - readOnlyDbProvider.Dispose(); // For blocktree. The read only db has a buffer that need to be cleared. + readOnlyDbProvider.ClearTempChanges(); // For blocktree. The read only db has a buffer that need to be cleared. } } diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnvFactory.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnvFactory.cs index 4e2a2e9e399..0f4aa1242df 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnvFactory.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateReadOnlyBlocksProcessingEnvFactory.cs @@ -48,6 +48,7 @@ public ISimulateReadOnlyBlocksProcessingEnv Create() ILifetimeScope envLifetimeScope = rootLifetimeScope.BeginLifetimeScope((builder) => builder .AddModule(overridableEnv) // worldstate related override here + .AddSingleton(editableDbProvider) .AddSingleton(overrideBlockTree) .AddSingleton(overrideBlockTree) .AddSingleton(tmpHeaderStore) @@ -64,6 +65,7 @@ public ISimulateReadOnlyBlocksProcessingEnv Create() .AddScoped() .AddScoped()); + envLifetimeScope.Disposer.AddInstanceForDisposal(editableDbProvider); rootLifetimeScope.Disposer.AddInstanceForAsyncDisposal(envLifetimeScope); return envLifetimeScope.Resolve(); } @@ -76,10 +78,10 @@ private static BlockTree CreateTempBlockTree( SimulateDictionaryHeaderStore tmpHeaderStore, IBlockAccessListStore tmpBalStore) { - IBlockStore mainblockStore = new BlockStore(editableDbProvider.BlocksDb); + IBlockStore mainBlockStore = new BlockStore(editableDbProvider.BlocksDb); const int badBlocksStored = 1; - SimulateDictionaryBlockStore tmpBlockStore = new(mainblockStore); + SimulateDictionaryBlockStore tmpBlockStore = new(mainBlockStore); IBadBlockStore badBlockStore = new BadBlockStore(editableDbProvider.BadBlocksDb, badBlocksStored); return new(tmpBlockStore, diff --git a/src/Nethermind/Nethermind.Facade/Simulate/SimulateTxTracer.cs b/src/Nethermind/Nethermind.Facade/Simulate/SimulateTxTracer.cs index 161c044e970..25f078f9fb1 100644 --- a/src/Nethermind/Nethermind.Facade/Simulate/SimulateTxTracer.cs +++ b/src/Nethermind/Nethermind.Facade/Simulate/SimulateTxTracer.cs @@ -90,7 +90,7 @@ public override void MarkAsSuccess(Address recipient, GasConsumed gasSpent, byte Address = entry.Address, Topics = entry.Topics, Data = entry.Data, - LogIndex = (ulong)i, + LogIndex = _txIndex + (ulong)i, TransactionHash = _tx.Hash!, TransactionIndex = _txIndex, BlockHash = _currentBlockHash, diff --git a/src/Nethermind/Nethermind.Flashbots.Test/BidTraceSerializationTests.cs b/src/Nethermind/Nethermind.Flashbots.Test/BidTraceSerializationTests.cs new file mode 100644 index 00000000000..1f3bc928097 --- /dev/null +++ b/src/Nethermind/Nethermind.Flashbots.Test/BidTraceSerializationTests.cs @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Text.Json; +using FluentAssertions; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Flashbots.Data; +using Nethermind.Serialization.Json; +using NUnit.Framework; + +namespace Nethermind.Flashbots.Test +{ + [TestFixture] + public class BidTraceSerializationTests + { + private readonly EthereumJsonSerializer _serializer = new(); + + [Test] + public void BidTrace_WithFullPublicKeysJson_DeserializesSuccessfully() + { + const string builderKey = "a49ac7010c2e0a444dfeeabadbafa4856ba4a2d732acb86d20c577b3b365f52e5a8728693008d97ae83d51194f273455acf1a30e6f3926aefaede484c07d8ec3"; + const string proposerKey = "b49ac7010c2e0a444dfeeabadbafa4856ba4a2d732acb86d20c577b3b365f52e5a8728693008d97ae83d51194f273455acf1a30e6f3926aefaede484c07d8ec4"; + byte[] builderKeyBytes = Bytes.FromHexString(builderKey); + byte[] proposerKeyBytes = Bytes.FromHexString(proposerKey); + + string fullKeyJson = $$""" + { + "slot": 12345, + "builder_public_key": "0x{{builderKey}}", + "proposer_public_key": "0x{{proposerKey}}", + "value": "1000000000000000000" + } + """; + + BidTrace trace = _serializer.Deserialize(fullKeyJson); + + trace.BuilderPublicKey.Should().NotBeNull(); + trace.ProposerPublicKey.Should().NotBeNull(); + trace.BuilderPublicKey.Bytes.Length.Should().Be(64); + trace.ProposerPublicKey.Bytes.Length.Should().Be(64); + trace.BuilderPublicKey.Bytes.Should().BeEquivalentTo(builderKeyBytes); + trace.ProposerPublicKey.Bytes.Should().BeEquivalentTo(proposerKeyBytes); + } + } +} diff --git a/src/Nethermind/Nethermind.Flashbots.Test/FlashbotsModuleTests.cs b/src/Nethermind/Nethermind.Flashbots.Test/FlashbotsModuleTests.cs index b024ebe1e23..e5b74f0a7a7 100644 --- a/src/Nethermind/Nethermind.Flashbots.Test/FlashbotsModuleTests.cs +++ b/src/Nethermind/Nethermind.Flashbots.Test/FlashbotsModuleTests.cs @@ -67,7 +67,7 @@ public virtual async Task TestValidateBuilderSubmissionV3() private Block CreateBlock(EngineModuleTests.MergeTestBlockchain chain) { BlockHeader currentHeader = chain.BlockTree.Head.Header; - IWorldState State = chain.WorldStateManager.GlobalWorldState; + IWorldState State = chain.MainWorldState; using var _ = State.BeginScope(IWorldState.PreGenesis); State.CreateAccount(TestKeysAndAddress.TestAddr, TestKeysAndAddress.TestBalance); UInt256 nonce = State.GetNonce(TestKeysAndAddress.TestAddr); diff --git a/src/Nethermind/Nethermind.Flashbots.Test/Rbuilder/RbuilderRpcModuleTests.cs b/src/Nethermind/Nethermind.Flashbots.Test/Rbuilder/RbuilderRpcModuleTests.cs index b7688fa13d8..013e78afd1d 100644 --- a/src/Nethermind/Nethermind.Flashbots.Test/Rbuilder/RbuilderRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.Flashbots.Test/Rbuilder/RbuilderRpcModuleTests.cs @@ -18,6 +18,7 @@ using Nethermind.Specs.Forks; using Nethermind.Evm.State; using Nethermind.JsonRpc; +using Nethermind.Logging; using Nethermind.State; using NUnit.Framework; using Bytes = Nethermind.Core.Extensions.Bytes; @@ -61,7 +62,7 @@ public async Task Test_getCodeByHash() byte[] theCodeBytes = Bytes.FromHexString(theCode); Hash256 theHash = Keccak.Compute(theCodeBytes); - IWorldState worldState = _worldStateManager.GlobalWorldState; + IWorldState worldState = new WorldState(_worldStateManager.GlobalWorldState, LimboLogs.Instance); using (worldState.BeginScope(IWorldState.PreGenesis)) { worldState.CreateAccount(TestItem.AddressA, 100000); diff --git a/src/Nethermind/Nethermind.Flashbots/Data/BidTrace.cs b/src/Nethermind/Nethermind.Flashbots/Data/BidTrace.cs index e27ca437a8a..2af6ac9c37d 100644 --- a/src/Nethermind/Nethermind.Flashbots/Data/BidTrace.cs +++ b/src/Nethermind/Nethermind.Flashbots/Data/BidTrace.cs @@ -4,6 +4,7 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Int256; +using Nethermind.Serialization.Json; namespace Nethermind.Flashbots.Data; @@ -21,9 +22,11 @@ public class BidTrace public Hash256 BlockHash { get; set; } [JsonPropertyName("builder_public_key")] + [JsonConverter(typeof(PublicKeyConverter))] public PublicKey BuilderPublicKey { get; set; } [JsonPropertyName("proposer_public_key")] + [JsonConverter(typeof(PublicKeyConverter))] public PublicKey ProposerPublicKey { get; set; } [JsonPropertyName("proposer_fee_recipient")] diff --git a/src/Nethermind/Nethermind.Grpc/Clients/GrpcClient.cs b/src/Nethermind/Nethermind.Grpc/Clients/GrpcClient.cs index 2855a1a2e0f..2c6fc5e8510 100644 --- a/src/Nethermind/Nethermind.Grpc/Clients/GrpcClient.cs +++ b/src/Nethermind/Nethermind.Grpc/Clients/GrpcClient.cs @@ -38,9 +38,7 @@ public GrpcClient(string host, int port, int reconnectionInterval, ILogManager l nameof(reconnectionInterval)); } - _address = string.IsNullOrWhiteSpace(host) - ? throw new ArgumentException("Missing gRPC host", nameof(host)) - : $"{host}:{port}"; + _address = $"{host}:{port}"; _reconnectionInterval = reconnectionInterval; _logger = logManager.GetClassLogger(); } @@ -137,7 +135,7 @@ await stream.ResponseStream.MoveNext(cancellationToken)) private async Task TryReconnectAsync() { - _connected = false; + await StopAsync(); _retry++; if (_logger.IsWarn) _logger.Warn($"Retrying ({_retry}) gRPC connection to: '{_address}' in {_reconnectionInterval} ms."); await Task.Delay(_reconnectionInterval); diff --git a/src/Nethermind/Nethermind.HealthChecks.Test/ClHealthTrackerTests.cs b/src/Nethermind/Nethermind.HealthChecks.Test/ClHealthTrackerTests.cs index a7169e7e4e8..06b086dff91 100644 --- a/src/Nethermind/Nethermind.HealthChecks.Test/ClHealthTrackerTests.cs +++ b/src/Nethermind/Nethermind.HealthChecks.Test/ClHealthTrackerTests.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Threading.Tasks; using FluentAssertions; using Nethermind.Core; using Nethermind.Logging; @@ -12,44 +11,96 @@ namespace Nethermind.HealthChecks.Test; public class ClHealthTrackerTests { - [Test] - public async Task ClHealthRequestsTracker_multiple_requests() + private const int MaxIntervalSeconds = 300; + + private static ClHealthRequestsTracker CreateHealthTracker(ManualTimestamper timestamper) { - ManualTimestamper timestamper = new(DateTime.Parse("18:23:00")); - await using ClHealthRequestsTracker healthTracker = new( + return new ClHealthRequestsTracker( timestamper, new HealthChecksConfig() { - MaxIntervalClRequestTime = 300 + MaxIntervalClRequestTime = MaxIntervalSeconds }, LimboLogs.Instance); + } + + [Test] + public void CheckClAlive_Initially_ReturnsTrue() + { + ManualTimestamper timestamper = new(DateTime.Parse("18:23:00")); + ClHealthRequestsTracker healthTracker = CreateHealthTracker(timestamper); healthTracker.CheckClAlive().Should().BeTrue(); + } - timestamper.Add(TimeSpan.FromSeconds(299)); - healthTracker.CheckClAlive().Should().BeTrue(); // Not enough time from start + [Test] + public void CheckClAlive_AfterMaxInterval_ReturnsFalse() + { + ManualTimestamper timestamper = new(DateTime.Parse("18:23:00")); + ClHealthRequestsTracker healthTracker = CreateHealthTracker(timestamper); - timestamper.Add(TimeSpan.FromSeconds(2)); - healthTracker.CheckClAlive().Should().BeFalse(); // More than 300 since start + timestamper.Add(TimeSpan.FromSeconds(MaxIntervalSeconds + 1)); + healthTracker.CheckClAlive().Should().BeFalse(); + } + + [Test] + public void CheckClAlive_BeforeMaxInterval_ReturnsTrue() + { + ManualTimestamper timestamper = new(DateTime.Parse("18:23:00")); + ClHealthRequestsTracker healthTracker = CreateHealthTracker(timestamper); + + timestamper.Add(TimeSpan.FromSeconds(MaxIntervalSeconds - 1)); + healthTracker.CheckClAlive().Should().BeTrue(); + } + + [Test] + public void CheckClAlive_AfterForkchoiceUpdated_ResetsTimer() + { + ManualTimestamper timestamper = new(DateTime.Parse("18:23:00")); + ClHealthRequestsTracker healthTracker = CreateHealthTracker(timestamper); + + timestamper.Add(TimeSpan.FromSeconds(MaxIntervalSeconds + 1)); + healthTracker.CheckClAlive().Should().BeFalse(); healthTracker.OnForkchoiceUpdatedCalled(); - healthTracker.CheckClAlive().Should().BeTrue(); // Fcu + healthTracker.CheckClAlive().Should().BeTrue(); + } + + [Test] + public void CheckClAlive_AfterNewPayload_ResetsTimer() + { + ManualTimestamper timestamper = new(DateTime.Parse("18:23:00")); + ClHealthRequestsTracker healthTracker = CreateHealthTracker(timestamper); + + timestamper.Add(TimeSpan.FromSeconds(MaxIntervalSeconds + 1)); + healthTracker.CheckClAlive().Should().BeFalse(); healthTracker.OnNewPayloadCalled(); - healthTracker.CheckClAlive().Should().BeTrue(); // Fcu + Np + healthTracker.CheckClAlive().Should().BeTrue(); + } - timestamper.Add(TimeSpan.FromSeconds(301)); - healthTracker.CheckClAlive().Should().BeFalse(); // Big gap since fcu + Np + [Test] + public void CheckClAlive_WithBothRequests_WorksCorrectly() + { + ManualTimestamper timestamper = new(DateTime.Parse("18:23:00")); + ClHealthRequestsTracker healthTracker = CreateHealthTracker(timestamper); healthTracker.OnForkchoiceUpdatedCalled(); healthTracker.CheckClAlive().Should().BeTrue(); - timestamper.Add(TimeSpan.FromSeconds(301)); - healthTracker.CheckClAlive().Should().BeFalse(); // Big gap - healthTracker.OnNewPayloadCalled(); healthTracker.CheckClAlive().Should().BeTrue(); - timestamper.Add(TimeSpan.FromSeconds(299)); + timestamper.Add(TimeSpan.FromSeconds(MaxIntervalSeconds + 1)); + healthTracker.CheckClAlive().Should().BeFalse(); + } + + [Test] + public void CheckClAlive_AtBoundaryConditions_WorksCorrectly() + { + ManualTimestamper timestamper = new(DateTime.Parse("18:23:00")); + ClHealthRequestsTracker healthTracker = CreateHealthTracker(timestamper); + + timestamper.Add(TimeSpan.FromSeconds(MaxIntervalSeconds - 1)); healthTracker.OnForkchoiceUpdatedCalled(); healthTracker.CheckClAlive().Should().BeTrue(); diff --git a/src/Nethermind/Nethermind.HealthChecks/HealthChecksWebhookInfo.cs b/src/Nethermind/Nethermind.HealthChecks/HealthChecksWebhookInfo.cs index 3314176be13..3b1e4fb9d8c 100644 --- a/src/Nethermind/Nethermind.HealthChecks/HealthChecksWebhookInfo.cs +++ b/src/Nethermind/Nethermind.HealthChecks/HealthChecksWebhookInfo.cs @@ -1,4 +1,4 @@ -/* Class to provide useful information in healh checks' webhook notifications */ +/* Class to provide useful information in health checks' webhook notifications */ using System.Net; using System; diff --git a/src/Nethermind/Nethermind.HealthChecks/IHealthChecksConfig.cs b/src/Nethermind/Nethermind.HealthChecks/IHealthChecksConfig.cs index eac6efa88bd..14733d674f5 100644 --- a/src/Nethermind/Nethermind.HealthChecks/IHealthChecksConfig.cs +++ b/src/Nethermind/Nethermind.HealthChecks/IHealthChecksConfig.cs @@ -19,7 +19,7 @@ public interface IHealthChecksConfig : IConfig [ConfigItem(Description = "The web hook URL.", DefaultValue = "null")] public string WebhooksUri { get; set; } - [ConfigItem(Description = "An escaped JSON paylod to be sent to the web hook on failure.", + [ConfigItem(Description = "An escaped JSON payload to be sent to the web hook on failure.", DefaultValue = """ ```json { @@ -46,7 +46,7 @@ public interface IHealthChecksConfig : IConfig """)] public string WebhooksPayload { get; set; } - [ConfigItem(Description = "An escaped JSON paylod to be sent to the web hook on recovery.", + [ConfigItem(Description = "An escaped JSON payload to be sent to the web hook on recovery.", DefaultValue = """ ```json { diff --git a/src/Nethermind/Nethermind.HealthChecks/Nethermind.HealthChecks.csproj b/src/Nethermind/Nethermind.HealthChecks/Nethermind.HealthChecks.csproj index 3fe6267162e..7f6f3d606ef 100644 --- a/src/Nethermind/Nethermind.HealthChecks/Nethermind.HealthChecks.csproj +++ b/src/Nethermind/Nethermind.HealthChecks/Nethermind.HealthChecks.csproj @@ -3,6 +3,7 @@ + diff --git a/src/Nethermind/Nethermind.History.Test/HistoryPrunerTests.cs b/src/Nethermind/Nethermind.History.Test/HistoryPrunerTests.cs index bc80429e567..801c6283290 100644 --- a/src/Nethermind/Nethermind.History.Test/HistoryPrunerTests.cs +++ b/src/Nethermind/Nethermind.History.Test/HistoryPrunerTests.cs @@ -39,7 +39,7 @@ public class HistoryPrunerTests { AncientBodiesBarrier = BeaconGenesisBlockNumber, AncientReceiptsBarrier = BeaconGenesisBlockNumber, - PivotNumber = "100", + PivotNumber = 100, SnapSync = true }; @@ -74,7 +74,7 @@ public async Task Can_prune_blocks_older_than_specified_epochs() CheckOldestAndCutoff(1, cutoff, historyPruner); - await historyPruner.TryPruneHistory(CancellationToken.None); + historyPruner.TryPruneHistory(CancellationToken.None); CheckGenesisPreserved(testBlockchain, blockHashes[0]); for (int i = 1; i <= blocks; i++) @@ -122,7 +122,7 @@ public async Task Can_prune_to_ancient_barriers() CheckOldestAndCutoff(1, BeaconGenesisBlockNumber, historyPruner); - await historyPruner.TryPruneHistory(CancellationToken.None); + historyPruner.TryPruneHistory(CancellationToken.None); CheckGenesisPreserved(testBlockchain, blockHashes[0]); @@ -171,7 +171,7 @@ public async Task Prunes_up_to_sync_pivot() CheckOldestAndCutoff(1, BeaconGenesisBlockNumber, historyPruner); - await historyPruner.TryPruneHistory(CancellationToken.None); + historyPruner.TryPruneHistory(CancellationToken.None); CheckGenesisPreserved(testBlockchain, blockHashes[0]); @@ -221,7 +221,7 @@ public async Task Can_find_oldest_block() CheckOldestAndCutoff(1, cutoff, historyPruner); - await historyPruner.TryPruneHistory(CancellationToken.None); + historyPruner.TryPruneHistory(CancellationToken.None); historyPruner.SetDeletePointerToOldestBlock(); // recalculate oldest block with binary search CheckOldestAndCutoff(cutoff, cutoff, historyPruner); @@ -248,7 +248,7 @@ public async Task Does_not_prune_when_disabled() } var historyPruner = (HistoryPruner)testBlockchain.Container.Resolve(); - await historyPruner.TryPruneHistory(CancellationToken.None); + historyPruner.TryPruneHistory(CancellationToken.None); CheckGenesisPreserved(testBlockchain, blockHashes[0]); diff --git a/src/Nethermind/Nethermind.History/HistoryPruner.cs b/src/Nethermind/Nethermind.History/HistoryPruner.cs index df2313a3f50..0e82890ee05 100644 --- a/src/Nethermind/Nethermind.History/HistoryPruner.cs +++ b/src/Nethermind/Nethermind.History/HistoryPruner.cs @@ -14,7 +14,9 @@ using Nethermind.Consensus.Processing; using Nethermind.Consensus.Scheduler; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using Nethermind.Core.Specs; using Nethermind.Db; using Nethermind.Logging; @@ -56,6 +58,7 @@ public class HistoryPruner : IHistoryPruner private long? _cutoffPointer; private ulong? _cutoffTimestamp; private bool _hasLoadedDeletePointer = false; + private int _currentlyPruning = 0; public event EventHandler? NewOldestBlock; @@ -126,25 +129,26 @@ public long? CutoffBlockNumber } long? cutoffBlockNumber = null; - long searchCutoff = _blockTree.Head is null ? _blockTree.SyncPivot.BlockNumber : _blockTree.Head.Number; + long to = _blockTree.Head?.Number ?? _blockTree.SyncPivot.BlockNumber; + + // cutoff is unchanged, can reuse + if (_cutoffTimestamp is not null && cutoffTimestamp == _cutoffTimestamp) + { + return _cutoffPointer; + } + bool lockTaken = false; try { Monitor.TryEnter(_searchLock, LockWaitTimeoutMs, ref lockTaken); - if (lockTaken) { - // cutoff is unchanged, can reuse - if (_cutoffTimestamp is not null && cutoffTimestamp == _cutoffTimestamp) - { - return _cutoffPointer; - } - - // optimisticly search a few blocks from old pointer + // optimistically search a few blocks from an old pointer if (_cutoffPointer is not null) { int attempts = 0; - _ = GetBlocksByNumber(_cutoffPointer.Value, searchCutoff, b => + long from = _cutoffPointer.Value; + using ArrayPoolListRef x = GetBlocksByNumber(from, to, b => { if (attempts >= MaxOptimisticSearchAttempts) { @@ -158,11 +162,11 @@ public long? CutoffBlockNumber } attempts++; return afterCutoff; - }).ToList(); + }).ToPooledListRef((int)(to - from + 1)); } - // if linear search fails fallback to binary search - cutoffBlockNumber ??= BlockTree.BinarySearchBlockNumber(_deletePointer, searchCutoff, (n, _) => + // if linear search fails, fallback to binary search + cutoffBlockNumber ??= BlockTree.BinarySearchBlockNumber(_deletePointer, to, (n, _) => { BlockInfo[]? blockInfos = _chainLevelInfoRepository.LoadLevel(n)?.BlockInfos; @@ -196,7 +200,9 @@ public long? CutoffBlockNumber finally { if (lockTaken) + { Monitor.Exit(_searchLock); + } } return cutoffBlockNumber; @@ -230,7 +236,9 @@ public BlockHeader? OldestBlockHeader finally { if (lockTaken) + { Monitor.Exit(_pruneLock); + } } } @@ -242,38 +250,82 @@ private void OnBlockProcessorQueueEmpty(object? sender, EventArgs e) => SchedulePruneHistory(_processExitSource.Token); private void SchedulePruneHistory(CancellationToken cancellationToken) - => _backgroundTaskScheduler.ScheduleTask(1, - (_, backgroundTaskToken) => + { + if (Volatile.Read(ref _currentlyPruning) == 0) + { + Task.Run(() => { - var cts = CancellationTokenSource.CreateLinkedTokenSource(backgroundTaskToken, cancellationToken); - return TryPruneHistory(cts.Token); + if (Interlocked.CompareExchange(ref _currentlyPruning, 1, 0) == 0) + { + try + { + if (!_backgroundTaskScheduler.TryScheduleTask(1, + (_, backgroundTaskToken) => + { + try + { + using var cts = CancellationTokenSource.CreateLinkedTokenSource(backgroundTaskToken, + cancellationToken); + TryPruneHistory(cts.Token); + } + finally + { + Interlocked.Exchange(ref _currentlyPruning, 0); + } + + return Task.CompletedTask; + })) + { + Interlocked.Exchange(ref _currentlyPruning, 0); + if (_logger.IsDebug) _logger.Debug("Failed to schedule historical block pruning (queue full). Will retry on next trigger."); + } + } + catch + { + Interlocked.Exchange(ref _currentlyPruning, 0); + throw; + } + } }); + } + } - internal Task TryPruneHistory(CancellationToken cancellationToken) + internal void TryPruneHistory(CancellationToken cancellationToken) { + if (_blockTree.Head is null || + _blockTree.SyncPivot.BlockNumber == 0 || + !_hasLoadedDeletePointer || + !ShouldPruneHistory(out ulong? cutoffTimestamp)) + { + SkipLocalPruning(); + return; + } + bool lockTaken = false; + Monitor.TryEnter(_pruneLock, LockWaitTimeoutMs, ref lockTaken); try { - Monitor.TryEnter(_pruneLock, LockWaitTimeoutMs, ref lockTaken); if (lockTaken) { - if (_blockTree.Head is null || - _blockTree.SyncPivot.BlockNumber == 0 || - !TryLoadDeletePointer() || - !ShouldPruneHistory(out ulong? cutoffTimestamp)) + if (!TryLoadDeletePointer() || + !ShouldPruneHistory(out cutoffTimestamp)) { - if (_logger.IsDebug) _logger.Debug($"Skipping historical block pruning."); - return Task.CompletedTask; + SkipLocalPruning(); + return; } if (_logger.IsInfo) { - long? cutoff = CutoffBlockNumber; - cutoff = cutoff is null ? null : long.Min(cutoff!.Value, _blockTree.SyncPivot.BlockNumber); + long? cutoffBlockNumber = CutoffBlockNumber; + long? cutoff = cutoffBlockNumber is null ? null : long.Min(cutoffBlockNumber.Value, _blockTree.SyncPivot.BlockNumber); long? toDelete = cutoff - _deletePointer; - string cutoffString = cutoffTimestamp is null ? $"#{(cutoff is null ? "unknown" : cutoff)}" : $"timestamp {cutoffTimestamp} (#{(cutoff is null ? "unknown" : cutoff)})"; - _logger.Info($"Pruning historical blocks up to {cutoffString}. Estimated {(toDelete is null ? "unknown" : toDelete)} blocks will be deleted."); + string cutoffString = cutoffTimestamp is null + ? $"#{FormatNumberOrUnknown(cutoff)}" + : $"timestamp {cutoffTimestamp} (#{FormatNumberOrUnknown(cutoff)})"; + _logger.Info($"Pruning historical blocks up to {cutoffString}. Estimated {FormatNumberOrUnknown(toDelete)} blocks will be deleted."); + + static string FormatNumberOrUnknown(long? l) => l?.ToString() ?? "unknown"; } PruneBlocksAndReceipts(cutoffTimestamp, cancellationToken); @@ -286,10 +338,15 @@ internal Task TryPruneHistory(CancellationToken cancellationToken) finally { if (lockTaken) + { Monitor.Exit(_pruneLock); + } } - return Task.CompletedTask; + void SkipLocalPruning() + { + if (_logger.IsTrace) _logger.Trace("Skipping historical block pruning."); + } } internal bool SetDeletePointerToOldestBlock() @@ -359,12 +416,13 @@ private bool PruningIntervalHasElapsed() private void PruneBlocksAndReceipts(ulong? cutoffTimestamp, CancellationToken cancellationToken) { int deletedBlocks = 0; - ulong? lastDeletedTimstamp = null; + ulong? lastDeletedTimestamp = null; try { IEnumerable blocks = _historyConfig.Pruning == PruningModes.UseAncientBarriers ? GetBlocksBeforeAncientBarrier() : GetBlocksBeforeTimestamp(cutoffTimestamp!.Value); + foreach (Block block in blocks) { long number = block.Number; @@ -379,7 +437,7 @@ private void PruneBlocksAndReceipts(ulong? cutoffTimestamp, CancellationToken ca // should never happen if (number == 0 || number >= _blockTree.SyncPivot.BlockNumber) { - if (_logger.IsWarn) _logger.Warn($"Encountered unexepected block #{number} while pruning history, this block will not be deleted. Should be in range (0, {_blockTree.SyncPivot.BlockNumber})."); + if (_logger.IsWarn) _logger.Warn($"Encountered unexpected block #{number} while pruning history, this block will not be deleted. Should be in range (0, {_blockTree.SyncPivot.BlockNumber})."); continue; } @@ -396,20 +454,21 @@ private void PruneBlocksAndReceipts(ulong? cutoffTimestamp, CancellationToken ca _receiptStorage.RemoveReceipts(block); UpdateDeletePointer(number + 1, remaining is null || remaining == 0); - lastDeletedTimstamp = block.Timestamp; + lastDeletedTimestamp = block.Timestamp; deletedBlocks++; Metrics.BlocksPruned++; } } finally { - if (_cutoffPointer < _deletePointer && lastDeletedTimstamp is not null) + if (_cutoffPointer < _deletePointer && lastDeletedTimestamp is not null) { _cutoffPointer = _deletePointer; - _cutoffTimestamp = lastDeletedTimstamp; + _cutoffTimestamp = lastDeletedTimestamp; Metrics.PruningCutoffBlocknumber = _cutoffPointer; Metrics.PruningCutoffTimestamp = _cutoffTimestamp; } + SaveDeletePointer(); if (!cancellationToken.IsCancellationRequested) @@ -445,7 +504,7 @@ private IEnumerable GetBlocksByNumber(long from, long to, Predicate GetBlocksByNumber(long from, long to, Predicate() - .AddScoped() + .AddScoped() .AddScoped() .AddSingleton() - .AddScoped() + .AddScoped() + .AddDecorator() + .AddScoped() .AddScoped() .AddSingleton() .AddScoped() diff --git a/src/Nethermind/Nethermind.Init/Modules/DbModule.cs b/src/Nethermind/Nethermind.Init/Modules/DbModule.cs index 6d654d5e0b1..b695e1bd58d 100644 --- a/src/Nethermind/Nethermind.Init/Modules/DbModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/DbModule.cs @@ -57,11 +57,6 @@ protected override void Load(ContainerBuilder builder) return sortedKeyValue; }) - // Monitoring use these to track active db. We intercept db factory to keep them lazy. Does not - // track db that is not created by db factory though... - .AddSingleton() - .AddDecorator() - .AddDatabase(DbNames.State) .AddDatabase(DbNames.Code) .AddDatabase(DbNames.Metadata) @@ -70,14 +65,14 @@ protected override void Load(ContainerBuilder builder) .AddDatabase(DbNames.Blocks) .AddDatabase(DbNames.Headers) .AddDatabase(DbNames.BlockInfos) - .AddDatabase(DbNames.BadBlocks) .AddDatabase(DbNames.Bloom) - .AddDatabase(DbNames.Metadata) .AddDatabase(DbNames.BlobTransactions) .AddDatabase(DbNames.BlockAccessLists) .AddColumnDatabase(DbNames.Receipts) .AddColumnDatabase(DbNames.BlobTransactions) + + .AddSingleton((ctx) => new HyperClockCacheWrapper(ctx.Resolve().SharedBlockCacheSize)) ; switch (initConfig.DiagnosticMode) diff --git a/src/Nethermind/Nethermind.Init/Modules/DbMonitoringModule.cs b/src/Nethermind/Nethermind.Init/Modules/DbMonitoringModule.cs new file mode 100644 index 00000000000..74065a80626 --- /dev/null +++ b/src/Nethermind/Nethermind.Init/Modules/DbMonitoringModule.cs @@ -0,0 +1,138 @@ +// SPDX-FileCopyrightText: 2026 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using Autofac; +using Nethermind.Consensus.Processing; +using Nethermind.Core; +using Nethermind.Db; +using Nethermind.Db.Rocks; +using Nethermind.Logging; +using Nethermind.Monitoring; +using Nethermind.Monitoring.Config; + +namespace Nethermind.Init.Modules; + +public class DbMonitoringModule : Module +{ + protected override void Load(ContainerBuilder builder) + { + base.Load(builder); + + // Intercept created db to publish metric. + // Dont use constructor injection to get all db because that would resolve all db + // making them not lazy. + builder + .AddSingleton() + .AddDecorator() + + // Intercept block processing by checking the queue and pausing the metrics when that happen. + // Dont use constructor injection because this would prevent the metric from being updated before + // the block processing chain is constructed, eg: VerifyTrie or import jobs. + .Intercept((processingQueue, ctx) => + { + if (!ctx.Resolve().PauseDbMetricDuringBlockProcessing) return; + + // Do not update db metrics while processing a block + DbTracker updater = ctx.Resolve(); + processingQueue.BlockAdded += (sender, args) => updater.Paused = !processingQueue.IsEmpty; + processingQueue.BlockRemoved += (sender, args) => updater.Paused = !processingQueue.IsEmpty; + }) + ; + } + + public class DbTracker + { + private readonly ConcurrentDictionary _createdDbs = new ConcurrentDictionary(); + private readonly int _intervalSec; + private readonly HyperClockCacheWrapper _sharedBlockCache; + private long _lastDbMetricsUpdate = 0; + + private ILogger _logger; + + public DbTracker(IMonitoringService monitoringService, IMetricsConfig metricsConfig, HyperClockCacheWrapper sharedBlockCache, ILogManager logManager) + { + _intervalSec = metricsConfig.DbMetricIntervalSeconds; + _logger = logManager.GetClassLogger(); + _sharedBlockCache = sharedBlockCache; + + if (metricsConfig.EnableDbSizeMetrics) + { + monitoringService.AddMetricsUpdateAction(UpdateDbMetrics); + } + } + + public void AddDb(string name, IDbMeta dbMeta) + { + _createdDbs.TryAdd(name, dbMeta); + } + + public IEnumerable> GetAllDbMeta() + { + return _createdDbs; + } + + public bool Paused { get; set; } = false; + + private void UpdateDbMetrics() + { + try + { + if (Paused) return; + + if (Environment.TickCount64 - _lastDbMetricsUpdate < _intervalSec * 1000) + { + // Update based on configured interval + return; + } + + foreach (KeyValuePair kv in GetAllDbMeta()) + { + // Note: At the moment, the metric for a columns db is combined across column. + IDbMeta.DbMetric dbMetric = kv.Value.GatherMetric(); + Db.Metrics.DbSize[kv.Key] = dbMetric.Size; + Db.Metrics.DbBlockCacheSize[kv.Key] = dbMetric.CacheSize; + Db.Metrics.DbMemtableSize[kv.Key] = dbMetric.MemtableSize; + Db.Metrics.DbIndexFilterSize[kv.Key] = dbMetric.IndexSize; + Db.Metrics.DbReads[kv.Key] = dbMetric.TotalReads; + Db.Metrics.DbWrites[kv.Key] = dbMetric.TotalWrites; + } + + Db.Metrics.DbBlockCacheSize["Shared"] = _sharedBlockCache.GetUsage(); + + _lastDbMetricsUpdate = Environment.TickCount64; + } + catch (Exception e) + { + if (_logger.IsError) _logger.Error("Error during updating db metrics", e); + } + } + + public class DbFactoryInterceptor(DbTracker tracker, IDbFactory baseFactory) : IDbFactory + { + public IDb CreateDb(DbSettings dbSettings) + { + IDb db = baseFactory.CreateDb(dbSettings); + if (db is IDbMeta dbMeta) + { + tracker.AddDb(dbSettings.DbName, dbMeta); + } + return db; + } + + public IColumnsDb CreateColumnsDb(DbSettings dbSettings) where T : struct, Enum + { + IColumnsDb db = baseFactory.CreateColumnsDb(dbSettings); + if (db is IDbMeta dbMeta) + { + tracker.AddDb(dbSettings.DbName, dbMeta); + } + return db; + } + + public string GetFullDbPath(DbSettings dbSettings) => baseFactory.GetFullDbPath(dbSettings); + } + } +} diff --git a/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs b/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs index 9b4969d3962..41b75f80d98 100644 --- a/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs +++ b/src/Nethermind/Nethermind.Init/Modules/MainProcessingContext.cs @@ -24,25 +24,27 @@ public class MainProcessingContext : IMainProcessingContext, BlockProcessor.Bloc public MainProcessingContext( ILifetimeScope rootLifetimeScope, IReceiptConfig receiptConfig, - IBlocksConfig blocksConfig, IInitConfig initConfig, IBlockValidationModule[] blockValidationModules, IMainProcessingModule[] mainProcessingModules, IWorldStateManager worldStateManager, CompositeBlockPreprocessorStep compositeBlockPreprocessorStep, IBlockTree blockTree, - IPrecompileProvider precompileProvider, ILogManager logManager) { - IWorldState mainWorldState = worldStateManager.GlobalWorldState; + IWorldStateScopeProvider worldState = worldStateManager.GlobalWorldState; + if (logManager.GetClassLogger().IsTrace) + { + worldState = new WorldStateScopeOperationLogger(worldStateManager.GlobalWorldState, logManager); + } + ILifetimeScope innerScope = rootLifetimeScope.BeginLifetimeScope((builder) => { builder // These are main block processing specific - .AddSingleton(mainWorldState) + .AddSingleton(worldState) .AddModule(blockValidationModules) - .AddScoped() .AddSingleton(this) .AddModule(mainProcessingModules) @@ -65,20 +67,6 @@ public MainProcessingContext( // And finally, to wrap things up. .AddScoped() ; - - if (blocksConfig.PreWarmStateOnBlockProcessing) - { - builder - .AddScoped((mainWorldState as IPreBlockCaches)!.Caches) - .AddScoped() - .AddDecorator((ctx, originalCodeInfoRepository) => - { - PreBlockCaches preBlockCaches = ctx.Resolve(); - // Note: The use of FrozenDictionary means that this cannot be used for other processing env also due to risk of memory leak. - return new CachedCodeInfoRepository(precompileProvider, originalCodeInfoRepository, blocksConfig.CachePrecompilesOnBlockProcessing ? preBlockCaches?.PrecompileCache : null); - }) - ; - } }); _components = innerScope.Resolve(); diff --git a/src/Nethermind/Nethermind.Init/Modules/MonitoringModule.cs b/src/Nethermind/Nethermind.Init/Modules/MonitoringModule.cs new file mode 100644 index 00000000000..814ab35de12 --- /dev/null +++ b/src/Nethermind/Nethermind.Init/Modules/MonitoringModule.cs @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Generic; +using System.Linq; +using Autofac; +using DotNetty.Buffers; +using Nethermind.Blockchain.Synchronization; +using Nethermind.Core; +using Nethermind.Core.Specs; +using Nethermind.Db; +using Nethermind.Facade.Eth; +using Nethermind.Logging; +using Nethermind.Monitoring; +using Nethermind.Monitoring.Config; +using Nethermind.Monitoring.Metrics; +using Nethermind.Serialization.Rlp; + +namespace Nethermind.Init.Modules; + +public class MonitoringModule(IMetricsConfig metricsConfig) : Module +{ + protected override void Load(ContainerBuilder builder) + { + if (metricsConfig.Enabled || metricsConfig.CountersEnabled) + { + builder + .AddSingleton() + .AddSingleton( + PrepareProductInfoMetrics) + + .AddSingleton() + .Intercept(ConfigureDefaultMetrics) + + .Intercept((syncInfo, ctx) => + { + ctx.Resolve().AddMetricsUpdateAction(() => + { + Synchronization.Metrics.SyncTime = (long?)syncInfo.UpdateAndGetSyncTime().TotalSeconds ?? 0; + }); + }) + + ; + } + else + { + builder.AddSingleton(); + } + } + + private IMetricsController PrepareProductInfoMetrics(IMetricsConfig metricsConfig, ISyncConfig syncConfig, IPruningConfig pruningConfig, ISpecProvider specProvider) + { + // Need to be set here, or we cant start before blocktree, which is the one that set this normally. + ProductInfo.Network = $"{(specProvider.ChainId == specProvider.NetworkId ? BlockchainIds.GetBlockchainName(specProvider.NetworkId) : specProvider.ChainId)}"; + + ProductInfo.Instance = metricsConfig.NodeName; + + ProductInfo.SyncType = syncConfig.FastSync + ? syncConfig.SnapSync ? "Snap" : "Fast" + : "Full"; + + + ProductInfo.PruningMode = pruningConfig.Mode.ToString(); + Metrics.Version = VersionToMetrics.ConvertToNumber(ProductInfo.Version); + + IMetricsController controller = new MetricsController(metricsConfig); + + IEnumerable metrics = TypeDiscovery.FindNethermindBasedTypes(nameof(Metrics)); + foreach (Type metric in metrics) + { + controller.RegisterMetrics(metric); + } + + return controller; + } + + private void ConfigureDefaultMetrics(IMonitoringService monitoringService, IComponentContext ctx) + { + // Note: Do not add dependencies outside of monitoring module. + AllocatorMetricsUpdater allocatorMetricsUpdater = ctx.Resolve(); + monitoringService.AddMetricsUpdateAction(() => allocatorMetricsUpdater.UpdateAllocatorMetrics()); + } + + private class AllocatorMetricsUpdater(ILogManager logManager) + { + ILogger _logger = logManager.GetClassLogger(); + + public void UpdateAllocatorMetrics() + { + try + { + SetAllocatorMetrics(NethermindBuffers.RlpxAllocator, "rlpx"); + SetAllocatorMetrics(NethermindBuffers.DiscoveryAllocator, "discovery"); + SetAllocatorMetrics(NethermindBuffers.Default, "default"); + SetAllocatorMetrics(PooledByteBufferAllocator.Default, "netty_default"); + } + catch (Exception e) + { + if (_logger.IsError) _logger.Error("Error during allocator metrics", e); + } + } + + private static void SetAllocatorMetrics(IByteBufferAllocator allocator, string name) + { + if (allocator is PooledByteBufferAllocator byteBufferAllocator) + { + PooledByteBufferAllocatorMetric metric = byteBufferAllocator.Metric; + Serialization.Rlp.Metrics.AllocatorArenaCount[name] = metric.DirectArenas().Count; + Serialization.Rlp.Metrics.AllocatorChunkSize[name] = metric.ChunkSize; + Serialization.Rlp.Metrics.AllocatorUsedHeapMemory[name] = metric.UsedHeapMemory; + Serialization.Rlp.Metrics.AllocatorUsedDirectMemory[name] = metric.UsedDirectMemory; + Serialization.Rlp.Metrics.AllocatorActiveAllocations[name] = metric.HeapArenas().Sum((it) => it.NumActiveAllocations); + Serialization.Rlp.Metrics.AllocatorActiveAllocationBytes[name] = metric.HeapArenas().Sum((it) => it.NumActiveBytes); + Serialization.Rlp.Metrics.AllocatorAllocations[name] = metric.HeapArenas().Sum((it) => it.NumAllocations); + } + } + } +} diff --git a/src/Nethermind/Nethermind.Init/Modules/NethermindModule.cs b/src/Nethermind/Nethermind.Init/Modules/NethermindModule.cs index 19dbd2105bb..41df70db238 100644 --- a/src/Nethermind/Nethermind.Init/Modules/NethermindModule.cs +++ b/src/Nethermind/Nethermind.Init/Modules/NethermindModule.cs @@ -6,7 +6,6 @@ using Nethermind.Abi; using Nethermind.Api; using Nethermind.Blockchain; -using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Receipts; using Nethermind.Blockchain.Spec; using Nethermind.Blockchain.Synchronization; @@ -16,11 +15,10 @@ using Nethermind.Core.Specs; using Nethermind.Core.Timers; using Nethermind.Crypto; -using Nethermind.Db; using Nethermind.Era1; -using Nethermind.History; using Nethermind.JsonRpc; using Nethermind.Logging; +using Nethermind.Monitoring.Config; using Nethermind.Network.Config; using Nethermind.Runner.Ethereum.Modules; using Nethermind.Specs.ChainSpecStyle; @@ -48,13 +46,16 @@ protected override void Load(ContainerBuilder builder) configProvider.GetConfig(), configProvider.GetConfig() )) + .AddModule(new DbMonitoringModule()) .AddModule(new WorldStateModule(configProvider.GetConfig())) + .AddModule(new PrewarmerModule(configProvider.GetConfig())) .AddModule(new BuiltInStepsModule()) .AddModule(new RpcModules(configProvider.GetConfig())) .AddModule(new EraModule()) .AddSource(new ConfigRegistrationSource()) .AddModule(new BlockProcessingModule(configProvider.GetConfig(), configProvider.GetConfig())) .AddModule(new BlockTreeModule(configProvider.GetConfig())) + .AddModule(new MonitoringModule(configProvider.GetConfig())) .AddSingleton() .AddKeyedSingleton(IProtectedPrivateKey.NodeKey, (ctx) => ctx.Resolve().NodeKey!) diff --git a/src/Nethermind/Nethermind.Init/Modules/PrewarmerModule.cs b/src/Nethermind/Nethermind.Init/Modules/PrewarmerModule.cs new file mode 100644 index 00000000000..935ee73eb4b --- /dev/null +++ b/src/Nethermind/Nethermind.Init/Modules/PrewarmerModule.cs @@ -0,0 +1,70 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Autofac; +using Nethermind.Blockchain; +using Nethermind.Config; +using Nethermind.Consensus.Processing; +using Nethermind.Core; +using Nethermind.Core.Container; +using Nethermind.Evm; +using Nethermind.Evm.State; +using Nethermind.Evm.TransactionProcessing; +using Nethermind.State; +using Nethermind.Trie; +using Nethermind.Trie.Pruning; + +namespace Nethermind.Init.Modules; + +public class PrewarmerModule(IBlocksConfig blocksConfig) : Module +{ + protected override void Load(ContainerBuilder builder) + { + if (blocksConfig.PreWarmStateOnBlockProcessing) + { + builder + + // Note: There is a special logic for this in `PruningTrieStateFactory`. + .AddSingleton() + + // Note: Need a small modification to have this work on all branch processor due to the shared + // NodeStorageCache and the FrozenDictionary and the fact that some processing does not have + // branch processor, and use block processor instead. + .AddSingleton(); + } + } + + public class PrewarmerMainProcessingModule : Module, IMainProcessingModule + { + protected override void Load(ContainerBuilder builder) + { + builder + // Singleton so that all child env share the same caches. Note: this module is applied per-processing + // module, so singleton here is like scoped but exclude inner prewarmer lifetime. + .AddSingleton() + .AddScoped() + .Add() + + // These are the actual decorated component that provide cached result + .AddDecorator((ctx, worldStateScopeProvider) => + { + if (worldStateScopeProvider is PrewarmerScopeProvider) return worldStateScopeProvider; // Inner world state + return new PrewarmerScopeProvider( + worldStateScopeProvider, + ctx.Resolve(), + populatePreBlockCache: false + ); + }) + .AddDecorator((ctx, originalCodeInfoRepository) => + { + IBlocksConfig blocksConfig = ctx.Resolve(); + PreBlockCaches preBlockCaches = ctx.Resolve(); + IPrecompileProvider precompileProvider = ctx.Resolve(); + // Note: The use of FrozenDictionary means that this cannot be used for other processing env also due to risk of memory leak. + return new CachedCodeInfoRepository(precompileProvider, originalCodeInfoRepository, + blocksConfig.CachePrecompilesOnBlockProcessing ? preBlockCaches?.PrecompileCache : null); + }) + .AddDecorator(); + } + } +} diff --git a/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs b/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs index 420c1fa26b0..c9ff690ad82 100644 --- a/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs +++ b/src/Nethermind/Nethermind.Init/Modules/RpcModules.cs @@ -8,7 +8,6 @@ using Nethermind.Blockchain.Filters; using Nethermind.Blockchain.Receipts; using Nethermind.Config; -using Nethermind.Consensus.Processing; using Nethermind.Consensus.Tracing; using Nethermind.Core; using Nethermind.Core.Timers; @@ -31,7 +30,6 @@ using Nethermind.JsonRpc.Modules.Trace; using Nethermind.JsonRpc.Modules.TxPool; using Nethermind.JsonRpc.Modules.Web3; -using Nethermind.Logging; using Nethermind.Network; using Nethermind.Network.Config; using Nethermind.Sockets; @@ -81,8 +79,8 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddScoped((ctx) => ctx.Resolve().CreateBlockchainBridge()) .AddSingleton() - .AddSingleton((timerFactory, rpcConfig) => new FilterStore(timerFactory, rpcConfig.FiltersTimeout)) - .AddSingleton() + .AddSingleton((timerFactory, rpcConfig) => new FilterStore(timerFactory, rpcConfig.FiltersTimeout)) + .AddSingleton() .AddSingleton() // Proof diff --git a/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs b/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs index 276e69e8ceb..c200432e0ad 100644 --- a/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs +++ b/src/Nethermind/Nethermind.Init/PruningTrieStateFactory.cs @@ -33,7 +33,6 @@ public class PruningTrieStateFactory( ISyncConfig syncConfig, IInitConfig initConfig, IPruningConfig pruningConfig, - IBlocksConfig blockConfig, IDbProvider dbProvider, IBlockTree blockTree, IFileSystem fileSystem, @@ -45,7 +44,8 @@ public class PruningTrieStateFactory( ChainSpec chainSpec, IDisposableStack disposeStack, Lazy pathRecovery, - ILogManager logManager + ILogManager logManager, + NodeStorageCache? nodeStorageCache = null ) { private readonly ILogger _logger = logManager.GetClassLogger(); @@ -55,35 +55,29 @@ ILogManager logManager CompositePruningTrigger compositePruningTrigger = new(); IPruningTrieStore trieStore = mainPruningTrieStoreFactory.PruningTrieStore; + ITrieStore mainWorldTrieStore = trieStore; - PreBlockCaches? preBlockCaches = null; - if (blockConfig.PreWarmStateOnBlockProcessing) + + if (nodeStorageCache is not null) { - preBlockCaches = new PreBlockCaches(); - mainWorldTrieStore = new PreCachedTrieStore(trieStore, preBlockCaches.RlpCache); + mainWorldTrieStore = new PreCachedTrieStore(mainWorldTrieStore, nodeStorageCache); } IKeyValueStoreWithBatching codeDb = dbProvider.CodeDb; - IWorldState worldState = syncConfig.TrieHealing - ? new HealingWorldState( + IWorldStateScopeProvider scopeProvider = syncConfig.TrieHealing + ? new HealingWorldStateScopeProvider( mainWorldTrieStore, - mainNodeStorage, codeDb, + mainNodeStorage, pathRecovery, - logManager, - preBlockCaches, - // Main thread should only read from prewarm caches, not spend extra time updating them. - populatePreBlockCache: false) - : new WorldState( + logManager) + : new TrieStoreScopeProvider( mainWorldTrieStore, codeDb, - logManager, - preBlockCaches, - // Main thread should only read from prewarm caches, not spend extra time updating them. - populatePreBlockCache: false); + logManager); - WorldStateManager stateManager = new( - new TracedAccessWorldState(worldState), + IWorldStateManager stateManager = new WorldStateManager( + new TracedAccessWorldStateScopeProvider(scopeProvider), trieStore, dbProvider, logManager, @@ -102,13 +96,13 @@ ILogManager logManager mainNodeStorage, nodeStorageFactory, trieStore, - compositePruningTrigger, - preBlockCaches + compositePruningTrigger ); VerifyTrieStarter verifyTrieStarter = new(stateManager, processExit!, logManager); ManualPruningTrigger pruningTrigger = new(); compositePruningTrigger.Add(pruningTrigger); + disposeStack.Push(compositePruningTrigger); PruningTrieStateAdminRpcModule adminRpcModule = new( pruningTrigger, blockTree, @@ -124,8 +118,7 @@ private void InitializeFullPruning(IDb stateDb, INodeStorage mainNodeStorage, INodeStorageFactory nodeStorageFactory, IPruningTrieStore trieStore, - CompositePruningTrigger compositePruningTrigger, - PreBlockCaches? preBlockCaches) + CompositePruningTrigger compositePruningTrigger) { IPruningTrigger? CreateAutomaticTrigger(string dbPath) { @@ -263,7 +256,7 @@ private void AdviseConfig(IPruningConfig pruningConfig, IDbConfig dbConfig, IHar } } - // On a 7950x (32 logical coree), assuming write buffer is large enough, the pruning time is about 3 second + // On a 7950x (32 logical cores), assuming write buffer is large enough, the pruning time is about 3 second // with 8GB of pruning cache. Lets assume that this is a safe estimate as the ssd can be a limitation also. long maximumDirtyCacheMb = Environment.ProcessorCount * 250; // It must be at least 1GB as on mainnet at least 500MB will remain to support snap sync. So pruning cache only drop to about 500MB after pruning. @@ -271,7 +264,7 @@ private void AdviseConfig(IPruningConfig pruningConfig, IDbConfig dbConfig, IHar if (pruningConfig.DirtyCacheMb > maximumDirtyCacheMb) { // The user can also change `--Db.StateDbWriteBufferSize`. - // Which may or may not be better as each read will need to go through eacch write buffer. + // Which may or may not be better as each read will need to go through each write buffer. // So having less of them is probably better.. if (_logger.IsWarn) _logger.Warn($"Detected {pruningConfig.DirtyCacheMb}MB of dirty pruning cache config. Dirty cache more than {maximumDirtyCacheMb}MB is not recommended with {Environment.ProcessorCount} logical core as it may cause long memory pruning time which affect attestation."); } diff --git a/src/Nethermind/Nethermind.Init/Steps/EthereumStepsManager.cs b/src/Nethermind/Nethermind.Init/Steps/EthereumStepsManager.cs index a96786ba094..2a12b622109 100644 --- a/src/Nethermind/Nethermind.Init/Steps/EthereumStepsManager.cs +++ b/src/Nethermind/Nethermind.Init/Steps/EthereumStepsManager.cs @@ -254,10 +254,7 @@ private string BuildStepDependencyTree(Dictionary stepInfoMap List deps = deduplicatedDependency[node]; sb.Append(dependentsMap[node].Count == 0 ? "● " : "○ "); sb.Append(node); - if (deps.Count != 0) - { - sb.AppendLine($" (depends on {string.Join(", ", deps)})"); - } + sb.AppendLine(deps.Count != 0 ? $" (depends on {string.Join(", ", deps)})" : ""); } return sb.ToString(); diff --git a/src/Nethermind/Nethermind.Init/Steps/EvmWarmer.cs b/src/Nethermind/Nethermind.Init/Steps/EvmWarmer.cs index 41518e7914b..1256f090c0e 100644 --- a/src/Nethermind/Nethermind.Init/Steps/EvmWarmer.cs +++ b/src/Nethermind/Nethermind.Init/Steps/EvmWarmer.cs @@ -26,7 +26,7 @@ public Task Execute(CancellationToken cancellationToken) builder.AddModule(env); }); - VirtualMachine.WarmUpEvmInstructions(childContainerScope.Resolve(), childContainerScope.Resolve()); + EthereumVirtualMachine.WarmUpEvmInstructions(childContainerScope.Resolve(), childContainerScope.Resolve()); return Task.CompletedTask; } diff --git a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs index 0b97300a5e1..d2219630519 100644 --- a/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs +++ b/src/Nethermind/Nethermind.Init/Steps/InitializeBlockchain.cs @@ -8,7 +8,6 @@ using Nethermind.Api; using Nethermind.Api.Steps; using Nethermind.Blockchain; -using Nethermind.Blockchain.Spec; using Nethermind.Config; using Nethermind.Consensus; using Nethermind.Consensus.Comparers; @@ -18,6 +17,7 @@ using Nethermind.Consensus.Scheduler; using Nethermind.Core; using Nethermind.Core.Attributes; +using Nethermind.Logging; using Nethermind.State; using Nethermind.TxPool; using Nethermind.Wallet; @@ -33,6 +33,7 @@ namespace Nethermind.Init.Steps public class InitializeBlockchain(INethermindApi api, IChainHeadInfoProvider chainHeadInfoProvider) : IStep { private readonly INethermindApi _api = api; + private ILogManager _logManager = api.LogManager; public async Task Execute(CancellationToken _) { @@ -53,8 +54,6 @@ protected virtual Task InitBlockchain() blocksConfig.ExtraData : "- binary data -"); - IStateReader stateReader = setApi.StateReader!; - _api.TxGossipPolicy.Policies.Add(new SpecDrivenTxGossipPolicy(chainHeadInfoProvider)); ITxPool txPool = _api.TxPool = CreateTxPool(chainHeadInfoProvider); diff --git a/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs b/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs index ed451c7272a..bb808aad08d 100644 --- a/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs +++ b/src/Nethermind/Nethermind.Init/Steps/RegisterRpcModules.cs @@ -30,7 +30,7 @@ public class RegisterRpcModules( IBlockTree blockTree, ISpecProvider specProvider, IReceiptMonitor receiptMonitor, - IFilterStore filterStore, + FilterStore filterStore, ITxPool txPool, IEthSyncingInfo ethSyncingInfo, IPeerPool peerPool, diff --git a/src/Nethermind/Nethermind.Init/Steps/StartMonitoring.cs b/src/Nethermind/Nethermind.Init/Steps/StartMonitoring.cs index 9f4026090f6..a3ac17f3ea7 100644 --- a/src/Nethermind/Nethermind.Init/Steps/StartMonitoring.cs +++ b/src/Nethermind/Nethermind.Init/Steps/StartMonitoring.cs @@ -1,38 +1,20 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using DotNetty.Buffers; using Nethermind.Api.Steps; -using Nethermind.Blockchain; -using Nethermind.Blockchain.Synchronization; -using Nethermind.Core; -using Nethermind.Core.ServiceStopper; -using Nethermind.Db; -using Nethermind.Facade.Eth; using Nethermind.Logging; using Nethermind.Monitoring; using Nethermind.Monitoring.Config; -using Nethermind.Monitoring.Metrics; -using Nethermind.Serialization.Rlp; -using Type = System.Type; namespace Nethermind.Init.Steps; -[RunnerStepDependencies(typeof(InitializeBlockchain))] +[RunnerStepDependencies()] public class StartMonitoring( - IEthSyncingInfo ethSyncingInfo, - DbTracker dbTracker, - IPruningConfig pruningConfig, - ISyncConfig syncConfig, - IServiceStopper serviceStopper, + IMonitoringService monitoringService, ILogManager logManager, - IMetricsConfig metricsConfig, - ChainHeadInfoProvider chainHeadInfoProvider + IMetricsConfig metricsConfig ) : IStep { private readonly ILogger _logger = logManager.GetClassLogger(); @@ -45,32 +27,13 @@ public async Task Execute(CancellationToken cancellationToken) logManager.SetGlobalVariable("nodeName", metricsConfig.NodeName); } - MetricsController? controller = null; - if (metricsConfig.Enabled || metricsConfig.CountersEnabled) - { - PrepareProductInfoMetrics(); - controller = new(metricsConfig); - - IEnumerable metrics = TypeDiscovery.FindNethermindBasedTypes(nameof(Metrics)); - foreach (Type metric in metrics) - { - controller.RegisterMetrics(metric); - } - } - if (metricsConfig.Enabled) { - MonitoringService monitoringService = new(controller, metricsConfig, logManager); - - SetupMetrics(monitoringService); - await monitoringService.StartAsync().ContinueWith(x => { if (x.IsFaulted && _logger.IsError) _logger.Error("Error during starting a monitoring.", x.Exception); }, cancellationToken); - - serviceStopper.AddStoppable(monitoringService); } else { @@ -86,125 +49,5 @@ await monitoringService.StartAsync().ContinueWith(x => } } - private void SetupMetrics(MonitoringService monitoringService) - { - if (metricsConfig.EnableDbSizeMetrics) - { - monitoringService.AddMetricsUpdateAction(() => Task.Run(() => UpdateDbMetrics())); - } - - if (metricsConfig.EnableDetailedMetric) - { - monitoringService.AddMetricsUpdateAction(() => Task.Run(() => UpdateAllocatorMetrics())); - } - - monitoringService.AddMetricsUpdateAction(() => - { - Synchronization.Metrics.SyncTime = (long?)ethSyncingInfo?.UpdateAndGetSyncTime().TotalSeconds ?? 0; - }); - } - - private bool _isUpdatingDbMetrics = false; - private long _lastDbMetricsUpdate = 0; - private void UpdateDbMetrics() - { - if (!Interlocked.Exchange(ref _isUpdatingDbMetrics, true)) - { - try - { - if (Environment.TickCount64 - _lastDbMetricsUpdate < 60_000) - { - // Update max every minute - return; - } - if (chainHeadInfoProvider.IsProcessingBlock) - { - // Do not update db metrics while processing a block - return; - } - - foreach (KeyValuePair kv in dbTracker.GetAllDbMeta()) - { - // Note: At the moment, the metric for a columns db is combined across column. - IDbMeta.DbMetric dbMetric = kv.Value.GatherMetric(includeSharedCache: kv.Key == DbNames.State); // Only include shared cache if state db - Db.Metrics.DbSize[kv.Key] = dbMetric.Size; - Db.Metrics.DbBlockCacheSize[kv.Key] = dbMetric.CacheSize; - Db.Metrics.DbMemtableSize[kv.Key] = dbMetric.MemtableSize; - Db.Metrics.DbIndexFilterSize[kv.Key] = dbMetric.IndexSize; - Db.Metrics.DbReads[kv.Key] = dbMetric.TotalReads; - Db.Metrics.DbWrites[kv.Key] = dbMetric.TotalWrites; - } - _lastDbMetricsUpdate = Environment.TickCount64; - } - catch (Exception e) - { - if (_logger.IsError) _logger.Error("Error during updating db metrics", e); - } - finally - { - Volatile.Write(ref _isUpdatingDbMetrics, false); - } - } - } - - private bool _isUpdatingAllocatorMetrics = false; - private void UpdateAllocatorMetrics() - { - if (!Interlocked.Exchange(ref _isUpdatingAllocatorMetrics, true)) - { - try - { - SetAllocatorMetrics(NethermindBuffers.RlpxAllocator, "rlpx"); - SetAllocatorMetrics(NethermindBuffers.DiscoveryAllocator, "discovery"); - SetAllocatorMetrics(NethermindBuffers.Default, "default"); - SetAllocatorMetrics(PooledByteBufferAllocator.Default, "netty_default"); - } - catch (Exception e) - { - if (_logger.IsError) _logger.Error("Error during allocator metrics", e); - } - finally - { - Volatile.Write(ref _isUpdatingAllocatorMetrics, false); - } - } - } - - private static void SetAllocatorMetrics(IByteBufferAllocator allocator, string name) - { - if (allocator is PooledByteBufferAllocator byteBufferAllocator) - { - PooledByteBufferAllocatorMetric metric = byteBufferAllocator.Metric; - Serialization.Rlp.Metrics.AllocatorArenaCount[name] = metric.DirectArenas().Count; - Serialization.Rlp.Metrics.AllocatorChunkSize[name] = metric.ChunkSize; - Serialization.Rlp.Metrics.AllocatorUsedHeapMemory[name] = metric.UsedHeapMemory; - Serialization.Rlp.Metrics.AllocatorUsedDirectMemory[name] = metric.UsedDirectMemory; - Serialization.Rlp.Metrics.AllocatorActiveAllocations[name] = metric.HeapArenas().Sum((it) => it.NumActiveAllocations); - Serialization.Rlp.Metrics.AllocatorActiveAllocationBytes[name] = metric.HeapArenas().Sum((it) => it.NumActiveBytes); - Serialization.Rlp.Metrics.AllocatorAllocations[name] = metric.HeapArenas().Sum((it) => it.NumAllocations); - } - } - - private void PrepareProductInfoMetrics() - { - ProductInfo.Instance = metricsConfig.NodeName; - - if (syncConfig.SnapSync) - { - ProductInfo.SyncType = "Snap"; - } - else if (syncConfig.FastSync) - { - ProductInfo.SyncType = "Fast"; - } - else - { - ProductInfo.SyncType = "Full"; - } - - ProductInfo.PruningMode = pruningConfig.Mode.ToString(); - Metrics.Version = VersionToMetrics.ConvertToNumber(ProductInfo.Version); - } - public bool MustInitialize => false; } diff --git a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs index 31e5bf34e9a..9cbb7b4e5f7 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Benchmark/EthModuleBenchmarks.cs @@ -43,7 +43,7 @@ public void GlobalSetup() .AddSingleton(MainnetSpecProvider.Instance) .Build(); - IWorldState stateProvider = _container.Resolve().GlobalWorldState; + IWorldState stateProvider = _container.Resolve().WorldState; stateProvider.CreateAccount(Address.Zero, 1000.Ether()); IReleaseSpec spec = MainnetSpecProvider.Instance.GenesisSpec; stateProvider.Commit(spec); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.ReceiptsJsonRpcDataSource.cs b/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.ReceiptsJsonRpcDataSource.cs index 107c7b4f2f6..46318f1ac3c 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.ReceiptsJsonRpcDataSource.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/ConsensusHelperTests.ReceiptsJsonRpcDataSource.cs @@ -22,28 +22,28 @@ public ReceiptsJsonRpcDataSource(Uri uri, IJsonSerializer serializer) : base(uri public Hash256 Parameter { get; set; } = null!; - public override async Task GetJsonData() => GetJson(await GetJsonDatas()); + public override async Task GetJsonData() => GetJson(await GetJsonPayloads()); - private string GetJson(IEnumerable jsons) => $"[{string.Join(',', jsons)}]"; + private string GetJson(IEnumerable jsonItems) => $"[{string.Join(',', jsonItems)}]"; - private async Task> GetJsonDatas() + private async Task> GetJsonPayloads() { JsonRpcRequest request = CreateRequest("eth_getBlockByHash", Parameter.ToString(), false); string blockJson = await SendRequest(request); BlockForRpcTxHashes block = _serializer.Deserialize>(blockJson).Result; - List transactionsJsons = new(block.Transactions!.Length); + List transactionJsonPayloads = new(block.Transactions!.Length); foreach (string tx in block.Transactions) { - transactionsJsons.Add(await SendRequest(CreateRequest("eth_getTransactionReceipt", tx))); + transactionJsonPayloads.Add(await SendRequest(CreateRequest("eth_getTransactionReceipt", tx))); } - return transactionsJsons; + return transactionJsonPayloads; } public override async Task<(IEnumerable, string)> GetData() { - IEnumerable receiptJsons = (await GetJsonDatas()).ToArray(); - return (receiptJsons.Select(j => _serializer.Deserialize>(j).Result), GetJson(receiptJsons)); + IEnumerable receiptJsonPayloads = (await GetJsonPayloads()).ToArray(); + return (receiptJsonPayloads.Select(j => _serializer.Deserialize>(j).Result), GetJson(receiptJsonPayloads)); } private class BlockForRpcTxHashes : BlockForRpc diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Data/Base64ConverterTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Data/Base64ConverterTests.cs index 5c166c2c636..e72256dada9 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Data/Base64ConverterTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Data/Base64ConverterTests.cs @@ -1,5 +1,6 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +/* cSpell:disable */ using Nethermind.Serialization.Json; using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Data/IdConverterTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Data/IdConverterTests.cs index f447c6cd77b..6f5a8238378 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Data/IdConverterTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Data/IdConverterTests.cs @@ -69,7 +69,7 @@ public void Can_do_roundtrip_long() [Test] public void Can_do_roundtrip_string() { - TestRoundtrip("{\"id\":\"asdasdasd\"}"); + TestRoundtrip("{\"id\":\"test\"}"); } [Test] diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Data/TransactionForRpcDeserializationTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Data/TransactionForRpcDeserializationTests.cs index 6da4e0606c2..b79672c487a 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Data/TransactionForRpcDeserializationTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Data/TransactionForRpcDeserializationTests.cs @@ -31,7 +31,7 @@ public static IEnumerable TxJsonTestCases { get { - static TestCaseData Make(TxType expectedTxType, string json) => new(json) { TestName = $"Deserilizes into {expectedTxType} from {json}", ExpectedResult = expectedTxType }; + static TestCaseData Make(TxType expectedTxType, string json) => new(json) { TestName = $"Deserializes into {expectedTxType} from {json}", ExpectedResult = expectedTxType }; yield return Make(TxType.Legacy, """{"nonce":"0x0","to":null,"value":"0x0","gasPrice":"0x0","gas":"0x0","input":null}"""); yield return Make(TxType.Legacy, """{"nonce":"0x0","to":null,"gasPrice":"0x0","gas":"0x0","input":null}"""); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcUrlCollectionTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcUrlCollectionTests.cs index 7ace8f6392f..714a083672e 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcUrlCollectionTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/JsonRpcUrlCollectionTests.cs @@ -132,7 +132,7 @@ public void Clears_flag_on_additional_ws_urls_when_ws_disabled() } [Test] - public void Skips_additional_urls_with_port_conficts() + public void Skips_additional_urls_with_port_conflicts() { JsonRpcConfig jsonRpcConfig = new JsonRpcConfig() { diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs index cb64343d7e7..7ae6ebfb56b 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/AdminModuleTests.cs @@ -13,6 +13,7 @@ using Nethermind.Config; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using Nethermind.Core.Test.Builders; using Nethermind.JsonRpc.Modules; using Nethermind.JsonRpc.Modules.Admin; @@ -743,4 +744,30 @@ public void Admin_peers_supports_legacy_eth_versions() peerInfo.Protocols.Should().NotContainKey("snap"); // Old versions don't support snap } + [Test] + public void PeerInfo_WithHashedPublicKeyJson_DeserializesSuccessfully() + { + const string fullKeyHex = "a49ac7010c2e0a444dfeeabadbafa4856ba4a2d732acb86d20c577b3b365f52e5a8728693008d97ae83d51194f273455acf1a30e6f3926aefaede484c07d8ec3"; + byte[] fullPublicKeyBytes = Bytes.FromHexString(fullKeyHex); + byte[] expectedHashBytes = Keccak.Compute(fullPublicKeyBytes).Bytes.ToArray(); + string expectedHashHex = Convert.ToHexString(expectedHashBytes).ToLower(); + + string json = $$""" + { + "id": "0x{{expectedHashHex}}", + "name": "test-peer", + "enode": "enode://{{fullKeyHex}}@127.0.0.1:30303", + "caps": [], + "network": { "localAddress": "127.0.0.1", "remoteAddress": "127.0.0.1:30303" }, + "protocols": { "eth": { "version": 0 } } + } + """; + + EthereumJsonSerializer serializer = new(); + PeerInfo peerInfo = serializer.Deserialize(json); + + peerInfo.Id.Should().NotBeNull(); + peerInfo.Id.Bytes.Length.Should().Be(64); + peerInfo.Id.Bytes.AsSpan(32, 32).ToArray().Should().BeEquivalentTo(expectedHashBytes); + } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.cs index a0471307c08..b4d3b60fd4c 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugRpcModuleTests.cs @@ -60,7 +60,7 @@ public async Task Debug_traceCall_fails_when_not_enough_balance() ); response.Should().BeOfType() - .Which.Error?.Message?.Should().Contain("insufficient funds"); + .Which.Error?.Message?.Should().Contain("insufficient sender balance"); } [Test] @@ -101,7 +101,7 @@ public async Task Debug_traceCall_runs_on_top_of_specified_block() "00000000000000000000000000000000000000000000003635c9adc5de9f09e5" )] [TestCase( - "Executes precompile using overriden address", + "Executes precompile using overridden address", """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0xB6E16D27AC5AB427A7F68900AC5559CE272DC6C37C82B3E052246C82244C50E4000000000000000000000000000000000000000000000000000000000000001C7B8B1991EB44757BC688016D27940DF8FB971D7C87F77A6BC4E938E3202C44037E9267B0AEAA82FA765361918F2D8ABD9CDD86E64AA6F2B81D3C4E0B69A7B055"}""", """{"0x0000000000000000000000000000000000000001":{"movePrecompileToAddress":"0xc200000000000000000000000000000000000000", "code": "0x"}}""", "000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099" @@ -126,12 +126,12 @@ public async Task Debug_traceCall_with_state_override(string name, string transa } [TestCase( - "When balance is overriden", + "When balance is overridden", """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xbe5c953dd0ddb0ce033a98f36c981f1b74d3b33f","value":"0x100"}""", """{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":"0x100"}}""" )] [TestCase( - "When address code is overriden", + "When address code is overridden", """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0xf8b2cb4f000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099"}""", """{"0xc200000000000000000000000000000000000000":{"code":"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f8b2cb4f14610030575b600080fd5b61004a600480360381019061004591906100e4565b610060565b604051610057919061012a565b60405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100b182610086565b9050919050565b6100c1816100a6565b81146100cc57600080fd5b50565b6000813590506100de816100b8565b92915050565b6000602082840312156100fa576100f9610081565b5b6000610108848285016100cf565b91505092915050565b6000819050919050565b61012481610111565b82525050565b600060208201905061013f600083018461011b565b9291505056fea2646970667358221220172c443a163d8a43e018c339d1b749c312c94b6de22835953d960985daf228c764736f6c63430008120033"}}""" )] diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugSimulateTestsBlocksAndTransactions.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugSimulateTestsBlocksAndTransactions.cs index 20fb25101bd..b468009e9f1 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugSimulateTestsBlocksAndTransactions.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/DebugSimulateTestsBlocksAndTransactions.cs @@ -28,7 +28,7 @@ public async Task Test_debug_simulate_serialization() { TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain(); - SimulatePayload payload = EthSimulateTestsBlocksAndTransactions.CreateSerialisationPayload(chain); + SimulatePayload payload = EthSimulateTestsBlocksAndTransactions.CreateSerializationPayload(chain); //Force persistence of head block in main chain chain.BlockTree.UpdateMainChain(new List { chain.BlockFinder.Head! }, true, true); @@ -67,7 +67,7 @@ public async Task Test_debug_simulate_eth_moved() chain.Bridge.GetReceipt(txMainnetAtoB.Hash!); - //Force persistancy of head block in main chain + //Force persistence of head block in main chain chain.BlockTree.UpdateMainChain(new List { chain.BlockFinder.Head! }, true, true); chain.BlockTree.UpdateHeadBlock(chain.BlockFinder.Head!.Hash!); @@ -105,7 +105,7 @@ public async Task Test_debug_simulate_transactions_forced_fail() chain.Bridge.GetReceipt(txMainnetAtoB.Hash!); - //Force persistancy of head block in main chain + //Force persistence of head block in main chain chain.BlockTree.UpdateMainChain(new List { chain.BlockFinder.Head! }, true, true); chain.BlockTree.UpdateHeadBlock(chain.BlockFinder.Head!.Hash!); @@ -124,7 +124,7 @@ public async Task TestTransferLogsAddress() TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain(); Console.WriteLine("current test: simulateTransferOverBlockStateCalls"); var result = chain.DebugRpcModule.debug_simulateV1(payload!, BlockParameter.Latest); - Assert.That(result.Data.First().Traces.First().TxHash, Is.EqualTo(new Core.Crypto.Hash256("0xea104c39737cea12ae19c0985b3bc799096f3dbd2574594c2ecb4d0731e042cc"))); + Assert.That(result.Data.First().Traces.First().TxHash, Is.EqualTo(new Core.Crypto.Hash256("0xe690a6e09e13d163bc8b92c725202e9633770b14c8541a0ee48794ae014351f0"))); } [Test] @@ -136,7 +136,7 @@ public async Task TestSerializationDebugSimulate() Assert.That(response, Is.TypeOf()); JsonRpcSuccessResponse successResponse = (JsonRpcSuccessResponse)response; IReadOnlyList> data = (IReadOnlyList>)successResponse.Result!; - Assert.That(data.First().Traces.First().TxHash, Is.EqualTo(new Core.Crypto.Hash256("0xea104c39737cea12ae19c0985b3bc799096f3dbd2574594c2ecb4d0731e042cc"))); + Assert.That(data.First().Traces.First().TxHash, Is.EqualTo(new Core.Crypto.Hash256("0xe690a6e09e13d163bc8b92c725202e9633770b14c8541a0ee48794ae014351f0"))); } [Test] diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EstimateGas.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EstimateGas.cs index 1347539d7e7..e44e58ce24b 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EstimateGas.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EstimateGas.cs @@ -49,7 +49,7 @@ public async Task Eth_estimateGas_web3_sample_not_enough_gas_system_account() using Context ctx = await Context.Create(); ctx.Test.ReadOnlyState.AccountExists(Address.SystemUser).Should().BeFalse(); TransactionForRpc transaction = ctx.Test.JsonSerializer.Deserialize( - "{\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); + "{\"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); string serialized = await ctx.Test.TestEthRpc("eth_estimateGas", transaction); Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":\"0x53b8\",\"id\":67}")); @@ -63,7 +63,7 @@ public async Task Eth_estimateGas_web3_sample_not_enough_gas_other_account() Address someAccount = new("0x0001020304050607080910111213141516171819"); ctx.Test.ReadOnlyState.AccountExists(someAccount).Should().BeFalse(); TransactionForRpc transaction = ctx.Test.JsonSerializer.Deserialize( - "{\"from\":\"0x0001020304050607080910111213141516171819\",\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); + "{\"from\":\"0x0001020304050607080910111213141516171819\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); string serialized = await ctx.Test.TestEthRpc("eth_estimateGas", transaction); Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":\"0x53b8\",\"id\":67}")); @@ -77,7 +77,7 @@ public async Task Eth_estimateGas_web3_above_block_gas_limit() Address someAccount = new("0x0001020304050607080910111213141516171819"); ctx.Test.ReadOnlyState.AccountExists(someAccount).Should().BeFalse(); TransactionForRpc transaction = ctx.Test.JsonSerializer.Deserialize( - "{\"from\":\"0x0001020304050607080910111213141516171819\",\"gas\":\"0x100000\",\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); + "{\"from\":\"0x0001020304050607080910111213141516171819\",\"gas\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); string serialized = await ctx.Test.TestEthRpc("eth_estimateGas", transaction); Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":\"0x53b8\",\"id\":67}")); @@ -174,7 +174,7 @@ public async Task Estimate_gas_with_gas_pricing() { using Context ctx = await Context.Create(); TransactionForRpc transaction = ctx.Test.JsonSerializer.Deserialize( - "{\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"to\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"gasPrice\": \"0x10\"}"); + $"{{\"from\": \"{TestItem.AddressA}\", \"to\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"gasPrice\": \"0x10\"}}"); string serialized = await ctx.Test.TestEthRpc("eth_estimateGas", transaction); Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":\"0x5208\",\"id\":67}")); } @@ -184,7 +184,7 @@ public async Task Estimate_gas_without_gas_pricing_after_1559_legacy() { using Context ctx = await Context.CreateWithLondonEnabled(); TransactionForRpc transaction = ctx.Test.JsonSerializer.Deserialize( - "{\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"to\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"gasPrice\": \"0x10\"}"); + $"{{\"from\": \"{TestItem.AddressA}\", \"to\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"gasPrice\": \"0x100000000\"}}"); string serialized = await ctx.Test.TestEthRpc("eth_estimateGas", transaction); Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":\"0x5208\",\"id\":67}")); } @@ -290,7 +290,8 @@ public async Task should_estimate_transaction_with_deployed_code_when_eip3607_en Transaction tx = Build.A.Transaction.SignedAndResolved(TestItem.PrivateKeyA).TestObject; LegacyTransactionForRpc transaction = new LegacyTransactionForRpc(tx, 1, Keccak.Zero, 1L) { - To = TestItem.AddressB + To = TestItem.AddressB, + GasPrice = 0 }; string serialized = @@ -317,7 +318,7 @@ public async Task should_estimate_transaction_with_deployed_code_when_eip3607_en """{"jsonrpc":"2.0","result":"0xabdd","id":67}""" // Store uint256 (cold access) + few other light instructions + intrinsic transaction cost )] [TestCase( - "Executes precompile using overriden address", + "Executes precompile using overridden address", """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0xB6E16D27AC5AB427A7F68900AC5559CE272DC6C37C82B3E052246C82244C50E4000000000000000000000000000000000000000000000000000000000000001C7B8B1991EB44757BC688016D27940DF8FB971D7C87F77A6BC4E938E3202C44037E9267B0AEAA82FA765361918F2D8ABD9CDD86E64AA6F2B81D3C4E0B69A7B055"}""", """{"0x0000000000000000000000000000000000000001":{"movePrecompileToAddress":"0xc200000000000000000000000000000000000000", "code": "0x"}}""", """{"jsonrpc":"2.0","result":"0x6440","id":67}""" // EcRecover call + intrinsic transaction cost @@ -336,12 +337,12 @@ public async Task Estimate_gas_with_state_override(string name, string transacti } [TestCase( - "When balance and nonce is overriden", + "When balance and nonce is overridden", """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","value":"0x123"}""", """{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":"0x123", "nonce": "0x123"}}""" )] [TestCase( - "When address code is overriden", + "When address code is overridden", """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0x60fe47b1112233445566778899001122334455667788990011223344556677889900112233445566778899001122"}""", """{"0xc200000000000000000000000000000000000000":{"code":"0x6080604052348015600e575f80fd5b50600436106030575f3560e01c80632a1afcd914603457806360fe47b114604d575b5f80fd5b603b5f5481565b60405190815260200160405180910390f35b605c6058366004605e565b5f55565b005b5f60208284031215606d575f80fd5b503591905056fea2646970667358221220fd4e5f3894be8e57fc7460afebb5c90d96c3486d79bf47b00c2ed666ab2f82b364736f6c634300081a0033"}}""" )] diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EthCall.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EthCall.cs index c564a24787b..0934acc45df 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EthCall.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.EthCall.cs @@ -54,7 +54,7 @@ public async Task Eth_call_web3_sample_not_enough_gas_system_account() using Context ctx = await Context.Create(); ctx.Test.ReadOnlyState.AccountExists(Address.SystemUser).Should().BeFalse(); TransactionForRpc transaction = ctx.Test.JsonSerializer.Deserialize( - "{\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); + "{\"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); string serialized = await ctx.Test.TestEthRpc("eth_call", transaction, "0x0"); Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":\"0x\",\"id\":67}")); @@ -82,7 +82,7 @@ public async Task Eth_call_web3_sample_not_enough_gas_other_account() Address someAccount = new("0x0001020304050607080910111213141516171819"); ctx.Test.ReadOnlyState.AccountExists(someAccount).Should().BeFalse(); TransactionForRpc transaction = ctx.Test.JsonSerializer.Deserialize( - "{\"from\":\"0x0001020304050607080910111213141516171819\",\"gasPrice\":\"0x100000\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); + "{\"from\":\"0x0001020304050607080910111213141516171819\", \"data\": \"0x70a082310000000000000000000000006c1f09f6271fbe133db38db9c9280307f5d22160\", \"to\": \"0x0d8775f648430679a709e98d2b0cb6250d2887ef\"}"); string serialized = await ctx.Test.TestEthRpc("eth_call", transaction, "0x0"); Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":\"0x\",\"id\":67}")); @@ -136,7 +136,8 @@ public async Task should_not_reject_transactions_with_deployed_code_when_eip3607 Transaction tx = Build.A.Transaction.SignedAndResolved(TestItem.PrivateKeyA).TestObject; LegacyTransactionForRpc transaction = new(tx, 1, Keccak.Zero, 1L) { - To = TestItem.AddressB + To = TestItem.AddressB, + GasPrice = 0 }; string serialized = @@ -248,7 +249,7 @@ public async Task Eth_call_with_gas_pricing() { using Context ctx = await Context.Create(); TransactionForRpc transaction = ctx.Test.JsonSerializer.Deserialize( - "{\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"to\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"gasPrice\": \"0x10\"}"); + $"{{\"from\": \"{TestItem.AddressA}\", \"to\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"gasPrice\": \"0x10\"}}"); string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":\"0x\",\"id\":67}")); } @@ -258,7 +259,7 @@ public async Task Eth_call_without_gas_pricing_after_1559_legacy() { using Context ctx = await Context.CreateWithLondonEnabled(); TransactionForRpc transaction = ctx.Test.JsonSerializer.Deserialize( - "{\"from\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"to\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"gasPrice\": \"0x10\"}"); + $"{{\"from\": \"{TestItem.AddressA}\", \"to\": \"0x32e4e4c7c5d1cea5db5f9202a9e4d99e56c91a24\", \"gasPrice\": \"0x100000000\"}}"); string serialized = await ctx.Test.TestEthRpc("eth_call", transaction); Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":\"0x\",\"id\":67}")); } @@ -394,7 +395,7 @@ public async Task Eth_call_with_revert() """{"jsonrpc":"2.0","result":"0x00000000000000000000000000000000000000000000003635c9adc5de9f09e5","id":67}""" )] [TestCase( - "Executes precompile using overriden address", + "Executes precompile using overridden address", """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0xB6E16D27AC5AB427A7F68900AC5559CE272DC6C37C82B3E052246C82244C50E4000000000000000000000000000000000000000000000000000000000000001C7B8B1991EB44757BC688016D27940DF8FB971D7C87F77A6BC4E938E3202C44037E9267B0AEAA82FA765361918F2D8ABD9CDD86E64AA6F2B81D3C4E0B69A7B055"}""", """{"0x0000000000000000000000000000000000000001":{"movePrecompileToAddress":"0xc200000000000000000000000000000000000000", "code": "0x"}}""", """{"jsonrpc":"2.0","result":"0x000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099","id":67}""" @@ -412,12 +413,12 @@ public async Task Eth_call_with_state_override(string name, string transactionJs } [TestCase( - "When balance and nonce is overriden", + "When balance and nonce is overridden", """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xbe5c953dd0ddb0ce033a98f36c981f1b74d3b33f","value":"0x1"}""", """{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":"0x123", "nonce": "0x123"}}""" )] [TestCase( - "When address code is overriden", + "When address code is overridden", """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0xc200000000000000000000000000000000000000","input":"0xf8b2cb4f000000000000000000000000b7705ae4c6f81b66cdb323c65f4e8133690fc099"}""", """{"0xc200000000000000000000000000000000000000":{"code":"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f8b2cb4f14610030575b600080fd5b61004a600480360381019061004591906100e4565b610060565b604051610057919061012a565b60405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100b182610086565b9050919050565b6100c1816100a6565b81146100cc57600080fd5b50565b6000813590506100de816100b8565b92915050565b6000602082840312156100fa576100f9610081565b5b6000610108848285016100cf565b91505092915050565b6000819050919050565b61012481610111565b82525050565b600060208201905061013f600083018461011b565b9291505056fea2646970667358221220172c443a163d8a43e018c339d1b749c312c94b6de22835953d960985daf228c764736f6c63430008120033"}}""" )] diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.FeeHistory.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.FeeHistory.cs index 0109a2beec6..aa61625cdf4 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.FeeHistory.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.FeeHistory.cs @@ -108,7 +108,7 @@ public static IEnumerable FeeHistoryBlobTestCases yield return new TestCaseData(new ulong?[] { 49152, 1 }, new ulong?[] { 0, 49152 }) { - TestName = "Blocks with arbitary values", + TestName = "Blocks with arbitrary values", ExpectedResult = (new UInt256?[] { 1, 1, 1 }, new double?[] { 0.0, 0.0625 }) }; } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs index 0e6b8f73d9c..023b6188d30 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/EthRpcModuleTests.cs @@ -33,6 +33,7 @@ using Nethermind.Facade.Filters; using Nethermind.Int256; using Nethermind.JsonRpc.Client; +using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Serialization.Json; using Nethermind.Serialization.Rlp; using Nethermind.Specs; @@ -214,7 +215,7 @@ public async Task Eth_get_uncle_by_block_hash_and_index(bool eip1559, string exp if (eip1559) { specProvider = Substitute.For(); - ReleaseSpec releaseSpec = new() { IsEip1559Enabled = true, Eip1559TransitionBlock = 1 }; + ReleaseSpec releaseSpec = new() { IsEip1559Enabled = true, Eip1559TransitionBlock = 0 }; specProvider.GetSpec(Arg.Any()).Returns(releaseSpec); } @@ -579,9 +580,9 @@ void HandleNewBlock(object? sender, BlockReplacementEventArgs e) await test.AddBlock(createCodeTx); - string getLogsSerialized = await test.TestEthRpc("eth_getLogs", $"{{\"fromBlock\":\"{blockHash}\"}}"); + string getLogsSerialized = await test.TestEthRpc("eth_getLogs", $"{{\"blockHash\":\"{blockHash}\"}}"); - using JsonRpcResponse? newFilterResp = await RpcTest.TestRequest(test.EthRpcModule, "eth_newFilter", new { fromBlock = blockHash }); + using JsonRpcResponse? newFilterResp = await RpcTest.TestRequest(test.EthRpcModule, "eth_newFilter", new { blockHash = blockHash }); Assert.That(newFilterResp is not null && newFilterResp is JsonRpcSuccessResponse, Is.True); @@ -591,13 +592,13 @@ void HandleNewBlock(object? sender, BlockReplacementEventArgs e) } [TestCase("{}", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] - [TestCase("""{"fromBlock":"0x2","toBlock":"latest","address":"0x00000000000000000001","topics":["0x00000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] - [TestCase("""{"fromBlock":"earliest","toBlock":"pending","address":["0x00000000000000000001", "0x00000000000000000001"],"topics":["0x00000000000000000000000000000001", "0x00000000000000000000000000000002"]}""", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] - [TestCase("""{"topics":[null, ["0x00000000000000000000000000000001", "0x00000000000000000000000000000002"]]}""", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] - [TestCase("""{"fromBlock":"0x10","toBlock":"latest","address":"0x00000000000000000001","topics":["0x00000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","error":{"code":-32602,"message":"invalid block range params"},"id":67}""")] - [TestCase("""{"fromBlock":"0x2","toBlock":"0x11","address":"0x00000000000000000001","topics":["0x00000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] - [TestCase("""{"fromBlock":"0x2","toBlock":"0x1","address":"0x00000000000000000001","topics":["0x00000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","error":{"code":-32602,"message":"invalid block range params"},"id":67}""")] - [TestCase("""{"fromBlock":"0x11","toBlock":"0x12","address":"0x00000000000000000001","topics":["0x00000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","result":[],"id":67}""")] + [TestCase("""{"fromBlock":"0x2","toBlock":"latest","address":"0x0000000000000000000000000000000000000001","topics":["0x0000000000000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] + [TestCase("""{"fromBlock":"earliest","toBlock":"pending","address":["0x0000000000000000000000000000000000000001", "0x0000000000000000000000000000000000000001"],"topics":["0x0000000000000000000000000000000000000001", "0x00000000000000000000000000000002"]}""", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] + [TestCase("""{"topics":[null, ["0x0000000000000000000000000000000000000001", "0x00000000000000000000000000000002"]]}""", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] + [TestCase("""{"fromBlock":"0x10","toBlock":"latest","address":"0x0000000000000000000000000000000000000001","topics":["0x0000000000000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","error":{"code":-32602,"message":"requested block range is in the future"},"id":67}""")] + [TestCase("""{"fromBlock":"0x2","toBlock":"0x3","address":"0x0000000000000000000000000000000000000001","topics":["0x0000000000000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","result":[{"address":"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099","blockHash":"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760","blockNumber":"0x1","blockTimestamp":"0x1","data":"0x010203","logIndex":"0x1","removed":false,"topics":["0x017e667f4b8c174291d1543c466717566e206df1bfd6f30271055ddafdb18f72","0x6c3fd336b49dcb1c57dd4fbeaf5f898320b0da06a5ef64e798c6497600bb79f2"],"transactionHash":"0x1f675bff07515f5df96737194ea945c36c41e7b4fcef307b7cd4d0e602a69111","transactionIndex":"0x1"}],"id":67}""")] + [TestCase("""{"fromBlock":"0x2","toBlock":"0x1","address":"0x0000000000000000000000000000000000000001","topics":["0x0000000000000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","error":{"code":-32602,"message":"invalid block range params"},"id":67}""")] + [TestCase("""{"fromBlock":"0x11","toBlock":"0x12","address":"0x0000000000000000000000000000000000000001","topics":["0x0000000000000000000000000000000000000001"]}""", """{"jsonrpc":"2.0","error":{"code":-32602,"message":"requested block range is in the future"},"id":67}""")] public async Task Eth_get_logs(string parameter, string expected) { using Context ctx = await Context.Create(); @@ -773,7 +774,6 @@ public async Task Eth_get_block_by_number_null() [Test] public async Task Eth_protocol_version() { - // TODO: test case when eth/69 is added dynamically using Context ctx = await Context.Create(); string serialized = await ctx.Test.TestEthRpc("eth_protocolVersion"); Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":\"0x44\",\"id\":67}")); @@ -820,13 +820,21 @@ public async Task Eth_get_proof() } [Test] - public async Task Eth_get_proof_withTrimmedStorageKey() + public async Task Eth_get_proof_withTrimmedAndDuplicatedStorageKey() { using Context ctx = await Context.Create(); string serialized = await ctx.Test.TestEthRpc("eth_getProof", TestBlockchain.AccountA.ToString(), "[\"0x1\"]", "0x2"); Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":{\"accountProof\":[\"0xf8718080808080a0fc8311b2cabe1a1b33ea04f1865132a44aa0c17c567acd233422f9cfb516877480808080a0be8ea164b2fb1567e2505295dae6d8a9fe5f09e9c5ac854a7da23b2bc5f8523ca053692ab7cdc9bb02a28b1f45afe7be86cb27041ea98586e6ff05d98c9b0667138080808080\",\"0xf8518080808080a00dd1727b2abb59c0a6ac75c01176a9d1a276b0049d5fe32da3e1551096549e258080808080808080a038ca33d3070331da1ccf804819da57fcfc83358cadbef1d8bde89e1a346de5098080\",\"0xf872a020227dead52ea912e013e7641ccd6b3b174498e55066b0c174a09c8c3cc4bf5eb84ff84d01893635c9adc5de9fadf7a0475ae75f323761db271e75cbdae41aede237e48bc04127fb6611f0f33298f72ba0dbe576b4818846aa77e82f4ed5fa78f92766b141f282d36703886d196df39322\"],\"address\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\",\"balance\":\"0x3635c9adc5de9fadf7\",\"codeHash\":\"0xdbe576b4818846aa77e82f4ed5fa78f92766b141f282d36703886d196df39322\",\"nonce\":\"0x1\",\"storageHash\":\"0x475ae75f323761db271e75cbdae41aede237e48bc04127fb6611f0f33298f72b\",\"storageProof\":[{\"key\":\"0x1\",\"proof\":[\"0xe7a120b10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf68483abcdef\"],\"value\":\"0xabcdef\"}]},\"id\":67}"), serialized.Replace("\"", "\\\"")); } + [Test] + public async Task Eth_get_proof_withTooManyKeys() + { + using Context ctx = await Context.Create(); + string serialized = await ctx.Test.TestEthRpc("eth_getProof", TestBlockchain.AccountA.ToString(), $"[{string.Join(", ", Enumerable.Range(1, EthRpcModule.GetProofStorageKeyLimit + 1).Select(i => "\"" + i.ToHexString() + "\""))}]", "0x2"); + Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"storageKeys: 1001 is over the query limit 1000.\"},\"id\":67}")); + } + [Test] public async Task Eth_get_block_by_number_empty_param() { @@ -1665,7 +1673,7 @@ public static async Task CreateWithAncientBarriers(long blockNumber) var cutBlock = blockNumber; config.AncientBodiesBarrier = cutBlock; config.AncientReceiptsBarrier = cutBlock; - config.PivotNumber = cutBlock.ToString(); + config.PivotNumber = cutBlock; config.SnapSync = true; return config; }); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/FilterTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/FilterTests.cs index 2ab35c9ae37..af06485f1bd 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/FilterTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/FilterTests.cs @@ -6,6 +6,8 @@ using FluentAssertions; using Nethermind.Blockchain.Find; +using Nethermind.Core; +using Nethermind.Core.Crypto; using Nethermind.JsonRpc.Data; using Nethermind.JsonRpc.Modules.Eth; using Nethermind.Serialization.Json; @@ -23,7 +25,7 @@ public static IEnumerable JsonTests yield return new TestCaseData("{}", new Filter { - FromBlock = BlockParameter.Latest, + FromBlock = BlockParameter.Earliest, ToBlock = BlockParameter.Latest, }); @@ -31,8 +33,6 @@ public static IEnumerable JsonTests JsonSerializer.Serialize( new { - fromBlock = "earliest", - toBlock = "pending", topics = new object?[] { null, @@ -47,17 +47,16 @@ public static IEnumerable JsonTests new Filter { FromBlock = BlockParameter.Earliest, - ToBlock = BlockParameter.Pending, - Topics = new object?[] - { + ToBlock = BlockParameter.Latest, + Topics = + [ null, - "0xe194ef610f9150a2db4110b3db5116fd623175dca3528d7ae7046a1042f84fe7", - new[] - { - "0x000500002bd87daa34d8ff0daf3465c96044d8f6667614850000000000000001", - "0xe194ef610f9150a2db4110b3db5116fd623175dca3528d7ae7046a1042f84fe7" - } - } + [new("0xe194ef610f9150a2db4110b3db5116fd623175dca3528d7ae7046a1042f84fe7")], + [ + new Hash256("0x000500002bd87daa34d8ff0daf3465c96044d8f6667614850000000000000001"), + new Hash256("0xe194ef610f9150a2db4110b3db5116fd623175dca3528d7ae7046a1042f84fe7") + ] + ] }); yield return new TestCaseData( @@ -66,7 +65,6 @@ public static IEnumerable JsonTests { address = "0xc2d77d118326c33bbe36ebeabf4f7ed6bc2dda5c", fromBlock = "0x1143ade", - toBlock = "latest", topics = new object?[] { "0xe194ef610f9150a2db4110b3db5116fd623175dca3528d7ae7046a1042f84fe7", @@ -77,16 +75,16 @@ public static IEnumerable JsonTests }), new Filter { - Address = "0xc2d77d118326c33bbe36ebeabf4f7ed6bc2dda5c", + Address = [new Address("0xc2d77d118326c33bbe36ebeabf4f7ed6bc2dda5c")], FromBlock = new BlockParameter(0x1143ade), ToBlock = BlockParameter.Latest, - Topics = new object?[] - { - "0xe194ef610f9150a2db4110b3db5116fd623175dca3528d7ae7046a1042f84fe7", + Topics = + [ + [new("0xe194ef610f9150a2db4110b3db5116fd623175dca3528d7ae7046a1042f84fe7")], null, null, - "0x000500002bd87daa34d8ff0daf3465c96044d8f6667614850000000000000001" - } + [new("0x000500002bd87daa34d8ff0daf3465c96044d8f6667614850000000000000001")] + ] }); var blockHash = "0x892a8b3ccc78359e059e67ec44c83bfed496721d48c2d1dd929d6e4cd6559d35"; diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsBlocksAndTransactions.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsBlocksAndTransactions.cs index 4cae9fee60b..c883b835da6 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsBlocksAndTransactions.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsBlocksAndTransactions.cs @@ -25,7 +25,7 @@ namespace Nethermind.JsonRpc.Test.Modules.Eth; public class EthSimulateTestsBlocksAndTransactions { - public static SimulatePayload CreateSerialisationPayload(TestRpcBlockchain chain) + public static SimulatePayload CreateSerializationPayload(TestRpcBlockchain chain) { UInt256 nonceA = chain.ReadOnlyState.GetNonce(TestItem.AddressA); Transaction txToFail = GetTransferTxData(nonceA, chain.EthereumEcdsa, TestItem.PrivateKeyA, TestItem.AddressB, 10_000_000); @@ -160,11 +160,11 @@ public static Transaction GetTransferTxData(UInt256 nonce, IEthereumEcdsa ethere } [Test] - public async Task Test_eth_simulate_serialisation() + public async Task Test_eth_simulate_serialization() { TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain(); - SimulatePayload payload = CreateSerialisationPayload(chain); + SimulatePayload payload = CreateSerializationPayload(chain); //Force persistence of head block in main chain chain.BlockTree.UpdateMainChain(new List { chain.BlockFinder.Head! }, true, true); @@ -205,7 +205,7 @@ public async Task Test_eth_simulate_eth_moved() chain.Bridge.GetReceipt(txMainnetAtoB.Hash!); - //Force persistancy of head block in main chain + //Force persistence of head block in main chain chain.BlockTree.UpdateMainChain(new List { chain.BlockFinder.Head! }, true, true); chain.BlockTree.UpdateHeadBlock(chain.BlockFinder.Head!.Hash!); @@ -246,7 +246,7 @@ public async Task Test_eth_simulate_transactions_forced_fail() chain.Bridge.GetReceipt(txMainnetAtoB.Hash!); - //Force persistancy of head block in main chain + //Force persistence of head block in main chain chain.BlockTree.UpdateMainChain(new List { chain.BlockFinder.Head! }, true, true); chain.BlockTree.UpdateHeadBlock(chain.BlockFinder.Head!.Hash!); @@ -277,7 +277,7 @@ public static SimulatePayload CreateTransferLogsAddressPayloa }, "calls": [ { - "type": "0x3", + "type": "0x2", "from": "0xc000000000000000000000000000000000000000", "to": "0xc100000000000000000000000000000000000000", "gas": "0x5208", diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsHiveBase.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsHiveBase.cs index 0f478d0bda9..45e931f91e8 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsHiveBase.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Eth/Simulate/EthSimulateTestsHiveBase.cs @@ -11,6 +11,7 @@ using Nethermind.Core.Specs; using Nethermind.Core.Test.Blockchain; using Nethermind.Core.Test.Builders; +using Nethermind.Facade.Eth; using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.Facade.Proxy.Models.Simulate; using Nethermind.Int256; @@ -28,6 +29,7 @@ public class EthSimulateTestsHiveBase private static readonly object[] HiveTestCases = { new object[] {"baseFee", true, "{\"blockStateCalls\": [\r\n {\r\n \"blockOverrides\": {\r\n \"blobBaseFee\": \"0x0\"\r\n },\r\n \"stateOverrides\": {\r\n \"0xc000000000000000000000000000000000000000\": {\r\n \"balance\": \"0x5f5e100\"\r\n },\r\n \"0xc200000000000000000000000000000000000000\": {\r\n \"code\": \"0x\"\r\n }\r\n },\r\n \"calls\": [\r\n {\r\n \"from\": \"0xc000000000000000000000000000000000000000\",\r\n \"to\": \"0xc200000000000000000000000000000000000000\",\r\n \"maxFeePerBlobGas\": \"0xa\",\r\n \"blobVersionedHashes\": [\r\n \"0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014\"\r\n ]\r\n }\r\n ]\r\n },\r\n {\r\n \"blockOverrides\": {\r\n \"blobBaseFee\": \"0x1\"\r\n },\r\n \"calls\": [\r\n {\r\n \"from\": \"0xc000000000000000000000000000000000000000\",\r\n \"to\": \"0xc200000000000000000000000000000000000000\",\r\n \"maxFeePerBlobGas\": \"0xa\",\r\n \"blobVersionedHashes\": [\r\n \"0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014\"\r\n ]\r\n }\r\n ]\r\n }\r\n ]\r\n }"}, +new object[] {"baseFee-no-blob-base-fee-override", true, "{\"blockStateCalls\": [\r\n {\r\n \"stateOverrides\": {\r\n \"0xc000000000000000000000000000000000000000\": {\r\n \"balance\": \"0x5f5e100\"\r\n },\r\n \"0xc200000000000000000000000000000000000000\": {\r\n \"code\": \"0x\"\r\n }\r\n },\r\n \"calls\": [\r\n {\r\n \"from\": \"0xc000000000000000000000000000000000000000\",\r\n \"to\": \"0xc200000000000000000000000000000000000000\",\r\n \"maxFeePerBlobGas\": \"0xa\",\r\n \"blobVersionedHashes\": [\r\n \"0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014\"\r\n ]\r\n }\r\n ]\r\n },\r\n {\r\n \"blockOverrides\": {\r\n \"blobBaseFee\": \"0x1\"\r\n },\r\n \"calls\": [\r\n {\r\n \"from\": \"0xc000000000000000000000000000000000000000\",\r\n \"to\": \"0xc200000000000000000000000000000000000000\",\r\n \"maxFeePerBlobGas\": \"0xa\",\r\n \"blobVersionedHashes\": [\r\n \"0x010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c444014\"\r\n ]\r\n }\r\n ]\r\n }\r\n ]\r\n }"}, new object[] {"multicall-add-more-non-defined-BlockStateCalls-than-fit-but-now-with-fit", true, "{\"blockStateCalls\": [{\"blockOverrides\": {\"number\": \"0xa\"}, \"stateOverrides\": {\"0xc100000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506000366060484641444543425a3a60014361002c919061009b565b406040516020016100469a99989796959493929190610138565b6040516020818303038152906040529050915050805190602001f35b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006100a682610062565b91506100b183610062565b92508282039050818111156100c9576100c861006c565b5b92915050565b6100d881610062565b82525050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610109826100de565b9050919050565b610119816100fe565b82525050565b6000819050919050565b6101328161011f565b82525050565b60006101408201905061014e600083018d6100cf565b61015b602083018c6100cf565b610168604083018b610110565b610175606083018a6100cf565b61018260808301896100cf565b61018f60a08301886100cf565b61019c60c08301876100cf565b6101a960e08301866100cf565b6101b76101008301856100cf565b6101c5610120830184610129565b9b9a505050505050505050505056fea26469706673582212205139ae3ba8d46d11c29815d001b725f9840c90e330884ed070958d5af4813d8764736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x\"}]}, {\"blockOverrides\": {\"number\": \"0x14\"}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x\"}]}, {\"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x\"}]}]}"}, new object[] {"multicall-basefee-too-low-without-validation-38012", true, "{\"blockStateCalls\": [{\"blockOverrides\": {\"baseFeePerGas\": \"0xa\"}, \"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}}, \"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"maxFeePerGas\": \"0x0\", \"maxPriorityFeePerGas\": \"0x0\"}]}]}"}, new object[] {"multicall-block-timestamps-incrementing", true, "{\"blockStateCalls\": [{\"blockOverrides\": {\"time\": \"0x5e47e91a\"}}, {\"blockOverrides\": {\"time\": \"0x5e47e91b\"}}]}"}, @@ -43,7 +45,7 @@ public class EthSimulateTestsHiveBase new object[] {"multicall-eth-send-should-produce-more-logs-on-forward", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}, \"0xc100000000000000000000000000000000000000\": {\"code\": \"0x60806040526004361061001e5760003560e01c80634b64e49214610023575b600080fd5b61003d6004803603810190610038919061011f565b61003f565b005b60008173ffffffffffffffffffffffffffffffffffffffff166108fc349081150290604051600060405180830381858888f193505050509050806100b8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100af906101a9565b60405180910390fd5b5050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100ec826100c1565b9050919050565b6100fc816100e1565b811461010757600080fd5b50565b600081359050610119816100f3565b92915050565b600060208284031215610135576101346100bc565b5b60006101438482850161010a565b91505092915050565b600082825260208201905092915050565b7f4661696c656420746f2073656e64204574686572000000000000000000000000600082015250565b600061019360148361014c565b915061019e8261015d565b602082019050919050565b600060208201905081810360008301526101c281610186565b905091905056fea2646970667358221220563acd6f5b8ad06a3faf5c27fddd0ecbc198408b99290ce50d15c2cf7043694964736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\", \"input\": \"0x4b64e4920000000000000000000000000000000000000000000000000000000000000100\"}]}], \"traceTransfers\": true}"}, new object[] {"multicall-eth-send-should-produce-no-logs-on-forward-revert", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}, \"0xc100000000000000000000000000000000000000\": {\"code\": \"0x60806040526004361061001e5760003560e01c80634b64e49214610023575b600080fd5b61003d6004803603810190610038919061011f565b61003f565b005b60008173ffffffffffffffffffffffffffffffffffffffff166108fc349081150290604051600060405180830381858888f193505050509050806100b8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100af906101a9565b60405180910390fd5b5050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100ec826100c1565b9050919050565b6100fc816100e1565b811461010757600080fd5b50565b600081359050610119816100f3565b92915050565b600060208284031215610135576101346100bc565b5b60006101438482850161010a565b91505092915050565b600082825260208201905092915050565b7f4661696c656420746f2073656e64204574686572000000000000000000000000600082015250565b600061019360148361014c565b915061019e8261015d565b602082019050919050565b600060208201905081810360008301526101c281610186565b905091905056fea2646970667358221220563acd6f5b8ad06a3faf5c27fddd0ecbc198408b99290ce50d15c2cf7043694964736f6c63430008120033\"}, \"0xc200000000000000000000000000000000000000\": {\"code\": \"0x608060405260006042576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401603990609d565b60405180910390fd5b005b600082825260208201905092915050565b7f416c7761797320726576657274696e6720636f6e747261637400000000000000600082015250565b600060896019836044565b91506092826055565b602082019050919050565b6000602082019050818103600083015260b481607e565b905091905056fea264697066735822122005cbbbc709291f66fadc17416c1b0ed4d72941840db11468a21b8e1a0362024c64736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"value\": \"0x3e8\", \"input\": \"0x4b64e492c200000000000000000000000000000000000000000000000000000000000000\"}]}], \"traceTransfers\": true}"}, new object[] {"multicall-get-block-properties", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc100000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506000366060484641444543425a3a60014361002c919061009b565b406040516020016100469a99989796959493929190610138565b6040516020818303038152906040529050915050805190602001f35b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006100a682610062565b91506100b183610062565b92508282039050818111156100c9576100c861006c565b5b92915050565b6100d881610062565b82525050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610109826100de565b9050919050565b610119816100fe565b82525050565b6000819050919050565b6101328161011f565b82525050565b60006101408201905061014e600083018d6100cf565b61015b602083018c6100cf565b610168604083018b610110565b610175606083018a6100cf565b61018260808301896100cf565b61018f60a08301886100cf565b61019c60c08301876100cf565b6101a960e08301866100cf565b6101b76101008301856100cf565b6101c5610120830184610129565b9b9a505050505050505050505056fea26469706673582212205139ae3ba8d46d11c29815d001b725f9840c90e330884ed070958d5af4813d8764736f6c63430008120033\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"input\": \"0x\"}]}]}"}, -new object[] {"multicall-instrict-gas-38013", false, "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"gas\": \"0x0\"}]}]}"}, +new object[] {"multicall-insufficient-gas-38013", false, "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0xc100000000000000000000000000000000000000\", \"gas\": \"0x0\"}]}]}"}, new object[] {"multicall-logs", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc200000000000000000000000000000000000000\": {\"code\": \"0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff80600080a1600080f3\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc200000000000000000000000000000000000000\", \"input\": \"0x6057361d0000000000000000000000000000000000000000000000000000000000000005\"}]}]}"}, new object[] {"multicall-move-precompile-twice", true, "{\"blockStateCalls\": [{\"stateOverrides\": {\"0xc000000000000000000000000000000000000000\": {\"balance\": \"0x3e8\"}, \"0xc200000000000000000000000000000000000000\": {\"balance\": \"0x7d0\"}, \"0xc300000000000000000000000000000000000000\": {\"balance\": \"0xbb8\"}, \"0xc400000000000000000000000000000000000000\": {\"code\": \"0x608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f8b2cb4f14610030575b600080fd5b61004a600480360381019061004591906100e4565b610060565b604051610057919061012a565b60405180910390f35b60008173ffffffffffffffffffffffffffffffffffffffff16319050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006100b182610086565b9050919050565b6100c1816100a6565b81146100cc57600080fd5b50565b6000813590506100de816100b8565b92915050565b6000602082840312156100fa576100f9610081565b5b6000610108848285016100cf565b91505092915050565b6000819050919050565b61012481610111565b82525050565b600060208201905061013f600083018461011b565b9291505056fea2646970667358221220172c443a163d8a43e018c339d1b749c312c94b6de22835953d960985daf228c764736f6c63430008120033\"}}}, {\"stateOverrides\": {\"0x0000000000000000000000000000000000000001\": {\"balance\": \"0xbb8\", \"MovePrecompileToAddress\": \"0xc200000000000000000000000000000000000000\"}, \"0xc200000000000000000000000000000000000000\": {\"balance\": \"0xfa0\", \"MovePrecompileToAddress\": \"0xc300000000000000000000000000000000000000\"}}, \"calls\": [{\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc400000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c000000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc400000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c200000000000000000000000000000000000000\"}, {\"from\": \"0xc000000000000000000000000000000000000000\", \"to\": \"0xc400000000000000000000000000000000000000\", \"input\": \"0xf8b2cb4f000000000000000000000000c300000000000000000000000000000000000000\"}]}]}"}, new object[] {"multicall-move-ecrecover-and-call", true, "{\"blockStateCalls\": [{\"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000001\", \"input\": \"0x4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554480000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000000001\", \"input\": \"0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8000000000000000000000000000000000000000000000000000000000000001cb7cf302145348387b9e69fde82d8e634a0f8761e78da3bfa059efced97cbed0d2a66b69167cafe0ccfc726aec6ee393fea3cf0e4f3f9c394705e0f56d9bfe1c9\"}]}, {\"stateOverrides\": {\"0x0000000000000000000000000000000000000001\": {\"MovePrecompileToAddress\": \"0x0000000000000000000000000000000000123456\"}}, \"calls\": [{\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000123456\", \"input\": \"0x4554480000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b45544800000000000000000000000000000000000000000000000000000000004554480000000000000000000000000000000000000000000000000000000000\"}, {\"from\": \"0xc100000000000000000000000000000000000000\", \"to\": \"0x0000000000000000000000000000000000123456\", \"input\": \"0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8000000000000000000000000000000000000000000000000000000000000001cb7cf302145348387b9e69fde82d8e634a0f8761e78da3bfa059efced97cbed0d2a66b69167cafe0ccfc726aec6ee393fea3cf0e4f3f9c394705e0f56d9bfe1c9\"}]}]}"}, @@ -79,7 +81,7 @@ public async Task TestsimulateHive(string name, bool shouldSucceed, string data) { EthereumJsonSerializer serializer = new(); SimulatePayload? payload = serializer.Deserialize>(data); - TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain(); + TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain(Osaka.Instance); Console.WriteLine($"current test: {name}"); ResultWrapper>> result = chain.EthRpcModule.eth_simulateV1(payload!, BlockParameter.Latest); @@ -127,9 +129,9 @@ public async Task TestSimulate_TimestampIsComputedCorrectly_WhenNoTimestampOverr } """; var serializer = new EthereumJsonSerializer(); - var payload = serializer.Deserialize>(data); + SimulatePayload? payload = serializer.Deserialize>(data); - var chain = await TestRpcBlockchain + TestRpcBlockchain chain = await TestRpcBlockchain .ForTest(new TestRpcBlockchain()) .WithBlocksConfig(new BlocksConfig { @@ -145,9 +147,8 @@ public async Task TestSimulate_TimestampIsComputedCorrectly_WhenNoTimestampOverr await chain.AddBlock(BuildSimpleTransaction.WithNonce(3).TestObject); await chain.AddBlock(BuildSimpleTransaction.WithNonce(4).TestObject, BuildSimpleTransaction.WithNonce(5).TestObject); - var blockParameter = new BlockParameter(blockNumber); - var parent = chain.EthRpcModule.eth_getBlockByNumber(blockParameter).Data; - var simulated = chain.EthRpcModule.eth_simulateV1(payload, blockParameter).Data[0]; + BlockForRpc parent = chain.EthRpcModule.eth_getBlockByNumber(new BlockParameter(blockNumber)).Data; + SimulateBlockResult simulated = chain.EthRpcModule.eth_simulateV1(payload, new BlockParameter(blockNumber)).Data[0]; simulated.ParentHash.Should().Be(parent.Hash); (simulated.Number - parent.Number).Should().Be(1); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/GasPriceOracleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/GasPriceOracleTests.cs index bf3fef65b98..ad5623d7f90 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/GasPriceOracleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/GasPriceOracleTests.cs @@ -24,6 +24,66 @@ namespace Nethermind.JsonRpc.Test.Modules [TestFixture] public class GasPriceOracleTests { + [TestCaseSource(nameof(SelectKthSmallestInPlace_Cases))] + public void SelectKthSmallestInPlace_ReturnsCorrectKthElement(uint[] values, int k) + { + List list = values.Select(static v => (UInt256)v).ToList(); + UInt256 expected = values.OrderBy(static v => v).Select(static v => (UInt256)v).ElementAt(k); + + UInt256 result = GasPriceOracle.SelectKthSmallestInPlace(list, k); + + result.Should().Be(expected); + list.Should().HaveCount(values.Length); + list.Should().BeEquivalentTo(values.Select(static v => (UInt256)v)); // ensures mutation doesn't lose/duplicate items + } + + [TestCaseSource(nameof(SelectKthSmallestInPlace_InvalidKCases))] + public void SelectKthSmallestInPlace_InvalidK_Throws(uint[] values, int k) + { + List list = values.Select(static v => (UInt256)v).ToList(); + + Action act = () => GasPriceOracle.SelectKthSmallestInPlace(list, k); + + act.Should().Throw(); + } + + private static IEnumerable SelectKthSmallestInPlace_Cases() + { + // single element + yield return new TestCaseData(new uint[] { 7 }, 0).SetName("SelectKthSmallestInPlace_SingleElement_k0"); + + // two elements + yield return new TestCaseData(new uint[] { 2, 1 }, 0).SetName("SelectKthSmallestInPlace_TwoElements_k0"); + yield return new TestCaseData(new uint[] { 2, 1 }, 1).SetName("SelectKthSmallestInPlace_TwoElements_kLast"); + + // already sorted + yield return new TestCaseData(new uint[] { 1, 2, 3, 4, 5 }, 0).SetName("SelectKthSmallestInPlace_Sorted_k0"); + yield return new TestCaseData(new uint[] { 1, 2, 3, 4, 5 }, 4).SetName("SelectKthSmallestInPlace_Sorted_kLast"); + yield return new TestCaseData(new uint[] { 1, 2, 3, 4, 5 }, 2).SetName("SelectKthSmallestInPlace_Sorted_kMid"); + + // reverse sorted (forces lots of swaps) + yield return new TestCaseData(new uint[] { 5, 4, 3, 2, 1 }, 0).SetName("SelectKthSmallestInPlace_Reverse_k0"); + yield return new TestCaseData(new uint[] { 5, 4, 3, 2, 1 }, 4).SetName("SelectKthSmallestInPlace_Reverse_kLast"); + yield return new TestCaseData(new uint[] { 5, 4, 3, 2, 1 }, 2).SetName("SelectKthSmallestInPlace_Reverse_kMid"); + + // duplicates (partition uses < pivot, so equal elements are a good edge case) + yield return new TestCaseData(new uint[] { 7, 7, 7, 7 }, 0).SetName("SelectKthSmallestInPlace_AllEqual_k0"); + yield return new TestCaseData(new uint[] { 7, 7, 7, 7 }, 3).SetName("SelectKthSmallestInPlace_AllEqual_kLast"); + yield return new TestCaseData(new uint[] { 5, 1, 5, 3, 5, 2 }, 2).SetName("SelectKthSmallestInPlace_Duplicates_kMid"); + + // pivot-in-the-middle style layouts (median-of-range pivot) + yield return new TestCaseData(new uint[] { 9, 1, 8, 2, 7, 3, 6, 4, 5 }, 4).SetName("SelectKthSmallestInPlace_ZigZag_kMid"); + yield return new TestCaseData(new uint[] { 100, 1, 50, 2, 49, 3, 48, 4 }, 3).SetName("SelectKthSmallestInPlace_ZigZagEven_kMid"); + } + + private static IEnumerable SelectKthSmallestInPlace_InvalidKCases() + { + yield return new TestCaseData(Array.Empty(), 0).SetName("SelectKthSmallestInPlace_Empty_Throws"); + yield return new TestCaseData(new uint[] { 1 }, -1).SetName("SelectKthSmallestInPlace_kNegative_Throws"); + yield return new TestCaseData(new uint[] { 1 }, 1).SetName("SelectKthSmallestInPlace_kEqualsCount_Throws"); + yield return new TestCaseData(new uint[] { 1, 2 }, 2).SetName("SelectKthSmallestInPlace_kGreaterThanLast_Throws"); + } + [Test] public async ValueTask GasPriceEstimate_NoChangeInHeadBlock_ReturnsPreviousGasPrice() { @@ -192,7 +252,7 @@ public void GetGasPricesFromRecentBlocks_IfBlockHasMoreThanThreeValidTx_AddOnlyT blockFinder.FindBlock(0).Returns(headBlock); GasPriceOracle testGasPriceOracle = new(blockFinder, Substitute.For(), LimboLogs.Instance); - IEnumerable results = testGasPriceOracle.GetSortedGasPricesFromRecentBlocks(0); + IEnumerable results = testGasPriceOracle.GetGasPricesFromRecentBlocks(0); results.Count().Should().Be(3); } @@ -206,7 +266,7 @@ public void GetGasPricesFromRecentBlocks_IfBlockHasMoreThanThreeValidTxs_OnlyAdd GasPriceOracle testGasPriceOracle = new(blockFinder, Substitute.For(), LimboLogs.Instance); List expected = new() { 2, 3, 4 }; - IEnumerable results = testGasPriceOracle.GetSortedGasPricesFromRecentBlocks(0); + IEnumerable results = testGasPriceOracle.GetGasPricesFromRecentBlocks(0); results.Should().BeEquivalentTo(expected); } @@ -240,7 +300,7 @@ public void GetGasPricesFromRecentBlocks_TxsSentByMiner_ShouldNotHaveGasPriceInT GasPriceOracle gasPriceOracle = new(blockFinder, Substitute.For(), LimboLogs.Instance); List expected = new() { 8, 9 }; - IEnumerable results = gasPriceOracle.GetSortedGasPricesFromRecentBlocks(0); + IEnumerable results = gasPriceOracle.GetGasPricesFromRecentBlocks(0); results.Should().BeEquivalentTo(expected); } @@ -258,7 +318,7 @@ public void AddValidTxAndReturnCount_GivenEip1559Txs_EffectiveGasPriceProperlyCa blockFinder.FindBlock(0).Returns(eip1559Block); GasPriceOracle gasPriceOracle = new(blockFinder, GetSpecProviderWithEip1559EnabledAs(eip1559Enabled), LimboLogs.Instance); - IEnumerable results = gasPriceOracle.GetSortedGasPricesFromRecentBlocks(0); + IEnumerable results = gasPriceOracle.GetGasPricesFromRecentBlocks(0); List expectedList = expected.Select(static n => (UInt256)n).ToList(); results.Should().BeEquivalentTo(expectedList); @@ -278,7 +338,7 @@ public void AddValidTxAndReturnCount_GivenNonEip1559Txs_EffectiveGasPriceProperl blockFinder.FindBlock(0).Returns(nonEip1559Block); GasPriceOracle gasPriceOracle = new(blockFinder, GetSpecProviderWithEip1559EnabledAs(eip1559Enabled), LimboLogs.Instance); - IEnumerable results = gasPriceOracle.GetSortedGasPricesFromRecentBlocks(0); + IEnumerable results = gasPriceOracle.GetGasPricesFromRecentBlocks(0); List expectedList = expected.Select(static n => (UInt256)n).ToList(); results.Should().BeEquivalentTo(expectedList); @@ -302,7 +362,7 @@ public void GetGasPricesFromRecentBlocks_IfNoValidTxsInABlock_DefaultPriceAddedT GasPriceOracle gasPriceOracle = new(blockFinder, Substitute.For(), LimboLogs.Instance) { _gasPriceEstimation = new(null, 7) }; List expected = new() { 7 }; - IEnumerable results = gasPriceOracle.GetSortedGasPricesFromRecentBlocks(0); + IEnumerable results = gasPriceOracle.GetGasPricesFromRecentBlocks(0); results.ToList().Should().BeEquivalentTo(expected); } @@ -317,7 +377,7 @@ public void AddValidTxAndReturnCount_IfNoTxsInABlock_DefaultPriceAddedToListInst GasPriceOracle gasPriceOracle = new(blockFinder, Substitute.For(), LimboLogs.Instance) { _gasPriceEstimation = new(null, 7) }; List expected = new() { 7 }; - IEnumerable results = gasPriceOracle.GetSortedGasPricesFromRecentBlocks(0); + IEnumerable results = gasPriceOracle.GetGasPricesFromRecentBlocks(0); results.ToList().Should().BeEquivalentTo(expected); } @@ -337,7 +397,7 @@ public void AddValidTxAndReturnCount_Eip1559NotEnabled_EffectiveGasPricesShouldB GasPriceOracle gasPriceOracle = new(blockFinder, GetSpecProviderWithEip1559EnabledAs(false), LimboLogs.Instance); List expected = new() { 2, 3 }; - IEnumerable results = gasPriceOracle.GetSortedGasPricesFromRecentBlocks(0); + IEnumerable results = gasPriceOracle.GetGasPricesFromRecentBlocks(0); results.Should().BeEquivalentTo(expected); } @@ -357,7 +417,7 @@ public void AddValidTxAndReturnCount_Eip1559Enabled_EffectiveGasPricesShouldBeMo GasPriceOracle gasPriceOracle = new(blockFinder, GetSpecProviderWithEip1559EnabledAs(true), LimboLogs.Instance); List expected = new() { 3, 4 }; - IEnumerable results = gasPriceOracle.GetSortedGasPricesFromRecentBlocks(0); + IEnumerable results = gasPriceOracle.GetGasPricesFromRecentBlocks(0); results.Should().BeEquivalentTo(expected); } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/ParityRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/ParityRpcModuleTests.cs index 54aa2244d4f..54da5ec75d8 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/ParityRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/ParityRpcModuleTests.cs @@ -5,6 +5,7 @@ using System.IO; using System.Net; using System.Threading.Tasks; +using System.Text.Json; using FluentAssertions; using Nethermind.Blockchain; using Nethermind.Blockchain.Receipts; @@ -15,6 +16,7 @@ using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; using Nethermind.Core.Test; using Nethermind.Specs; using Nethermind.Core.Test.Builders; @@ -36,6 +38,7 @@ using System; using Nethermind.Core.Test.Db; using Nethermind.State; +using Nethermind.Serialization.Json; namespace Nethermind.JsonRpc.Test.Modules { @@ -375,5 +378,26 @@ public async Task parity_netPeers_null_ActivePeers() string expectedResult = "{\"jsonrpc\":\"2.0\",\"result\":{\"active\":0,\"connected\":0,\"max\":0,\"peers\":[]},\"id\":67}"; Assert.That(serialized, Is.EqualTo(expectedResult)); } + + [Test] + public void ParityTransaction_WithFullPublicKeyJson_DeserializesSuccessfully() + { + const string fullKeyHex = "a49ac7010c2e0a444dfeeabadbafa4856ba4a2d732acb86d20c577b3b365f52e5a8728693008d97ae83d51194f273455acf1a30e6f3926aefaede484c07d8ec3"; + byte[] fullPublicKeyBytes = Bytes.FromHexString(fullKeyHex); + + string json = $$""" + { + "publicKey": "0x{{fullKeyHex}}", + "hash": "0xd4720d1b81c70ed4478553a213a83bd2bf6988291677f5d05c6aae0b287f947e" + } + """; + + EthereumJsonSerializer serializer = new(); + ParityTransaction tx = serializer.Deserialize(json); + + tx.PublicKey.Should().NotBeNull(); + tx.PublicKey.Bytes.Length.Should().Be(64); + tx.PublicKey.Bytes.Should().BeEquivalentTo(fullPublicKeyBytes); + } } } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Proof/ProofRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Proof/ProofRpcModuleTests.cs index d32babccfc7..bb6296d4993 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Proof/ProofRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Proof/ProofRpcModuleTests.cs @@ -61,7 +61,7 @@ public async Task Setup() _worldStateManager = TestWorldStateFactory.CreateWorldStateManagerForTest(_dbProvider, LimboLogs.Instance); Hash256 stateRoot; - IWorldState worldState = _worldStateManager.GlobalWorldState; + IWorldState worldState = new WorldState(_worldStateManager.GlobalWorldState, LimboLogs.Instance); using (var _ = worldState.BeginScope(IWorldState.PreGenesis)) { worldState.CreateAccount(TestItem.AddressA, 100000); @@ -843,19 +843,6 @@ private async Task TestCallWithStorageAndCode(byte[] code, UInt256 gasPrice, Add CallResultWithProof callResultWithProof = _proofRpcModule.proof_call(tx, new BlockParameter(blockOnTop.Number)).Data; Assert.That(callResultWithProof.Accounts.Length, Is.GreaterThan(0)); - // just the keys for debugging - byte[] span = new byte[32]; - new UInt256(0).ToBigEndian(span); - _ = Keccak.Compute(span); - - // just the keys for debugging - new UInt256(1).ToBigEndian(span); - _ = Keccak.Compute(span); - - // just the keys for debugging - new UInt256(2).ToBigEndian(span); - _ = Keccak.Compute(span); - foreach (AccountProof accountProof in callResultWithProof.Accounts) { // this is here for diagnostics - so you can read what happens in the test @@ -884,7 +871,7 @@ private async Task TestCallWithStorageAndCode(byte[] code, UInt256 gasPrice, Add private (IWorldState, Hash256) CreateInitialState(byte[]? code) { - IWorldState stateProvider = _worldStateManager.GlobalWorldState; + IWorldState stateProvider = new WorldState(_worldStateManager.GlobalWorldState, LimboLogs.Instance); using var _ = stateProvider.BeginScope(IWorldState.PreGenesis); AddAccount(stateProvider, TestItem.AddressA, 1.Ether()); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/AccessListTransactionForRpcTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/AccessListTransactionForRpcTests.cs index fe7adfe2e48..d69dca748a3 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/AccessListTransactionForRpcTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/RpcTransaction/AccessListTransactionForRpcTests.cs @@ -81,7 +81,7 @@ public static void ValidateSchema(JsonElement json) json.GetProperty("value").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); json.GetProperty("input").GetString().Should().MatchRegex("^0x[0-9a-f]*$"); json.GetProperty("gasPrice").GetString().Should().MatchRegex("^0x([1-9a-f]+[0-9a-f]*|0)$"); - // Suprising inconsistency in `FluentAssertions` where `AllSatisfy` fails on empty collections. + // Surprising inconsistency in `FluentAssertions` where `AllSatisfy` fails on empty collections. // This requires wrapping the assertion in a condition. // See: https://github.com/fluentassertions/fluentassertions/discussions/2143#discussioncomment-9677309 var accessList = json.GetProperty("accessList").EnumerateArray(); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs index 64c5f12b867..4a988139e3b 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/SubscribeModuleTests.cs @@ -52,7 +52,7 @@ public class SubscribeModuleTests private IBlockTree _blockTree = null!; private ITxPool _txPool = null!; private IReceiptStorage _receiptStorage = null!; - private IFilterStore _filterStore = null!; + private FilterStore _filterStore = null!; private ISubscriptionManager _subscriptionManager = null!; private IJsonRpcDuplexClient _jsonRpcDuplexClient = null!; private IJsonSerializer _jsonSerializer = null!; @@ -603,8 +603,8 @@ public void LogsSubscription_on_NewHeadBlock_event_with_few_TxReceipts_with_few_ { FromBlock = BlockParameter.Latest, ToBlock = BlockParameter.Latest, - Address = "0xb7705ae4c6f81b66cdb323c65f4e8133690fc099", - Topics = new[] { TestItem.KeccakA } + Address = [new Address("0xb7705ae4c6f81b66cdb323c65f4e8133690fc099")], + Topics = [[TestItem.KeccakA]] }; LogEntry logEntryA = Build.A.LogEntry.WithAddress(TestItem.AddressA).WithTopics(TestItem.KeccakA).WithData(TestItem.RandomDataA).TestObject; @@ -651,8 +651,8 @@ public void LogsSubscription_on_NewHeadBlock_event_with_few_TxReceipts_with_few_ { FromBlock = BlockParameter.Latest, ToBlock = BlockParameter.Latest, - Address = "0xb7705ae4c6f81b66cdb323c65f4e8133690fc099", - Topics = new[] { TestItem.KeccakA } + Address = [new Address("0xb7705ae4c6f81b66cdb323c65f4e8133690fc099")], + Topics = [[TestItem.KeccakA]] }; LogEntry logEntryA = Build.A.LogEntry.WithAddress(TestItem.AddressA).WithTopics(TestItem.KeccakA).WithData(TestItem.RandomDataA).TestObject; @@ -699,8 +699,12 @@ public void LogsSubscription_on_NewHeadBlock_event_with_few_TxReceipts_with_few_ { FromBlock = BlockParameter.Latest, ToBlock = BlockParameter.Latest, - Address = new[] { "0xb7705ae4c6f81b66cdb323c65f4e8133690fc099", "0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358" }, - Topics = new[] { TestItem.KeccakA, TestItem.KeccakD } + Address = + [ + new Address("0xb7705ae4c6f81b66cdb323c65f4e8133690fc099"), + new Address("0x942921b14f1b1c385cd7e0cc2ef7abe5598c8358") + ], + Topics = [[TestItem.KeccakA, TestItem.KeccakD]] }; LogEntry logEntryA = Build.A.LogEntry.WithAddress(TestItem.AddressA).WithTopics(TestItem.KeccakA, TestItem.KeccakD).WithData(TestItem.RandomDataA).TestObject; @@ -922,12 +926,14 @@ public async Task MultipleSubscriptions_concurrent_fast_messages(int messages) Task subA = Task.Run(() => { ITxPool txPool = Substitute.For(); - using NewPendingTransactionsSubscription subscription = new( - // ReSharper disable once AccessToDisposedClosure - jsonRpcDuplexClient: client, - txPool: txPool, - specProvider: _specProvider, - logManager: LimboLogs.Instance); + using NewPendingTransactionsSubscription subscription = + new( + // ReSharper disable once AccessToDisposedClosure + jsonRpcDuplexClient: client, + txPool: txPool, + specProvider: _specProvider, + logManager: LimboLogs.Instance + ); for (int i = 0; i < messages; i++) { @@ -938,12 +944,14 @@ public async Task MultipleSubscriptions_concurrent_fast_messages(int messages) Task subB = Task.Run(() => { IBlockTree blockTree = Substitute.For(); - using NewHeadSubscription subscription = new( - // ReSharper disable once AccessToDisposedClosure - jsonRpcDuplexClient: client, - blockTree: blockTree, - specProvider: new TestSpecProvider(new ReleaseSpec()), - logManager: LimboLogs.Instance); + using NewHeadSubscription subscription = + new( + // ReSharper disable once AccessToDisposedClosure + jsonRpcDuplexClient: client, + blockTree: blockTree, + specProvider: new TestSpecProvider(new ReleaseSpec()), + logManager: LimboLogs.Instance + ); for (int i = 0; i < messages; i++) { diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs index 921baa41f7c..a00b1905ef4 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TestRpcBlockchain.cs @@ -67,8 +67,8 @@ public class TestRpcBlockchain : TestBlockchain public IFeeHistoryOracle? FeeHistoryOracle { get; private set; } public static Builder ForTest(string sealEngineType, long? testTimeout = null) => ForTest(sealEngineType, testTimeout); - public static Builder ForTest(string sealEngineType, long? testTimout = null) where T : TestRpcBlockchain, new() => - new(new T { SealEngineType = sealEngineType, TestTimout = testTimout ?? DefaultTimeout }); + public static Builder ForTest(string sealEngineType, long? testTimeout = null) where T : TestRpcBlockchain, new() => + new(new T { SealEngineType = sealEngineType, TestTimeout = testTimeout ?? DefaultTimeout }); public static Builder ForTest(T blockchain) where T : TestRpcBlockchain => new(blockchain); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParitySimulateTestsBlocksAndTransactions.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParitySimulateTestsBlocksAndTransactions.cs index fb5fd892d3f..cbd96f3430f 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParitySimulateTestsBlocksAndTransactions.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParitySimulateTestsBlocksAndTransactions.cs @@ -25,7 +25,7 @@ public async Task Test_trace_simulate_serialization() { TestRpcBlockchain chain = await EthRpcSimulateTestsBase.CreateChain(); - SimulatePayload payload = EthSimulateTestsBlocksAndTransactions.CreateSerialisationPayload(chain); + SimulatePayload payload = EthSimulateTestsBlocksAndTransactions.CreateSerializationPayload(chain); //Force persistence of head block in main chain chain.BlockTree.UpdateMainChain(new List { chain.BlockFinder.Head! }, true, true); @@ -63,7 +63,7 @@ public async Task Test_trace_simulate_eth_moved() chain.Bridge.GetReceipt(txMainnetAtoB.Hash!); - //Force persistancy of head block in main chain + //Force persistence of head block in main chain chain.BlockTree.UpdateMainChain(new List { chain.BlockFinder.Head! }, true, true); chain.BlockTree.UpdateHeadBlock(chain.BlockFinder.Head!.Hash!); @@ -101,7 +101,7 @@ public async Task Test_trace_simulate_transactions_forced_fail() chain.Bridge.GetReceipt(txMainnetAtoB.Hash!); - //Force persistancy of head block in main chain + //Force persistence of head block in main chain chain.BlockTree.UpdateMainChain(new List { chain.BlockFinder.Head! }, true, true); chain.BlockTree.UpdateHeadBlock(chain.BlockFinder.Head!.Hash!); diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs index 339f5804a13..9ee6c353c93 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/Trace/ParityStyleTracerTests.cs @@ -70,7 +70,7 @@ public async Task TearDownAsync() [Test] public void Can_trace_raw_parity_style() { - ResultWrapper result = _traceRpcModule.trace_rawTransaction(Bytes.FromHexString("f889808609184e72a00082271094000000000000000000000000000000000000000080a47f74657374320000000000000000000000000000000000000000000000000000006000571ca08a8bbf888cfa37bbf0bb965423625641fc956967b81d12e23709cead01446075a01ce999b56a8a88504be365442ea61239198e23d1fce7d00fcfc5cd3b44b7215f"), new[] { "trace" }); + ResultWrapper result = _traceRpcModule.trace_rawTransaction(Bytes.FromHexString("f8838080829c4094000000000000000000000000000000000000000080a47f74657374320000000000000000000000000000000000000000000000000000006000571ca08a8bbf888cfa37bbf0bb965423625641fc956967b81d12e23709cead01446075a01ce999b56a8a88504be365442ea61239198e23d1fce7d00fcfc5cd3b44b7215f"), new[] { "trace" }); Assert.That(result.Data, Is.Not.Null); } diff --git a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs index f3cd2df13ec..479098fe41c 100644 --- a/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs +++ b/src/Nethermind/Nethermind.JsonRpc.Test/Modules/TraceRpcModuleTests.cs @@ -675,6 +675,8 @@ public async Task Trace_call_runs_on_top_of_specified_block() Transaction transaction = Build.A.Transaction .SignedAndResolved(addressKey) .WithTo(TestItem.AddressC) + .WithMaxFeePerGas(0) + .WithMaxPriorityFeePerGas(0) .WithValue(send) .TestObject; @@ -704,6 +706,8 @@ public async Task Trace_callMany_runs_on_top_of_specified_block() Transaction transaction = Build.A.Transaction .SignedAndResolved(addressKey) .WithTo(TestItem.AddressC) + .WithMaxFeePerGas(0) + .WithMaxPriorityFeePerGas(0) .WithValue(send) .TestObject; @@ -733,6 +737,8 @@ public async Task Trace_rawTransaction_runs_on_top_of_specified_block() Transaction transaction = Build.A.Transaction .WithTo(TestItem.AddressC) .WithValue(send) + .WithMaxFeePerGas(0) + .WithMaxPriorityFeePerGas(0) .SignedAndResolved(addressKey) .TestObject; @@ -762,8 +768,8 @@ public async Task Trace_call_simple_tx_test() } private static readonly IEnumerable<(object, string[], string)> Trace_call_without_blockParameter_test_cases = [ - (new { from = "0x7f554713be84160fdf0178cc8df86f5aabd33397", to = "0xbe5c953dd0ddb0ce033a98f36c981f1b74d3b33f", value = "0x0", gasPrice = "0x119e04a40a", gas = "0xf4240" }, ["trace"], "{\"jsonrpc\":\"2.0\",\"result\":{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0x7f554713be84160fdf0178cc8df86f5aabd33397\",\"gas\":\"0xef038\",\"input\":\"0x\",\"to\":\"0xbe5c953dd0ddb0ce033a98f36c981f1b74d3b33f\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null},\"id\":67}"), - (new { from = "0xc71acc7863f3bc7347b24c3b835643bd89d4d161", to = "0xa760e26aa76747020171fcf8bda108dfde8eb930", value = "0x0", gasPrice = "0x2108eea5bc", gas = "0xf4240" }, ["trace"], "{\"jsonrpc\":\"2.0\",\"result\":{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0xc71acc7863f3bc7347b24c3b835643bd89d4d161\",\"gas\":\"0xef038\",\"input\":\"0x\",\"to\":\"0xa760e26aa76747020171fcf8bda108dfde8eb930\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null},\"id\":67}") + (new { from = TestItem.AddressA, to = "0xbe5c953dd0ddb0ce033a98f36c981f1b74d3b33f", value = "0x0", gasPrice = "0x119e04a40a", gas = "0xf4240" }, ["trace"], $"{{\"jsonrpc\":\"2.0\",\"result\":{{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{{\"action\":{{\"callType\":\"call\",\"from\":\"{TestItem.AddressA}\",\"gas\":\"0xef038\",\"input\":\"0x\",\"to\":\"0xbe5c953dd0ddb0ce033a98f36c981f1b74d3b33f\",\"value\":\"0x0\"}},\"result\":{{\"gasUsed\":\"0x0\",\"output\":\"0x\"}},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}}],\"vmTrace\":null}},\"id\":67}}"), + (new { from = TestItem.AddressA, to = "0xa760e26aa76747020171fcf8bda108dfde8eb930", value = "0x0", gasPrice = "0x2108eea5bc", gas = "0xf4240" }, ["trace"], $"{{\"jsonrpc\":\"2.0\",\"result\":{{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{{\"action\":{{\"callType\":\"call\",\"from\":\"{TestItem.AddressA}\",\"gas\":\"0xef038\",\"input\":\"0x\",\"to\":\"0xa760e26aa76747020171fcf8bda108dfde8eb930\",\"value\":\"0x0\"}},\"result\":{{\"gasUsed\":\"0x0\",\"output\":\"0x\"}},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}}],\"vmTrace\":null}},\"id\":67}}") ]; [TestCaseSource(nameof(Trace_call_without_blockParameter_test_cases))] public async Task Trace_call_without_blockParameter_test((object transaction, string[] traceTypes, string expectedResult) testCase) @@ -817,7 +823,7 @@ public async Task Trace_callMany_is_blockParameter_optional_test() { Context context = new(); await context.Build(); - string calls = "[[{\"from\":\"0xfe35e70599578efef562e1f1cdc9ef693b865e9d\",\"to\":\"0x8cf85548ae57a91f8132d0831634c0fcef06e505\",\"gas\":\"0xf4240\"},[\"trace\"]],[{\"from\":\"0x2a6ae6f33729384a00b4ffbd25e3f1bf1b9f5b8d\",\"to\":\"0xab736519b5433974059da38da74b8db5376942cd\",\"gasPrice\":\"0xb2b29a6dc\",\"gas\":\"0xf4240\"},[\"trace\"]]]"; + string calls = $"[[{{\"from\":\"{TestItem.AddressA}\",\"to\":\"0x8cf85548ae57a91f8132d0831634c0fcef06e505\",\"gas\":\"0xf4240\"}},[\"trace\"]],[{{\"from\":\"{TestItem.AddressB}\",\"to\":\"0xab736519b5433974059da38da74b8db5376942cd\",\"gasPrice\":\"0xb2b29a6dc\",\"gas\":\"0xf4240\"}},[\"trace\"]]]"; string serialized = await RpcTest.TestSerializedRequest( context.TraceRpcModule, @@ -827,8 +833,8 @@ public async Task Trace_callMany_is_blockParameter_optional_test() context.TraceRpcModule, "trace_callMany", calls); - Assert.That(serialized, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":[{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0xfe35e70599578efef562e1f1cdc9ef693b865e9d\",\"gas\":\"0xef038\",\"input\":\"0x\",\"to\":\"0x8cf85548ae57a91f8132d0831634c0fcef06e505\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null},{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0x2a6ae6f33729384a00b4ffbd25e3f1bf1b9f5b8d\",\"gas\":\"0xef038\",\"input\":\"0x\",\"to\":\"0xab736519b5433974059da38da74b8db5376942cd\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null}],\"id\":67}"), serialized.Replace("\"", "\\\"")); - Assert.That(serialized_without_blockParameter_param, Is.EqualTo("{\"jsonrpc\":\"2.0\",\"result\":[{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0xfe35e70599578efef562e1f1cdc9ef693b865e9d\",\"gas\":\"0xef038\",\"input\":\"0x\",\"to\":\"0x8cf85548ae57a91f8132d0831634c0fcef06e505\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null},{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{\"action\":{\"callType\":\"call\",\"from\":\"0x2a6ae6f33729384a00b4ffbd25e3f1bf1b9f5b8d\",\"gas\":\"0xef038\",\"input\":\"0x\",\"to\":\"0xab736519b5433974059da38da74b8db5376942cd\",\"value\":\"0x0\"},\"result\":{\"gasUsed\":\"0x0\",\"output\":\"0x\"},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}],\"vmTrace\":null}],\"id\":67}"), serialized_without_blockParameter_param.Replace("\"", "\\\"")); + Assert.That(serialized, Is.EqualTo($"{{\"jsonrpc\":\"2.0\",\"result\":[{{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{{\"action\":{{\"callType\":\"call\",\"from\":\"{TestItem.AddressA}\",\"gas\":\"0xef038\",\"input\":\"0x\",\"to\":\"0x8cf85548ae57a91f8132d0831634c0fcef06e505\",\"value\":\"0x0\"}},\"result\":{{\"gasUsed\":\"0x0\",\"output\":\"0x\"}},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}}],\"vmTrace\":null}},{{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{{\"action\":{{\"callType\":\"call\",\"from\":\"{TestItem.AddressB}\",\"gas\":\"0xef038\",\"input\":\"0x\",\"to\":\"0xab736519b5433974059da38da74b8db5376942cd\",\"value\":\"0x0\"}},\"result\":{{\"gasUsed\":\"0x0\",\"output\":\"0x\"}},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}}],\"vmTrace\":null}}],\"id\":67}}"), serialized.Replace("\"", "\\\"")); + Assert.That(serialized_without_blockParameter_param, Is.EqualTo($"{{\"jsonrpc\":\"2.0\",\"result\":[{{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{{\"action\":{{\"callType\":\"call\",\"from\":\"{TestItem.AddressA}\",\"gas\":\"0xef038\",\"input\":\"0x\",\"to\":\"0x8cf85548ae57a91f8132d0831634c0fcef06e505\",\"value\":\"0x0\"}},\"result\":{{\"gasUsed\":\"0x0\",\"output\":\"0x\"}},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}}],\"vmTrace\":null}},{{\"output\":\"0x\",\"stateDiff\":null,\"trace\":[{{\"action\":{{\"callType\":\"call\",\"from\":\"{TestItem.AddressB}\",\"gas\":\"0xef038\",\"input\":\"0x\",\"to\":\"0xab736519b5433974059da38da74b8db5376942cd\",\"value\":\"0x0\"}},\"result\":{{\"gasUsed\":\"0x0\",\"output\":\"0x\"}},\"subtraces\":0,\"traceAddress\":[],\"type\":\"call\"}}],\"vmTrace\":null}}],\"id\":67}}"), serialized_without_blockParameter_param.Replace("\"", "\\\"")); } [Test] @@ -954,7 +960,7 @@ public async Task Trace_replayBlockTransactions_stateDiff() """{"jsonrpc":"2.0","result":{"output":null,"stateDiff":{"0x7f554713be84160fdf0178cc8df86f5aabd33397":{"balance":{"\u002B":"0x0"},"code":"=","nonce":{"\u002B":"0x1"},"storage":{}},"0xc200000000000000000000000000000000000000":{"balance":"=","code":"=","nonce":"=","storage":{"0x0000000000000000000000000000000000000000000000000000000000000000":{"*":{"from":"0x0000000000000000000000000000000000000000000000000000000000123456","to":"0x1122334455667788990011223344556677889900112233445566778899001122"}}}}},"trace":[],"vmTrace":null},"id":67}""" )] [TestCase( - "Executes precompile using overriden address", + "Executes precompile using overridden address", """{"from":"0x7f554713be84160fdf0178cc8df86f5aabd33397","to":"0x0000000000000000000000000000000000123456","input":"0xB6E16D27AC5AB427A7F68900AC5559CE272DC6C37C82B3E052246C82244C50E4000000000000000000000000000000000000000000000000000000000000001C7B8B1991EB44757BC688016D27940DF8FB971D7C87F77A6BC4E938E3202C44037E9267B0AEAA82FA765361918F2D8ABD9CDD86E64AA6F2B81D3C4E0B69A7B055","gas":"0xf4240"}""", "trace", """{"0x0000000000000000000000000000000000000001":{"movePrecompileToAddress":"0x0000000000000000000000000000000000123456", "code": "0x"}}""", diff --git a/src/Nethermind/Nethermind.JsonRpc.TraceStore/TraceStoreRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc.TraceStore/TraceStoreRpcModule.cs index a38952dc51d..a26bf787bc3 100644 --- a/src/Nethermind/Nethermind.JsonRpc.TraceStore/TraceStoreRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc.TraceStore/TraceStoreRpcModule.cs @@ -211,7 +211,7 @@ private bool TryTraceTransaction( if (TryGetBlockTraces(block.Header, out List? traces) && traces is not null) { - ParityLikeTxTrace? trace = GetTxTrace(block, txHash, traces); + ParityLikeTxTrace? trace = GetTxTrace(txHash, traces); if (trace is not null) { FilterTrace(trace, traceTypes); @@ -249,7 +249,7 @@ private bool TryGetBlockTraces(BlockHeader block, out List? t } } - private static ParityLikeTxTrace? GetTxTrace(Block block, Hash256 txHash, List traces) + private static ParityLikeTxTrace? GetTxTrace(Hash256 txHash, List traces) { int index = traces.FindIndex(t => t.TransactionHash == txHash); return index != -1 ? traces[index] : null; diff --git a/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs b/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs index 71e4190e423..cea92a933c0 100644 --- a/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs +++ b/src/Nethermind/Nethermind.JsonRpc/ErrorCodes.cs @@ -123,7 +123,7 @@ public static class ErrorCodes public const int ClientLimitExceededError = -38026; /// - /// Block is not available due to history expirty policy + /// Block is not available due to history expiry policy /// public const int PrunedHistoryUnavailable = 4444; @@ -178,7 +178,7 @@ public static class ErrorCodes public const int MaxInitCodeSizeExceeded = -38025; /// - /// Transaction reverted. Geth sets it to -32000 in simulate but suppose to 3. We kepp it as geth for now + /// Transaction reverted. Geth sets it to -32000 in simulate but supposed to be 3. We keep it as Geth for now /// public const int RevertedSimulate = -32000; diff --git a/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs b/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs index 2398cfda693..1246c6b49cf 100644 --- a/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs +++ b/src/Nethermind/Nethermind.JsonRpc/JsonRpcService.cs @@ -11,6 +11,7 @@ using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Nethermind.Blockchain; using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.JsonRpc.Exceptions; @@ -271,6 +272,15 @@ private JsonRpcErrorResponse HandleInvocationException(Exception ex, string meth { InnerException: InsufficientBalanceException } => GetErrorResponse(methodName, ErrorCodes.InvalidInput, ex.InnerException.Message, ex.ToString(), request.Id, returnAction), + { InnerException: InvalidTransactionException e } => + GetErrorResponse(methodName, ErrorCodes.Default, e.Reason.ErrorDescription, null, request.Id, returnAction), + + InvalidTransactionException e => + GetErrorResponse(methodName, ErrorCodes.Default, e.Reason.ErrorDescription, null, request.Id, returnAction), + + InvalidBlockException or { InnerException: InvalidBlockException } => + GetErrorResponse(methodName, ErrorCodes.Default, ex.Message, null, request.Id, returnAction), + _ => HandleException(ex, methodName, request, returnAction) }; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/PeerInfo.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/PeerInfo.cs index 230767bb7dc..58998ca48f5 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/PeerInfo.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Admin/PeerInfo.cs @@ -19,7 +19,6 @@ public class PeerInfo public string Enode { get; set; } = string.Empty; - [JsonConverter(typeof(PublicKeyHashedConverter))] public PublicKey Id { get; set; } = null!; public string? Name { get; set; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/BlockFinderExtensions.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/BlockFinderExtensions.cs index 16d0a22526e..85b63ed25b5 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/BlockFinderExtensions.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/BlockFinderExtensions.cs @@ -35,23 +35,15 @@ public static SearchResult SearchForHeader(this IBlockFinder blockF blockParameter ??= BlockParameter.Latest; - BlockHeader header; - if (blockParameter.RequireCanonical) + BlockHeader header = blockFinder.FindHeader(blockParameter); + if (blockParameter.RequireCanonical && header is null && !allowNulls && blockParameter.BlockHash is not null) { - header = blockFinder.FindHeader(blockParameter.BlockHash, BlockTreeLookupOptions.RequireCanonical); - if (header is null && !allowNulls) + header = blockFinder.FindHeader(blockParameter.BlockHash); + if (header is not null) { - header = blockFinder.FindHeader(blockParameter.BlockHash); - if (header is not null) - { - return new SearchResult($"{blockParameter.BlockHash} block is not canonical", ErrorCodes.InvalidInput); - } + return new SearchResult($"{blockParameter.BlockHash} block is not canonical", ErrorCodes.InvalidInput); } } - else - { - header = blockFinder.FindHeader(blockParameter); - } return header is null && !allowNulls ? new SearchResult($"{blockParameter.BlockHash?.ToString() ?? blockParameter.BlockNumber?.ToString() ?? blockParameter.Type.ToString()} could not be found", ErrorCodes.ResourceNotFound) @@ -62,23 +54,15 @@ public static SearchResult SearchForBlock(this IBlockFinder blockFinder, { blockParameter ??= BlockParameter.Latest; - Block block; - if (blockParameter.RequireCanonical) + Block block = blockFinder.FindBlock(blockParameter); + if (blockParameter.RequireCanonical && block is null && !allowNulls && blockParameter.BlockHash is not null) { - block = blockFinder.FindBlock(blockParameter.BlockHash!, BlockTreeLookupOptions.RequireCanonical); - if (block is null && !allowNulls) + BlockHeader? header = blockFinder.FindHeader(blockParameter.BlockHash); + if (header is not null) { - BlockHeader? header = blockFinder.FindHeader(blockParameter.BlockHash); - if (header is not null) - { - return new SearchResult($"{blockParameter.BlockHash} block is not canonical", ErrorCodes.InvalidInput); - } + return new SearchResult($"{blockParameter.BlockHash} block is not canonical", ErrorCodes.InvalidInput); } } - else - { - block = blockFinder.FindBlock(blockParameter); - } if (block is null) { diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugBridge.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugBridge.cs index 47120645ba6..7a5d5a8025d 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugBridge.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugBridge.cs @@ -196,9 +196,9 @@ public IReadOnlyCollection GetBlockTrace(Block block, Cancellat public object GetConfigValue(string category, string name) => _configProvider.GetRawValue(category, name); - public SyncReportSymmary GetCurrentSyncStage() + public SyncReportSummary GetCurrentSyncStage() { - return new SyncReportSymmary + return new SyncReportSummary { CurrentStage = _syncModeSelector.Current.ToString() }; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs index 7ae8f672122..2f0dca1873b 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugModuleFactory.cs @@ -32,7 +32,7 @@ public IDebugRpcModule Create() { IOverridableEnv env = envFactory.Create(); - ILifetimeScope tracerLifecyccle = rootLifetimeScope.BeginLifetimeScope((builder) => + ILifetimeScope tracerLifecycle = rootLifetimeScope.BeginLifetimeScope((builder) => ConfigureTracerContainer(builder) .AddModule(env)); @@ -40,9 +40,9 @@ public IDebugRpcModule Create() // This is to prevent leaking processor or world state accidentally. // `GethStyleTracer` must be very careful to always dispose overridable env. ILifetimeScope debugRpcModuleLifetime = rootLifetimeScope.BeginLifetimeScope((builder) => builder - .AddScoped(tracerLifecyccle.Resolve())); + .AddScoped(tracerLifecycle.Resolve())); - debugRpcModuleLifetime.Disposer.AddInstanceForAsyncDisposal(tracerLifecyccle); + debugRpcModuleLifetime.Disposer.AddInstanceForAsyncDisposal(tracerLifecycle); rootLifetimeScope.Disposer.AddInstanceForAsyncDisposal(debugRpcModuleLifetime); return debugRpcModuleLifetime.Resolve(); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs index a351ecf964c..802e49066ef 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/DebugRpcModule.cs @@ -416,9 +416,9 @@ public ResultWrapper debug_getRawHeader(BlockParameter blockParameter) return ResultWrapper.Success(rlp.Bytes); } - public Task> debug_getSyncStage() + public Task> debug_getSyncStage() { - return ResultWrapper.Success(debugBridge.GetCurrentSyncStage()); + return ResultWrapper.Success(debugBridge.GetCurrentSyncStage()); } public ResultWrapper> debug_standardTraceBlockToFile(Hash256 blockHash, GethTraceOptions options = null) diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugBridge.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugBridge.cs index 9f18d1476f2..5e2d720b04a 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugBridge.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugBridge.cs @@ -35,7 +35,7 @@ public interface IDebugBridge void UpdateHeadBlock(Hash256 blockHash); Task MigrateReceipts(long from, long to); void InsertReceipts(BlockParameter blockParameter, TxReceipt[] receipts); - SyncReportSymmary GetCurrentSyncStage(); + SyncReportSummary GetCurrentSyncStage(); bool HaveNotSyncedHeadersYet(); IEnumerable TraceBlockToFile(Hash256 blockHash, CancellationToken cancellationToken, GethTraceOptions? gethTraceOptions = null); IEnumerable TraceBadBlockToFile(Hash256 blockHash, CancellationToken cancellationToken, GethTraceOptions? gethTraceOptions = null); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugRpcModule.cs index 6e0e7e3f132..8b2e532425e 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/DebugModule/IDebugRpcModule.cs @@ -104,8 +104,8 @@ public interface IDebugRpcModule : IRpcModule [JsonRpcMethod(Description = "Get Raw Transaction format.")] ResultWrapper debug_getRawTransaction(Hash256 transactionHash); - [JsonRpcMethod(Description = "Retrives Nethermind Sync Stage, With extra Metadata")] - Task> debug_getSyncStage(); + [JsonRpcMethod(Description = "Retrieves Nethermind Sync Stage, With extra Metadata")] + Task> debug_getSyncStage(); [JsonRpcMethod(Description = "Writes to a file the full stack trace of all invoked opcodes of the transaction specified (or all transactions if not specified) that was included in the block specified. The parent of the block must be present or it will fail.", IsImplemented = true, IsSharable = false)] diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.TransactionExecutor.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.TransactionExecutor.cs index 3e1ab8a41d6..f731ae4786e 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.TransactionExecutor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.TransactionExecutor.cs @@ -42,7 +42,7 @@ private abstract class TxExecutor(IBlockchainBridge blockchainBridge, I return ZeroMaxFeePerGasError; if (eip1559Transaction.MaxFeePerGas < eip1559Transaction.MaxPriorityFeePerGas) - return MaxFeePerGasSmallerThenMaxPriorityFeePerGasError( + return MaxFeePerGasSmallerThanMaxPriorityFeePerGasError( eip1559Transaction.MaxFeePerGas, eip1559Transaction.MaxPriorityFeePerGas); } @@ -138,7 +138,7 @@ protected ResultWrapper CreateResultWrapper(bool inputError, string? er private const string MissingToInBlobTxError = "missing \"to\" in blob transaction"; private const string ZeroMaxFeePerBlobGasError = "maxFeePerBlobGas, if specified, must be non-zero"; private const string ZeroMaxFeePerGasError = "maxFeePerGas must be non-zero"; - private static string MaxFeePerGasSmallerThenMaxPriorityFeePerGasError( + private static string MaxFeePerGasSmallerThanMaxPriorityFeePerGasError( UInt256? maxFeePerGas, UInt256? maxPriorityFeePerGas) => $"maxFeePerGas ({maxFeePerGas}) < maxPriorityFeePerGas ({maxPriorityFeePerGas})"; diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs index 6adb304f637..af3617fe38a 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs @@ -67,6 +67,7 @@ public partial class EthRpcModule( IForkInfo forkInfo, ulong? secondsPerSlot) : IEthRpcModule { + public const int GetProofStorageKeyLimit = 1000; protected readonly Encoding _messageEncoding = Encoding.UTF8; protected readonly IJsonRpcConfig _rpcConfig = rpcConfig ?? throw new ArgumentNullException(nameof(rpcConfig)); protected readonly IBlockchainBridge _blockchainBridge = blockchainBridge ?? throw new ArgumentNullException(nameof(blockchainBridge)); @@ -142,9 +143,7 @@ public ResultWrapper> eth_accounts() { try { - Address[] result = _wallet.GetAccounts(); - Address[] data = result.ToArray(); - return ResultWrapper>.Success(data.ToArray()); + return ResultWrapper>.Success(_wallet.GetAccounts()); } catch (Exception) { @@ -584,47 +583,49 @@ public ResultWrapper> eth_getFilterLogs(UInt256 filterId) public ResultWrapper> eth_getLogs(Filter filter) { - BlockParameter fromBlock = filter.FromBlock; - BlockParameter toBlock = filter.ToBlock; + BlockParameter fromBlock = filter.FromBlock!; + BlockParameter toBlock = filter.ToBlock!; // because of lazy evaluation of enumerable, we need to do the validation here first using CancellationTokenSource timeout = BuildTimeoutCancellationTokenSource(); CancellationToken cancellationToken = timeout.Token; - if (!TryFindBlockHeaderOrUseLatest(_blockFinder, ref toBlock, out SearchResult toBlockResult, out long? sourceToBlockNumber)) + long? headNumber = _blockFinder.Head?.Number; + if (headNumber < fromBlock.BlockNumber || headNumber < toBlock.BlockNumber) { - return FailWithNoHeadersSyncedYet(toBlockResult); + return ResultWrapper>.Fail("requested block range is in the future", ErrorCodes.InvalidParams); } - - cancellationToken.ThrowIfCancellationRequested(); - - SearchResult fromBlockResult; - long? sourceFromBlockNumber; - - if (fromBlock == toBlock) + if (fromBlock.BlockNumber > toBlock.BlockNumber) { - fromBlockResult = toBlockResult; - sourceFromBlockNumber = sourceToBlockNumber; + return ResultWrapper>.Fail("invalid block range params", ErrorCodes.InvalidParams); } - else if (!TryFindBlockHeaderOrUseLatest(_blockFinder, ref fromBlock, out fromBlockResult, out sourceFromBlockNumber)) + + SearchResult fromResult = blockFinder.SearchForHeader(fromBlock); + if (fromResult.IsError) { - return FailWithNoHeadersSyncedYet(fromBlockResult); + return FailWithNoHeadersSyncedYet(fromResult); } - if (sourceFromBlockNumber > sourceToBlockNumber) + cancellationToken.ThrowIfCancellationRequested(); + + SearchResult toResult; + if (fromBlock == toBlock) { - return ResultWrapper>.Fail("invalid block range params", ErrorCodes.InvalidParams); + toResult = fromResult; } - - if (_blockFinder.Head?.Number is not null && sourceFromBlockNumber > _blockFinder.Head.Number) + else { - return ResultWrapper>.Success([]); + toResult = blockFinder.SearchForHeader(toBlock); + if (toResult.IsError) + { + return FailWithNoHeadersSyncedYet(toResult); + } } cancellationToken.ThrowIfCancellationRequested(); - BlockHeader fromBlockHeader = fromBlockResult.Object; - BlockHeader toBlockHeader = toBlockResult.Object; + BlockHeader fromBlockHeader = fromResult.Object!; + BlockHeader toBlockHeader = toResult.Object!; try { @@ -655,36 +656,18 @@ public ResultWrapper> eth_getLogs(Filter filter) ResultWrapper> FailWithNoHeadersSyncedYet(SearchResult blockResult) => GetFailureResult, BlockHeader>(blockResult, _ethSyncingInfo.SyncMode.HaveNotSyncedHeadersYet()); - - // If there is an error, we check if we seach by number and it's after the head, then try to use head instead - static bool TryFindBlockHeaderOrUseLatest(IBlockFinder blockFinder, ref BlockParameter blockParameter, out SearchResult blockResult, out long? sourceBlockNumber) - { - blockResult = blockFinder.SearchForHeader(blockParameter); - - if (blockResult.IsError) - { - if (blockParameter.Type is BlockParameterType.BlockNumber && - blockFinder.Head?.Number < blockParameter.BlockNumber) - { - blockResult = new SearchResult(blockFinder.Head.Header); - - sourceBlockNumber = blockParameter.BlockNumber.Value; - return true; - } - - sourceBlockNumber = null; - return false; - } - - sourceBlockNumber = blockResult.Object.Number; - return true; - } } // https://github.com/ethereum/EIPs/issues/1186 - public ResultWrapper eth_getProof(Address accountAddress, UInt256[] storageKeys, - BlockParameter blockParameter) + public ResultWrapper eth_getProof(Address accountAddress, HashSet storageKeys, BlockParameter? blockParameter) { + if (storageKeys.Count > GetProofStorageKeyLimit) + { + return ResultWrapper.Fail( + $"storageKeys: {storageKeys.Count} is over the query limit {GetProofStorageKeyLimit}.", + ErrorCodes.InvalidParams); + } + SearchResult searchResult = _blockFinder.SearchForHeader(blockParameter); if (searchResult.IsError) { @@ -699,7 +682,7 @@ public ResultWrapper eth_getProof(Address accountAddress, UInt256[ } AccountProofCollector accountProofCollector = new(accountAddress, storageKeys); - _blockchainBridge.RunTreeVisitor(accountProofCollector, header!.StateRoot!); + _blockchainBridge.RunTreeVisitor(accountProofCollector, header!); return ResultWrapper.Success(accountProofCollector.BuildResult()); } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs index 0b3a0284bdb..57f26cee883 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/Filter.cs @@ -1,28 +1,29 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; using System.Text.Json; - using Nethermind.Blockchain.Find; +using Nethermind.Core; +using Nethermind.Core.Crypto; using Nethermind.JsonRpc.Data; namespace Nethermind.JsonRpc.Modules.Eth; public class Filter : IJsonRpcParam { - public object? Address { get; set; } + public HashSet? Address { get; set; } - public BlockParameter? FromBlock { get; set; } + public BlockParameter FromBlock { get; set; } - public BlockParameter? ToBlock { get; set; } + public BlockParameter ToBlock { get; set; } - public IEnumerable? Topics { get; set; } + public IEnumerable? Topics { get; set; } public void ReadJson(JsonElement filter, JsonSerializerOptions options) { JsonDocument doc = null; - string blockHash = null; try { if (filter.ValueKind == JsonValueKind.String) @@ -31,34 +32,39 @@ public void ReadJson(JsonElement filter, JsonSerializerOptions options) filter = doc.RootElement; } - if (filter.TryGetProperty("blockHash"u8, out JsonElement blockHashElement)) - { - blockHash = blockHashElement.GetString(); - } + bool hasBlockHash = filter.TryGetProperty("blockHash"u8, out JsonElement blockHashElement); + bool hasFromBlock = filter.TryGetProperty("fromBlock"u8, out JsonElement fromBlockElement); + bool hasToBlock = filter.TryGetProperty("toBlock"u8, out JsonElement toBlockElement); - if (blockHash is null) + if (hasBlockHash && blockHashElement.ValueKind != JsonValueKind.Null) { - filter.TryGetProperty("fromBlock"u8, out JsonElement fromBlockElement); - FromBlock = BlockParameterConverter.GetBlockParameter(fromBlockElement.ToString()); - filter.TryGetProperty("toBlock"u8, out JsonElement toBlockElement); - ToBlock = BlockParameterConverter.GetBlockParameter(toBlockElement.ToString()); + if (hasFromBlock || hasToBlock) + { + throw new ArgumentException("cannot specify both BlockHash and FromBlock/ToBlock, choose one or the other"); + } + + FromBlock = new(new Hash256(blockHashElement.ToString())); + ToBlock = FromBlock; } else { - FromBlock = ToBlock = BlockParameterConverter.GetBlockParameter(blockHash); + FromBlock = hasFromBlock && fromBlockElement.ValueKind != JsonValueKind.Null + ? BlockParameterConverter.GetBlockParameter(fromBlockElement.ToString()) + : BlockParameter.Earliest; + ToBlock = hasToBlock && toBlockElement.ValueKind != JsonValueKind.Null + ? BlockParameterConverter.GetBlockParameter(toBlockElement.ToString()) + : BlockParameter.Latest; } - filter.TryGetProperty("address"u8, out JsonElement addressElement); - Address = GetAddress(addressElement, options); + if (filter.TryGetProperty("address"u8, out JsonElement addressElement)) + { + Address = GetAddress(addressElement, options); + } if (filter.TryGetProperty("topics"u8, out JsonElement topicsElement) && topicsElement.ValueKind == JsonValueKind.Array) { Topics = GetTopics(topicsElement, options); } - else - { - Topics = null; - } } finally { @@ -66,27 +72,57 @@ public void ReadJson(JsonElement filter, JsonSerializerOptions options) } } - private static object? GetAddress(JsonElement? token, JsonSerializerOptions options) => GetSingleOrMany(token, options); + private static HashSet? GetAddress(JsonElement token, JsonSerializerOptions options) + { + switch (token.ValueKind) + { + case JsonValueKind.Undefined or JsonValueKind.Null: + return null; + case JsonValueKind.String: + return [new AddressAsKey(new Address(token.ToString()))]; + case JsonValueKind.Array: + HashSet result = new(token.GetArrayLength()); + foreach (JsonElement element in token.EnumerateArray()) + { + result.Add(new(new Address(element.ToString()))); + } + + return result; + default: + throw new ArgumentException("invalid address field"); + } + } - private static IEnumerable GetTopics(JsonElement? array, JsonSerializerOptions options) + private static IEnumerable? GetTopics(JsonElement? array, JsonSerializerOptions options) { if (array is null) { yield break; } - foreach (var token in array.GetValueOrDefault().EnumerateArray()) + foreach (JsonElement token in array.Value.EnumerateArray()) { - yield return GetSingleOrMany(token, options); + switch (token.ValueKind) + { + case JsonValueKind.Undefined or JsonValueKind.Null: + yield return null; + break; + case JsonValueKind.String: + yield return [new Hash256(token.GetString()!)]; + break; + case JsonValueKind.Array: + Hash256[] result = new Hash256[token.GetArrayLength()]; + int i = 0; + foreach (JsonElement element in token.EnumerateArray()) + { + result[i++] = new Hash256(element.ToString()); + } + + yield return result; + break; + default: + throw new ArgumentException("invalid topics field"); + } } } - - private static object? GetSingleOrMany(JsonElement? token, JsonSerializerOptions options) => token switch - { - null => null, - { ValueKind: JsonValueKind.Undefined } _ => null, - { ValueKind: JsonValueKind.Null } _ => null, - { ValueKind: JsonValueKind.Array } _ => token.GetValueOrDefault().Deserialize(options), - _ => token.GetValueOrDefault().GetString(), - }; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/GasPrice/GasPriceOracle.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/GasPrice/GasPriceOracle.cs index 5dba25f2d09..ff517e18c4f 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/GasPrice/GasPriceOracle.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/GasPrice/GasPriceOracle.cs @@ -17,6 +17,8 @@ namespace Nethermind.JsonRpc.Modules.Eth.GasPrice { public class GasPriceOracle : IGasPriceOracle { + private static readonly IComparer UInt256Comparer = Comparer.Default; + protected readonly IBlockFinder _blockFinder; protected readonly ILogger _logger; protected readonly UInt256 _minGasPrice; @@ -55,14 +57,14 @@ public virtual ValueTask GetGasPriceEstimate() return ValueTask.FromResult(price!.Value); } - IEnumerable txGasPrices = GetSortedGasPricesFromRecentBlocks(headBlock.Number); + IEnumerable txGasPrices = GetGasPricesFromRecentBlocks(headBlock.Number); UInt256 gasPriceEstimate = GetGasPriceAtPercentile(txGasPrices.ToList()) ?? GetMinimumGasPrice(headBlock.BaseFeePerGas); gasPriceEstimate = UInt256.Min(gasPriceEstimate!, EthGasPriceConstants.MaxGasPrice); _gasPriceEstimation.Set(headBlockHash, gasPriceEstimate); return ValueTask.FromResult(gasPriceEstimate!); } - internal IEnumerable GetSortedGasPricesFromRecentBlocks(long blockNumber) => + internal IEnumerable GetGasPricesFromRecentBlocks(long blockNumber) => GetGasPricesFromRecentBlocks(blockNumber, BlockLimit, static (transaction, eip1559Enabled, baseFee) => transaction.CalculateEffectiveGasPrice(eip1559Enabled, baseFee)); @@ -106,8 +108,7 @@ IEnumerable GetBlocks(long currentBlockNumber) } } - return GetGasPricesFromRecentBlocks(GetBlocks(blockNumber), numberOfBlocks, calculateGasFromTransaction) - .OrderBy(gasPrice => gasPrice); + return GetGasPricesFromRecentBlocks(GetBlocks(blockNumber), numberOfBlocks, calculateGasFromTransaction); } private IEnumerable GetGasPricesFromRecentBlocks(IEnumerable blocks, int blocksToGoBack, CalculateGas calculateGasFromTransaction) @@ -161,7 +162,73 @@ private IEnumerable GetGasPricesFromRecentBlocks(IEnumerable blo return roundedIndex < 0 ? null - : txGasPriceList[roundedIndex]; + : SelectKthSmallestInPlace(txGasPriceList, roundedIndex); + } + + /// + /// Selects the kth smallest element (0-based) in-place using a Quickselect-style partitioning algorithm. + /// This mutates the input list order. + /// + internal static UInt256 SelectKthSmallestInPlace(List list, int k) + { + if ((uint)k >= (uint)list.Count) + { + throw new ArgumentOutOfRangeException(nameof(k), k, "k must be within [0, list.Count)."); + } + + int left = 0; + int right = list.Count - 1; + + while (true) + { + if (left == right) + { + return list[left]; + } + + // Deterministic pivot for stable perf/repro (median-of-range). + int pivotIndex = left + ((right - left) >> 1); + pivotIndex = Partition(list, left, right, pivotIndex, UInt256Comparer); + + if (k == pivotIndex) + { + return list[k]; + } + + if (k < pivotIndex) + { + right = pivotIndex - 1; + } + else + { + left = pivotIndex + 1; + } + } + } + + private static int Partition(List list, int left, int right, int pivotIndex, IComparer comparer) + { + UInt256 pivotValue = list[pivotIndex]; + Swap(list, pivotIndex, right); + + int storeIndex = left; + for (int i = left; i < right; i++) + { + if (comparer.Compare(list[i], pivotValue) < 0) + { + Swap(list, storeIndex, i); + storeIndex++; + } + } + + Swap(list, right, storeIndex); + return storeIndex; + } + + private static void Swap(List list, int a, int b) + { + if (a == b) return; + (list[a], list[b]) = (list[b], list[a]); } private static int GetRoundedIndexAtPercentile(int count) diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs index 3e829e01007..fd6ca2c2770 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/IEthRpcModule.cs @@ -95,7 +95,7 @@ public interface IEthRpcModule : IRpcModule ResultWrapper eth_getStorageAt([JsonRpcParameter(ExampleValue = "[\"0x000000000000000000000000c666d239cbda32aa7ebca894b6dc598ddb881285\",\"0x2\"]")] Address address, UInt256 positionIndex, BlockParameter? blockParameter = null); [JsonRpcMethod(IsImplemented = true, - Description = "Returns account nonce (number of trnsactions from the account since genesis) at the given block number", + Description = "Returns account nonce (number of transactions from the account since genesis) at the given block number", IsSharable = true, ExampleResponse = "0x3e")] Task> eth_getTransactionCount([JsonRpcParameter(ExampleValue = "[\"0xae3ed7a6ccdddf2914133d0669b5f02ff6fa8ad2\"]")] Address address, BlockParameter? blockParameter = null); @@ -281,7 +281,11 @@ ResultWrapper eth_getTransactionByBlockNumberAndIndex( IsImplemented = true, IsSharable = true, ExampleResponse = " \"accountProof\": [\"0xf90211a0446f43a2d3e433732c75bcf3519f4844e0441a4d39b31395ee9a65700c30d3b4a0b9720db63afe9909418fb6e02c9d9f225310856549cc1b66b486041f2d867250a046e6e560e52d4fe0d2f6609f489ba85f18ad1655fee18452588dc08388fbd711a01e68f36c91bd15cbf65587d6db2a7cbd6635907291e77dd80152161da9a28a48a0d2178a1891c26ccaa2d2cec82c231a0640a26a1f5e07c7b5493761bdb3aa94e5a0fa909327d406980a2e602eadd3f56cf8dc89320d4662340962e9cac2beee3d8da0a0fc71e7dec6320a993b4b65b2f82544910d0a4a7c6f8c5a1ebaa38357d259e3a0680161dec84c5f1c8d5e2a585c9708b1b6fbc2dc664a432e045d99f5e7d89259a0f76a745765be58d46d795c44d3900a4a05b6396530244d50822616c8bbb11e19a0594824352d58f5caff819c8df9581b6a41d0e94eb584ed0431d48b48f320bb5ca0e762eb52b2bcacd728fac605de6229dc83588001ecddcd3b454b64c393ee69eda0d319cf1021af0a8535e4916c3404c84917957d73d0711f71fd6456b4533993bba0878240238a894e6fa798671ac3792563c6666a7c7fba8066d090b65d6a7aa701a03c03fdb4d8f4b241442814cbab24ddb42b75c78874f92fedc162b65d0820fc4da06a3318509aa9ff009b9acb9b348f197a134a46a46295714f436d4fbb19057e69a04139df1b6e0a59b093b35f34f9e5e890bc06832e63b366d768dc29e8638b828480\",\"0xf90211a023459f17e04fba3d19c6993f5be413969842fdbdc85d234a91b2f6b08a38be87a0153060eafecbff55ef0794802ef722b6c66698141cdc0610352d3a426976adeba0bd642b7c5111a1fd09da33feb6df031dc352b6cb20fbbe5ebe3eb328db233bd4a0705bff29e05c7ef69c07fecaa5db2457b2f124befc82f9fe6b0e54c8e35632eba03c1b4ffc076434de97050d2351c24689cfaefaa6cf8dc398dd3b8ce365e652c1a0a1ebf845ea0eb252e2a2e5c422ccd74878a3733144dfd62bcaad34758cc98652a01e4184586f5bdbb17ba74fd87539f02378c7adcef99f1538108f9555520e32d6a0b8acdfd5b644fa2c9a54f68039a3af4c6562c1e7f91ea9e63bda5a849f1260b6a05c1f036a2e7a5829799fc7df2d87eac3e7aee55df461b040c36f5b5c61781059a0a67fd871d32642e44120331f76c2616096d04d7fa1a7db421bafbc39713d8bfba085c15b7ab64f61670f4422adb82176d5808fad4abde6fddda507b0e5ff92ba14a0d95e8f16a39d4e52c67c617eef486adcd947854373ac074ff498150c7ca1ab5da03d9d7be595000872ad6aec05667ad85e1aaaeb2050a692818d3e60d8f1628d8ba0984c657192b052d13fb717051631d67fbc83dd5dcb4d074a2fddc01aa122d95ba03643408862d758aea269c05027a1cd616c957e0db5daea529b56964db8b4f04ba01020dce8d692c3d84d9ae3e42c35e4d8adbddf7b4dd3e09e543fc980849f016e80\",\"0xf90211a04c71b4b56ed723da1c1353ec1b4c23e71dfa821664d4041c1ee1770221f507b6a031c851f261a38df9b2bece1a1cb6985bccfaa10d2bb15954b82cd2ceaad87032a08a4a3d0cc260cf0e0fef54490ce45796fdd3f522451976ca7834563c839c630fa003d074f79074566cd33a3d6a57b6ca8426ca9ea972f66b5dfde00f73287fcfcea07003d29a5bd192038600118ab5941af5c79c1f0fc6184ad564180b809c36c7c4a05f181c50402dcff567abe1c6679a8d5e3825125abca4d969c7cbf76503416813a06a85dfca80e442ef79b66162099d52eaf67718589eb794755ce57dc071a85cdaa085cba9e6937a8a5f0a7d1b5ee9eb9f03c40f89eb13d9d4e0e5fbc574c2b852faa063f93dce441a3373cfc2d1c855884682dfd8d09d1eb9844c73d88eb8d5a7cdfda0e4bc0d2597e5fd0a4cd5e76a03b657ef8959264bdeaf95c4412ebd4ff736ce44a00239290e698aa04485e0c342cfb76ccf27a3e45a161b8b1b534e0c46707b92c8a0080c3439fb84730924539797aad8d017c5f7e008314ed9086450d80ec2b0d7aba0861dbe37b9b9e0f58b6fdb83eec28045c5f7f1861530f47f78fc8a2b18a6bd8da0036697e8dc063e9086d115d468c934a01123adb3c66dcc236ee4aa8141888626a033c6f574ee79d9b1322e9ca1131a5984b33cc8881e6ac8ebd6ca36f3437cedcda07fc2855e6bb0f276202094dffe49f2b62f2366d9aba9db3ffe76d62bcdc29f0d80\",\"0xf90211a06995d919b53eefa0b097d75c2a5dee2c54109a06d3b60586327fa0086437b801a05c7d7c92f9f1e49cf27c5d97b4a96302f033d42df5b1d7c013ef05031d67e567a05278417d007913a1e7d6606fb464e7b81f6cee91b6a1d250c67b3822d9fc68d8a0fba6d9cd68fe72db07af9d99e30c32502e0afb15ee9712f6781014195444b9e1a07dca25ba23f429b5960d9feb23367e2a088e50211f280118b7f1703e6d47103fa0399eb6e0d4390688f6b28df56c7ad72d6b6cbac9066110c6a727fe35cd889e9da08ef84ddaa3b70095fb5624878289744740a9f8761ef1132ba722abc977a218ffa04296811ae184892e2d5ecc18d05fc6279d6168eb0f3abb1f97d8d0a0721c12fba05c46766c579b8a0b8a0b79b84f6cd1e5dae1c53a2988883b0385daa2cf3bdf82a01a4ba17dd1b59147a321dd374a22a0d959f1a79d70132db7f1a8b89968ff6062a0f7ffc6f3921c6bccd47c862519409e63f51d39aaa215819c664b1adb48a940b0a0dc6e636385407900a649917fb772b0972d50d197e9fd5cdb853a1c98a29e6a47a0674b224cf784c59ca937bfebbdcd8dec05ddbd57400b04f5965558a0c2d2299ca0f95ce8c921c5b17ebf74563f2496a88631aa6a697bfd9e3e22b326efa453115ea0fc133bc6b9dd098933c816660df2959074f47dfc4ab3a10fd2059a2b2e0e911aa057cac15218d6013890df78eec099144ba2000e3eea73a3498d0eb9b1f733459080\",\"0xf90211a0400aafe69a1a482277db720d12b9c0b98695f5dd78c6faf5421b3ddac50165a6a0235987542e4b37aa8e6957776c9dff11d6818797db5ad505de5e0049045c7e20a0f573b4776f8b323b7d55850300d53855cfa6fa5fe6e36ba64da6bb263fef774aa0b3a36d14d660c3492785b0f1488a2231b6d83bd51268685b95ba9267aa331fe2a0096e8c65bae8fce7d234710a1e1b8c98bd4fb2d56f8bb2eda7ef20d1cf31c7e2a059194c8bf50c2ac393c4c60a59c7ddf0c515bd9f545fc4ef212629a8b96af62aa0ffe882f4e2f1e8e49c7777f6f9b4438a9f31d4e5cefe82c96fdd3587d9a95173a00127ced7fdbdd57cd5ed8b766c9312c09e0c67a350087d22b4cc7b2d17a45479a0cfc030a250448838caa716cd2767cd1a4837b29019f474980720c94fe2ce412ea079ec358d2b4122692bf70eb73a0ddb0ff4bfeb05d503fe1acafe068e2d3d33cfa0733e2ccdc638ca3c940c566c742a1b9d58f7caaa062e8a121c07f5e3367160a8a0aa1f403798b71c67b821e6f6128cc5366bebe145ebd563714cf9972b2474814ea01b988afc628922aeed3de606a8a462093f1c0c803a563bbe178552a360bad1e1a0082741e2219024bf4e19f5b1b0643e5e885cb7dfb4cdc0a51faf5bd9f92ff9b6a03c86490fe8f0256be44b95815086d95cb62fdbc3ede63ca08d12d68f274b7fc5a03a81565e860ac32921ed4c9f4e0ace3b341c342abd030d4955f2d1e64dd81d2b80\",\"0xf8f1a0bd9a0d9559513a6c7bf427816142d254d5a9049e9ff385f3514b50cb828951fc808080a07d37353f509c9bdc99635bd75fde71a6ef99271154ac4ffd5c437e0b951d5eaca029e3beec2f52c99a1fa73251ed64486f2766af3dcb950900679f7fd740badfdaa09b348c93803521a41bd2a754e3ea5435bb2663724cdfb70a87984458b53f03dea0904e696aceac8c89e2825e0dae8add52a9b46faef2ffbabb932e8bc1267e48ba80a0935dedba6ec5fb5b89285993c5f1be0cb77492e63e11bb38b5aca18011699eb8a06b52f587932dfb669f6cbefe35b251c6d8e6b5e8a2e1c1a7c2a452a4f2917b0d808080808080\"],\"address\":\"0x7f0d15c7faae65896648c8273b6d7e43f58fa842\",\"balance\":\"0x0\",\"codeHash\":\"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470\",\"nonce\":\"0x0\",\"storageHash\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"storageProof\":[{\"key\":\"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\",\"proof\":[],\"value\":\"0x00\"]")] - ResultWrapper eth_getProof([JsonRpcParameter(ExampleValue = "[\"0x7F0d15C7FAae65896648C8273B6d7E43f58Fa842\",[ \"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\" ],\"latest\"]")] Address accountAddress, UInt256[] hashRate, BlockParameter blockParameter); + ResultWrapper eth_getProof( + [JsonRpcParameter(ExampleValue = "[\"0x7F0d15C7FAae65896648C8273B6d7E43f58Fa842\",[ \"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421\" ],\"latest\"]")] + Address accountAddress, + HashSet storageKeys, + BlockParameter? blockParameter = null); [JsonRpcMethod(IsImplemented = true, Description = "Retrieves Accounts via Address and Blocknumber", IsSharable = true)] ResultWrapper eth_getAccount([JsonRpcParameter(ExampleValue = "[\"0xaa00000000000000000000000000000000000000\", \"latest\"]")] Address accountAddress, BlockParameter? blockParameter = null); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SimulateTxExecutor.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SimulateTxExecutor.cs index 02aafe4a5f5..9036676f507 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SimulateTxExecutor.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Eth/SimulateTxExecutor.cs @@ -1,7 +1,6 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -214,7 +213,8 @@ private static int MapSimulateErrorCode(TransactionResult txResult) TransactionResult.ErrorType.SenderHasDeployedCode => ErrorCodes.InvalidParams, TransactionResult.ErrorType.SenderNotSpecified => ErrorCodes.InternalError, TransactionResult.ErrorType.TransactionSizeOverMaxInitCodeSize => ErrorCodes.MaxInitCodeSizeExceeded, - TransactionResult.ErrorType.WrongTransactionNonce => ErrorCodes.InternalError, + TransactionResult.ErrorType.TransactionNonceTooHigh => ErrorCodes.InternalError, + TransactionResult.ErrorType.TransactionNonceTooLow => ErrorCodes.InternalError, _ => ErrorCodes.InternalError }; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityTransaction.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityTransaction.cs index 5aeb8b1d20a..5d31454d193 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityTransaction.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Parity/ParityTransaction.cs @@ -8,6 +8,7 @@ using Nethermind.Core.Extensions; using Nethermind.Int256; using Nethermind.Evm; +using Nethermind.Serialization.Json; namespace Nethermind.JsonRpc.Modules.Parity { @@ -31,6 +32,7 @@ public class ParityTransaction public byte[] Raw { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.Never)] public Address Creates { get; set; } + [JsonConverter(typeof(PublicKeyConverter))] public PublicKey PublicKey { get; set; } public ulong? ChainId { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.Never)] diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Personal/IPersonalRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Personal/IPersonalRpcModule.cs index cb6350d5e1d..eb63b3bbf43 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Personal/IPersonalRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Personal/IPersonalRpcModule.cs @@ -34,7 +34,7 @@ public interface IPersonalRpcModule : IRpcModule ExampleResponse = "0x1ddea39c8b8a2202cd9f56bc9a6ecdbf1cf3d5f5")] ResultWrapper
personal_ecRecover([JsonRpcParameter(ExampleValue = "[\"0xdeadbeaf\", \"0xa3f20717a250c2b0b729b7e5becbff67fdaef7e0699da4de7ca5895b02a170a12d887fd3b17bfdce3481f10bea41f45ba9f709d39ce8325427b57afcfc994cee1b\"]")] byte[] message, byte[] signature); - [JsonRpcMethod(Description = "The sign method calculates an Ethereum specific signature with: sign(keccack256(\"\x19Ethereum Signed Message:\n\" + len(message) + message))).", + [JsonRpcMethod(Description = "The sign method calculates an Ethereum specific signature with: sign(keccak256(\"\x19Ethereum Signed Message:\n\" + len(message) + message))).", IsImplemented = false, ExampleResponse = "0xa3f20717a250c2b0b729b7e5becbff67fdaef7e0699da4de7ca5895b02a170a12d887fd3b17bfdce3481f10bea41f45ba9f709d39ce8325427b57afcfc994cee1b")] ResultWrapper personal_sign([JsonRpcParameter(ExampleValue = "[\"0xdeadbeaf\", \"0x9b2055d370f73ec7d8a03e965129118dc8f5bf83\"]")] byte[] message, Address address, string passphrase = null); diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofRpcModule.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofRpcModule.cs index 6a816708016..4ba87a89e52 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofRpcModule.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Proof/ProofRpcModule.cs @@ -88,7 +88,7 @@ public ResultWrapper proof_call(TransactionForRpc tx, Block // we collect proofs from before execution (after learning which addresses will be touched) // if we wanted to collect post execution proofs then we would need to use BeforeRestore on the tracer - callResultWithProof.Accounts = CollectAccountProofs(scope.Component, sourceHeader.StateRoot, proofTxTracer); + callResultWithProof.Accounts = CollectAccountProofs(scope.Component, sourceHeader, proofTxTracer); return ResultWrapper.Success(callResultWithProof); } @@ -165,7 +165,7 @@ public ResultWrapper proof_getTransactionReceipt(Hash256 txHas return ResultWrapper.Success(receiptWithProof); } - private AccountProof[] CollectAccountProofs(ITracer tracer, Hash256 stateRoot, ProofTxTracer proofTxTracer) + private AccountProof[] CollectAccountProofs(ITracer tracer, BlockHeader? baseBlock, ProofTxTracer proofTxTracer) { List accountProofs = new(); foreach (Address address in proofTxTracer.Accounts) @@ -174,7 +174,7 @@ private AccountProof[] CollectAccountProofs(ITracer tracer, Hash256 stateRoot, P .Where(s => s.Address == address) .Select(s => s.Index).ToArray()); - tracer.Accept(collector, stateRoot); + tracer.Accept(collector, baseBlock); accountProofs.Add(collector.BuildResult()); } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/LogsSubscription.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/LogsSubscription.cs index 5afc5f20663..4ea94824146 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/LogsSubscription.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/LogsSubscription.cs @@ -24,7 +24,7 @@ public class LogsSubscription : Subscription public LogsSubscription( IJsonRpcDuplexClient jsonRpcDuplexClient, IReceiptMonitor receiptCanonicalityMonitor, - IFilterStore? store, + FilterStore? store, IBlockTree? blockTree, ILogManager? logManager, Filter? filter = null) @@ -33,7 +33,7 @@ public LogsSubscription( _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); _receiptCanonicalityMonitor = receiptCanonicalityMonitor ?? throw new ArgumentNullException(nameof(receiptCanonicalityMonitor)); _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - IFilterStore filterStore = store ?? throw new ArgumentNullException(nameof(store)); + FilterStore filterStore = store ?? throw new ArgumentNullException(nameof(store)); if (filter is not null) { diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerAddDropResponse.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerAddDropResponse.cs index 0a86ec1810d..fe66e540eea 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerAddDropResponse.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerAddDropResponse.cs @@ -19,7 +19,6 @@ public PeerAddDropResponse(PeerInfo peerInfo, string subscriptionType, string? e [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string Type { get; set; } - [JsonConverter(typeof(PublicKeyHashedConverter))] public PublicKey Peer { get; set; } public string Local { get; set; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerEventResponse.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerEventResponse.cs index 1147d50ff00..6083a13d1d4 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerEventResponse.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerEventResponse.cs @@ -14,7 +14,6 @@ public class PeerEventResponse { [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Type { get; set; } - [JsonConverter(typeof(PublicKeyHashedConverter))] public PublicKey? Peer { get; set; } public string? Protocol { get; set; } public int? MsgPacketType { get; set; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerMsgSendRecvResponse.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerMsgSendRecvResponse.cs index f5afa80c503..9c5a37062de 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerMsgSendRecvResponse.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/PeerMsgSendRecvResponse.cs @@ -15,9 +15,9 @@ protected PeerMsgSendRecvResponse() } - public PeerMsgSendRecvResponse(EventArgs eventArgs, string subscripionType, string? e) + public PeerMsgSendRecvResponse(EventArgs eventArgs, string subscriptionType, string? e) { - Type = subscripionType; + Type = subscriptionType; Error = e; } diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactoryExtensions.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactoryExtensions.cs index 3b4fcc3d4df..bc5ee9b6d7c 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactoryExtensions.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SubscriptionFactoryExtensions.cs @@ -2,10 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Nethermind.Blockchain; using Nethermind.Blockchain.Filters; using Nethermind.Core.Specs; @@ -37,7 +33,7 @@ ISpecProvider specProvider public static void RegisterLogsSubscription( this ISubscriptionFactory subscriptionFactory, IReceiptMonitor receiptMonitor, - IFilterStore? filterStore, + FilterStore? filterStore, IBlockTree? blockTree, ILogManager? logManager ) @@ -110,7 +106,7 @@ public static void RegisterStandardSubscriptions( ILogManager? logManager, ISpecProvider specProvider, IReceiptMonitor receiptMonitor, - IFilterStore? filterStore, + FilterStore? filterStore, ITxPool? txPool, IEthSyncingInfo ethSyncingInfo, IPeerPool? peerPool, @@ -131,7 +127,7 @@ public static void RegisterStandardEthSubscriptions( ILogManager? logManager, ISpecProvider specProvider, IReceiptMonitor receiptMonitor, - IFilterStore? filterStore, + FilterStore? filterStore, ITxPool? txPool, IEthSyncingInfo ethSyncingInfo ) diff --git a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SyncingSubscription.cs b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SyncingSubscription.cs index f750e8a40e0..026acce91b7 100644 --- a/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SyncingSubscription.cs +++ b/src/Nethermind/Nethermind.JsonRpc/Modules/Subscribe/SyncingSubscription.cs @@ -40,8 +40,6 @@ public SyncingSubscription( private class SubscriptionSyncingResult { - [JsonIgnore] - public bool? IsSyncing { get; set; } public long? StartingBlock { get; set; } public long? CurrentBlock { get; set; } public long? HighestBlock { get; set; } @@ -63,24 +61,15 @@ private void OnConditionsChange(object? sender, BlockEventArgs e) if (_logger.IsTrace) _logger.Trace($"Syncing subscription {Id} changed syncing status from {_lastIsSyncing} to {isSyncing}"); _lastIsSyncing = isSyncing; - JsonRpcResult result; - if (isSyncing == false) - { - result = CreateSubscriptionMessage(isSyncing); - } - else - { - result = CreateSubscriptionMessage(new SubscriptionSyncingResult() - { - IsSyncing = syncingResult.IsSyncing, - StartingBlock = syncingResult.StartingBlock, - CurrentBlock = syncingResult.CurrentBlock, - HighestBlock = syncingResult.HighestBlock - }); - } - - using (result) + using (JsonRpcResult result = !isSyncing + ? CreateSubscriptionMessage(false) + : CreateSubscriptionMessage(new SubscriptionSyncingResult() + { + StartingBlock = syncingResult.StartingBlock, + CurrentBlock = syncingResult.CurrentBlock, + HighestBlock = syncingResult.HighestBlock + })) { await JsonRpcDuplexClient.SendJsonRpcResult(result); } diff --git a/src/Nethermind/Nethermind.JsonRpc/PipeReaderExtensions.cs b/src/Nethermind/Nethermind.JsonRpc/PipeReaderExtensions.cs index 44aaa377457..1f57e3b7611 100644 --- a/src/Nethermind/Nethermind.JsonRpc/PipeReaderExtensions.cs +++ b/src/Nethermind/Nethermind.JsonRpc/PipeReaderExtensions.cs @@ -3,6 +3,7 @@ using System.Buffers; using System.IO.Pipelines; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -15,11 +16,21 @@ public static async Task ReadToEndAsync(this PipeReader reader, Canc while (true) { ReadResult result = await reader.ReadAsync(cancellationToken).ConfigureAwait(false); - ReadOnlySequence buffer = result.Buffer; if (result.IsCompleted || result.IsCanceled) { return result; } + + // Separate method to shrink the async state machine by not including + // the ReadOnlySequence buffer in the main method + AdvanceReaderToEnd(reader, in result); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + static void AdvanceReaderToEnd(PipeReader reader, in ReadResult result) + { + // Extract buffer reading to a separate method to reduce async state machine size + ReadOnlySequence buffer = result.Buffer; reader.AdvanceTo(buffer.Start, buffer.End); } } diff --git a/src/Nethermind/Nethermind.Logging.NLog/NLogManager.cs b/src/Nethermind/Nethermind.Logging.NLog/NLogManager.cs index 4497f7cf048..02060ce14fa 100644 --- a/src/Nethermind/Nethermind.Logging.NLog/NLogManager.cs +++ b/src/Nethermind/Nethermind.Logging.NLog/NLogManager.cs @@ -101,7 +101,7 @@ private static void SetupLogRules(string logRules) IEnumerable loggingRules = ParseRules(logRules, targets); foreach (LoggingRule loggingRule in loggingRules) { - RemoveOverridenRules(configurationLoggingRules, loggingRule); + RemoveOverriddenRules(configurationLoggingRules, loggingRule); configurationLoggingRules.Add(loggingRule); } } @@ -111,12 +111,12 @@ private static void SetupLogRules(string logRules) private static Target[] GetTargets(IList configurationLoggingRules) => configurationLoggingRules.SelectMany(static r => r.Targets).Distinct().ToArray(); - private static void RemoveOverridenRules(IList configurationLoggingRules, LoggingRule loggingRule) + private static void RemoveOverriddenRules(IList configurationLoggingRules, LoggingRule loggingRule) { - string reqexPattern = $"^{loggingRule.LoggerNamePattern.Replace(".", "\\.").Replace("*", ".*")}$"; + string regexPattern = $"^{loggingRule.LoggerNamePattern.Replace(".", "\\.").Replace("*", ".*")}$"; for (int j = 0; j < configurationLoggingRules.Count;) { - if (Regex.IsMatch(configurationLoggingRules[j].LoggerNamePattern, reqexPattern)) + if (Regex.IsMatch(configurationLoggingRules[j].LoggerNamePattern, regexPattern)) { configurationLoggingRules.RemoveAt(j); } diff --git a/src/Nethermind/Nethermind.Logging/ILogger.cs b/src/Nethermind/Nethermind.Logging/ILogger.cs index 6791b0a00af..e226478b2cf 100644 --- a/src/Nethermind/Nethermind.Logging/ILogger.cs +++ b/src/Nethermind/Nethermind.Logging/ILogger.cs @@ -12,9 +12,9 @@ namespace Nethermind.Logging; /// the struct rather than being an interface call each time. /// #if DEBUG -public struct ILogger +public struct ILogger : IEquatable #else -public readonly struct ILogger +public readonly struct ILogger : IEquatable #endif { private readonly InterfaceLogger _logger; @@ -56,6 +56,8 @@ public readonly void Debug(string text) _logger.Debug(text); } + public bool Equals(ILogger other) => _logger == other._logger; + [MethodImpl(MethodImplOptions.NoInlining)] public readonly void Error(string text, Exception ex = null) { diff --git a/src/Nethermind/Nethermind.Logging/PathUtils.cs b/src/Nethermind/Nethermind.Logging/PathUtils.cs index 564d531032a..e6ec6ac69dd 100644 --- a/src/Nethermind/Nethermind.Logging/PathUtils.cs +++ b/src/Nethermind/Nethermind.Logging/PathUtils.cs @@ -2,32 +2,27 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Diagnostics; using System.IO; using System.Linq; -using System.Reflection; namespace Nethermind.Logging; public static class PathUtils { - public static string ExecutingDirectory { get; } - static PathUtils() { - Process process = Process.GetCurrentProcess(); - if (process.ProcessName.StartsWith("dotnet", StringComparison.OrdinalIgnoreCase) - || process.ProcessName.Equals("ReSharperTestRunner", StringComparison.OrdinalIgnoreCase)) - { - ExecutingDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - } - else - { - ExecutingDirectory = Path.GetDirectoryName(process.MainModule.FileName); - //Console.WriteLine($"Resolved executing directory as {ExecutingDirectory}."); - } + string processName = Path.GetFileNameWithoutExtension(Environment.ProcessPath); + + ExecutingDirectory = processName.Equals("dotnet", StringComparison.OrdinalIgnoreCase) + || processName.Equals("ReSharperTestRunner", StringComparison.OrdinalIgnoreCase) + // A workaround for tests in JetBrains Rider ignoring MTP: + // https://youtrack.jetbrains.com/projects/RIDER/issues/RIDER-131530 + ? AppContext.BaseDirectory + : Path.GetDirectoryName(Environment.ProcessPath); } + public static string ExecutingDirectory { get; } + public static string GetApplicationResourcePath(this string resourcePath, string overridePrefixPath = null) { if (string.IsNullOrWhiteSpace(resourcePath)) @@ -50,13 +45,13 @@ public static string GetApplicationResourcePath(this string resourcePath, string : Path.Combine(ExecutingDirectory, overridePrefixPath, resourcePath); } - static readonly string[] RelativePrefixes = new[] + static readonly string[] RelativePrefixes = [.. new[] { - "." + Path.DirectorySeparatorChar, - "." + Path.AltDirectorySeparatorChar, - ".." + Path.DirectorySeparatorChar, - ".." + Path.AltDirectorySeparatorChar, - }.Distinct().ToArray(); + $".{Path.DirectorySeparatorChar}", + $".{Path.AltDirectorySeparatorChar}", + $"..{Path.DirectorySeparatorChar}", + $"..{Path.AltDirectorySeparatorChar}", + }.Distinct()]; public static bool IsExplicitlyRelative(string resourcePath) => RelativePrefixes.Any(resourcePath.StartsWith); } diff --git a/src/Nethermind/Nethermind.Logging/SimpleConsoleLogManager.cs b/src/Nethermind/Nethermind.Logging/SimpleConsoleLogManager.cs index db87f13ec7b..7409d29e114 100644 --- a/src/Nethermind/Nethermind.Logging/SimpleConsoleLogManager.cs +++ b/src/Nethermind/Nethermind.Logging/SimpleConsoleLogManager.cs @@ -8,21 +8,23 @@ namespace Nethermind.Logging { public class SimpleConsoleLogManager(LogLevel logLevel = LogLevel.Trace, string dateFormat = "yyyy-MM-dd HH-mm-ss.ffff|") : ILogManager { + private readonly SimpleConsoleLogger _logger = new(logLevel, dateFormat); + public static ILogManager Instance { get; } = new SimpleConsoleLogManager(); public ILogger GetClassLogger() { - return new(new SimpleConsoleLogger(logLevel, dateFormat)); + return new(_logger); } public ILogger GetClassLogger([CallerFilePath] string filePath = "") { - return new(new SimpleConsoleLogger(logLevel, dateFormat)); + return new(_logger); } public ILogger GetLogger(string loggerName) { - return new(new SimpleConsoleLogger(logLevel, dateFormat)); + return new(_logger); } } } diff --git a/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs index 5a281b2097d..07dd1df0b51 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuRaMergeEngineModuleTests.cs @@ -84,7 +84,7 @@ public override Task processing_block_should_serialize_valid_responses(string bl public override Task forkchoiceUpdatedV1_should_communicate_with_boost_relay_through_http(string blockHash, string parentHash) => base.forkchoiceUpdatedV1_should_communicate_with_boost_relay_through_http(blockHash, parentHash); - [Ignore("Withdrawals are not withdrawan due to lack of Aura contract in tests")] + [Ignore("Withdrawals are not withdrawn due to lack of Aura contract in tests")] public override Task Can_apply_withdrawals_correctly((Withdrawal[][] Withdrawals, (Address Account, UInt256 BalanceIncrease)[] ExpectedAccountIncrease) input) { return base.Can_apply_withdrawals_correctly(input); @@ -139,7 +139,7 @@ protected override ContainerBuilder ConfigureContainer(ContainerBuilder builder, .AddDecorator((ctx, api) => { // Yes getting from `TestBlockchain` itself, since steps are not run - // and some of these are not from DI. you know... chicken and egg, but dont forgot about rooster. + // and some of these are not from DI. you know... chicken and egg, but don't forget about the rooster. api.TxPool = TxPool; api.TransactionComparerProvider = TransactionComparerProvider; api.FinalizationManager = Substitute.For(); diff --git a/src/Nethermind/Nethermind.Merge.AuRa.Test/AuraWithdrawalProcessorTests.cs b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuraWithdrawalProcessorTests.cs index fc3851d358c..e7a563cc98b 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa.Test/AuraWithdrawalProcessorTests.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa.Test/AuraWithdrawalProcessorTests.cs @@ -79,7 +79,7 @@ public void Should_not_invoke_contract_before_Shanghai() .ExecuteWithdrawals( Arg.Any(), Arg.Any(), - Arg.Any(), - Arg.Any()); + Arg.Any>(), + Arg.Any>()); } } diff --git a/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergePlugin.cs b/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergePlugin.cs index 07e945056a2..56e2615e47b 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa/AuRaMergePlugin.cs @@ -63,7 +63,7 @@ protected override PostMergeBlockProducerFactory CreateBlockProducerFactory() _blocksConfig, _api.LogManager); - protected override IBlockFinalizationManager InitializeMergeFinilizationManager() + protected override IBlockFinalizationManager InitializeMergeFinalizationManager() { return new AuRaMergeFinalizationManager(_api.Context.Resolve(), _auraApi!.FinalizationManager ?? diff --git a/src/Nethermind/Nethermind.Merge.AuRa/AuRaPostMergeBlockProducerFactory.cs b/src/Nethermind/Nethermind.Merge.AuRa/AuRaPostMergeBlockProducerFactory.cs index 7e7d16409bc..32dbe8ed998 100644 --- a/src/Nethermind/Nethermind.Merge.AuRa/AuRaPostMergeBlockProducerFactory.cs +++ b/src/Nethermind/Nethermind.Merge.AuRa/AuRaPostMergeBlockProducerFactory.cs @@ -29,25 +29,5 @@ public AuRaPostMergeBlockProducerFactory( gasLimitCalculator) { } - - public override PostMergeBlockProducer Create( - IBlockProducerEnv producerEnv, - ITxSource? txSource = null) - { - TargetAdjustedGasLimitCalculator targetAdjustedGasLimitCalculator = - new(_specProvider, _blocksConfig); - - return new PostMergeBlockProducer( - txSource ?? producerEnv.TxSource, - producerEnv.ChainProcessor, - producerEnv.BlockTree, - producerEnv.ReadOnlyStateProvider, - _gasLimitCalculator ?? targetAdjustedGasLimitCalculator, - _sealEngine, - _timestamper, - _specProvider, - _logManager, - _blocksConfig); - } } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs index d8d3db23462..acedf80e7f0 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.HelperFunctions.cs @@ -103,7 +103,7 @@ private static ExecutionPayload CreateBlockRequest(MergeTestBlockchain chain, Ex ExecutionPayload blockRequest = CreateBlockRequestInternal(parent, miner, withdrawals, blobGasUsed, excessBlobGas, transactions: transactions, parentBeaconBlockRoot: parentBeaconBlockRoot); Block? block = blockRequest.TryGetBlock().Block; - IWorldState globalWorldState = chain.WorldStateManager.GlobalWorldState; + IWorldState globalWorldState = chain.MainWorldState; using (globalWorldState.BeginScope(parent.TryGetBlock().Block!.Header)) { chain.WithdrawalProcessor?.ProcessWithdrawals(block!, chain.SpecProvider.GenesisSpec); @@ -131,7 +131,7 @@ private static ExecutionPayloadV3 CreateBlockRequestV3( ExecutionPayloadV3 blockRequestV3 = CreateBlockRequestInternal(parent, miner, withdrawals, blobGasUsed, excessBlobGas, transactions: transactions, parentBeaconBlockRoot: parentBeaconBlockRoot); Block? block = blockRequestV3.TryGetBlock().Block; - IWorldState globalWorldState = chain.WorldStateManager.GlobalWorldState; + IWorldState globalWorldState = chain.MainWorldState; using (globalWorldState.BeginScope(parent.TryGetBlock().Block!.Header)) { var blockHashStore = new BlockhashStore(globalWorldState); @@ -154,12 +154,12 @@ private static ExecutionPayloadV3 CreateBlockRequestV4(MergeTestBlockchain chain ExecutionPayloadV3 blockRequestV4 = CreateBlockRequestInternal(parent, miner, withdrawals, blobGasUsed, excessBlobGas, transactions: transactions, parentBeaconBlockRoot: parentBeaconBlockRoot); Block? block = blockRequestV4.TryGetBlock().Block; - var beaconBlockRootHandler = new BeaconBlockRootHandler(chain.TxProcessor, chain.WorldStateManager.GlobalWorldState); + var beaconBlockRootHandler = new BeaconBlockRootHandler(chain.TxProcessor, chain.MainWorldState); IReleaseSpec spec = chain.SpecProvider.GetSpec(block!.Header); - chain.TxProcessor.SetBlockExecutionContext(new BlockExecutionContext(block.Header, spec)); - beaconBlockRootHandler.StoreBeaconRoot(block, spec, NullTxTracer.Instance); - IWorldState globalWorldState = chain.WorldStateManager.GlobalWorldState; + chain.TxProcessor.SetBlockExecutionContext(new BlockExecutionContext(block!.Header, spec)); + beaconBlockRootHandler.StoreBeaconRoot(block!, spec, NullTxTracer.Instance); + IWorldState globalWorldState = chain.MainWorldState; Snapshot before = globalWorldState.TakeSnapshot(); var blockHashStore = new BlockhashStore(globalWorldState); blockHashStore.ApplyBlockhashStateChanges(block.Header, chain.SpecProvider.GetSpec(block.Header)); diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.PayloadProduction.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.PayloadProduction.cs index ac451d5cab7..8e91862ee53 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.PayloadProduction.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.PayloadProduction.cs @@ -483,7 +483,7 @@ public async Task TestTwoTransaction_SameContract_WithBlockImprovement() [Test] [Retry(3)] - public async Task getPayloadV1_doesnt_wait_for_improvement_when_block_is_not_empty() + public async Task getPayloadV1_does_not_wait_for_improvement_when_block_is_not_empty() { TimeSpan delay = TimeSpan.FromMilliseconds(10); TimeSpan timePerSlot = 50 * delay; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.RelayBuilder.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.RelayBuilder.cs index 7291adf8344..241e3ab6f9c 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.RelayBuilder.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.RelayBuilder.cs @@ -107,7 +107,7 @@ public virtual async Task forkchoiceUpdatedV1_should_communicate_with_boost_rela .WithContent("{\"timestamp\":\"0x3e8\",\"prevRandao\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"suggestedFeeRecipient\":\"0x0000000000000000000000000000000000000000\"}") .Respond("application/json", "{\"timestamp\":\"0x3e9\",\"prevRandao\":\"0x03783fac2efed8fbc9ad443e592ee30e61d65f471140c10ca155e937b435b760\",\"suggestedFeeRecipient\":\"0xb7705ae4c6f81b66cdb323c65f4e8133690fc099\"}"); - //TODO: think about extracting an essely serialisable class, test its serializatoin sepratly, refactor with it similar methods like the one above + // TODO: extract an easily serializable class, test its serialization separately, and refactor similar methods like the one above var expected_parentHash = parentHash; var expected_feeRecipient = "0xb7705ae4c6f81b66cdb323c65f4e8133690fc099"; var expected_stateRoot = "0x1ef7300d8961797263939a3d29bbba4ccf1702fabf02d8ad7a20b454edb6fd2f"; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs index 0d04a8ac427..9fd3627c86c 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.Synchronization.cs @@ -91,7 +91,7 @@ public async Task forkChoiceUpdatedV1_unknown_block_initiates_syncing() } [Test] - public async Task forkChoiceUpdatedV1_unknown_block_without_newpayload_initiates_syncing() + public async Task forkChoiceUpdatedV1_unknown_block_without_newPayload_initiates_syncing() { using MergeTestBlockchain chain = await CreateBlockchain(); IEngineRpcModule rpc = chain.EngineRpcModule; @@ -137,7 +137,7 @@ public async Task forkChoiceUpdatedV1_unknown_block_without_newpayload_initiates } [Test] - public async Task forkChoiceUpdatedV1_unknown_block_without_newpayload_and_peer_timeout__it_does_not_initiates_syncing() + public async Task forkChoiceUpdatedV1_unknown_block_without_newPayload_and_peer_timeout__it_does_not_initiate_syncing() { using MergeTestBlockchain chain = await CreateBlockchain(); IEngineRpcModule rpc = chain.EngineRpcModule; @@ -845,7 +845,7 @@ public async Task Blocks_before_pivots_should_not_be_added_if_node_has_never_bee syncConfig = new SyncConfig { FastSync = true, - PivotNumber = syncedBlockTree.Head?.Number.ToString() ?? "", + PivotNumber = syncedBlockTree.Head?.Number ?? 0, PivotHash = syncedBlockTree.HeadHash?.ToString() ?? "", PivotTotalDifficulty = syncedBlockTree.Head?.TotalDifficulty?.ToString() ?? "" }; @@ -873,7 +873,7 @@ public async Task Blocks_before_pivots_should_not_be_added_if_node_has_been_sync syncConfig = new SyncConfig { FastSync = true, - PivotNumber = syncedBlockTree.Head?.Number.ToString() ?? "", + PivotNumber = syncedBlockTree.Head?.Number ?? 0, PivotHash = syncedBlockTree.HeadHash?.ToString() ?? "", PivotTotalDifficulty = syncedBlockTree.Head?.TotalDifficulty?.ToString() ?? "" }; @@ -903,7 +903,7 @@ public async Task Maintain_correct_pointers_for_beacon_sync_in_fast_sync() syncConfig = new SyncConfig { FastSync = true, - PivotNumber = syncedBlockTree.Head?.Number.ToString() ?? "", + PivotNumber = syncedBlockTree.Head?.Number ?? 0, PivotHash = syncedBlockTree.HeadHash?.ToString() ?? "", PivotTotalDifficulty = syncedBlockTree.Head?.TotalDifficulty?.ToString() ?? "" }; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs index fe40eae39f4..4182f6d6fe2 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V1.cs @@ -441,14 +441,14 @@ public async Task executePayloadV1_rejects_block_with_invalid_receiptsRoot() } [Test] - public async Task executePayloadV1_result_is_fail_when_blockchainprocessor_report_exception() + public async Task executePayloadV1_result_is_fail_when_blockchain_processor_reports_exception() { using MergeTestBlockchain chain = await CreateBaseBlockchain() .Build(new TestSingleReleaseSpecProvider(London.Instance)); IEngineRpcModule rpc = chain.EngineRpcModule; ((TestBranchProcessorInterceptor)chain.BranchProcessor).ExceptionToThrow = - new Exception("unxpected exception"); + new Exception("unexpected exception"); ExecutionPayload executionPayload = CreateBlockRequest(chain, CreateParentBlockRequestOnHead(chain.BlockTree), TestItem.AddressD); ResultWrapper resultWrapper = await rpc.engine_newPayloadV1(executionPayload); @@ -729,7 +729,7 @@ public async Task Invalid_block_on_processing_wont_be_accepted_if_sent_twice_in_ chain.ReadOnlyState.GetBalance(TestItem.AddressA).Should().BeGreaterThan(UInt256.One); // block is an invalid block, but it is impossible to detect until we process it. - // it is invalid because after you processs its transactions, the root of the state trie + // it is invalid because after you process its transactions, the root of the state trie // doesn't match the state root in the block Block? block = Build.A.Block .WithNumber(head.Number + 1) @@ -1553,7 +1553,8 @@ public void Should_return_expected_capabilities_for_mainnet() nameof(IEngineRpcModule.engine_newPayloadV4), nameof(IEngineRpcModule.engine_getPayloadV5), - nameof(IEngineRpcModule.engine_getBlobsV2) + nameof(IEngineRpcModule.engine_getBlobsV2), + nameof(IEngineRpcModule.engine_getBlobsV3) }; Assert.That(result, Is.EquivalentTo(expectedMethods)); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs index 023499de894..b073337efc6 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V3.cs @@ -386,7 +386,7 @@ public async Task NewPayloadV3_should_verify_blob_versioned_hashes_again Substitute.For>(), Substitute.For, IEnumerable>>(), Substitute.For>>(), - Substitute.For?>>(), + Substitute.For?>>(), Substitute.For>>(), Substitute.For?>>(), Substitute.For(), diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V4.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V4.cs index a9e4bf15321..54112b3112a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V4.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V4.cs @@ -279,7 +279,7 @@ private async Task> ProduceBranchV4(IEngineRpcMo ExecutionPayloadV3? getPayloadResult = await BuildAndGetPayloadOnBranchV4(rpc, chain, parentHeader, parentBlock.Timestamp + 12, random ?? TestItem.KeccakA, Address.Zero); - PayloadStatusV1 payloadStatusResponse = (await rpc.engine_newPayloadV4(getPayloadResult, [], Keccak.Zero, executionRequests: withRequests ? ExecutionRequestsProcessorMock.Requests : new byte[][] { })).Data; + PayloadStatusV1 payloadStatusResponse = (await rpc.engine_newPayloadV4(getPayloadResult, [], Keccak.Zero, executionRequests: withRequests ? ExecutionRequestsProcessorMock.Requests : Array.Empty())).Data; payloadStatusResponse.Status.Should().Be(PayloadStatus.Valid); if (setHead) { diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs index 681110c95fc..825da6cc4e1 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/EngineModuleTests.V5.cs @@ -52,7 +52,7 @@ public async Task GetBlobsV2_should_throw_if_more_than_128_requested_blobs([Valu request.Add(Bytes.FromHexString(i.ToString("X64"))); } - ResultWrapper?> result = await rpcModule.engine_getBlobsV2(request.ToArray()); + ResultWrapper?> result = await rpcModule.engine_getBlobsV2(request.ToArray()); if (requestSize > 128) { @@ -75,7 +75,7 @@ public async Task GetBlobsV2_should_handle_empty_request() }); IEngineRpcModule rpcModule = chain.EngineRpcModule; - ResultWrapper?> result = await rpcModule.engine_getBlobsV2([]); + ResultWrapper?> result = await rpcModule.engine_getBlobsV2([]); result.Result.Should().Be(Result.Success); result.Data.Should().BeEquivalentTo(ArraySegment.Empty); @@ -99,14 +99,14 @@ public async Task GetBlobsV2_should_return_requested_blobs([Values(1, 2, 3, 4, 5 chain.TxPool.SubmitTx(blobTx, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); - ResultWrapper?> result = await rpcModule.engine_getBlobsV2(blobTx.BlobVersionedHashes!); + ResultWrapper?> result = await rpcModule.engine_getBlobsV2(blobTx.BlobVersionedHashes!); ShardBlobNetworkWrapper wrapper = (ShardBlobNetworkWrapper)blobTx.NetworkWrapper!; result.Data.Should().NotBeNull(); - result.Data!.Select(static b => b.Blob).Should().BeEquivalentTo(wrapper.Blobs); - result.Data!.Select(static b => b.Proofs.Length).Should().HaveCount(numberOfBlobs); - result.Data!.Select(static b => b.Proofs).Should().BeEquivalentTo(wrapper.Proofs.Chunk(128)); + result.Data!.Select(static b => b!.Blob).Should().BeEquivalentTo(wrapper.Blobs); + result.Data!.Select(static b => b!.Proofs.Length).Should().HaveCount(numberOfBlobs); + result.Data!.Select(static b => b!.Proofs).Should().BeEquivalentTo(wrapper.Proofs.Chunk(128)); } [Test] @@ -127,7 +127,7 @@ public async Task GetBlobsV2_should_return_empty_array_when_blobs_not_found([Val .SignedAndResolved(chain.EthereumEcdsa, TestItem.PrivateKeyA).TestObject; // requesting hashes that are not present in TxPool - ResultWrapper?> result = await rpcModule.engine_getBlobsV2(blobTx.BlobVersionedHashes!); + ResultWrapper?> result = await rpcModule.engine_getBlobsV2(blobTx.BlobVersionedHashes!); result.Result.Should().Be(Result.Success); result.Data.Should().BeNull(); @@ -162,7 +162,7 @@ public async Task GetBlobsV2_should_return_empty_array_when_only_some_blobs_foun blobVersionedHashesRequest.Add(addActualHash ? blobTx.BlobVersionedHashes![actualIndex++]! : Bytes.FromHexString(i.ToString("X64"))); } - ResultWrapper?> result = await rpcModule.engine_getBlobsV2(blobVersionedHashesRequest.ToArray()); + ResultWrapper?> result = await rpcModule.engine_getBlobsV2(blobVersionedHashesRequest.ToArray()); if (multiplier > 1) { result.Result.Should().Be(Result.Success); @@ -173,9 +173,65 @@ public async Task GetBlobsV2_should_return_empty_array_when_only_some_blobs_foun ShardBlobNetworkWrapper wrapper = (ShardBlobNetworkWrapper)blobTx.NetworkWrapper!; result.Data.Should().NotBeNull(); - result.Data!.Select(static b => b.Blob).Should().BeEquivalentTo(wrapper.Blobs); - result.Data!.Select(static b => b.Proofs.Length).Should().HaveCount(numberOfBlobs); - result.Data!.Select(static b => b.Proofs).Should().BeEquivalentTo(wrapper.Proofs.Chunk(128)); + result.Data!.Select(static b => b!.Blob).Should().BeEquivalentTo(wrapper.Blobs); + result.Data!.Select(static b => b!.Proofs.Length).Should().HaveCount(numberOfBlobs); + result.Data!.Select(static b => b!.Proofs).Should().BeEquivalentTo(wrapper.Proofs.Chunk(128)); + } + } + + [Test] + public async Task GetBlobsV3_should_return_partial_results_with_nulls_for_missing_blobs([Values(1, 2, 3, 4, 5, 6)] int numberOfBlobs, [Values(1, 2)] int multiplier) + { + int requestSize = multiplier * numberOfBlobs; + + MergeTestBlockchain chain = await CreateBlockchain(releaseSpec: Osaka.Instance, mergeConfig: new MergeConfig() + { + NewPayloadBlockProcessingTimeout = (int)TimeSpan.FromDays(1).TotalMilliseconds + }); + IEngineRpcModule rpcModule = chain.EngineRpcModule; + + Transaction blobTx = Build.A.Transaction + .WithShardBlobTxTypeAndFields(numberOfBlobs, spec: Osaka.Instance) + .WithMaxFeePerGas(1.GWei()) + .WithMaxPriorityFeePerGas(1.GWei()) + .WithMaxFeePerBlobGas(1000.Wei()) + .SignedAndResolved(chain.EthereumEcdsa, TestItem.PrivateKeyA).TestObject; + + chain.TxPool.SubmitTx(blobTx, TxHandlingOptions.None).Should().Be(AcceptTxResult.Accepted); + + List blobVersionedHashesRequest = new List(requestSize); + + int actualIndex = 0; + for (int i = 0; i < requestSize; i++) + { + bool addActualHash = i % multiplier == 0; + blobVersionedHashesRequest.Add(addActualHash ? blobTx.BlobVersionedHashes![actualIndex++]! : Bytes.FromHexString(i.ToString("X64"))); + } + + ResultWrapper?> result = await rpcModule.engine_getBlobsV3(blobVersionedHashesRequest.ToArray()); + + result.Result.Should().Be(Result.Success); + result.Data.Should().NotBeNull(); + result.Data!.Should().HaveCount(requestSize); + + ShardBlobNetworkWrapper wrapper = (ShardBlobNetworkWrapper)blobTx.NetworkWrapper!; + + // V3 returns partial results with nulls for missing blobs + int foundIndex = 0; + for (int i = 0; i < requestSize; i++) + { + bool shouldBeFound = i % multiplier == 0; + if (shouldBeFound) + { + result.Data!.ElementAt(i).Should().NotBeNull(); + result.Data!.ElementAt(i)!.Blob.Should().BeEquivalentTo(wrapper.Blobs[foundIndex]); + result.Data!.ElementAt(i)!.Proofs.Should().BeEquivalentTo(wrapper.Proofs.Skip(foundIndex * 128).Take(128)); + foundIndex++; + } + else + { + result.Data!.ElementAt(i).Should().BeNull(); + } } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs index de53c5116f4..f111b2197bc 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/JwtTest.cs @@ -1,5 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +/* cSpell:disable */ using System; using System.Threading.Tasks; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/BeaconHeadersSyncTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/BeaconHeadersSyncTests.cs index 78ccb2a059e..13702ebf637 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/BeaconHeadersSyncTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/BeaconHeadersSyncTests.cs @@ -150,7 +150,7 @@ public async Task Can_keep_returning_nulls_after_all_batches_were_prepared() SyncConfig = new SyncConfig { FastSync = true, - PivotNumber = "1000", + PivotNumber = 1000, PivotHash = Keccak.Zero.ToString(), PivotTotalDifficulty = "1000" }, @@ -180,7 +180,7 @@ public async Task Finishes_when_all_downloaded() ISyncConfig syncConfig = new SyncConfig { FastSync = true, - PivotNumber = "1000", + PivotNumber = 1000, PivotHash = Keccak.Zero.ToString(), PivotTotalDifficulty = "1000" }; @@ -214,7 +214,7 @@ public void Feed_able_to_sync_when_new_pivot_is_set() ISyncConfig syncConfig = new SyncConfig { FastSync = true, - PivotNumber = "500", + PivotNumber = 500, PivotHash = Keccak.Zero.ToString(), PivotTotalDifficulty = "1000000" // default difficulty in block tree builder }; @@ -302,7 +302,7 @@ public async Task When_pivot_changed_during_header_sync_after_chain_merged__do_n ISyncConfig syncConfig = new SyncConfig { FastSync = true, - PivotNumber = "0", + PivotNumber = 0, PivotHash = Keccak.Zero.ToString(), PivotTotalDifficulty = "0" }; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/BeaconPivotTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/BeaconPivotTests.cs index 08b3311202e..99c271e5c2d 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/BeaconPivotTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/BeaconPivotTests.cs @@ -29,7 +29,7 @@ public void Setup() _syncConfig = new SyncConfig { FastSync = true, - PivotNumber = "1000", + PivotNumber = 1000, PivotHash = Keccak.Zero.ToString(), PivotTotalDifficulty = "1000" }; diff --git a/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/StartingSyncPivotUpdaterTests.cs b/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/StartingSyncPivotUpdaterTests.cs index f9e6227d24b..969bc1bb607 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/StartingSyncPivotUpdaterTests.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin.Test/Synchronization/StartingSyncPivotUpdaterTests.cs @@ -49,7 +49,7 @@ public void Setup() ISyncPeer? fakePeer = Substitute.For(); fakePeer.GetHeadBlockHeader(default, default).ReturnsForAnyArgs(x => _externalPeerBlockTree!.Head!.Header); - // for unsafe pivot updator + // for unsafe pivot updater Hash256 pivotHash = _externalPeerBlockTree!.FindLevel(35)!.BlockInfos[0].BlockHash; fakePeer.GetBlockHeaders(35, 1, 0, default).ReturnsForAnyArgs(x => _externalPeerBlockTree!.FindHeaders(pivotHash, 1, 0, default)); @@ -102,7 +102,7 @@ public void TrySetFreshPivot_saves_FinalizedHash_in_db() } [Test] - public void TrySetFreshPivot_for_unsafe_updator_saves_pivot_64_blocks_behind_HeadBlockHash_in_db() + public void TrySetFreshPivot_for_unsafe_updater_saves_pivot_64_blocks_behind_HeadBlockHash_in_db() { _ = new UnsafeStartingSyncPivotUpdater( _blockTree!, diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceUpdatedV1Result.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceUpdatedV1Result.cs index 70fac33c3fb..9a00701af1a 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceUpdatedV1Result.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/ForkchoiceUpdatedV1Result.cs @@ -11,7 +11,7 @@ namespace Nethermind.Merge.Plugin.Data /// /// Result of engine_forkChoiceUpdate call. /// - /// + /// /// public class ForkchoiceUpdatedV1Result { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Data/IExecutionPayloadParams.cs b/src/Nethermind/Nethermind.Merge.Plugin/Data/IExecutionPayloadParams.cs index c15857d0000..1932720394e 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Data/IExecutionPayloadParams.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Data/IExecutionPayloadParams.cs @@ -20,7 +20,7 @@ public enum ValidationResult : byte { Success, Fail, Invalid }; public class ExecutionPayloadParams(byte[][]? executionRequests = null) { /// - /// Gets or sets as defined in + /// Gets or sets as defined in /// EIP-7685. /// public byte[][]? ExecutionRequests { get; set; } = executionRequests; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Osaka.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Osaka.cs index fe52ec00e62..fcc4877246c 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Osaka.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Osaka.cs @@ -12,11 +12,14 @@ namespace Nethermind.Merge.Plugin; public partial class EngineRpcModule : IEngineRpcModule { private readonly IAsyncHandler _getPayloadHandlerV5; - private readonly IAsyncHandler?> _getBlobsHandlerV2; + private readonly IAsyncHandler?> _getBlobsHandlerV2; public Task> engine_getPayloadV5(byte[] payloadId) => _getPayloadHandlerV5.HandleAsync(payloadId); - public Task?>> engine_getBlobsV2(byte[][] blobVersionedHashes) - => _getBlobsHandlerV2.HandleAsync(blobVersionedHashes); + public Task?>> engine_getBlobsV2(byte[][] blobVersionedHashes) + => _getBlobsHandlerV2.HandleAsync(new(blobVersionedHashes)); + + public Task?>> engine_getBlobsV3(byte[][] blobVersionedHashes) + => _getBlobsHandlerV2.HandleAsync(new(blobVersionedHashes, true)); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Paris.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Paris.cs index 8ffde88cf20..59989242ae3 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Paris.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Paris.cs @@ -52,7 +52,7 @@ protected async Task> ForkchoiceUpdated } finally { - Metrics.ForkchoiceUpdedExecutionTime = (long)Stopwatch.GetElapsedTime(startTime).TotalMilliseconds; + Metrics.ForkchoiceUpdatedExecutionTime = (long)Stopwatch.GetElapsedTime(startTime).TotalMilliseconds; _locker.Release(); } } @@ -90,8 +90,15 @@ protected async Task> NewPayload(IExecutionPayloa long startTime = Stopwatch.GetTimestamp(); try { - using IDisposable region = _gcKeeper.TryStartNoGCRegion(); - return await _newPayloadV1Handler.HandleAsync(executionPayload); + Task regionTask = _gcKeeper.TryStartNoGCRegionAsync(); + try + { + return await _newPayloadV1Handler.HandleAsync(executionPayload); + } + finally + { + (await regionTask).Dispose(); + } } catch (BlockchainException exception) { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Prague.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Prague.cs index 126011ca6b8..c72cccbb3ab 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Prague.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.Prague.cs @@ -15,7 +15,7 @@ public partial class EngineRpcModule : IEngineRpcModule readonly IAsyncHandler _getPayloadHandlerV4; /// - /// Method parameter list is extended with parameter. + /// Method parameter list is extended with parameter. /// EIP-7685. /// public Task> engine_newPayloadV4(ExecutionPayloadV3 executionPayload, byte[]?[] blobVersionedHashes, Hash256? parentBeaconBlockRoot, byte[][]? executionRequests) diff --git a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs index 1fdc1f8d465..7b1013fcb93 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/EngineRpcModule.cs @@ -35,7 +35,7 @@ public EngineRpcModule( IHandler transitionConfigurationHandler, IHandler, IEnumerable> capabilitiesHandler, IAsyncHandler> getBlobsHandler, - IAsyncHandler?> getBlobsHandlerV2, + IAsyncHandler?> getBlobsHandlerV2, IAsyncHandler> getBALsByHashV1Handler, IAsyncHandler<(long, long), IEnumerable?> getBALsByRangeV1Handler, IEngineRequestsTracker engineRequestsTracker, diff --git a/src/Nethermind/Nethermind.Merge.Plugin/GC/GCKeeper.cs b/src/Nethermind/Nethermind.Merge.Plugin/GC/GCKeeper.cs index a581d7e6e05..6062bbb67ba 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/GC/GCKeeper.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/GC/GCKeeper.cs @@ -6,7 +6,7 @@ using System.Threading; using System.Threading.Tasks; using FastEnumUtility; -using Nethermind.Consensus; +using Nethermind.Core; using Nethermind.Core.Extensions; using Nethermind.Logging; @@ -17,26 +17,32 @@ public class GCKeeper private static ulong _forcedGcCount = 0; private readonly Lock _lock = new(); private readonly IGCStrategy _gcStrategy; + private readonly int _postBlockDelayMs; private readonly ILogger _logger; private static readonly long _defaultSize = 512.MB(); private Task _gcScheduleTask = Task.CompletedTask; + private readonly Func _tryStartNoGCRegionFunc; public GCKeeper(IGCStrategy gcStrategy, ILogManager logManager) { _gcStrategy = gcStrategy; + _postBlockDelayMs = gcStrategy.PostBlockDelayMs; _logger = logManager.GetClassLogger(); + _tryStartNoGCRegionFunc = TryStartNoGCRegion; } - public IDisposable TryStartNoGCRegion(long? size = null) + public Task TryStartNoGCRegionAsync() => Task.Run(_tryStartNoGCRegionFunc); + + private IDisposable TryStartNoGCRegion() { - size ??= _defaultSize; + long size = _defaultSize; bool pausedGCScheduler = GCScheduler.MarkGCPaused(); if (_gcStrategy.CanStartNoGCRegion()) { FailCause failCause = FailCause.None; try { - if (!System.GC.TryStartNoGCRegion(size.Value, true)) + if (!System.GC.TryStartNoGCRegion(size, disallowFullBlockingGC: true)) { failCause = FailCause.GCFailedToStartNoGCRegion; } @@ -151,7 +157,16 @@ private async Task ScheduleGCInternal() // This should give time to finalize response in Engine API // Normally we should get block every 12s (5s on some chains) // Lets say we process block in 2s, then delay 125ms, then invoke GC - await Task.Delay(125); + int postBlockDelayMs = _postBlockDelayMs; + if (postBlockDelayMs <= 0) + { + // Always async + await Task.Yield(); + } + else + { + await Task.Delay(postBlockDelayMs); + } if (GCSettings.LatencyMode != GCLatencyMode.NoGCRegion) { diff --git a/src/Nethermind/Nethermind.Merge.Plugin/GC/IGCStrategy.cs b/src/Nethermind/Nethermind.Merge.Plugin/GC/IGCStrategy.cs index 42d8012f590..d71d267db7f 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/GC/IGCStrategy.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/GC/IGCStrategy.cs @@ -8,6 +8,7 @@ namespace Nethermind.Merge.Plugin.GC; public interface IGCStrategy { int CollectionsPerDecommit { get; } + int PostBlockDelayMs { get; } bool CanStartNoGCRegion(); (GcLevel Generation, GcCompaction Compacting) GetForcedGCParams(); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/GC/NoGCStrategy.cs b/src/Nethermind/Nethermind.Merge.Plugin/GC/NoGCStrategy.cs index 3a92c3e366c..043b09109d8 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/GC/NoGCStrategy.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/GC/NoGCStrategy.cs @@ -7,6 +7,7 @@ public class NoGCStrategy : IGCStrategy { public static readonly NoGCStrategy Instance = new(); public int CollectionsPerDecommit => -1; + public int PostBlockDelayMs => 0; public bool CanStartNoGCRegion() => false; public (GcLevel Generation, GcCompaction Compacting) GetForcedGCParams() => (GcLevel.NoGC, GcCompaction.No); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/GC/NoSyncGcRegionStrategy.cs b/src/Nethermind/Nethermind.Merge.Plugin/GC/NoSyncGcRegionStrategy.cs index fcc3ea994b0..3aee165f6e6 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/GC/NoSyncGcRegionStrategy.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/GC/NoSyncGcRegionStrategy.cs @@ -20,9 +20,12 @@ public NoSyncGcRegionStrategy(ISyncModeSelector syncModeSelector, IMergeConfig m GcLevel gcLevel = (GcLevel)Math.Min((int)GcLevel.Gen2, (int)mergeConfig.SweepMemory); GcCompaction gcCompaction = (GcCompaction)Math.Min((int)GcCompaction.Full, (int)mergeConfig.CompactMemory); _gcParams = (gcLevel, gcCompaction); + PostBlockDelayMs = mergeConfig.PostBlockGcDelayMs ?? (int)((mergeConfig.SecondsPerSlot * 1000) / 8); } public int CollectionsPerDecommit { get; } + public int PostBlockDelayMs { get; } + public bool CanStartNoGCRegion() => _canStartNoGCRegion && _syncModeSelector.Current == SyncMode.WaitingForBlock; public (GcLevel, GcCompaction) GetForcedGCParams() => _gcParams; } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs index 853161e55d2..ae55b2cbdcc 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/EngineRpcCapabilitiesProvider.cs @@ -48,6 +48,7 @@ public class EngineRpcCapabilitiesProvider(ISpecProvider specProvider) : IRpcCap // Osaka _capabilities[nameof(IEngineRpcModule.engine_getPayloadV5)] = (spec.IsEip7594Enabled, spec.IsEip7594Enabled); _capabilities[nameof(IEngineRpcModule.engine_getBlobsV2)] = (spec.IsEip7594Enabled, false); + _capabilities[nameof(IEngineRpcModule.engine_getBlobsV3)] = (spec.IsEip7594Enabled, false); // Amsterdam _capabilities[nameof(IEngineRpcModule.engine_getPayloadV6)] = (spec.IsEip7928Enabled, spec.IsEip7928Enabled); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeTransitionConfigurationV1Handler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeTransitionConfigurationV1Handler.cs index 5d19260b008..e00f06fe20d 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeTransitionConfigurationV1Handler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/ExchangeTransitionConfigurationV1Handler.cs @@ -16,7 +16,8 @@ public class ExchangeTransitionConfigurationV1Handler : IHandler blocksList = new() { newHeadBlock }; Block? predecessor = newHeadBlock; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs index c04978430f3..47d22383328 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/GetBlobsHandlerV2.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Nethermind.Core.Collections; using Nethermind.JsonRpc; @@ -11,41 +10,45 @@ namespace Nethermind.Merge.Plugin.Handlers; -public class GetBlobsHandlerV2(ITxPool txPool) : IAsyncHandler?> +public class GetBlobsHandlerV2(ITxPool txPool) : IAsyncHandler?> { private const int MaxRequest = 128; - private static readonly Task?>> NotFound = Task.FromResult(ResultWrapper?>.Success(null)); + private static readonly Task?>> NotFound = Task.FromResult(ResultWrapper?>.Success(null)); - public Task?>> HandleAsync(byte[][] request) + public Task?>> HandleAsync(GetBlobsHandlerV2Request request) { - if (request.Length > MaxRequest) + if (request.BlobVersionedHashes.Length > MaxRequest) { var error = $"The number of requested blobs must not exceed {MaxRequest}"; - return ResultWrapper?>.Fail(error, MergeErrorCodes.TooLargeRequest); + return ResultWrapper?>.Fail(error, MergeErrorCodes.TooLargeRequest); } - Metrics.GetBlobsRequestsTotal += request.Length; + Metrics.GetBlobsRequestsTotal += request.BlobVersionedHashes.Length; - var count = txPool.GetBlobCounts(request); + int count = txPool.GetBlobCounts(request.BlobVersionedHashes); Metrics.GetBlobsRequestsInBlobpoolTotal += count; - // quick fail if we don't have some blob - if (count != request.Length) + // quick fail if we don't have some blob (unless partial return is allowed) + if (!request.AllowPartialReturn && count != request.BlobVersionedHashes.Length) { return ReturnEmptyArray(); } - ArrayPoolList response = new(request.Length); + ArrayPoolList response = new(request.BlobVersionedHashes.Length); try { - foreach (byte[] requestedBlobVersionedHash in request) + foreach (byte[] requestedBlobVersionedHash in request.BlobVersionedHashes) { if (txPool.TryGetBlobAndProofV1(requestedBlobVersionedHash, out byte[]? blob, out byte[][]? cellProofs)) { response.Add(new BlobAndProofV2(blob, cellProofs)); } + else if (request.AllowPartialReturn) + { + response.Add(null); + } else { // fail if we were not able to collect full blob data @@ -55,7 +58,7 @@ public class GetBlobsHandlerV2(ITxPool txPool) : IAsyncHandler?>.Success(response); + return ResultWrapper?>.Success(response); } catch { @@ -64,9 +67,11 @@ public class GetBlobsHandlerV2(ITxPool txPool) : IAsyncHandler?>> ReturnEmptyArray() + private Task?>> ReturnEmptyArray() { Metrics.GetBlobsRequestsFailureTotal++; return NotFound; } } + +public readonly record struct GetBlobsHandlerV2Request(byte[][] BlobVersionedHashes, bool AllowPartialReturn = false); diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs index 2a54cb725d7..29a2f0fe02c 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Handlers/NewPayloadHandler.cs @@ -367,7 +367,7 @@ ValidationResult TryCacheResult(ValidationResult result, string? errorMessage) // been suggested. there are three possibilities, either the block hasn't been processed yet, // the block was processed and returned invalid but this wasn't saved anywhere or the block was // processed and marked as valid. - // if marked as processed by the blocktree then return VALID, otherwise null so that it's process a few lines below + // if marked as processed by the block tree then return VALID, otherwise null so that it's processed a few lines below AddBlockResult.AlreadyKnown => _blockTree.WasProcessed(block.Number, block.Hash!) ? ValidationResult.Valid : null, _ => null }; diff --git a/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Osaka.cs b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Osaka.cs index b2cd12fe96f..bd05aa9e0c4 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Osaka.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/IEngineRpcModule.Osaka.cs @@ -21,5 +21,11 @@ public partial interface IEngineRpcModule : IRpcModule Description = "Returns requested blobs and proofs.", IsSharable = true, IsImplemented = true)] - public Task?>> engine_getBlobsV2(byte[][] blobVersionedHashes); + public Task?>> engine_getBlobsV2(byte[][] blobVersionedHashes); + + [JsonRpcMethod( + Description = "Returns requested blobs and proofs.", + IsSharable = true, + IsImplemented = true)] + public Task?>> engine_getBlobsV3(byte[][] blobVersionedHashes); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/IMergeConfig.cs b/src/Nethermind/Nethermind.Merge.Plugin/IMergeConfig.cs index c737e54c232..d2b5ce16536 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/IMergeConfig.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/IMergeConfig.cs @@ -72,4 +72,7 @@ The number of requests to the garbage collector (GC) to release the process memo [ConfigItem(Description = "[TECHNICAL] Simulate block production for every possible slot. Just for stress-testing purposes.", DefaultValue = "false", HiddenFromDocs = true)] bool SimulateBlockProduction { get; set; } + + [ConfigItem(Description = "Delay, in milliseconds, between `newPayload` and GC trigger. If not set, defaults to 1/8th of `Blocks.SecondsPerSlot`.", DefaultValue = null, HiddenFromDocs = true)] + int? PostBlockGcDelayMs { get; set; } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergeConfig.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergeConfig.cs index 220a25c9705..f5a82770d3d 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergeConfig.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergeConfig.cs @@ -35,5 +35,6 @@ public class MergeConfig : IMergeConfig public int NewPayloadCacheSize { get; set; } = 50; public bool SimulateBlockProduction { get; set; } = false; + public int? PostBlockGcDelayMs { get; set; } = null; } } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs index 07e0071a53f..a3bdcbc4d3d 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/MergePlugin.cs @@ -155,7 +155,7 @@ private void EnsureReceiptAvailable() private void EnsureJsonRpcUrl() { - if (HasTtd() == false) // by default we have Merge.Enabled = true, for chains that are not post-merge, wwe can skip this check, but we can still working with MergePlugin + if (HasTtd() == false) // by default we have Merge.Enabled = true, for chains that are not post-merge, we can skip this check, but we can still working with MergePlugin return; IJsonRpcConfig jsonRpcConfig = _api.Config(); @@ -214,7 +214,7 @@ public Task InitNetworkProtocol() _mergeBlockProductionPolicy = new MergeBlockProductionPolicy(_api.BlockProductionPolicy); _api.BlockProductionPolicy = _mergeBlockProductionPolicy; - _api.FinalizationManager = InitializeMergeFinilizationManager(); + _api.FinalizationManager = InitializeMergeFinalizationManager(); if (_poSSwitcher.TransitionFinished) { @@ -236,7 +236,7 @@ private void AddEth69() _api.ProtocolsManager!.AddSupportedCapability(new(Protocol.Eth, 69)); } - protected virtual IBlockFinalizationManager InitializeMergeFinilizationManager() + protected virtual IBlockFinalizationManager InitializeMergeFinalizationManager() { return new MergeFinalizationManager(_api.Context.Resolve(), _api.FinalizationManager, _poSSwitcher); } @@ -331,7 +331,7 @@ protected override void Load(ContainerBuilder builder) .AddSingleton, IEnumerable>, ExchangeCapabilitiesHandler>() .AddSingleton() .AddSingleton>, GetBlobsHandler>() - .AddSingleton?>, GetBlobsHandlerV2>() + .AddSingleton?>, GetBlobsHandlerV2>() .AddSingleton>, GetBALsByHashV1Handler>() .AddSingleton?>, GetBALsByRangeV1Handler>() .AddSingleton() @@ -346,6 +346,7 @@ protected override void Load(ContainerBuilder builder) : NoGCStrategy.Instance, ctx.Resolve()); }) + .AddSingleton() ; } @@ -361,11 +362,8 @@ IBlockImprovementContextFactory CreateBlockImprovementContextFactory(IComponentC return new BlockImprovementContextFactory(blockProducer!, TimeSpan.FromSeconds(maxSingleImprovementTimePerSlot)); } - ILogManager logManager = ctx.Resolve(); IStateReader stateReader = ctx.Resolve(); - IJsonSerializer jsonSerializer = ctx.Resolve(); - - DefaultHttpClient httpClient = new(new HttpClient(), jsonSerializer, logManager, retryDelayMilliseconds: 100); + IHttpClient httpClient = ctx.Resolve(); IBoostRelay boostRelay = new BoostRelay(httpClient, mergeConfig.BuilderRelayUrl); return new BoostBlockImprovementContextFactory(blockProducer!, TimeSpan.FromSeconds(maxSingleImprovementTimePerSlot), boostRelay, stateReader); } diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Metrics.cs b/src/Nethermind/Nethermind.Merge.Plugin/Metrics.cs index 752459cfcd3..809c2c29137 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Metrics.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Metrics.cs @@ -14,8 +14,8 @@ public static class Metrics public static long NewPayloadExecutionTime { get; set; } [GaugeMetric] - [Description("ForkchoiceUpded request execution time")] - public static long ForkchoiceUpdedExecutionTime { get; set; } + [Description("ForkchoiceUpdated request execution time")] + public static long ForkchoiceUpdatedExecutionTime { get; set; } [CounterMetric] [Description("Number of GetPayload Requests")] diff --git a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/BeaconHeadersSyncFeed.cs b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/BeaconHeadersSyncFeed.cs index eaf122d7f2a..4a5ed73063b 100644 --- a/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/BeaconHeadersSyncFeed.cs +++ b/src/Nethermind/Nethermind.Merge.Plugin/Synchronization/BeaconHeadersSyncFeed.cs @@ -112,16 +112,6 @@ protected override void FinishAndCleanUp() FallAsleep(); PostFinishCleanUp(); } - - protected override void PostFinishCleanUp() - { - HeadersSyncProgressLoggerReport.Update(TotalBlocks); - HeadersSyncProgressLoggerReport.MarkEnd(); - ClearDependencies(); // there may be some dependencies from wrong branches - _pending.Clear(); // there may be pending wrong branches - _sent.Clear(); // we my still be waiting for some bad branches - } - public override Task PrepareRequest(CancellationToken cancellationToken = default) { if (_pivotNumber != ExpectedPivotNumber) diff --git a/src/Nethermind/Nethermind.Merkleization/Merkle.cs b/src/Nethermind/Nethermind.Merkleization/Merkle.cs index a16074877f3..9d70ff0da3d 100644 --- a/src/Nethermind/Nethermind.Merkleization/Merkle.cs +++ b/src/Nethermind/Nethermind.Merkleization/Merkle.cs @@ -28,12 +28,9 @@ private static void BuildZeroHashes() } } - private static readonly UInt256 RootOfNull; - static Merkle() { BuildZeroHashes(); - RootOfNull = new UInt256(new Root(SHA256.HashData([])).AsSpan().ToArray()); } public static ulong NextPowerOfTwo(uint v) diff --git a/src/Nethermind/Nethermind.Merkleization/MerkleTree.cs b/src/Nethermind/Nethermind.Merkleization/MerkleTree.cs index f5f0c4f7317..aef4d5bd102 100644 --- a/src/Nethermind/Nethermind.Merkleization/MerkleTree.cs +++ b/src/Nethermind/Nethermind.Merkleization/MerkleTree.cs @@ -301,7 +301,7 @@ public IList GetProof(in uint leafIndex) { if (leafIndex >= Count) { - throw new InvalidOperationException("Unpexected query for a proof for a value beyond Count"); + throw new InvalidOperationException("Unexpected query for a proof for a value beyond Count"); } Index index = new Index(LeafRow, leafIndex); diff --git a/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs b/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs index 3da65ed5080..f152b18ab00 100644 --- a/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs +++ b/src/Nethermind/Nethermind.Monitoring.Test/MetricsTests.cs @@ -43,7 +43,7 @@ public static class TestMetrics [SummaryMetric] public static IMetricObserver SomeObservation { get; set; } = NoopMetricObserver.Instance; - [System.ComponentModel.Description("Histograrm metric")] + [System.ComponentModel.Description("Histogram metric")] [ExponentialPowerHistogramMetric(Start = 1, Factor = 2, Count = 10)] public static IMetricObserver HistogramObservation { get; set; } = NoopMetricObserver.Instance; diff --git a/src/Nethermind/Nethermind.Monitoring/Config/IMetricsConfig.cs b/src/Nethermind/Nethermind.Monitoring/Config/IMetricsConfig.cs index 83ac5bcd7e5..0eb30fade85 100644 --- a/src/Nethermind/Nethermind.Monitoring/Config/IMetricsConfig.cs +++ b/src/Nethermind/Nethermind.Monitoring/Config/IMetricsConfig.cs @@ -26,6 +26,12 @@ public interface IMetricsConfig : IConfig [ConfigItem(DefaultValue = "5", Description = "The frequency of pushing metrics to Prometheus, in seconds.")] int IntervalSeconds { get; } + [ConfigItem(DefaultValue = "60", Description = "The frequency of updating db metrics, in seconds.")] + int DbMetricIntervalSeconds { get; } + + [ConfigItem(DefaultValue = "true", Description = "Pause db metric collection during block processing to prevent overhead.")] + bool PauseDbMetricDuringBlockProcessing { get; } + [ConfigItem(Description = "The name to display on the Grafana dashboard.", DefaultValue = "Nethermind")] string NodeName { get; } diff --git a/src/Nethermind/Nethermind.Monitoring/Config/MetricsConfig.cs b/src/Nethermind/Nethermind.Monitoring/Config/MetricsConfig.cs index 77e194cc7f8..c9dc00e6f7d 100644 --- a/src/Nethermind/Nethermind.Monitoring/Config/MetricsConfig.cs +++ b/src/Nethermind/Nethermind.Monitoring/Config/MetricsConfig.cs @@ -11,6 +11,8 @@ public class MetricsConfig : IMetricsConfig public bool CountersEnabled { get; set; } = false; public string PushGatewayUrl { get; set; } = null; public int IntervalSeconds { get; set; } = 5; + public int DbMetricIntervalSeconds { get; set; } = 60; + public bool PauseDbMetricDuringBlockProcessing { get; set; } = true; public string NodeName { get; set; } = "Nethermind"; public bool EnableDbSizeMetrics { get; set; } = true; public string MonitoringGroup { get; set; } = "nethermind"; diff --git a/src/Nethermind/Nethermind.Monitoring/IMonitoringService .cs b/src/Nethermind/Nethermind.Monitoring/IMonitoringService.cs similarity index 92% rename from src/Nethermind/Nethermind.Monitoring/IMonitoringService .cs rename to src/Nethermind/Nethermind.Monitoring/IMonitoringService.cs index f8d13fc417f..d023c62503a 100644 --- a/src/Nethermind/Nethermind.Monitoring/IMonitoringService .cs +++ b/src/Nethermind/Nethermind.Monitoring/IMonitoringService.cs @@ -9,7 +9,6 @@ namespace Nethermind.Monitoring public interface IMonitoringService { Task StartAsync(); - Task StopAsync(); void AddMetricsUpdateAction(Action callback); } } diff --git a/src/Nethermind/Nethermind.Monitoring/Metrics/IMetricsController.cs b/src/Nethermind/Nethermind.Monitoring/Metrics/IMetricsController.cs index a5ed5783c91..e9a2ab9ee21 100644 --- a/src/Nethermind/Nethermind.Monitoring/Metrics/IMetricsController.cs +++ b/src/Nethermind/Nethermind.Monitoring/Metrics/IMetricsController.cs @@ -2,14 +2,15 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Threading; +using System.Threading.Tasks; namespace Nethermind.Monitoring.Metrics { public interface IMetricsController { void RegisterMetrics(Type type); - void StartUpdating(); - void StopUpdating(); + Task RunTimer(CancellationToken cancellationToken); void AddMetricsUpdateAction(Action callback); } } diff --git a/src/Nethermind/Nethermind.Monitoring/Metrics/MetricsController.cs b/src/Nethermind/Nethermind.Monitoring/Metrics/MetricsController.cs index 2a3c91c3a89..c28e777eca0 100644 --- a/src/Nethermind/Nethermind.Monitoring/Metrics/MetricsController.cs +++ b/src/Nethermind/Nethermind.Monitoring/Metrics/MetricsController.cs @@ -14,6 +14,7 @@ using System.Runtime.Serialization; using System.Text.RegularExpressions; using System.Threading; +using System.Threading.Tasks; using Nethermind.Core; using Nethermind.Core.Attributes; using Nethermind.Core.Collections; @@ -29,11 +30,9 @@ namespace Nethermind.Monitoring.Metrics public partial class MetricsController : IMetricsController { private readonly int _intervalMilliseconds; - private Timer _timer = null!; - private static bool _staticLabelsInitialized = false; + private static bool _staticLabelsInitialized; private readonly Dictionary _metricUpdaters = new(); - private readonly HashSet _metricTypes = new(); // Largely for testing reason internal readonly Dictionary _individualUpdater = new(); @@ -157,14 +156,6 @@ public void Observe(double value, IMetricLabels? labels = null) } } - public void RegisterMetrics(Type type) - { - if (_metricTypes.Add(type)) - { - EnsurePropertiesCached(type); - } - } - internal record CommonMetricInfo(string Name, string Description, Dictionary Tags); private static CommonMetricInfo DetermineMetricInfo(MemberInfo member) @@ -182,7 +173,7 @@ Dictionary CreateTags() => private static Gauge CreateMemberInfoMetricsGauge(MemberInfo member, params string[] labels) { - var metricInfo = DetermineMetricInfo(member); + CommonMetricInfo metricInfo = DetermineMetricInfo(member); return CreateGauge(metricInfo.Name, metricInfo.Description, metricInfo.Tags, labels); } @@ -214,11 +205,11 @@ private static string GetStaticMemberInfo(Type givenInformer, string givenName) Type type = givenInformer; PropertyInfo[] tagsData = type.GetProperties(BindingFlags.Static | BindingFlags.Public); PropertyInfo info = tagsData.FirstOrDefault(info => info.Name == givenName) ?? throw new NotSupportedException("Developer error: a requested static description field was not implemented!"); - object value = info.GetValue(null) ?? throw new NotSupportedException("Developer error: a requested static description field was not initialised!"); + object value = info.GetValue(null) ?? throw new NotSupportedException("Developer error: a requested static description field was not initialized!"); return value.ToString()!; } - private void EnsurePropertiesCached(Type type) + public void RegisterMetrics(Type type) { if (!_metricUpdaters.ContainsKey(type)) { @@ -230,7 +221,7 @@ private void EnsurePropertiesCached(Type type) IList metricUpdaters = new List(); IEnumerable members = type.GetProperties().Concat(type.GetFields()); - foreach (var member in members) + foreach (MemberInfo member in members) { if (member.GetCustomAttribute() is not null && !_enableDetailedMetric) continue; if (TryCreateMetricUpdater(type, meter, member, out IMetricUpdater updater)) @@ -335,55 +326,39 @@ public MetricsController(IMetricsConfig metricsConfig) _enableDetailedMetric = metricsConfig.EnableDetailedMetric; } - public void StartUpdating() => _timer = new Timer(UpdateAllMetrics, null, TimeSpan.Zero, TimeSpan.FromMilliseconds(_intervalMilliseconds)); - - public void StopUpdating() => _timer?.Change(Timeout.Infinite, 0); - - private void UpdateAllMetrics(object? state) => UpdateAllMetrics(); - - private bool _isUpdating = false; - public void UpdateAllMetrics() + public async Task RunTimer(CancellationToken cancellationToken) { - if (!Interlocked.Exchange(ref _isUpdating, true)) + using var standardTimer = new PeriodicTimer(TimeSpan.FromMilliseconds(_intervalMilliseconds)); + + try { - try - { - UpdateAllMetricsInner(); - } - finally + while (await standardTimer.WaitForNextTickAsync(cancellationToken)) { - Volatile.Write(ref _isUpdating, false); + UpdateAllMetrics(); } } + catch (OperationCanceledException) + { + } } - private void UpdateAllMetricsInner() + public void UpdateAllMetrics() { foreach (Action callback in _callbacks) { callback(); } - foreach (Type metricType in _metricTypes) + foreach (IMetricUpdater[] updaters in _metricUpdaters.Values) { - UpdateMetrics(metricType); + foreach (IMetricUpdater metricUpdater in updaters) + { + metricUpdater.Update(); + } } } - public void AddMetricsUpdateAction(Action callback) - { - _callbacks.Add(callback); - } - - private void UpdateMetrics(Type type) - { - EnsurePropertiesCached(type); - - foreach (IMetricUpdater metricUpdater in _metricUpdaters[type]) - { - metricUpdater.Update(); - } - } + public void AddMetricsUpdateAction(Action callback) => _callbacks.Add(callback); private static string GetGaugeNameKey(params string[] par) => string.Join('.', par); diff --git a/src/Nethermind/Nethermind.Monitoring/MonitoringService.cs b/src/Nethermind/Nethermind.Monitoring/MonitoringService.cs index 1ee91ac12db..eac30b0f2fe 100644 --- a/src/Nethermind/Nethermind.Monitoring/MonitoringService.cs +++ b/src/Nethermind/Nethermind.Monitoring/MonitoringService.cs @@ -8,12 +8,12 @@ using Nethermind.Monitoring.Metrics; using Nethermind.Monitoring.Config; using System.Net.Sockets; -using Nethermind.Core.ServiceStopper; +using System.Threading; using Prometheus; namespace Nethermind.Monitoring; -public class MonitoringService : IMonitoringService, IStoppableService +public class MonitoringService : IMonitoringService, IAsyncDisposable { private readonly IMetricsController _metricsController; private readonly ILogger _logger; @@ -22,19 +22,26 @@ public class MonitoringService : IMonitoringService, IStoppableService private readonly string _exposeHost; private readonly int? _exposePort; private readonly string _nodeName; - private readonly bool _pushEnabled; private readonly string _pushGatewayUrl; private readonly int _intervalSeconds; + private readonly CancellationTokenSource _timerCancellationSource; - public MonitoringService(IMetricsController metricsController, IMetricsConfig metricsConfig, ILogManager logManager) + private Task _monitoringTimerTask = Task.CompletedTask; + private int _isDisposed = 0; + + public MonitoringService( + IMetricsController metricsController, + IMetricsConfig metricsConfig, + ILogManager logManager + ) { + _timerCancellationSource = new CancellationTokenSource(); _metricsController = metricsController ?? throw new ArgumentNullException(nameof(metricsController)); string exposeHost = metricsConfig.ExposeHost; int? exposePort = metricsConfig.ExposePort; string nodeName = metricsConfig.NodeName; string pushGatewayUrl = metricsConfig.PushGatewayUrl; - bool pushEnabled = metricsConfig.Enabled; int intervalSeconds = metricsConfig.IntervalSeconds; _exposeHost = exposeHost; @@ -43,7 +50,6 @@ public MonitoringService(IMetricsController metricsController, IMetricsConfig me ? throw new ArgumentNullException(nameof(nodeName)) : nodeName; _pushGatewayUrl = pushGatewayUrl; - _pushEnabled = pushEnabled; _intervalSeconds = intervalSeconds <= 0 ? throw new ArgumentException($"Invalid monitoring push interval: {intervalSeconds}s") : intervalSeconds; @@ -85,7 +91,17 @@ public Task StartAsync() new NethermindKestrelMetricServer(_exposeHost, _exposePort.Value).Start(); } - _metricsController.StartUpdating(); + _monitoringTimerTask = Task.Run(async () => + { + try + { + await _metricsController.RunTimer(_timerCancellationSource.Token); + } + catch (Exception ex) + { + if (_logger.IsError) _logger.Error($"Monitoring timer failed: {ex}"); + } + }); if (_logger.IsInfo) _logger.Info($"Started monitoring for the group: {_options.Group}, instance: {_options.Instance}"); return Task.CompletedTask; @@ -96,13 +112,6 @@ public void AddMetricsUpdateAction(Action callback) _metricsController.AddMetricsUpdateAction(callback); } - public Task StopAsync() - { - _metricsController.StopUpdating(); - - return Task.CompletedTask; - } - public string Description => "Monitoring service"; private Options GetOptions(IMetricsConfig config) @@ -121,4 +130,12 @@ private class Options(string job, string group, string instance) public string Instance { get; } = instance; public string Group { get; } = group; } + + public async ValueTask DisposeAsync() + { + if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) != 0) return; + await _timerCancellationSource.CancelAsync(); + await _monitoringTimerTask; + _timerCancellationSource.Dispose(); + } } diff --git a/src/Nethermind/Nethermind.Monitoring/NoopMonitoringService.cs b/src/Nethermind/Nethermind.Monitoring/NoopMonitoringService.cs new file mode 100644 index 00000000000..9f7fc17d71c --- /dev/null +++ b/src/Nethermind/Nethermind.Monitoring/NoopMonitoringService.cs @@ -0,0 +1,22 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading.Tasks; +using Autofac; + +namespace Nethermind.Monitoring; + +public class NoopMonitoringService : IMonitoringService +{ + public static IMonitoringService Instance = new NoopMonitoringService(); + + public Task StartAsync() + { + return Task.CompletedTask; + } + + public void AddMetricsUpdateAction(Action callback) + { + } +} diff --git a/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs b/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs index 4175cc638a8..42ed5fd4d50 100644 --- a/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs +++ b/src/Nethermind/Nethermind.Network.Contract/P2P/Protocol.cs @@ -1,45 +1,46 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -namespace Nethermind.Network.Contract.P2P +using System; + +namespace Nethermind.Network.Contract.P2P; + +public static class Protocol { - public static class Protocol - { - /// - /// devp2p Wire - /// - public const string P2P = "p2p"; - /// - /// Ethereum Wire - /// - public const string Eth = "eth"; - /// - /// Ethereum Snap Sync - /// - public const string Snap = "snap"; - /// - /// Node Data - /// - public const string NodeData = "nodedata"; - /// - /// Whisper - /// - public const string Shh = "shh"; - /// - /// Swarm - /// - public const string Bzz = "bzz"; - /// - /// Parity Warp Sync - /// - public const string Par = "par"; - /// - /// Nethermind Data Marketplace - /// - public const string Ndm = "ndm"; - /// - /// Account Abstraction - /// - public const string AA = "aa"; - } + /// + /// devp2p Wire + /// + public const string P2P = "p2p"; + /// + /// Ethereum Wire + /// + public const string Eth = "eth"; + /// + /// Ethereum Snap Sync + /// + public const string Snap = "snap"; + /// + /// Node Data + /// + public const string NodeData = "nodedata"; + /// + /// Whisper + /// + public const string Shh = "shh"; + /// + /// Swarm + /// + public const string Bzz = "bzz"; + /// + /// Parity Warp Sync + /// + public const string Par = "par"; + /// + /// Nethermind Data Marketplace + /// + public const string Ndm = "ndm"; + /// + /// Account Abstraction + /// + public const string AA = "aa"; } diff --git a/src/Nethermind/Nethermind.Network.Contract/P2P/ProtocolParser.cs b/src/Nethermind/Nethermind.Network.Contract/P2P/ProtocolParser.cs new file mode 100644 index 00000000000..a0f185b8082 --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Contract/P2P/ProtocolParser.cs @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Buffers.Binary; +using System.Diagnostics.CodeAnalysis; + +namespace Nethermind.Network.Contract.P2P; + +public static class ProtocolParser +{ + // Packed little-endian keys (b0 | b1<<8 | b2<<16 ...) + private const ushort AA = 0x6161; // "aa" + + private const uint Eth = 0x687465u; // "eth" + private const uint P2p = 0x703270u; // "p2p" + private const uint Shh = 0x686873u; // "shh" + private const uint Bzz = 0x7A7A62u; // "bzz" + private const uint Par = 0x726170u; // "par" + private const uint Ndm = 0x6D646Eu; // "ndm" + + private const uint Snap = 0x70616E73u; // "snap" + private const ulong Nodedata = 0x6174616465646F6Eul; // "nodedata" + + public static bool TryGetProtocolCode(ReadOnlySpan protocolSpan, [NotNullWhen(true)] out string? protocol) + { + protocol = null; + + // Bucket by size first - removes repeated length checks and helps bounds-check elimination. + switch (protocolSpan.Length) + { + case 3: + // Build a 24-bit key - JIT can eliminate bounds checks because Length == 3. + uint key3 = (uint)protocolSpan[0] + | ((uint)protocolSpan[1] << 8) + | ((uint)protocolSpan[2] << 16); + + // Put likely hits first if you know your traffic profile. + switch (key3) + { + case Eth: + protocol = Protocol.Eth; return true; + case P2p: + protocol = Protocol.P2P; return true; + case Shh: + protocol = Protocol.Shh; return true; + case Bzz: + protocol = Protocol.Bzz; return true; + case Par: + protocol = Protocol.Par; return true; + case Ndm: + protocol = Protocol.Ndm; return true; + } + break; + + case 4: + if (BinaryPrimitives.ReadUInt32LittleEndian(protocolSpan) == Snap) + { + protocol = Protocol.Snap; + return true; + } + break; + + case 8: + if (BinaryPrimitives.ReadUInt64LittleEndian(protocolSpan) == Nodedata) + { + protocol = Protocol.NodeData; + return true; + } + break; + + case 2: + // Manual pack is fine too, but BinaryPrimitives is also OK here. + ushort key2 = (ushort)(protocolSpan[0] | (protocolSpan[1] << 8)); + if (key2 == AA) + { + protocol = Protocol.AA; + return true; + } + break; + } + return false; + } +} diff --git a/src/Nethermind/Nethermind.Network.Discovery.Test/E2EDiscoveryTests.cs b/src/Nethermind/Nethermind.Network.Discovery.Test/E2EDiscoveryTests.cs index 96496142fc6..779863fc269 100644 --- a/src/Nethermind/Nethermind.Network.Discovery.Test/E2EDiscoveryTests.cs +++ b/src/Nethermind/Nethermind.Network.Discovery.Test/E2EDiscoveryTests.cs @@ -26,7 +26,7 @@ namespace Nethermind.Network.Discovery.Test; [TestFixture(DiscoveryVersion.V5)] public class E2EDiscoveryTests(DiscoveryVersion discoveryVersion) { - private static TimeSpan TestTimeout = TimeSpan.FromSeconds(5); + private static readonly TimeSpan TestTimeout = TimeSpan.FromSeconds(10); /// /// Common code for all node diff --git a/src/Nethermind/Nethermind.Network.Discovery.Test/NettyDiscoveryHandlerTests.cs b/src/Nethermind/Nethermind.Network.Discovery.Test/NettyDiscoveryHandlerTests.cs index 554a31ecbde..b9d241ea87f 100644 --- a/src/Nethermind/Nethermind.Network.Discovery.Test/NettyDiscoveryHandlerTests.cs +++ b/src/Nethermind/Nethermind.Network.Discovery.Test/NettyDiscoveryHandlerTests.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Collections.Generic; using System.Linq; using System.Net; @@ -137,7 +138,7 @@ public async Task FindNodeSentReceivedTest() [Test] public async Task NeighborsSentReceivedTest() { - NeighborsMsg msg = new(_privateKey2.PublicKey, Timestamper.Default.UnixTime.SecondsLong + 1200, new List().ToArray()) + NeighborsMsg msg = new(_privateKey2.PublicKey, Timestamper.Default.UnixTime.SecondsLong + 1200, Array.Empty()) { FarAddress = _address2 }; @@ -146,7 +147,7 @@ public async Task NeighborsSentReceivedTest() await SleepWhileWaiting(); _discoveryManagersMocks[1].Received(1).OnIncomingMsg(Arg.Is(static x => x.MsgType == MsgType.Neighbors)); - NeighborsMsg msg2 = new(_privateKey.PublicKey, Timestamper.Default.UnixTime.SecondsLong + 1200, new List().ToArray()) + NeighborsMsg msg2 = new(_privateKey.PublicKey, Timestamper.Default.UnixTime.SecondsLong + 1200, Array.Empty()) { FarAddress = _address, }; diff --git a/src/Nethermind/Nethermind.Network.Discovery/Discv5/DiscoveryV5App.cs b/src/Nethermind/Nethermind.Network.Discovery/Discv5/DiscoveryV5App.cs index 55746e2c850..1d6ade11338 100644 --- a/src/Nethermind/Nethermind.Network.Discovery/Discv5/DiscoveryV5App.cs +++ b/src/Nethermind/Nethermind.Network.Discovery/Discv5/DiscoveryV5App.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System.Diagnostics.CodeAnalysis; @@ -20,6 +20,7 @@ using NBitcoin.Secp256k1; using Nethermind.Config; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.ServiceStopper; using Nethermind.Crypto; @@ -205,64 +206,74 @@ public async Task StartAsync() public async IAsyncEnumerable DiscoverNodes([EnumeratorCancellation] CancellationToken token) { - Channel ch = Channel.CreateBounded(1); + Channel discoveredNodesChannel = Channel.CreateBounded(1); - async Task DiscoverAsync(IEnumerable startingNode, byte[] nodeId) + async Task DiscoverAsync(IEnumerable startingNode, ArrayPoolSpan nodeId, bool disposeNodeId = true) { - static int[] GetDistances(byte[] srcNodeId, byte[] destNodeId) + try { - const int WiderDistanceRange = 3; + static int[] GetDistances(byte[] srcNodeId, in ArrayPoolSpan destNodeId) + { + const int WiderDistanceRange = 3; - int[] distances = new int[WiderDistanceRange]; - distances[0] = TableUtility.Log2Distance(srcNodeId, destNodeId); + int[] distances = new int[WiderDistanceRange]; + distances[0] = TableUtility.Log2Distance(srcNodeId, destNodeId); - for (int n = 1, i = 1; n < WiderDistanceRange; i++) - { - if (distances[0] - i > 0) - { - distances[n++] = distances[0] - i; - } - if (distances[0] + i <= 256) + for (int n = 1, i = 1; n < WiderDistanceRange; i++) { - distances[n++] = distances[0] + i; + if (distances[0] - i > 0) + { + distances[n++] = distances[0] - i; + } + if (distances[0] + i <= 256) + { + distances[n++] = distances[0] + i; + } } - } - return distances; - } + return distances; + } - Queue nodesToCheck = new(startingNode); - HashSet checkedNodes = []; + Queue nodesToCheck = new(startingNode); + HashSet checkedNodes = []; - while (!token.IsCancellationRequested) - { - if (!nodesToCheck.TryDequeue(out IEnr? newEntry)) + while (!token.IsCancellationRequested) { - return; - } + if (!nodesToCheck.TryDequeue(out IEnr? newEntry)) + { + return; + } - if (TryGetNodeFromEnr(newEntry, out Node? node2)) - { - await ch.Writer.WriteAsync(node2!, token); - if (_logger.IsDebug) _logger.Debug($"A node discovered via discv5: {newEntry} = {node2}."); - _discoveryReport?.NodeFound(); - } + if (TryGetNodeFromEnr(newEntry, out Node? node2)) + { + await discoveredNodesChannel.Writer.WriteAsync(node2!, token); - if (!checkedNodes.Add(newEntry)) - { - continue; - } + if (_logger.IsDebug) _logger.Debug($"A node discovered via discv5: {newEntry} = {node2}."); - IEnumerable? newNodesFound = (await _discv5Protocol.SendFindNodeAsync(newEntry, GetDistances(newEntry.NodeId, nodeId)))?.Where(x => !checkedNodes.Contains(x)); + _discoveryReport?.NodeFound(); + } - if (newNodesFound is not null) - { - foreach (IEnr? node in newNodesFound) + if (!checkedNodes.Add(newEntry)) { - nodesToCheck.Enqueue(node); + continue; + } + + foreach (IEnr newEnr in await _discv5Protocol.SendFindNodeAsync(newEntry, GetDistances(newEntry.NodeId, in nodeId)) ?? []) + { + if (!checkedNodes.Contains(newEnr)) + { + nodesToCheck.Enqueue(newEnr); + } } } } + finally + { + if (disposeNodeId) + { + nodeId.Dispose(); + } + } } IEnumerable GetStartingNodes() => _discv5Protocol.GetAllNodes; @@ -272,32 +283,41 @@ static int[] GetDistances(byte[] srcNodeId, byte[] destNodeId) Task discoverTask = Task.Run(async () => { - byte[] randomNodeId = new byte[32]; + using ArrayPoolSpan selfNodeId = new(32); + _discv5Protocol.SelfEnr.NodeId.CopyTo(selfNodeId); + while (!token.IsCancellationRequested) { try { - List discoverTasks = new List(); - discoverTasks.Add(DiscoverAsync(GetStartingNodes(), _discv5Protocol.SelfEnr.NodeId)); + using ArrayPoolList discoverTasks = new(RandomNodesToLookupCount); + + discoverTasks.Add(DiscoverAsync(GetStartingNodes(), selfNodeId, false)); for (int i = 0; i < RandomNodesToLookupCount; i++) { + ArrayPoolSpan randomNodeId = new(32); random.NextBytes(randomNodeId); discoverTasks.Add(DiscoverAsync(GetStartingNodes(), randomNodeId)); } await Task.WhenAll(discoverTasks); + await Task.Delay(TimeSpan.FromSeconds(2), token); + } + catch (OperationCanceledException) + { + if (_logger.IsTrace) _logger.Trace($"Discovery has been stopped."); } catch (Exception ex) { if (_logger.IsError) _logger.Error($"Discovery via custom random walk failed.", ex); } } - }); + }, token); try { - await foreach (Node node in ch.Reader.ReadAllAsync(token)) + await foreach (Node node in discoveredNodesChannel.Reader.ReadAllAsync(token)) { yield return node; } diff --git a/src/Nethermind/Nethermind.Network.Discovery/Discv5/NettyDiscoveryV5Handler.cs b/src/Nethermind/Nethermind.Network.Discovery/Discv5/NettyDiscoveryV5Handler.cs index ea7d25a16f6..1cb6e9a4e91 100644 --- a/src/Nethermind/Nethermind.Network.Discovery/Discv5/NettyDiscoveryV5Handler.cs +++ b/src/Nethermind/Nethermind.Network.Discovery/Discv5/NettyDiscoveryV5Handler.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System.Net; @@ -36,9 +36,12 @@ public NettyDiscoveryV5Handler(ILogManager loggerManager) : base(loggerManager) protected override void ChannelRead0(IChannelHandlerContext ctx, DatagramPacket msg) { - var udpPacket = new UdpReceiveResult(msg.Content.ReadAllBytesAsArray(), (IPEndPoint)msg.Sender); - if (!_inboundQueue.Writer.TryWrite(udpPacket) && _logger.IsWarn) + UdpReceiveResult udpPacket = new(msg.Content.ReadAllBytesAsArray(), (IPEndPoint)msg.Sender); + + if (!_inboundQueue.Writer.TryWrite(udpPacket) && _logger.IsDebug) + { _logger.Warn("Skipping discovery v5 message as inbound buffer is full"); + } } public async Task SendAsync(byte[] data, IPEndPoint destination) @@ -53,7 +56,7 @@ public async Task SendAsync(byte[] data, IPEndPoint destination) } catch (SocketException exception) { - if (_logger.IsError) _logger.Error("Error sending data", exception); + if (_logger.IsDebug) _logger.Error("DEBUG/ERROR Error sending data", exception); throw; } } diff --git a/src/Nethermind/Nethermind.Network.Discovery/Nethermind.Network.Discovery.csproj b/src/Nethermind/Nethermind.Network.Discovery/Nethermind.Network.Discovery.csproj index ee01ab7f32e..e21eb0552dd 100644 --- a/src/Nethermind/Nethermind.Network.Discovery/Nethermind.Network.Discovery.csproj +++ b/src/Nethermind/Nethermind.Network.Discovery/Nethermind.Network.Discovery.csproj @@ -15,6 +15,7 @@ + diff --git a/src/Nethermind/Nethermind.Network.Discovery/NettyDiscoveryBaseHandler.cs b/src/Nethermind/Nethermind.Network.Discovery/NettyDiscoveryBaseHandler.cs index 17a3c34b12d..d733d4fe6a1 100644 --- a/src/Nethermind/Nethermind.Network.Discovery/NettyDiscoveryBaseHandler.cs +++ b/src/Nethermind/Nethermind.Network.Discovery/NettyDiscoveryBaseHandler.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using DotNetty.Common.Utilities; using DotNetty.Transport.Channels; using DotNetty.Transport.Channels.Sockets; using Nethermind.Logging; @@ -18,7 +19,10 @@ public abstract class NettyDiscoveryBaseHandler(ILogManager? logManager) : Simpl public override void ChannelRead(IChannelHandlerContext ctx, object msg) { if (msg is DatagramPacket packet && AcceptInboundMessage(packet) && !ValidatePacket(packet)) + { + ReferenceCountUtil.Release(msg); return; + } base.ChannelRead(ctx, msg); } diff --git a/src/Nethermind/Nethermind.Network.Dns.Test/EnrDiscoveryTests.cs b/src/Nethermind/Nethermind.Network.Dns.Test/EnrDiscoveryTests.cs index b8e72b938f5..36285381432 100644 --- a/src/Nethermind/Nethermind.Network.Dns.Test/EnrDiscoveryTests.cs +++ b/src/Nethermind/Nethermind.Network.Dns.Test/EnrDiscoveryTests.cs @@ -48,7 +48,7 @@ public async Task Test_enr_discovery2() NodeRecordSigner singer = new(new Ecdsa(), TestItem.PrivateKeyA); EnrRecordParser parser = new(singer); - EnrTreeCrawler crawler = new(new(Substitute.For())); + EnrTreeCrawler crawler = new(LimboTraceLogger.Instance); int verified = 0; await foreach (string record in crawler.SearchTree("all.mainnet.ethdisco.net", default)) { diff --git a/src/Nethermind/Nethermind.Network.Dns.Test/EnrTreeParserTests.cs b/src/Nethermind/Nethermind.Network.Dns.Test/EnrTreeParserTests.cs index 2131e3e2c4d..bd7ff19ab82 100644 --- a/src/Nethermind/Nethermind.Network.Dns.Test/EnrTreeParserTests.cs +++ b/src/Nethermind/Nethermind.Network.Dns.Test/EnrTreeParserTests.cs @@ -1,5 +1,6 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +/* cSpell:disable */ using NUnit.Framework; diff --git a/src/Nethermind/Nethermind.Network.Stats/Model/CapabilityConverter.cs b/src/Nethermind/Nethermind.Network.Stats/Model/CapabilityConverter.cs index f769b62934f..82eacb4a3eb 100644 --- a/src/Nethermind/Nethermind.Network.Stats/Model/CapabilityConverter.cs +++ b/src/Nethermind/Nethermind.Network.Stats/Model/CapabilityConverter.cs @@ -12,6 +12,8 @@ using System.Text.Json.Serialization; using Nethermind.Network.Contract.P2P; +#nullable enable + namespace Nethermind.Stats.Model { public class CapabilityConverter : JsonConverter @@ -20,7 +22,7 @@ public class CapabilityConverter : JsonConverter private const byte SeparatorByte = (byte)'/'; private const int StackAllocThreshold = 256; - public override Capability Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override Capability? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType == JsonTokenType.Null) { @@ -32,7 +34,7 @@ public override Capability Read(ref Utf8JsonReader reader, Type typeToConvert, J ThrowJsonException(); } - if (!TryParseCapability(ref reader, out Capability capability)) + if (!TryParseCapability(ref reader, out Capability? capability)) { ThrowJsonException(); } @@ -93,7 +95,7 @@ private static void WriteToBuffer(Utf8JsonWriter writer, Capability capability, } } - private static bool TryParseCapability(ref Utf8JsonReader reader, out Capability capability) + private static bool TryParseCapability(ref Utf8JsonReader reader, out Capability? capability) { capability = null; @@ -120,27 +122,14 @@ private static bool TryParseCapability(ref Utf8JsonReader reader, out Capability return false; } - string protocolCode = GetProtocolCode(protocolSpan); + if (!Network.Contract.P2P.ProtocolParser.TryGetProtocolCode(protocolSpan, out string? protocolCode)) + { + protocolCode = Encoding.UTF8.GetString(protocolSpan); + } capability = new Capability(protocolCode, version); return true; } - private static string GetProtocolCode(ReadOnlySpan protocolSpan) - { - if (protocolSpan.SequenceEqual("eth"u8)) return Protocol.Eth; - if (protocolSpan.SequenceEqual("snap"u8)) return Protocol.Snap; - if (protocolSpan.SequenceEqual("p2p"u8)) return Protocol.P2P; - if (protocolSpan.SequenceEqual("nodedata"u8)) return Protocol.NodeData; - if (protocolSpan.SequenceEqual("shh"u8)) return Protocol.Shh; - if (protocolSpan.SequenceEqual("bzz"u8)) return Protocol.Bzz; - if (protocolSpan.SequenceEqual("par"u8)) return Protocol.Par; - if (protocolSpan.SequenceEqual("ndm"u8)) return Protocol.Ndm; - if (protocolSpan.SequenceEqual("aa"u8)) return Protocol.AA; - - // Fallback for unknown protocols - return Encoding.UTF8.GetString(protocolSpan); - } - [DoesNotReturn, StackTraceHidden] private static void ThrowJsonException() => throw new JsonException(); } diff --git a/src/Nethermind/Nethermind.Network.Stats/Model/DisconnectReason.cs b/src/Nethermind/Nethermind.Network.Stats/Model/DisconnectReason.cs index 10b773a3b29..c14265ea159 100644 --- a/src/Nethermind/Nethermind.Network.Stats/Model/DisconnectReason.cs +++ b/src/Nethermind/Nethermind.Network.Stats/Model/DisconnectReason.cs @@ -92,7 +92,7 @@ public static EthDisconnectReason ToEthDisconnectReason(this DisconnectReason di DisconnectReason.ForwardSyncFailed => EthDisconnectReason.DisconnectRequested, DisconnectReason.GossipingInPoS => EthDisconnectReason.BreachOfProtocol, DisconnectReason.AppClosing => EthDisconnectReason.ClientQuitting, - DisconnectReason.InvalidTxOrUncle or DisconnectReason.HeaderResponseTooLong or DisconnectReason.InconsistentHeaderBatch or DisconnectReason.UnexpectedHeaderHash or DisconnectReason.HeaderBatchOnDifferentBranch or DisconnectReason.UnexpectedParentHeader or DisconnectReason.InvalidHeader or DisconnectReason.InvalidReceiptRoot or DisconnectReason.EthSyncException => EthDisconnectReason.BreachOfProtocol, + DisconnectReason.InvalidTxOrUncle or DisconnectReason.HeaderResponseTooLong or DisconnectReason.InconsistentHeaderBatch or DisconnectReason.UnexpectedHeaderHash or DisconnectReason.HeaderBatchOnDifferentBranch or DisconnectReason.UnexpectedParentHeader or DisconnectReason.InvalidHeader or DisconnectReason.InvalidReceiptRoot or DisconnectReason.EthSyncException or DisconnectReason.InvalidBlockRangeUpdate => EthDisconnectReason.BreachOfProtocol, DisconnectReason.EthDisconnectRequested => EthDisconnectReason.DisconnectRequested, DisconnectReason.TcpSubSystemError => EthDisconnectReason.TcpSubSystemError, DisconnectReason.BreachOfProtocol => EthDisconnectReason.BreachOfProtocol, diff --git a/src/Nethermind/Nethermind.Network.Stats/Model/Node.cs b/src/Nethermind/Nethermind.Network.Stats/Model/Node.cs index ebb4fc91bab..24d057d6961 100644 --- a/src/Nethermind/Nethermind.Network.Stats/Model/Node.cs +++ b/src/Nethermind/Nethermind.Network.Stats/Model/Node.cs @@ -2,7 +2,10 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Linq; using System.Net; +using System.Text.RegularExpressions; +using FastEnumUtility; using Nethermind.Config; using Nethermind.Core.Crypto; @@ -193,56 +196,66 @@ public bool Equals(Node other) return !(a == b); } + // Dynamically generates regex pattern from NodeClientType enum values (excluding Unknown). + // Pattern structure: (ClientName|OtherClient|...) + // Ordered by likelihood first, with longer names before potential substrings to prevent conflicts. + private static readonly Regex _clientTypeRegex = new( + string.Join("|", + // Most common clients (ordered by likelihood) + new[] + { + nameof(NodeClientType.Geth), + nameof(NodeClientType.Nethermind), + nameof(NodeClientType.Reth), + nameof(NodeClientType.Besu), + nameof(NodeClientType.Erigon), + nameof(NodeClientType.Nimbus), + nameof(NodeClientType.Ethrex), + nameof(NodeClientType.EthereumJS), + nameof(NodeClientType.OpenEthereum), + nameof(NodeClientType.Parity), + } + .Concat( + // Less common clients (ordered by length to prevent substring conflicts) + FastEnum.GetNames() + .Except(new[] + { + nameof(NodeClientType.Unknown), + nameof(NodeClientType.Geth), + nameof(NodeClientType.Nethermind), + nameof(NodeClientType.Reth), + nameof(NodeClientType.Besu), + nameof(NodeClientType.Erigon), + nameof(NodeClientType.Nimbus), + nameof(NodeClientType.Ethrex), + nameof(NodeClientType.EthereumJS), + nameof(NodeClientType.OpenEthereum), + nameof(NodeClientType.Parity), + }) + .OrderByDescending(name => name.Length))), + RegexOptions.Compiled | RegexOptions.IgnoreCase); + public static NodeClientType RecognizeClientType(string clientId) { if (clientId is null) { return NodeClientType.Unknown; } - else if (clientId.Contains(nameof(NodeClientType.Besu), StringComparison.OrdinalIgnoreCase)) - { - return NodeClientType.Besu; - } - else if (clientId.Contains(nameof(NodeClientType.Geth), StringComparison.OrdinalIgnoreCase)) - { - return NodeClientType.Geth; - } - else if (clientId.Contains(nameof(NodeClientType.Nethermind), StringComparison.OrdinalIgnoreCase)) - { - return NodeClientType.Nethermind; - } - else if (clientId.Contains(nameof(NodeClientType.Erigon), StringComparison.OrdinalIgnoreCase)) - { - return NodeClientType.Erigon; - } - else if (clientId.Contains(nameof(NodeClientType.Reth), StringComparison.OrdinalIgnoreCase)) - { - return NodeClientType.Reth; - } - else if (clientId.Contains(nameof(NodeClientType.Nimbus), StringComparison.OrdinalIgnoreCase)) - { - return NodeClientType.Nimbus; - } - else if (clientId.Contains(nameof(NodeClientType.EthereumJS), StringComparison.OrdinalIgnoreCase)) - { - return NodeClientType.EthereumJS; - } - else if (clientId.Contains(nameof(NodeClientType.Parity), StringComparison.OrdinalIgnoreCase)) - { - return NodeClientType.Parity; - } - else if (clientId.Contains(nameof(NodeClientType.OpenEthereum), StringComparison.OrdinalIgnoreCase)) - { - return NodeClientType.OpenEthereum; - } - else if (clientId.Contains(nameof(NodeClientType.Trinity), StringComparison.OrdinalIgnoreCase)) - { - return NodeClientType.Trinity; - } - else + + // Use EnumerateMatches to avoid allocations - it returns ValueMatch structs + foreach (ValueMatch match in _clientTypeRegex.EnumerateMatches(clientId)) { - return NodeClientType.Unknown; + // Get the matched text as a span to avoid allocations + ReadOnlySpan matchedText = clientId.AsSpan(match.Index, match.Length); + + // Try to parse the matched client name + if (FastEnum.TryParse(matchedText, ignoreCase: true, out NodeClientType clientType)) + { + return clientType; + } } + + return NodeClientType.Unknown; } public static class Format diff --git a/src/Nethermind/Nethermind.Network.Stats/Model/NodeClientType.cs b/src/Nethermind/Nethermind.Network.Stats/Model/NodeClientType.cs index 69c6e652049..ab8df133b21 100644 --- a/src/Nethermind/Nethermind.Network.Stats/Model/NodeClientType.cs +++ b/src/Nethermind/Nethermind.Network.Stats/Model/NodeClientType.cs @@ -15,6 +15,21 @@ public enum NodeClientType Erigon, Reth, Nimbus, - EthereumJS + EthereumJS, + Ethrex, + Bor, + Ronin, + Scraper, + Sentinel, + Grails, + Sonic, + Gait, + Diamond, + NodeCrawler, + Energi, + Opera, + Gwat, + Tempo, + Swarm } } diff --git a/src/Nethermind/Nethermind.Network.Test/ForkInfoTests.cs b/src/Nethermind/Nethermind.Network.Test/ForkInfoTests.cs index a8f77e3b9e9..c0c78ea903a 100644 --- a/src/Nethermind/Nethermind.Network.Test/ForkInfoTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/ForkInfoTests.cs @@ -116,7 +116,7 @@ public void Fork_id_and_hash_as_expected_with_merge_fork_id(long head, ulong hea } [TestCase(0, 0ul, "0xFE3366E7", 1735371ul, "Sepolia genesis")] - [TestCase(1735370, 0ul, "0xFE3366E7", 1_735_371ul, "Sepolia Last block before MergeForkIdTranstion")] + [TestCase(1735370, 0ul, "0xFE3366E7", 1_735_371ul, "Sepolia Last block before MergeForkIdTransition")] [TestCase(1735371, 0ul, "0xb96cbd13", 1_677_557_088ul, "First block - Sepolia MergeForkIdTransition")] [TestCase(1735372, 1_677_557_088ul, "0xf7f9bc08", 1_706_655_072ul, "Shanghai")] [TestCase(1735372, 1_706_655_071ul, "0xf7f9bc08", 1_706_655_072ul, "Future Shanghai")] @@ -151,9 +151,11 @@ public void Fork_id_and_hash_as_expected_on_sepolia(long head, ulong headTimesta [TestCase(21735000, 0ul, "0x018479d3", GnosisSpecProvider.ShanghaiTimestamp, "First GIP-31 block")] [TestCase(31735000, GnosisSpecProvider.ShanghaiTimestamp, "0x2efe91ba", GnosisSpecProvider.CancunTimestamp, "First Shanghai timestamp")] [TestCase(41735000, GnosisSpecProvider.CancunTimestamp, "0x1384dfc1", GnosisSpecProvider.PragueTimestamp, "First Cancun timestamp")] - [TestCase(91735000, GnosisSpecProvider.CancunTimestamp, "0x1384dfc1", GnosisSpecProvider.PragueTimestamp, "Future Cancun timestamp")] - [TestCase(101735000, GnosisSpecProvider.PragueTimestamp, "0x2f095d4a", 0ul, "First Prague timestamp")] - [TestCase(101735000, GnosisSpecProvider.PragueTimestamp, "0x2f095d4a", 0ul, "Future Prague timestamp")] + [TestCase(91735000, GnosisSpecProvider.PragueTimestamp - 1, "0x1384dfc1", GnosisSpecProvider.PragueTimestamp, "Future Cancun timestamp")] + [TestCase(101735000, GnosisSpecProvider.PragueTimestamp, "0x2f095d4a", GnosisSpecProvider.BalancerTimestamp, "First Prague timestamp")] + [TestCase(101735000, GnosisSpecProvider.BalancerTimestamp - 1, "0x2f095d4a", GnosisSpecProvider.BalancerTimestamp, "Future Prague timestamp")] + [TestCase(111735000, GnosisSpecProvider.BalancerTimestamp, "0xd00284ad", 0ul, "First Balancer timestamp")] + [TestCase(111735000, GnosisSpecProvider.BalancerTimestamp + 100, "0xd00284ad", 0ul, "First Balancer timestamp")] public void Fork_id_and_hash_as_expected_on_gnosis(long head, ulong headTimestamp, string forkHashHex, ulong next, string description) { ChainSpecFileLoader loader = new(new EthereumJsonSerializer(), LimboTraceLogger.Instance); @@ -370,13 +372,13 @@ public void Chain_id_and_network_id_have_proper_default_values(ulong? specNetwor provider.NetworkId.Should().Be(expectedNetworkId); } - private static void Test(long head, ulong headTimestamp, Hash256 genesisHash, string forkHashHex, ulong next, string description, ISpecProvider specProvider, string chainSpec, string path = "../../../../Chains") + public static void Test(long head, ulong headTimestamp, Hash256 genesisHash, string forkHashHex, ulong next, string description, ISpecProvider specProvider, string chainSpec, string path = "../../../../Chains") { Test(head, headTimestamp, genesisHash, forkHashHex, next, description, specProvider); Test(head, headTimestamp, genesisHash, forkHashHex, next, description, chainSpec, path); } - private static void Test(long head, ulong headTimestamp, Hash256 genesisHash, string forkHashHex, ulong next, string description, string chainSpec, string path = "../../../../Chains") + public static void Test(long head, ulong headTimestamp, Hash256 genesisHash, string forkHashHex, ulong next, string description, string chainSpec, string path = "../../../../Chains") { var loader = new ChainSpecFileLoader(new EthereumJsonSerializer(), LimboTraceLogger.Instance); ChainSpec spec = loader.LoadEmbeddedOrFromFile(Path.Combine(path, chainSpec)); @@ -386,7 +388,7 @@ private static void Test(long head, ulong headTimestamp, Hash256 genesisHash, st private static void Test(long head, ulong headTimestamp, Hash256 genesisHash, string forkHashHex, ulong next, string description, ISpecProvider specProvider) { - uint expectedForkHash = Bytes.ReadEthUInt32(Bytes.FromHexString(forkHashHex)); + uint expectedForkHash = Bytes.FromHexString(forkHashHex).ReadEthUInt32(); ISyncServer syncServer = Substitute.For(); syncServer.Genesis.Returns(Build.A.BlockHeader.WithHash(genesisHash).TestObject); diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/P2PProtocolInfoProviderTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/P2PProtocolInfoProviderTests.cs index ddfc60643b0..354f992b048 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/P2PProtocolInfoProviderTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/P2PProtocolInfoProviderTests.cs @@ -14,7 +14,7 @@ public class P2PProtocolInfoProviderTests public void DefaultCapabilitiesToString_ReturnExpectedResult() { string result = P2PProtocolInfoProvider.DefaultCapabilitiesToString(); - Assert.That(result, Is.EqualTo("eth/68,eth/67,eth/66,nodedata/1")); + Assert.That(result, Is.EqualTo("eth/68,nodedata/1")); } } } diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/ProtocolParserTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/ProtocolParserTests.cs new file mode 100644 index 00000000000..3baecbc75c8 --- /dev/null +++ b/src/Nethermind/Nethermind.Network.Test/P2P/ProtocolParserTests.cs @@ -0,0 +1,329 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Text; +using Nethermind.Network.Contract.P2P; +using NUnit.Framework; + +namespace Nethermind.Network.Test.P2P; + +[Parallelizable(ParallelScope.Self)] +public class ProtocolParserTests +{ + [Test] + public void TryGetProtocolCode_Eth_ReturnsTrue() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("eth"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.True); + Assert.That(protocol, Is.EqualTo(Protocol.Eth)); + } + + [Test] + public void TryGetProtocolCode_P2p_ReturnsTrue() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("p2p"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.True); + Assert.That(protocol, Is.EqualTo(Protocol.P2P)); + } + + [Test] + public void TryGetProtocolCode_Shh_ReturnsTrue() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("shh"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.True); + Assert.That(protocol, Is.EqualTo(Protocol.Shh)); + } + + [Test] + public void TryGetProtocolCode_Bzz_ReturnsTrue() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("bzz"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.True); + Assert.That(protocol, Is.EqualTo(Protocol.Bzz)); + } + + [Test] + public void TryGetProtocolCode_Par_ReturnsTrue() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("par"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.True); + Assert.That(protocol, Is.EqualTo(Protocol.Par)); + } + + [Test] + public void TryGetProtocolCode_Ndm_ReturnsTrue() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("ndm"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.True); + Assert.That(protocol, Is.EqualTo(Protocol.Ndm)); + } + + [Test] + public void TryGetProtocolCode_Snap_ReturnsTrue() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("snap"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.True); + Assert.That(protocol, Is.EqualTo(Protocol.Snap)); + } + + [Test] + public void TryGetProtocolCode_NodeData_ReturnsTrue() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("nodedata"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.True); + Assert.That(protocol, Is.EqualTo(Protocol.NodeData)); + } + + [Test] + public void TryGetProtocolCode_AA_ReturnsTrue() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("aa"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.True); + Assert.That(protocol, Is.EqualTo(Protocol.AA)); + } + + [Test] + public void TryGetProtocolCode_EmptySpan_ReturnsFalse() + { + ReadOnlySpan emptySpan = ReadOnlySpan.Empty; + bool result = ProtocolParser.TryGetProtocolCode(emptySpan, out string? protocol); + + Assert.That(result, Is.False); + Assert.That(protocol, Is.Null); + } + + [Test] + public void TryGetProtocolCode_UnknownProtocol_ReturnsFalse() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("xyz"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.False); + Assert.That(protocol, Is.Null); + } + + [Test] + public void TryGetProtocolCode_IncorrectLength_ReturnsFalse() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("e"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.False); + Assert.That(protocol, Is.Null); + } + + [Test] + public void TryGetProtocolCode_WrongLength_FiveChars_ReturnsFalse() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("ethxx"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.False); + Assert.That(protocol, Is.Null); + } + + [Test] + public void TryGetProtocolCode_UpperCase_ReturnsFalse() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("ETH"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.False); + Assert.That(protocol, Is.Null); + } + + [Test] + public void TryGetProtocolCode_MixedCase_ReturnsFalse() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("Eth"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.False); + Assert.That(protocol, Is.Null); + } + + [Test] + public void TryGetProtocolCode_ValidatesHexConstants_Eth() + { + byte[] ethBytes = Encoding.UTF8.GetBytes("eth"); + uint ethValue = CalculateThreeByteKey(ethBytes); + + Assert.That(ethValue, Is.EqualTo(0x687465u), "Hex constant for 'eth' should match"); + } + + [Test] + public void TryGetProtocolCode_ValidatesHexConstants_P2p() + { + byte[] p2pBytes = Encoding.UTF8.GetBytes("p2p"); + uint p2pValue = CalculateThreeByteKey(p2pBytes); + + Assert.That(p2pValue, Is.EqualTo(0x703270u), "Hex constant for 'p2p' should match"); + } + + [Test] + public void TryGetProtocolCode_ValidatesHexConstants_Shh() + { + byte[] shhBytes = Encoding.UTF8.GetBytes("shh"); + uint shhValue = CalculateThreeByteKey(shhBytes); + + Assert.That(shhValue, Is.EqualTo(0x686873u), "Hex constant for 'shh' should match"); + } + + [Test] + public void TryGetProtocolCode_ValidatesHexConstants_Bzz() + { + byte[] bzzBytes = Encoding.UTF8.GetBytes("bzz"); + uint bzzValue = CalculateThreeByteKey(bzzBytes); + + Assert.That(bzzValue, Is.EqualTo(0x7A7A62u), "Hex constant for 'bzz' should match"); + } + + [Test] + public void TryGetProtocolCode_ValidatesHexConstants_Par() + { + byte[] parBytes = Encoding.UTF8.GetBytes("par"); + uint parValue = CalculateThreeByteKey(parBytes); + + Assert.That(parValue, Is.EqualTo(0x726170u), "Hex constant for 'par' should match"); + } + + [Test] + public void TryGetProtocolCode_ValidatesHexConstants_Ndm() + { + byte[] ndmBytes = Encoding.UTF8.GetBytes("ndm"); + uint ndmValue = CalculateThreeByteKey(ndmBytes); + + Assert.That(ndmValue, Is.EqualTo(0x6D646Eu), "Hex constant for 'ndm' should match"); + } + + [Test] + public void TryGetProtocolCode_ValidatesHexConstants_Snap() + { + byte[] snapBytes = Encoding.UTF8.GetBytes("snap"); + uint snapValue = BitConverter.ToUInt32(snapBytes, 0); + + Assert.That(snapValue, Is.EqualTo(0x70616E73u), "Hex constant for 'snap' should match"); + } + + [Test] + public void TryGetProtocolCode_ValidatesHexConstants_NodeData() + { + byte[] nodedataBytes = Encoding.UTF8.GetBytes("nodedata"); + ulong nodedataValue = BitConverter.ToUInt64(nodedataBytes, 0); + + Assert.That(nodedataValue, Is.EqualTo(0x6174616465646F6Eul), "Hex constant for 'nodedata' should match"); + } + + [Test] + public void TryGetProtocolCode_ValidatesHexConstants_AA() + { + byte[] aaBytes = Encoding.UTF8.GetBytes("aa"); + ushort aaValue = BitConverter.ToUInt16(aaBytes, 0); + + Assert.That(aaValue, Is.EqualTo(0x6161), "Hex constant for 'aa' should match"); + } + + [Test] + public void TryGetProtocolCode_Length6_ReturnsFalse() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("abcdef"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.False); + Assert.That(protocol, Is.Null); + } + + [Test] + public void TryGetProtocolCode_Length7_ReturnsFalse() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("abcdefg"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.False); + Assert.That(protocol, Is.Null); + } + + [Test] + public void TryGetProtocolCode_Length9_ReturnsFalse() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("abcdefghi"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.False); + Assert.That(protocol, Is.Null); + } + + [Test] + public void TryGetProtocolCode_WithSpan_Eth_ReturnsTrue() + { + ReadOnlySpan protocolSpan = "eth"u8; + bool result = ProtocolParser.TryGetProtocolCode(protocolSpan, out string? protocol); + + Assert.That(result, Is.True); + Assert.That(protocol, Is.EqualTo(Protocol.Eth)); + } + + [Test] + public void TryGetProtocolCode_WithSpan_Snap_ReturnsTrue() + { + ReadOnlySpan protocolSpan = "snap"u8; + bool result = ProtocolParser.TryGetProtocolCode(protocolSpan, out string? protocol); + + Assert.That(result, Is.True); + Assert.That(protocol, Is.EqualTo(Protocol.Snap)); + } + + [Test] + public void TryGetProtocolCode_WithSpan_NodeData_ReturnsTrue() + { + ReadOnlySpan protocolSpan = "nodedata"u8; + bool result = ProtocolParser.TryGetProtocolCode(protocolSpan, out string? protocol); + + Assert.That(result, Is.True); + Assert.That(protocol, Is.EqualTo(Protocol.NodeData)); + } + + [Test] + public void TryGetProtocolCode_PartialMatchShouldFail_EthX() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("ethx"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.False); + Assert.That(protocol, Is.Null); + } + + [Test] + public void TryGetProtocolCode_PartialMatchShouldFail_Sna() + { + byte[] protocolBytes = Encoding.UTF8.GetBytes("sna"); + bool result = ProtocolParser.TryGetProtocolCode(protocolBytes, out string? protocol); + + Assert.That(result, Is.False); + Assert.That(protocol, Is.Null); + } + + private static uint CalculateThreeByteKey(byte[] bytes) + { + return (uint)bytes[0] | ((uint)bytes[1] << 8) | ((uint)bytes[2] << 16); + } +} diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/Eth62ProtocolHandlerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/Eth62ProtocolHandlerTests.cs index d409db422fd..c202a9b9c24 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/Eth62ProtocolHandlerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V62/Eth62ProtocolHandlerTests.cs @@ -405,12 +405,14 @@ public void Can_handle_transactions([Values(true, false)] bool canGossipTransact private class AlwaysTimeoutBackgroundTaskScheduler : IBackgroundTaskScheduler { internal int ScheduledTasks = 0; - public void ScheduleTask(TReq request, Func fulfillFunc, TimeSpan? timeout = null) + public bool TryScheduleTask(TReq request, Func fulfillFunc, + TimeSpan? timeout = null) { - CancellationTokenSource cts = new CancellationTokenSource(); + CancellationTokenSource cts = new(); cts.Cancel(); fulfillFunc(request, cts.Token); ScheduledTasks++; + return true; } } diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V63/GetReceiptsMessageTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V63/GetReceiptsMessageTests.cs index b6bde3d62e7..c12998f398d 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V63/GetReceiptsMessageTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V63/GetReceiptsMessageTests.cs @@ -14,7 +14,7 @@ namespace Nethermind.Network.Test.P2P.Subprotocols.Eth.V63 public class GetReceiptsMessageTests { [Test] - public void Sets_values_from_contructor_argument() + public void Sets_values_from_constructor_argument() { ArrayPoolList hashes = new(2) { TestItem.KeccakA, TestItem.KeccakB }; using GetReceiptsMessage message = new(hashes); diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/PooledTransactionsRequestingTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/PooledTransactionsRequestingTests.cs index 6bbf7ace5c0..3e7b97e6b71 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/PooledTransactionsRequestingTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V66/PooledTransactionsRequestingTests.cs @@ -77,11 +77,10 @@ public void Setup() new BlobTxStorage(), new ChainHeadInfoProvider( new ChainHeadSpecProvider(specProvider, blockTree), blockTree, TestWorldStateFactory.CreateForTestWithStateReader(TestMemDbProvider.Init(), LimboLogs.Instance).Item2), - new TxPoolConfig(), + new TxPoolConfig() { AcceptTxWhenNotSynced = true }, new TxValidator(specProvider.ChainId), LimboLogs.Instance, new TransactionComparerProvider(specProvider, blockTree).GetDefaultComparer()); - ISyncServer syncManager = Substitute.For(); syncManager.Head.Returns(_genesisBlock.Header); syncManager.Genesis.Returns(_genesisBlock.Header); @@ -150,7 +149,7 @@ public void TearDown() } [Test] - public async Task Should_request_from_others_after_timout() + public async Task Should_request_from_others_after_timeout() { await Task.Delay(Timeout); @@ -170,7 +169,7 @@ public async Task Should_not_request_from_others_if_received() [Test] - public async Task Should_not_request_from_others_if_received_immidietly() + public async Task Should_not_request_from_others_if_received_immediately() { HandleZeroMessage(_handler, new Network.P2P.Subprotocols.Eth.V66.Messages.PooledTransactionsMessage(1111, new PooledTransactionsMessage(_txs)), Eth65MessageCode.PooledTransactions); await Task.Delay(Timeout); diff --git a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandlerTests.cs b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandlerTests.cs index 657350833df..12d64964220 100644 --- a/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandlerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandlerTests.cs @@ -127,7 +127,7 @@ public void Can_handle_NewPooledTransactions_message([Values(0, 1, 2, 100)] int [TestCase(true)] [TestCase(false)] - public void Should_throw_when_sizes_doesnt_match(bool removeSize) + public void Should_throw_when_sizes_do_not_match(bool removeSize) { GenerateLists(4, out ArrayPoolList types, out ArrayPoolList sizes, out ArrayPoolList hashes); diff --git a/src/Nethermind/Nethermind.Network.Test/PacketTests.cs b/src/Nethermind/Nethermind.Network.Test/PacketTests.cs index 7d04dd64766..d48385292c8 100644 --- a/src/Nethermind/Nethermind.Network.Test/PacketTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/PacketTests.cs @@ -11,7 +11,7 @@ namespace Nethermind.Network.Test public class PacketTests { [Test] - public void Asggins_values_from_constructor() + public void Assigns_values_from_constructor() { byte[] data = { 3, 4, 5 }; Packet packet = new("eth", 2, data); diff --git a/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs b/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs index 2330957538e..3121523f1e0 100644 --- a/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/PeerManagerTests.cs @@ -63,7 +63,7 @@ public async Task Can_start_and_stop() "enode://3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333b@52.141.78.53:12345?discport=6789"; private const string enode8String = - "enode://3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333b@52.141.78.53:12345?somethingwrong=6789"; + "enode://3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333b@52.141.78.53:12345?somethingWrong=6789"; private const string enode9String = "enode://3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333b@52.141.78.53:12345?discport=6789?discport=67899"; @@ -119,31 +119,16 @@ public async Task Will_discard_a_duplicate_incoming_session() } [Test] - public void Will_return_exception_in_port() - { - Assert.Throws(static delegate - { - Enode unused = new(enode3String); - }); - } + public void Will_return_exception_in_port() => + Assert.Throws(static () => new Enode(enode3String)); [Test] - public void Will_return_exception_in_dns() - { - Assert.Throws(static delegate - { - Enode unused = new(enode4String); - }); - } + public void Will_return_exception_in_dns() => + Assert.Throws(static () => new Enode(enode4String)); [Test] - public void Will_return_exception_when_there_is_no_port() - { - Assert.Throws(static delegate - { - Enode unused = new(enode5String); - }); - } + public void Will_return_exception_when_there_is_no_port() => + Assert.Throws(static () => new Enode(enode5String)); [Test] public void Will_parse_ports_correctly_when_there_are_two_different_ports() @@ -162,32 +147,16 @@ public void Will_parse_port_correctly_when_there_is_only_one() } [Test] - public void Will_return_exception_on_wrong_ports_part() - { - Assert.Throws(static delegate - { - Enode unused = new(enode8String); - }); - } + public void Will_return_exception_on_wrong_ports_part() => + Assert.Throws(static () => new Enode(enode8String)); [Test] - public void Will_return_exception_on_duplicated_discovery_port_part() - { - Assert.Throws(static delegate - { - Enode unused = new(enode9String); - }); - } + public void Will_return_exception_on_duplicated_discovery_port_part() => + Assert.Throws(static () => new Enode(enode9String)); [Test] - public void Will_return_exception_on_wrong_form_of_discovery_port_part() - { - Assert.Throws(static delegate - { - Enode unused = new(enode10String); - }); - } - + public void Will_return_exception_on_wrong_form_of_discovery_port_part() => + Assert.Throws(static () => new Enode(enode10String)); [Test] public async Task Will_accept_static_connection() { diff --git a/src/Nethermind/Nethermind.Network.Test/PeerPoolTests.cs b/src/Nethermind/Nethermind.Network.Test/PeerPoolTests.cs index 92e13cb55d6..cca52bbda26 100644 --- a/src/Nethermind/Nethermind.Network.Test/PeerPoolTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/PeerPoolTests.cs @@ -2,9 +2,12 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Nethermind.Core.Crypto; using Nethermind.Core.Test; +using Nethermind.Config; using Nethermind.Crypto; using Nethermind.Logging; using Nethermind.Network.Config; @@ -64,4 +67,61 @@ public async Task PeerPool_ShouldThrottleSource_WhenFull() await pool.StopAsync(); } + + [Test] + public async Task PeerPool_RunPeerCommit_ShouldContinueAfterNoPendingChange() + { + var trustedNodesManager = Substitute.For(); + var nodeSource = new TestNodeSource(); + var stats = Substitute.For(); + var storage = new TestNetworkStorage(); + var networkConfig = new NetworkConfig + { + PeersPersistenceInterval = 50, + MaxActivePeers = 0, + MaxCandidatePeerCount = 0 + }; + + var pool = new PeerPool(nodeSource, stats, storage, networkConfig, LimboLogs.Instance, trustedNodesManager); + + storage.Pending = false; + pool.Start(); + + try + { + // allow a couple of ticks with no pending changes + await Task.Delay(200); + Assert.That(storage.CommitCount, Is.EqualTo(0)); + + // now flip to pending and expect a commit soon + storage.Pending = true; + Assert.That(() => storage.CommitCount, Is.AtLeast(1).After(2000, 10)); + + // StartBatch should be called once in ctor and once after commit + Assert.That(() => storage.StartBatchCount, Is.AtLeast(2).After(2000, 10)); + } + finally + { + await pool.StopAsync(); + } + } + + private sealed class TestNetworkStorage : INetworkStorage + { + public volatile bool Pending; + public int CommitCount { get; private set; } + public int StartBatchCount { get; private set; } + + public NetworkNode[] GetPersistedNodes() => Array.Empty(); + public int PersistedNodesCount => 0; + public void UpdateNode(NetworkNode node) { Pending = true; } + public void UpdateNodes(IEnumerable nodes) { Pending = true; } + public void RemoveNode(PublicKey nodeId) { Pending = true; } + public void StartBatch() { Interlocked.Increment(ref _startBatchCountBacking); StartBatchCount = _startBatchCountBacking; } + public void Commit() { Interlocked.Increment(ref _commitCountBacking); CommitCount = _commitCountBacking; } + public bool AnyPendingChange() => Pending; + + private int _commitCountBacking; + private int _startBatchCountBacking; + } } diff --git a/src/Nethermind/Nethermind.Network.Test/ProtocolsManagerTests.cs b/src/Nethermind/Nethermind.Network.Test/ProtocolsManagerTests.cs index 9eba656ac38..19271cce2db 100644 --- a/src/Nethermind/Nethermind.Network.Test/ProtocolsManagerTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/ProtocolsManagerTests.cs @@ -219,7 +219,7 @@ public Context ReceiveStatus() msg.NetworkId = TestBlockchainIds.NetworkId; msg.GenesisHash = _blockTree.Genesis.Hash; msg.BestHash = _blockTree.Genesis.Hash; - msg.ProtocolVersion = 66; + msg.ProtocolVersion = 68; msg.ForkId = new ForkId(0, 0); return ReceiveStatus(msg); @@ -239,7 +239,7 @@ public Context VerifyEthInitialized() INodeStats stats = _nodeStatsManager.GetOrAdd(_currentSession.Node); Assert.That(stats.EthNodeDetails.NetworkId, Is.EqualTo(TestBlockchainIds.NetworkId)); Assert.That(stats.EthNodeDetails.GenesisHash, Is.EqualTo(_blockTree.Genesis.Hash)); - Assert.That(stats.EthNodeDetails.ProtocolVersion, Is.EqualTo(66)); + Assert.That(stats.EthNodeDetails.ProtocolVersion, Is.EqualTo(68)); Assert.That(stats.EthNodeDetails.TotalDifficulty, Is.EqualTo(BigInteger.One)); return this; } @@ -265,7 +265,7 @@ private Context ReceiveHello(HelloMessage msg) public Context ReceiveHello(byte p2pVersion = 5) { using HelloMessage msg = new(); - msg.Capabilities = new ArrayPoolList(1) { new("eth", 66) }; + msg.Capabilities = new ArrayPoolList(1) { new("eth", 68) }; msg.NodeId = TestItem.PublicKeyB; msg.ClientId = "other client v1"; msg.P2PVersion = p2pVersion; @@ -309,7 +309,7 @@ public Context ReceiveStatusWrongChain(ulong networkId) msg.NetworkId = networkId; msg.GenesisHash = TestItem.KeccakA; msg.BestHash = TestItem.KeccakA; - msg.ProtocolVersion = 66; + msg.ProtocolVersion = 68; return ReceiveStatus(msg); } @@ -321,7 +321,7 @@ public Context ReceiveStatusWrongGenesis() msg.NetworkId = TestBlockchainIds.NetworkId; msg.GenesisHash = TestItem.KeccakB; msg.BestHash = TestItem.KeccakB; - msg.ProtocolVersion = 66; + msg.ProtocolVersion = 68; return ReceiveStatus(msg); } @@ -474,7 +474,7 @@ public void Disconnects_on_wrong_genesis_hash() } [Test] - public void Initialized_with_eth66_only() + public void Initialized_with_eth68_only() { When .CreateIncomingSession() @@ -482,7 +482,7 @@ public void Initialized_with_eth66_only() .Handshake() .Init() .VerifyInitialized() - .ReceiveHelloEth(66) + .ReceiveHelloEth(68) .VerifyInitialized(); } diff --git a/src/Nethermind/Nethermind.Network.Test/Rlpx/Handshake/EncryptionHandshakeServiceTests.cs b/src/Nethermind/Nethermind.Network.Test/Rlpx/Handshake/EncryptionHandshakeServiceTests.cs index c791e5a10b2..01e47e33d52 100644 --- a/src/Nethermind/Nethermind.Network.Test/Rlpx/Handshake/EncryptionHandshakeServiceTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/Rlpx/Handshake/EncryptionHandshakeServiceTests.cs @@ -29,7 +29,7 @@ public void SetUp() SerializerInfo.Create(new AckEip8MessageSerializer(new Eip8MessagePad(_testRandom))) ); - _eciesCipher = new EciesCipher(_trueCryptoRandom); // TODO: provide a separate test random with specific IV and epehemeral key for testing + _eciesCipher = new EciesCipher(_trueCryptoRandom); // TODO: provide a separate test random with specific IV and ephemeral key for testing _initiatorService = new HandshakeService(_messageSerializationService, _eciesCipher, _testRandom, _ecdsa, NetTestVectors.StaticKeyA, LimboLogs.Instance); _recipientService = new HandshakeService(_messageSerializationService, _eciesCipher, _testRandom, _ecdsa, NetTestVectors.StaticKeyB, LimboLogs.Instance); diff --git a/src/Nethermind/Nethermind.Network.Test/Rlpx/ZeroNettyFrameEncodeDecodeTests.cs b/src/Nethermind/Nethermind.Network.Test/Rlpx/ZeroNettyFrameEncodeDecodeTests.cs index bd779c7c5e6..571f6bced6c 100644 --- a/src/Nethermind/Nethermind.Network.Test/Rlpx/ZeroNettyFrameEncodeDecodeTests.cs +++ b/src/Nethermind/Nethermind.Network.Test/Rlpx/ZeroNettyFrameEncodeDecodeTests.cs @@ -85,9 +85,9 @@ private IChannelHandlerContext PipeWriteToChannel(IChannelHandler channelHandler pipeWrite.When((it) => it.WriteAsync(Arg.Any())) .Do((info => { - if (info[0] is IReferenceCounted refc) + if (info[0] is IReferenceCounted refCount) { - refc.Retain(); + refCount.Retain(); } channelHandler.WriteAsync(nextContext, info[0]).Wait(); })); @@ -101,9 +101,9 @@ private IChannelHandlerContext PipeWriteToChannelRead(IChannelHandler channelHan pipeWrite.When((it) => it.WriteAsync(Arg.Any())) .Do((info => { - if (info[0] is IReferenceCounted refc) + if (info[0] is IReferenceCounted refCount) { - refc.Retain(); + refCount.Retain(); } channelHandler.ChannelRead(nextContext, info[0]); })); diff --git a/src/Nethermind/Nethermind.Network/ForkId.cs b/src/Nethermind/Nethermind.Network/ForkId.cs index e1481984bcc..2f740229ab9 100644 --- a/src/Nethermind/Nethermind.Network/ForkId.cs +++ b/src/Nethermind/Nethermind.Network/ForkId.cs @@ -7,17 +7,11 @@ namespace Nethermind.Network { - public readonly struct ForkId : IEquatable + public readonly struct ForkId(uint forkHash, ulong next) : IEquatable { - public ForkId(uint forkHash, ulong next) - { - ForkHash = forkHash; - Next = next; - } - - public uint ForkHash { get; } + public uint ForkHash { get; } = forkHash; - public ulong Next { get; } + public ulong Next { get; } = next; public byte[] HashBytes { diff --git a/src/Nethermind/Nethermind.Network/Metrics.cs b/src/Nethermind/Nethermind.Network/Metrics.cs index abbfa0c15fd..6cd555d2709 100644 --- a/src/Nethermind/Nethermind.Network/Metrics.cs +++ b/src/Nethermind/Nethermind.Network/Metrics.cs @@ -103,7 +103,7 @@ public static class Metrics [KeyIsLabel("protocol", "message")] public static NonBlocking.ConcurrentDictionary IncomingP2PMessageBytes { get; } = new(); - [CounterMetric] + [GaugeMetric] [Description("Number of candidate peers in peer manager")] [DetailedMetric] public static int PeerCandidateCount { get; set; } diff --git a/src/Nethermind/Nethermind.Network/P2P/Messages/HelloMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Messages/HelloMessageSerializer.cs index 7ffa7ff586d..9b7db3f894e 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Messages/HelloMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Messages/HelloMessageSerializer.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Text; using DotNetty.Buffers; using Nethermind.Core.Crypto; using Nethermind.Serialization.Rlp; @@ -59,11 +60,15 @@ public HelloMessage Deserialize(IByteBuffer msgBytes) HelloMessage helloMessage = new(); helloMessage.P2PVersion = rlpStream.DecodeByte(); - helloMessage.ClientId = string.Intern(rlpStream.DecodeString()); + helloMessage.ClientId = rlpStream.DecodeString(); helloMessage.Capabilities = rlpStream.DecodeArrayPoolList(static ctx => { ctx.ReadSequenceLength(); - string protocolCode = string.Intern(ctx.DecodeString()); + ReadOnlySpan protocolSpan = ctx.DecodeByteArraySpan(); + if (!Contract.P2P.ProtocolParser.TryGetProtocolCode(protocolSpan, out string? protocolCode)) + { + protocolCode = Encoding.UTF8.GetString(protocolSpan); + } int version = ctx.DecodeByte(); return new Capability(protocolCode, version); }); diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ProtocolHandlerBase.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ProtocolHandlerBase.cs index 587e344d0d0..1ba8ffcc5a3 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ProtocolHandlerBase.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ProtocolHandlerBase.cs @@ -179,13 +179,13 @@ protected void ReportIn(string messageInfo, int size) } protected void HandleInBackground(ZeroPacket message, Func> handle) where TReq : P2PMessage where TRes : P2PMessage => - BackgroundTaskScheduler.ScheduleSyncServe(DeserializeAndReport(message), handle); + BackgroundTaskScheduler.TryScheduleSyncServe(DeserializeAndReport(message), handle); protected void HandleInBackground(ZeroPacket message, Func> handle) where TReq : P2PMessage where TRes : P2PMessage => - BackgroundTaskScheduler.ScheduleSyncServe(DeserializeAndReport(message), handle); + BackgroundTaskScheduler.TryScheduleSyncServe(DeserializeAndReport(message), handle); protected void HandleInBackground(ZeroPacket message, Func handle) where TReq : P2PMessage => - BackgroundTaskScheduler.ScheduleBackgroundTask(DeserializeAndReport(message), handle); + BackgroundTaskScheduler.TryScheduleBackgroundTask(DeserializeAndReport(message), handle); private TReq DeserializeAndReport(ZeroPacket message) where TReq : P2PMessage { diff --git a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroNettyP2PHandler.cs b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroNettyP2PHandler.cs index 436b97980b0..df1e7650381 100644 --- a/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroNettyP2PHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/ProtocolHandlers/ZeroNettyP2PHandler.cs @@ -106,14 +106,13 @@ protected override void ChannelRead0(IChannelHandlerContext ctx, ZeroPacket inpu public override void ExceptionCaught(IChannelHandlerContext context, Exception exception) { //In case of SocketException we log it as debug to avoid noise - string clientId = _session?.Node?.ToString(Node.Format.Console) ?? $"unknown {_session?.RemoteHost}"; if (exception is SocketException) { - if (_logger.IsTrace) _logger.Trace($"Error in communication with {clientId} (SocketException): {exception}"); + if (_logger.IsTrace) _logger.Trace($"Error in communication with {GetClientId(_session)} (SocketException): {exception}"); } else { - if (_logger.IsDebug) _logger.Debug($"Error in communication with {clientId}: {exception}"); + if (_logger.IsDebug) _logger.Debug($"Error in communication with {GetClientId(_session)}: {exception}"); } if (exception is IInternalNethermindException) @@ -122,8 +121,7 @@ public override void ExceptionCaught(IChannelHandlerContext context, Exception e } else if (_session?.Node?.IsStatic != true) { - _session.InitiateDisconnect(DisconnectReason.Exception, - $"Error in communication with {clientId} ({exception.GetType().Name}): {exception.Message}"); + _session.InitiateDisconnect(DisconnectReason.Exception, $"Error in communication with {GetClientId(_session)} ({exception.GetType().Name}): {exception.Message}"); } else { @@ -131,6 +129,9 @@ public override void ExceptionCaught(IChannelHandlerContext context, Exception e } } + private static string GetClientId(ISession? session) => + session?.Node?.ToString(Node.Format.Console) ?? $"unknown {session?.RemoteHost}"; + public void EnableSnappy() { SnappyEnabled = true; diff --git a/src/Nethermind/Nethermind.Network/P2P/Session.cs b/src/Nethermind/Nethermind.Network/P2P/Session.cs index a9d07402c3f..7faaee20745 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Session.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Session.cs @@ -553,15 +553,10 @@ public void AddProtocolHandler(IProtocolHandler handler) private AdaptiveCodeResolver GetOrCreateResolver() { string key = string.Join(":", _protocols.Select(static p => p.Value.Name).OrderBy(static x => x)); - if (!_resolvers.TryGetValue(key, out AdaptiveCodeResolver value)) - { - value = _resolvers.AddOrUpdate( - key, - addValueFactory: (k) => new AdaptiveCodeResolver(_protocols), - updateValueFactory: (k, v) => v); - } - - return value; + return _resolvers.GetOrAdd( + key, + static (_, protocols) => new AdaptiveCodeResolver(protocols), + _protocols); } public override string ToString() diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Eth62ProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Eth62ProtocolHandler.cs index ac6795f86d1..5850486aa32 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Eth62ProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V62/Eth62ProtocolHandler.cs @@ -236,7 +236,7 @@ private void Handle(StatusMessage status) protected void Handle(TransactionsMessage msg) { IOwnedReadOnlyList iList = msg.Transactions; - BackgroundTaskScheduler.ScheduleBackgroundTask((iList, 0), _handleSlow); + BackgroundTaskScheduler.TryScheduleBackgroundTask((iList, 0), _handleSlow); } protected virtual ValueTask HandleSlow((IOwnedReadOnlyList txs, int startIndex) request, CancellationToken cancellationToken) @@ -262,7 +262,7 @@ protected virtual ValueTask HandleSlow((IOwnedReadOnlyList txs, int } // Reschedule and with different start index - BackgroundTaskScheduler.ScheduleBackgroundTask((transactions, i), HandleSlow); + BackgroundTaskScheduler.TryScheduleBackgroundTask((transactions, i), HandleSlow); return ValueTask.CompletedTask; } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Eth63ProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Eth63ProtocolHandler.cs index 2dc39f7e537..56777942fd9 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Eth63ProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V63/Eth63ProtocolHandler.cs @@ -112,7 +112,7 @@ public override Task> GetNodeData(IReadOnlyList> SendRequest(GetNodeDataMessag { if (Logger.IsTrace) { - Logger.Trace("Sending node fata request:"); + Logger.Trace("Sending node data request:"); Logger.Trace($"Keys count: {message.Hashes.Count}"); } @@ -149,7 +149,7 @@ protected virtual Task> SendRequest(GetNodeDataMessag { if (Logger.IsTrace) { - Logger.Trace("Sending node fata request:"); + Logger.Trace("Sending receipts request:"); Logger.Trace($"Hashes count: {message.Hashes.Count}"); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Eth65ProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Eth65ProtocolHandler.cs index 7accd12689d..de56ae1be6d 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Eth65ProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V65/Eth65ProtocolHandler.cs @@ -229,7 +229,7 @@ private ArrayPoolList AddMarkUnknownHashes(ReadOnlySpan hashes Hash256 hash = hashes[i]; if (!_txPool.IsKnown(hash)) { - if (_txPool.AnnounceTx(hash, this) is AnnounceResult.New) + if (_txPool.NotifyAboutTx(hash, this) is AnnounceResult.RequestRequired) { discoveredTxHashesAndSizes.Add(hash); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Eth66ProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Eth66ProtocolHandler.cs index 99ea83e9892..dc0adcb8bab 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Eth66ProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V66/Eth66ProtocolHandler.cs @@ -221,7 +221,7 @@ protected override async Task> SendRequest(V63.Messag { if (Logger.IsTrace) { - Logger.Trace("Sending node fata request:"); + Logger.Trace("Sending node data request:"); Logger.Trace($"Keys count: {message.Hashes.Count}"); } @@ -239,7 +239,7 @@ protected override async Task> SendRequest(V63.Messag { if (Logger.IsTrace) { - Logger.Trace("Sending node fata request:"); + Logger.Trace("Sending receipts request:"); Logger.Trace($"Hashes count: {message.Hashes.Count}"); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandler.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandler.cs index 22e2f6a5903..512ef7f38e6 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandler.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Eth/V68/Eth68ProtocolHandler.cs @@ -171,7 +171,7 @@ private ArrayPoolListRef AddMarkUnknownHashes(ReadOnlySpan hashes) Hash256 hash = hashes[i]; if (!_txPool.IsKnown(hash)) { - if (_txPool.AnnounceTx(hash, this) is AnnounceResult.New) + if (_txPool.NotifyAboutTx(hash, this) is AnnounceResult.RequestRequired) { discoveredTxHashesAndSizes.Add(i); } diff --git a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetStorageRangesMessageSerializer.cs b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetStorageRangesMessageSerializer.cs index 5275e5f95c1..2a898746dbd 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetStorageRangesMessageSerializer.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Subprotocols/Snap/Messages/GetStorageRangesMessageSerializer.cs @@ -16,7 +16,14 @@ public override void Serialize(IByteBuffer byteBuffer, GetStorageRangeMessage me rlpStream.Encode(message.RequestId); rlpStream.Encode(message.StorageRange.RootHash); - rlpStream.Encode(message.StorageRange.Accounts.Select(static a => a.Path).ToArray()); // TODO: optimize this + var accounts = message.StorageRange.Accounts; + int accountsCount = accounts.Count; + int accountsPathsContentLength = accountsCount * Rlp.LengthOfKeccakRlp; + rlpStream.StartSequence(accountsPathsContentLength); + for (int i = 0; i < accountsCount; i++) + { + rlpStream.Encode(accounts[i].Path); + } rlpStream.Encode(message.StorageRange.StartingHash); rlpStream.Encode(message.StorageRange.LimitHash); rlpStream.Encode(message.ResponseBytes); @@ -48,7 +55,9 @@ public override int GetLength(GetStorageRangeMessage message, out int contentLen { contentLength = Rlp.LengthOf(message.RequestId); contentLength += Rlp.LengthOf(message.StorageRange.RootHash); - contentLength += Rlp.LengthOf(message.StorageRange.Accounts.Select(static a => a.Path).ToArray(), true); // TODO: optimize this + int accountsCount = message.StorageRange.Accounts.Count; + int accountsPathsContentLength = accountsCount * Rlp.LengthOfKeccakRlp; + contentLength += Rlp.LengthOfSequence(accountsPathsContentLength); contentLength += Rlp.LengthOf(message.StorageRange.StartingHash); contentLength += Rlp.LengthOf(message.StorageRange.LimitHash); contentLength += Rlp.LengthOf(message.ResponseBytes); diff --git a/src/Nethermind/Nethermind.Network/P2P/Utils/BackgroundTaskSchedulerWrapper.cs b/src/Nethermind/Nethermind.Network/P2P/Utils/BackgroundTaskSchedulerWrapper.cs index db849810a5d..f2fcc398c21 100644 --- a/src/Nethermind/Nethermind.Network/P2P/Utils/BackgroundTaskSchedulerWrapper.cs +++ b/src/Nethermind/Nethermind.Network/P2P/Utils/BackgroundTaskSchedulerWrapper.cs @@ -20,27 +20,27 @@ namespace Nethermind.Network.P2P.Utils; /// public class BackgroundTaskSchedulerWrapper(ProtocolHandlerBase handler, IBackgroundTaskScheduler backgroundTaskScheduler) { - internal void ScheduleSyncServe(TReq request, Func> fulfillFunc) where TRes : P2PMessage => - ScheduleBackgroundTask((request, fulfillFunc), BackgroundSyncSender); + internal bool TryScheduleSyncServe(TReq request, Func> fulfillFunc) where TRes : P2PMessage => + TryScheduleBackgroundTask((request, fulfillFunc), BackgroundSyncSender); - internal void ScheduleSyncServe(TReq request, Func> fulfillFunc) where TRes : P2PMessage => - ScheduleBackgroundTask((request, fulfillFunc), BackgroundSyncSenderValueTask); + internal bool TryScheduleSyncServe(TReq request, Func> fulfillFunc) where TRes : P2PMessage => + TryScheduleBackgroundTask((request, fulfillFunc), BackgroundSyncSenderValueTask); - internal void ScheduleBackgroundTask(TReq request, Func fulfillFunc) => - backgroundTaskScheduler.ScheduleTask((request, fulfillFunc), BackgroundTaskFailureHandlerValueTask); + internal bool TryScheduleBackgroundTask(TReq request, Func fulfillFunc) => + backgroundTaskScheduler.TryScheduleTask((request, fulfillFunc), BackgroundTaskFailureHandlerValueTask); // I just don't want to create a closure... so this happens. private async ValueTask BackgroundSyncSender( - (TReq Request, Func> FullfillFunc) input, CancellationToken cancellationToken) where TRes : P2PMessage + (TReq Request, Func> FulfillFunc) input, CancellationToken cancellationToken) where TRes : P2PMessage { - TRes response = await input.FullfillFunc(input.Request, cancellationToken); + TRes response = await input.FulfillFunc(input.Request, cancellationToken); handler.Send(response); } private async ValueTask BackgroundSyncSenderValueTask( - (TReq Request, Func> FullfillFunc) input, CancellationToken cancellationToken) where TRes : P2PMessage + (TReq Request, Func> FulfillFunc) input, CancellationToken cancellationToken) where TRes : P2PMessage { - TRes response = await input.FullfillFunc(input.Request, cancellationToken); + TRes response = await input.FulfillFunc(input.Request, cancellationToken); handler.Send(response); } diff --git a/src/Nethermind/Nethermind.Network/PeerPool.cs b/src/Nethermind/Nethermind.Network/PeerPool.cs index a49a7c2b829..659d2a9cad0 100644 --- a/src/Nethermind/Nethermind.Network/PeerPool.cs +++ b/src/Nethermind/Nethermind.Network/PeerPool.cs @@ -195,7 +195,7 @@ private async Task RunPeerCommit() if (!_peerStorage.AnyPendingChange()) { if (_logger.IsTrace) _logger.Trace("No changes in peer storage, skipping commit."); - return; + continue; } _peerStorage.Commit(); diff --git a/src/Nethermind/Nethermind.Network/ProtocolsManager.cs b/src/Nethermind/Nethermind.Network/ProtocolsManager.cs index e0d7ccd228b..562731cce69 100644 --- a/src/Nethermind/Nethermind.Network/ProtocolsManager.cs +++ b/src/Nethermind/Nethermind.Network/ProtocolsManager.cs @@ -41,8 +41,6 @@ public class ProtocolsManager : IProtocolsManager { public static readonly IEnumerable DefaultCapabilities = new Capability[] { - new(Protocol.Eth, 66), - new(Protocol.Eth, 67), new(Protocol.Eth, 68), new(Protocol.NodeData, 1) }; @@ -66,7 +64,7 @@ public class ProtocolsManager : IProtocolsManager private readonly IGossipPolicy _gossipPolicy; private readonly ITxGossipPolicy _txGossipPolicy; private readonly ILogManager _logManager; - private readonly ITxPoolConfig _txPoolConfdig; + private readonly ITxPoolConfig _txPoolConfig; private readonly ISpecProvider _specProvider; private readonly ILogger _logger; private readonly IDictionary> _protocolFactories; @@ -89,7 +87,7 @@ public ProtocolsManager( IGossipPolicy gossipPolicy, IWorldStateManager worldStateManager, ILogManager logManager, - ITxPoolConfig txPoolConfdig, + ITxPoolConfig txPoolConfig, ISpecProvider specProvider, ITxGossipPolicy? transactionsGossipPolicy = null) { @@ -107,7 +105,7 @@ public ProtocolsManager( _gossipPolicy = gossipPolicy ?? throw new ArgumentNullException(nameof(gossipPolicy)); _txGossipPolicy = transactionsGossipPolicy ?? ShouldGossip.Instance; _logManager = logManager ?? throw new ArgumentNullException(nameof(logManager)); - _txPoolConfdig = txPoolConfdig; + _txPoolConfig = txPoolConfig; _specProvider = specProvider; _snapServer = worldStateManager.SnapServer; _logger = _logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); @@ -225,8 +223,8 @@ private IDictionary> GetProtocolFa { 66 => new Eth66ProtocolHandler(session, _serializer, _stats, _syncServer, _backgroundTaskScheduler, _txPool, _gossipPolicy, _forkInfo, _logManager, _txGossipPolicy), 67 => new Eth67ProtocolHandler(session, _serializer, _stats, _syncServer, _backgroundTaskScheduler, _txPool, _gossipPolicy, _forkInfo, _logManager, _txGossipPolicy), - 68 => new Eth68ProtocolHandler(session, _serializer, _stats, _syncServer, _backgroundTaskScheduler, _txPool, _gossipPolicy, _forkInfo, _logManager, _txPoolConfdig, _specProvider, _txGossipPolicy), - 69 => new Eth69ProtocolHandler(session, _serializer, _stats, _syncServer, _backgroundTaskScheduler, _txPool, _gossipPolicy, _forkInfo, _logManager, _txPoolConfdig, _specProvider, _txGossipPolicy), + 68 => new Eth68ProtocolHandler(session, _serializer, _stats, _syncServer, _backgroundTaskScheduler, _txPool, _gossipPolicy, _forkInfo, _logManager, _txPoolConfig, _specProvider, _txGossipPolicy), + 69 => new Eth69ProtocolHandler(session, _serializer, _stats, _syncServer, _backgroundTaskScheduler, _txPool, _gossipPolicy, _forkInfo, _logManager, _txPoolConfig, _specProvider, _txGossipPolicy), _ => throw new NotSupportedException($"Eth protocol version {version} is not supported.") }; diff --git a/src/Nethermind/Nethermind.Network/Rlpx/FrameHeaderReader.cs b/src/Nethermind/Nethermind.Network/Rlpx/FrameHeaderReader.cs index b029327ae99..07229a2ef53 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/FrameHeaderReader.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/FrameHeaderReader.cs @@ -30,6 +30,8 @@ public FrameInfo ReadFrameHeader(IByteBuffer input) bool isChunked = totalPacketSize.HasValue || contextId.HasValue && _currentContextId == contextId && contextId != 0; bool isFirst = totalPacketSize.HasValue || !isChunked; + + headerBodyItems.Check(headerDataEnd); return new FrameInfo(isChunked, isFirst, frameSize, totalPacketSize ?? frameSize); } diff --git a/src/Nethermind/Nethermind.Network/Rlpx/RlpxHost.cs b/src/Nethermind/Nethermind.Network/Rlpx/RlpxHost.cs index 0b7c243373d..e3795965fd0 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/RlpxHost.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/RlpxHost.cs @@ -105,7 +105,7 @@ public async Task Init() { if (_isInitialized) { - throw new InvalidOperationException($"{nameof(PeerManager)} already initialized."); + throw new InvalidOperationException($"{nameof(RlpxHost)} already initialized."); } _isInitialized = true; @@ -168,7 +168,10 @@ public async Task Init() // Replacing to prevent double dispose which hangs var bossGroup = Interlocked.Exchange(ref _bossGroup, null); var workerGroup = Interlocked.Exchange(ref _workerGroup, null); - await Task.WhenAll(bossGroup?.ShutdownGracefullyAsync() ?? Task.CompletedTask, workerGroup?.ShutdownGracefullyAsync() ?? Task.CompletedTask); + await Task.WhenAll( + bossGroup?.ShutdownGracefullyAsync() ?? Task.CompletedTask, + workerGroup?.ShutdownGracefullyAsync() ?? Task.CompletedTask, + _group.ShutdownGracefullyAsync(_shutdownQuietPeriod, _shutdownCloseTimeout)); throw; } } @@ -289,7 +292,8 @@ public async Task Shutdown() Task closingTask = Task.WhenAll( _bossGroup is not null ? _bossGroup.ShutdownGracefullyAsync(_shutdownQuietPeriod, _shutdownCloseTimeout) : Task.CompletedTask, - _workerGroup is not null ? _workerGroup.ShutdownGracefullyAsync(_shutdownCloseTimeout, _shutdownCloseTimeout) : Task.CompletedTask); + _workerGroup is not null ? _workerGroup.ShutdownGracefullyAsync(_shutdownCloseTimeout, _shutdownCloseTimeout) : Task.CompletedTask, + _group.ShutdownGracefullyAsync(_shutdownQuietPeriod, _shutdownCloseTimeout)); // below comment may arise from not understanding the quiet period but the resolution is correct // we need to add additional timeout on our side as netty is not executing internal timeout properly, often it just hangs forever on closing diff --git a/src/Nethermind/Nethermind.Network/Rlpx/ZeroFrameDecoder.cs b/src/Nethermind/Nethermind.Network/Rlpx/ZeroFrameDecoder.cs index 6d78a09b414..9829661df65 100644 --- a/src/Nethermind/Nethermind.Network/Rlpx/ZeroFrameDecoder.cs +++ b/src/Nethermind/Nethermind.Network/Rlpx/ZeroFrameDecoder.cs @@ -37,7 +37,7 @@ protected override void Decode(IChannelHandlerContext context, IByteBuffer input // Note that ByteToMessageDecoder handles input.Release calls for us. // In fact, we receive here a potentially surviving _internalBuffer of the base class - // that is being built by its cumulator. + // that is being built by its accumulator. // Output buffers that we create will be released by the next handler in the pipeline. while (input.ReadableBytes >= Frame.BlockSize) diff --git a/src/Nethermind/Nethermind.Optimism.Test/CL/BatchDecoderTests.cs b/src/Nethermind/Nethermind.Optimism.Test/CL/BatchDecoderTests.cs index 012c9487377..6c9e36734f0 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/CL/BatchDecoderTests.cs +++ b/src/Nethermind/Nethermind.Optimism.Test/CL/BatchDecoderTests.cs @@ -45,7 +45,7 @@ public void DecodeSingleSpanBatch() new Address("0xa76fc24d4649325a236811710bd6ffe6c2d2cce4"), new Address("0x0f8570abb559266670ddcbc3fc995326b2f981b9"), ], - Datas = [ + Data = [ Bytes.FromHexString("0xf9032e8853444835ec58000088743dc13203ffb2eab90319485515d488227c969b561a04d56bec5d080c18cb61178511068ef972f059c5c4e91f7025af950e43af9288534c4aa7358ec0a1c769cff1d7d3d0dc64d68c997cb2e0f86d8228ea806645a57a196f669e5a075de1144153dbdfd5392a50ab79b226cf2b4fe3a80270dc784874246d1eb2f0c3bb458f94ccf129d9b8b8a3ba5fb1cde16f5de7a214c718a84c5a8d75e00d13d8d658e7a3db5d4d06c172d0f582b31ebdd9485cabe8aaba2fd4dc56f737f10a182f4cb352d641b8ff13e3d2601796f036159222314a73d8d19b183a4389fa4a74b4c5028596eec95f7d8f17afac441f074a1b4da6689f8ab1532a1f90586bdedff0e6ebf70fe5d1c26ac977a3ae9696ea4bf8009e0ee073e800db98783eab20a69d1b38de60e6dc1c20759863f4e8150504ae02b89c040d83befba021da4ee7031a924af56448349105c8c8e6e2e2f424133e5b3cd87cafc1083867e5543bd5fe8f35083d0fab1c43c499d445e50dc1b81adfc9b6b4bb6f64884467c4877a65d50f4faa6ba9dd6c81e07f1ef4783ed5cfcf4bfafedb5917e6163b5b17a3adb9145d0c9c8f3d0ca24188b3db1787cdb35c06315ea5d91f7f3d7c80e03c6cc80199b7277962a0d345a952784443b6ea695ff4e3ea5ce12728e6237b5e5206a72b7933db4991fbeff815f6bdff37c0fe0d86acccbb8756c580805dc97fab68d5bd4b9fdcf0e869ad693e3f23510a3c471cb94aefedbac5cd61a227d2a86eff1dbbc89ab34c1dc7a3c0a934fc3b6f70ced9a4c05029d7a4273dcddc9998fb825b8f1a93e772b8a11e250405aac197b2b10d432154f12d46e5aaf83dd7a38c38f61bac02b8348ae983d8d6d2d10a7a71ec98564f015910bb077d9324c550a1440592bf9c1bdfe1635440c6aee23e755f222bcdb8067efc1b6527db2247bf224da6055588e3674a5c30a0521147743a7d89421b7cdc0a5bc9f718eab3ab38a5e1baebc04621513a5d00199cfa25ea946a107adbd453a904298eebc0e5760777a6051099fe94fe6151b4ff2f04a80113788a2f9eb2e26613aa32bb0267362805aa6c7e535fbd6612d6951ea5bdde84c05be2d902793c8bc01a4455215c371a74c2f8cfa3f35b7a0302e572"), Bytes.FromHexString("0xf901e6886124fee993bc000088947902a97a7abf0bb901d15c5b0f5a62c90e175f998ee98115fa6ee1a0b9f708704902235345770e065ac4d3d43e6f66482b1f3488f38fbe158d2ed04f0d81a865bae80bd957d2fb1228fcc335afe1fc171ca17ff296415f2c7895a6ec3561bf71a34c40f2c6baf066ad4f41b5c89cecc91d44f517f1a0449ddc250873005a7f0cbca904b6176853218c63dd77758f2eeaced5da39a6ae2aaf9358c6268edfe91e7f161ba69a564fa81093b0c463b49bac1dad9c70df9c1622451a20c1c3ef225aed40f1db90d225eefe906a86938510267761d3fc518c11ab11f35e3f66feb784b8c8b84a2e3cd93bcf9c964a407a54bdcf9e594d81bab533cdcd659969543d75dbb2ce2411daccaf2675523a0f0f49bad0f26cc3cff853411f66c661a8ead90211081306838cc18c6e1aedb8df78f533a605edcbe4e24bd6404843d8b79c4a3cbb98c318378d990db39acb8154cecd1e5251bd258de852a2c5b4292a6bbce8383bb894e141ec80a78e51f1448850af993f18e5315c4cb4c8d397921608733a81370d7b803ba019a08ad75a25035f09ac53a89f3573282623b1e31d4829dafd3d801b04ca2d0aa2043e10bfe21619ec93fb3bddeac27f4a7ea969d6bf2a0563f3e7ed5df2e41f97308f21633b86fe4f44664cb65f225de096905a36"), Bytes.FromHexString("0xf90328880de0b6b3a764000088ce04f894f6dff6efb90312a1a85e158c277bebf76ffbdfbf0950af2a731471d53f18cdc3213621d648299a1309ec3fb66fcc871678003b370bd2c4f7763eb6ba56c525906496e979b20cbcd21254b44d7d3dea1d3b34d286e398b7529ff3a38b0b8ab7b9ab5fc9e3f04dd2f9a7ab7d9df3323f2211fb41352f03d4f74377df9621992ebfc376d6f85662e98e2fa440284bede4d4617a83cab8142b74dcd327b0177478bc4a156f557fb947ef45495b9041f86d2a247d5f1212a8b088ae5baa2b1dc7a829e0a3d2a0e766acb8a2a9bf674fd511756a8088843c2c8daabe10a652d44b546b9d0374e8daf3ead390f622ae286f0c664eecf567c415cf22e25aa69d8209444194c7d53aca10ad12d3f905a9511dca9445768063f18a08a6dcdb69e865ffcaff1f5a210c4b2bbbe4017eba33a0abe12cba2c99eced5dc42473ba6964dd58aebdb794d8203888c372c965c3b0bcc727e651823ea7d36e2c77378c6b00de06d640e5c87e0d2ff8d68585b56686a9d4ce02a00233e8181688f8a75f31803292942351c7156f0a6600a1ab2b206786969c33568d916cb1b404fa19d97b0e9b2e1fd282367d145777a2d451cbe94667b6025636caf49d4e37660467ca66cb2863c58254bf95023d36a4e3ebfe1294f95495a9348cd7f68bfd70a8368fb516a5bede14af5c98a3b0a61e95187eca74d09984c0ea8365aa8e0a870a9da1ad3049065ca20cf15799337366a915e12cca2cb5f3f557794dbc4bb0b66f6301fe0b07d06cbb58a610587813a116ad3a0262a2b167afb2919452acd26d2c4ca0b1cc54b0dcfb24017e1d5f86ae769664cdb327651ffcfcdd8108a798d50eaf2172e56d2737d318543e02f5575e3425dd8c5ad77dbbb3e196c0ab4a9f5a7e30b698819fbe95e66c608ba1eb70b3223c8654f44d04dd90a1e6e15b8f6b7d0f714b582f67584f59c49d497b34d8d084cf6d6b87937f198f50e79e4054f5a16f0d86e2dd2636c3aec9193a54d79f608bb5ebaef0240bee0fb35e4d5cc87602b99a45773d217265b402d8dc2c1eef10851a4163588f7c28b9548de8fab0db630e14bc491f28e7c61194c8e3a9490324e4c58116ba82b1f8ca2f9b3234964660a8aac0"), diff --git a/src/Nethermind/Nethermind.Optimism.Test/ForkInfoTests.cs b/src/Nethermind/Nethermind.Optimism.Test/ForkInfoTests.cs new file mode 100644 index 00000000000..4daa3d1c09e --- /dev/null +++ b/src/Nethermind/Nethermind.Optimism.Test/ForkInfoTests.cs @@ -0,0 +1,19 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core.Crypto; +using NUnit.Framework; + +namespace Nethermind.Optimism.Test; + +public class ForkInfoTests +{ + private static readonly Hash256 BaseMainnetGenesis = new("0xf712aa9241cc24369b143cf6dce85f0902a9731e70d66818a3a5845b296c73dd"); + + [TestCase(38_950_927, 1_764_691_201ul, "0x1cfeafc9", 0ul, "Base Mainnet - Jovian")] + public void Fork_id_and_hash_as_expected(long head, ulong headTimestamp, string forkHashHex, ulong next, string description) + { + Network.Test.ForkInfoTests.Test(head, headTimestamp, BaseMainnetGenesis, forkHashHex, next, description, "base-mainnet.json.zst"); + } + +} diff --git a/src/Nethermind/Nethermind.Optimism.Test/Nethermind.Optimism.Test.csproj b/src/Nethermind/Nethermind.Optimism.Test/Nethermind.Optimism.Test.csproj index 4cdd5dc0f59..97b69e3b902 100644 --- a/src/Nethermind/Nethermind.Optimism.Test/Nethermind.Optimism.Test.csproj +++ b/src/Nethermind/Nethermind.Optimism.Test/Nethermind.Optimism.Test.csproj @@ -13,6 +13,7 @@ + diff --git a/src/Nethermind/Nethermind.Optimism/CL/Decoding/BatchDecoder.cs b/src/Nethermind/Nethermind.Optimism/CL/Decoding/BatchDecoder.cs index e9d97865ef2..49f9381bff6 100644 --- a/src/Nethermind/Nethermind.Optimism/CL/Decoding/BatchDecoder.cs +++ b/src/Nethermind/Nethermind.Optimism/CL/Decoding/BatchDecoder.cs @@ -89,12 +89,12 @@ private static BatchV1 DecodeSpanBatch(BinaryMemoryReader reader) tos[i] = new Address(reader.Take(Address.Size).Span); } - var datas = new ReadOnlyMemory[(int)totalTxCount]; + var data = new ReadOnlyMemory[(int)totalTxCount]; var types = new TxType[(int)totalTxCount]; ulong legacyTxCnt = 0; for (var i = 0; i < (int)totalTxCount; ++i) { - (datas[i], types[i]) = reader.Read(TxParser.Data); + (data[i], types[i]) = reader.Read(TxParser.Data); if (types[i] == TxType.Legacy) { legacyTxCnt++; @@ -130,7 +130,7 @@ private static BatchV1 DecodeSpanBatch(BinaryMemoryReader reader) YParityBits = yParityBits, Signatures = signatures, Tos = tos, - Datas = datas, + Data = data, Types = types, TotalLegacyTxCount = legacyTxCnt, Nonces = nonces, diff --git a/src/Nethermind/Nethermind.Optimism/CL/Decoding/BatchV1.cs b/src/Nethermind/Nethermind.Optimism/CL/Decoding/BatchV1.cs index 875aa9be288..c94d04d4318 100644 --- a/src/Nethermind/Nethermind.Optimism/CL/Decoding/BatchV1.cs +++ b/src/Nethermind/Nethermind.Optimism/CL/Decoding/BatchV1.cs @@ -32,7 +32,7 @@ public sealed class Transactions public required BigInteger YParityBits; public required IReadOnlyList<(UInt256 R, UInt256 S)> Signatures; // TODO: Do we want to use `Nethermind.Core.Crypto.Signature`? public required IReadOnlyList
Tos; - public required IReadOnlyList> Datas; + public required IReadOnlyList> Data; public required IReadOnlyList Types; public required ulong TotalLegacyTxCount; public required IReadOnlyList Nonces; @@ -115,20 +115,20 @@ public IEnumerable ToSingularBatches(ulong chainId, ulong genesis v = 27u + (parityBit ? 1u : 0u); } - (tx.Value, tx.GasPrice, tx.Data) = DecodeLegacyTransaction(Txs.Datas[(int)txIdx].Span); + (tx.Value, tx.GasPrice, tx.Data) = DecodeLegacyTransaction(Txs.Data[(int)txIdx].Span); break; } case TxType.AccessList: { v = EthereumEcdsaExtensions.CalculateV(chainId, parityBit); - (tx.Value, tx.GasPrice, tx.Data, tx.AccessList) = DecodeAccessListTransaction(Txs.Datas[(int)txIdx].Span); + (tx.Value, tx.GasPrice, tx.Data, tx.AccessList) = DecodeAccessListTransaction(Txs.Data[(int)txIdx].Span); break; } case TxType.EIP1559: { v = EthereumEcdsaExtensions.CalculateV(chainId, parityBit); (tx.Value, tx.GasPrice, tx.DecodedMaxFeePerGas, tx.Data, tx.AccessList) = - DecodeEip1559Transaction(Txs.Datas[(int)txIdx].Span); + DecodeEip1559Transaction(Txs.Data[(int)txIdx].Span); break; } default: diff --git a/src/Nethermind/Nethermind.Optimism/CL/IL2Api.cs b/src/Nethermind/Nethermind.Optimism/CL/IL2Api.cs index 311b95ec989..fffbc284dad 100644 --- a/src/Nethermind/Nethermind.Optimism/CL/IL2Api.cs +++ b/src/Nethermind/Nethermind.Optimism/CL/IL2Api.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System.Collections.Generic; using System.Threading.Tasks; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -17,7 +18,7 @@ public interface IL2Api Task GetHeadBlock(); Task GetFinalizedBlock(); Task GetSafeBlock(); - Task GetProof(Address accountAddress, UInt256[] storageKeys, long blockNumber); + Task GetProof(Address accountAddress, HashSet storageKeys, long blockNumber); Task ForkChoiceUpdatedV3( Hash256 headHash, Hash256 finalizedHash, Hash256 safeHash, OptimismPayloadAttributes? payloadAttributes = null); diff --git a/src/Nethermind/Nethermind.Optimism/CL/L2Api.cs b/src/Nethermind/Nethermind.Optimism/CL/L2Api.cs index 41951eeec66..1c48a34e744 100644 --- a/src/Nethermind/Nethermind.Optimism/CL/L2Api.cs +++ b/src/Nethermind/Nethermind.Optimism/CL/L2Api.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Nethermind.Blockchain.Find; @@ -52,7 +53,7 @@ private PayloadAttributesRef PayloadAttributesFromBlockForRpc(BlockForRpc? block EIP1559Params = block.ExtraData.Length == 0 ? null : block.ExtraData[1..], GasLimit = block.GasLimit, ParentBeaconBlockRoot = block.ParentBeaconBlockRoot, - PrevRandao = block.MixHash, + PrevRandao = block.MixHash!, SuggestedFeeRecipient = block.Miner, Timestamp = block.Timestamp.ToUInt64(null), Withdrawals = block.Withdrawals?.ToArray() @@ -133,7 +134,7 @@ public async Task GetHeadBlock() }; } - public Task GetProof(Address accountAddress, UInt256[] storageKeys, long blockNumber) + public Task GetProof(Address accountAddress, HashSet storageKeys, long blockNumber) { // TODO: Retry logic var result = l2EthRpc.eth_getProof(accountAddress, storageKeys, new BlockParameter(blockNumber)); diff --git a/src/Nethermind/Nethermind.Optimism/CL/P2P/P2PBlockValidator.cs b/src/Nethermind/Nethermind.Optimism/CL/P2P/P2PBlockValidator.cs index 4f8c7495049..36b3655e20c 100644 --- a/src/Nethermind/Nethermind.Optimism/CL/P2P/P2PBlockValidator.cs +++ b/src/Nethermind/Nethermind.Optimism/CL/P2P/P2PBlockValidator.cs @@ -153,7 +153,7 @@ private bool IsSignatureValid(ReadOnlySpan payloadData, Span signatu byte[] signedHash = KeccakHash.ComputeHashBytes(sequencerSignedData); Span publicKey = stackalloc byte[65]; - bool success = SpanSecP256k1.RecoverKeyFromCompact( + bool success = SecP256k1.RecoverKeyFromCompact( publicKey, signedHash, signature.Slice(0, 64), diff --git a/src/Nethermind/Nethermind.Optimism/OptimismBlockReceiptTracer.cs b/src/Nethermind/Nethermind.Optimism/OptimismBlockReceiptTracer.cs index 4d93eb4bf0a..ca828e25490 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismBlockReceiptTracer.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismBlockReceiptTracer.cs @@ -7,6 +7,7 @@ using Nethermind.Core.Crypto; using Nethermind.Evm.State; using Nethermind.Evm.Tracing; +using Nethermind.Evm.TransactionProcessing; namespace Nethermind.Optimism; diff --git a/src/Nethermind/Nethermind.Optimism/OptimismChainSpecEngineParameters.cs b/src/Nethermind/Nethermind.Optimism/OptimismChainSpecEngineParameters.cs index 25d9701e25f..7ae4fe2f594 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismChainSpecEngineParameters.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismChainSpecEngineParameters.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Generic; using Nethermind.Core; using Nethermind.Int256; using Nethermind.Specs; @@ -54,4 +55,17 @@ public void ApplyToReleaseSpec(ReleaseSpec spec, long startBlock, ulong? startTi spec.BaseFeeCalculator = new OptimismBaseFeeCalculator(HoloceneTimestamp, JovianTimestamp, new DefaultBaseFeeCalculator()); } + + public void AddTransitions(SortedSet blockNumbers, SortedSet timestamps) + { + AddIfNotNull(timestamps, JovianTimestamp); + } + + private void AddIfNotNull(SortedSet timestamps, ulong? timestamp) + { + if (timestamp is not null) + { + timestamps.Add(timestamp.Value); + } + } } diff --git a/src/Nethermind/Nethermind.Optimism/OptimismCostHelper.cs b/src/Nethermind/Nethermind.Optimism/OptimismCostHelper.cs index 998fc542353..8d2ff036328 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismCostHelper.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismCostHelper.cs @@ -162,10 +162,12 @@ public UInt256 ComputeDaFootprint(Block block) continue; UInt256 flzLen = L1CostFastlzCoef * ComputeFlzCompressLen(tx); - UInt256 daUsageEstimate = UInt256.Max( - MinTransactionSizeScaled, - flzLen > L1CostInterceptNeg ? flzLen - L1CostInterceptNeg : 0 // avoid uint underflow - ) / DaFootprintScale; + UInt256 daUsageEstimate = DaFootprintScale.IsZero ? + default : + UInt256.Max( + MinTransactionSizeScaled, + flzLen > L1CostInterceptNeg ? flzLen - L1CostInterceptNeg : 0 // avoid uint underflow + ) / DaFootprintScale; footprint += daUsageEstimate * daFootprintScalar; } @@ -205,19 +207,19 @@ public static UInt256 ComputeL1CostFjord(UInt256 fastLzSize, UInt256 l1BaseFee, } estimatedSize = UInt256.Max(MinTransactionSizeScaled, fastLzCost); - return estimatedSize * l1FeeScaled / FjordDivisor; + return FjordDivisor.IsZero ? default : estimatedSize * l1FeeScaled / FjordDivisor; } // Ecotone formula: (dataGas) * (16 * l1BaseFee * l1BaseFeeScalar + l1BlobBaseFee*l1BlobBaseFeeScalar) / 16e6 public static UInt256 ComputeL1CostEcotone(UInt256 dataGas, UInt256 l1BaseFee, UInt256 blobBaseFee, UInt256 l1BaseFeeScalar, UInt256 l1BlobBaseFeeScalar) { - return dataGas * (PrecisionMultiplier * l1BaseFee * l1BaseFeeScalar + blobBaseFee * l1BlobBaseFeeScalar) / PrecisionDivisor; + return PrecisionDivisor.IsZero ? default : dataGas * (PrecisionMultiplier * l1BaseFee * l1BaseFeeScalar + blobBaseFee * l1BlobBaseFeeScalar) / PrecisionDivisor; } // Pre-Ecotone formula: (dataGas + overhead) * l1BaseFee * scalar / 1e6 public static UInt256 ComputeL1CostPreEcotone(UInt256 dataGasWithOverhead, UInt256 l1BaseFee, UInt256 feeScalar) { - return dataGasWithOverhead * l1BaseFee * feeScalar / BasicDivisor; + return BasicDivisor.IsZero ? default : dataGasWithOverhead * l1BaseFee * feeScalar / BasicDivisor; } // Based on: @@ -321,7 +323,7 @@ uint setNextHash(uint ip, ref Span ht) return FlzCompressLen(encoded); } - internal static UInt256 ComputeGasUsedFjord(UInt256 estimatedSize) => estimatedSize * GasCostOf.TxDataNonZeroEip2028 / BasicDivisor; + internal static UInt256 ComputeGasUsedFjord(UInt256 estimatedSize) => BasicDivisor.IsZero ? default : estimatedSize * GasCostOf.TxDataNonZeroEip2028 / BasicDivisor; // https://specs.optimism.io/protocol/jovian/exec-engine.html#scalar-loading // https://specs.optimism.io/protocol/jovian/l1-attributes.html diff --git a/src/Nethermind/Nethermind.Optimism/OptimismLegacyTxDecoder.cs b/src/Nethermind/Nethermind.Optimism/OptimismLegacyTxDecoder.cs index 73c69369e5c..d892fa8795c 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismLegacyTxDecoder.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismLegacyTxDecoder.cs @@ -41,7 +41,7 @@ public ValidationResult IsWellFormed(Transaction transaction, IReleaseSpec relea var isPreBedrock = !releaseSpec.IsEip1559Enabled; if (isPreBedrock) { - // Pre-Bedrock we peform no validation at all + // Pre-Bedrock we perform no validation at all return ValidationResult.Success; } diff --git a/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs b/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs index 0e0615b52e4..18854588d55 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismPlugin.cs @@ -55,8 +55,6 @@ public class OptimismPlugin(ChainSpec chainSpec) : IConsensusPlugin private OptimismNethermindApi? _api; private ILogger _logger; - private ManualBlockFinalizationManager? _blockFinalizationManager; - private OptimismCL? _cl; public bool Enabled => chainSpec.SealEngineType == SealEngineType; @@ -108,7 +106,7 @@ public Task Init(INethermindApi api) ArgumentNullException.ThrowIfNull(_api.SpecProvider); - _api.FinalizationManager = _blockFinalizationManager = new ManualBlockFinalizationManager(); + _api.FinalizationManager = new ManualBlockFinalizationManager(); _api.GossipPolicy = ShouldNotGossip.Instance; @@ -127,7 +125,7 @@ public Task InitRpcModules() ArgumentNullException.ThrowIfNull(_api.RpcModuleProvider); ArgumentNullException.ThrowIfNull(_api.BlockProducer); - ArgumentNullException.ThrowIfNull(_blockFinalizationManager); + ArgumentNullException.ThrowIfNull(_api.FinalizationManager); IEngineRpcModule engineRpcModule = _api.Context.Resolve(); diff --git a/src/Nethermind/Nethermind.Optimism/OptimismReceiptStorageDecoder.cs b/src/Nethermind/Nethermind.Optimism/OptimismReceiptStorageDecoder.cs index dc046174dd1..59f4d41627b 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismReceiptStorageDecoder.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismReceiptStorageDecoder.cs @@ -137,8 +137,7 @@ public OptimismTxReceipt Decode(ref ValueDecoderContext decoderContext, return txReceipt; } - public void DecodeStructRef(scoped ref ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors, - out TxReceiptStructRef item) + public void DecodeStructRef(scoped ref ValueDecoderContext decoderContext, RlpBehaviors rlpBehaviors, out TxReceiptStructRef item) { // Note: This method runs at 2.5 million times/sec on my machine item = new TxReceiptStructRef(); @@ -165,8 +164,7 @@ public void DecodeStructRef(scoped ref ValueDecoderContext decoderContext, RlpBe decoderContext.DecodeAddressStructRef(out item.Sender); item.GasUsedTotal = (long)decoderContext.DecodeUBigInt(); - (int prefixLength, int contentLength) = - decoderContext.PeekPrefixAndContentLength(); + (int prefixLength, int contentLength) = decoderContext.PeekPrefixAndContentLength(); int logsBytes = contentLength + prefixLength; item.LogsRlp = decoderContext.Data.Slice(decoderContext.Position, logsBytes); diff --git a/src/Nethermind/Nethermind.Optimism/OptimismSynchronizerModule.cs b/src/Nethermind/Nethermind.Optimism/OptimismSynchronizerModule.cs index d331fbbaaae..29ecf1e047d 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismSynchronizerModule.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismSynchronizerModule.cs @@ -10,7 +10,7 @@ namespace Nethermind.Optimism; /// -/// In Optimism Mainnet, the gets resetted to 0 in the Bedrock block unlike other chains that went through The Merge fork. +/// In Optimism Mainnet, the gets reset to 0 in the Bedrock block unlike other chains that went through The Merge fork. /// Calculation is still the same: the current block's is the parent's plus the current block's . /// /// diff --git a/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs b/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs index 74ef34c9529..2538b74c1ef 100644 --- a/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Optimism/OptimismTransactionProcessor.cs @@ -4,6 +4,7 @@ using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Evm; +using Nethermind.Evm.GasPolicy; using Nethermind.Evm.State; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; @@ -21,7 +22,7 @@ public class OptimismTransactionProcessor( ICostHelper costHelper, IOptimismSpecHelper opSpecHelper, ICodeInfoRepository? codeInfoRepository - ) : TransactionProcessorBase(blobBaseFeeCalculator, specProvider, worldState, virtualMachine, codeInfoRepository, logManager) + ) : EthereumTransactionProcessorBase(blobBaseFeeCalculator, specProvider, worldState, virtualMachine, codeInfoRepository, logManager) { private UInt256? _currentTxL1Cost; @@ -72,7 +73,7 @@ protected override TransactionResult BuyGas(Transaction tx, IReleaseSpec spec, I senderReservedGasPayment = UInt256.Zero; blobBaseFee = UInt256.Zero; - bool validate = !opts.HasFlag(ExecutionOptions.SkipValidation); + bool validate = ShouldValidateGas(tx, opts); UInt256 senderBalance = WorldState.GetBalance(tx.SenderAddress!); @@ -81,10 +82,10 @@ protected override TransactionResult BuyGas(Transaction tx, IReleaseSpec spec, I return TransactionResult.InsufficientSenderBalance; } - if (validate && !tx.IsDeposit()) + if (!tx.IsDeposit()) { BlockHeader header = VirtualMachine.BlockExecutionContext.Header; - if (!tx.TryCalculatePremiumPerGas(header.BaseFeePerGas, out premiumPerGas)) + if (validate && !tx.TryCalculatePremiumPerGas(header.BaseFeePerGas, out premiumPerGas)) { TraceLogInvalidTx(tx, "MINER_PREMIUM_IS_NEGATIVE"); return TransactionResult.MinerPremiumNegative; @@ -122,7 +123,7 @@ protected override TransactionResult BuyGas(Transaction tx, IReleaseSpec spec, I senderReservedGasPayment += l1Cost; // no overflow here, otherwise previous check would fail } - if (validate) + if (!senderReservedGasPayment.IsZero) WorldState.SubtractFromBalance(tx.SenderAddress!, senderReservedGasPayment, spec); return TransactionResult.Ok; @@ -172,7 +173,7 @@ protected override void PayFees(Transaction tx, BlockHeader header, IReleaseSpec } protected override GasConsumed Refund(Transaction tx, BlockHeader header, IReleaseSpec spec, ExecutionOptions opts, - in TransactionSubstate substate, in long unspentGas, in UInt256 gasPrice, int codeInsertRefunds, long floorGas) + in TransactionSubstate substate, in EthereumGasPolicy unspentGas, in UInt256 gasPrice, int codeInsertRefunds, EthereumGasPolicy floorGas) { // if deposit: skip refunds, skip tipping coinbase // Regolith changes this behaviour to report the actual gasUsed instead of always reporting all gas used. diff --git a/src/Nethermind/Nethermind.Runner.Test/ConfigFilesTests.cs b/src/Nethermind/Nethermind.Runner.Test/ConfigFilesTests.cs index 39f7c6a2c49..c166eb5cfdf 100644 --- a/src/Nethermind/Nethermind.Runner.Test/ConfigFilesTests.cs +++ b/src/Nethermind/Nethermind.Runner.Test/ConfigFilesTests.cs @@ -117,7 +117,7 @@ public void Eth_stats_disabled_by_default(string configWildcard) } [TestCase("mainnet archive", 4096000000)] - [TestCase("mainnet ^archive", 2048000000)] + [TestCase("mainnet ^archive", 1024000000)] [TestCase("volta archive", 768000000)] [TestCase("volta ^archive", 768000000)] [TestCase("gnosis archive", 1024000000)] diff --git a/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs b/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs index e0013891a0c..608216b70a3 100644 --- a/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs +++ b/src/Nethermind/Nethermind.Runner.Test/Ethereum/ContextWithMocks.cs @@ -86,7 +86,7 @@ public IEnumerable RegistrationsFor(Service service, Fun return []; } - // Dynamically resolve any interface with nsubstitue + // Dynamically resolve any interface with nsubstitute ComponentRegistration registration = new ComponentRegistration( Guid.NewGuid(), new DelegateActivator(swt.ServiceType, (c, p) => diff --git a/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/EthereumStepsManagerTests.cs b/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/EthereumStepsManagerTests.cs index 953abe1a301..88222c13a19 100644 --- a/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/EthereumStepsManagerTests.cs +++ b/src/Nethermind/Nethermind.Runner.Test/Ethereum/Steps/EthereumStepsManagerTests.cs @@ -112,7 +112,7 @@ public async Task With_constructor_without_nethermind_api() } [Test] - public async Task With_ambigious_steps() + public async Task With_ambiguous_steps() { await using IContainer container = CreateNethermindEnvironment( new StepInfo(typeof(StepWithLogManagerInConstructor)), diff --git a/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs b/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs index 06645e64121..1b9fd323b01 100644 --- a/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs +++ b/src/Nethermind/Nethermind.Runner.Test/EthereumRunnerTests.cs @@ -203,12 +203,12 @@ public async Task Smoke_CanResolveAllSteps((string file, ConfigProvider configPr INethermindApi api = runner.Api; - // They normally need the api to be populated by steps, so we mock ouf nethermind api here. + // They normally need the api to be populated by steps, so we mock out nethermind api here. Build.MockOutNethermindApi((NethermindApi)api); api.Config().LocalIp = "127.0.0.1"; api.Config().ExternalIp = "127.0.0.1"; - _ = api.Config(); // Randomly fail type disccovery if not resolved early. + _ = api.Config(); // Randomly fail type discovery if not resolved early. api.NodeKey = new InsecureProtectedPrivateKey(TestItem.PrivateKeyA); api.BlockProducerRunner = Substitute.For(); diff --git a/src/Nethermind/Nethermind.Runner.Test/Module/MainProcessingContextTests.cs b/src/Nethermind/Nethermind.Runner.Test/Module/MainProcessingContextTests.cs index 516f65a0bc8..bc895664665 100644 --- a/src/Nethermind/Nethermind.Runner.Test/Module/MainProcessingContextTests.cs +++ b/src/Nethermind/Nethermind.Runner.Test/Module/MainProcessingContextTests.cs @@ -22,7 +22,7 @@ public class MainProcessingContextTests { [Test] [CancelAfter(10000)] - public async Task Test_TransactionProcessed_EventIsFired(CancellationToken cancelationToken) + public async Task Test_TransactionProcessed_EventIsFired(CancellationToken cancellationToken) { await using IContainer ctx = new ContainerBuilder() .AddModule(new TestNethermindModule(Cancun.Instance)) @@ -36,8 +36,8 @@ public async Task Test_TransactionProcessed_EventIsFired(CancellationToken cance int totalTransactionProcessed = 0; mainProcessingContext.TransactionProcessed += (_, _) => totalTransactionProcessed++; - await ctx.Resolve().StartBlockProcessing(cancelationToken); - await ctx.Resolve().AddBlockAndWaitForHead(false, cancelationToken, + await ctx.Resolve().StartBlockProcessing(cancellationToken); + await ctx.Resolve().AddBlockAndWaitForHead(false, cancellationToken, Build.A.Transaction .WithGasLimit(100_000) .WithSenderAddress(TestItem.AddressA) diff --git a/src/Nethermind/Nethermind.Runner.Test/PluginDisposalTests.cs b/src/Nethermind/Nethermind.Runner.Test/PluginDisposalTests.cs new file mode 100644 index 00000000000..efc6b452145 --- /dev/null +++ b/src/Nethermind/Nethermind.Runner.Test/PluginDisposalTests.cs @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading.Tasks; +using Autofac; +using Nethermind.Api; +using Nethermind.Api.Extensions; +using Nethermind.Config; +using Nethermind.Core; +using Nethermind.Logging; +using Nethermind.Runner.Ethereum.Modules; +using Nethermind.Serialization.Json; +using Nethermind.Specs.ChainSpecStyle; +using NSubstitute; +using NSubstitute.Core; +using NUnit.Framework; + +namespace Nethermind.Runner.Test; + +[TestFixture] +public class PluginDisposalTests +{ + private IConsensusPlugin _consensusPlugin = null!; + + [SetUp] + public void Setup() + { + SubstitutionContext.Current?.ThreadContext?.DequeueAllArgumentSpecifications(); + _consensusPlugin = Substitute.For(); + _consensusPlugin.ApiType.Returns(typeof(NethermindApi)); + } + + [Test] + public void Sync_plugin_is_disposed_when_container_is_disposed() + { + INethermindPlugin plugin = Substitute.For(); + + using (BuildContainer(plugin)) { } + + ((IDisposable)plugin).Received(1).Dispose(); + } + + [Test] + public async Task Async_plugin_is_disposed_when_container_is_disposed_async() + { + INethermindPlugin plugin = Substitute.For(); + + await using (BuildContainer(plugin)) { } + + await ((IAsyncDisposable)plugin).Received(1).DisposeAsync(); + } + + [Test] + public void Async_plugin_is_disposed_when_container_is_disposed_sync() + { + INethermindPlugin plugin = Substitute.For(); + + using (BuildContainer(plugin)) { } + + _ = ((IAsyncDisposable)plugin).Received(1).DisposeAsync(); + } + + private IContainer BuildContainer(INethermindPlugin plugin) => new ContainerBuilder() + .AddModule(new NethermindRunnerModule( + new EthereumJsonSerializer(), + new ChainSpec + { + Name = "test", + Parameters = new ChainParameters(), + SealEngineType = SealEngineType.NethDev, + EngineChainSpecParametersProvider = Substitute.For(), + }, + new ConfigProvider(), + Substitute.For(), + new INethermindPlugin[] { _consensusPlugin, plugin }, + LimboLogs.Instance)) + .Build(); +} diff --git a/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs b/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs index a196e786b09..8378db80d5d 100644 --- a/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs +++ b/src/Nethermind/Nethermind.Runner/Ethereum/Modules/NethermindRunnerModule.cs @@ -78,13 +78,14 @@ protected override void Load(ContainerBuilder builder) .AddSingleton(consensusPlugin) ; - foreach (var plugin in plugins) + foreach (INethermindPlugin plugin in plugins) { if (plugin.Module is not null) { builder.AddModule(plugin.Module); } - builder.AddSingleton(plugin); + + builder.AddSingleton(plugin, takeOwnership: true); } builder.OnBuild((ctx) => diff --git a/src/Nethermind/Nethermind.Runner/JsonRpc/WebHost.cs b/src/Nethermind/Nethermind.Runner/JsonRpc/WebHost.cs index fc765747f04..a8ac6bfc76a 100644 --- a/src/Nethermind/Nethermind.Runner/JsonRpc/WebHost.cs +++ b/src/Nethermind/Nethermind.Runner/JsonRpc/WebHost.cs @@ -82,7 +82,6 @@ public async Task StartAsync(CancellationToken cancellationToken = default) _applicationLifetime = _applicationServices.GetRequiredService(); - _applicationServices.GetRequiredService(); var httpContextFactory = new HttpContextFactory(Services); var hostingApp = new HostingApplication(application, _logManager, httpContextFactory); diff --git a/src/Nethermind/Nethermind.Runner/Monitoring/DataFeed.cs b/src/Nethermind/Nethermind.Runner/Monitoring/DataFeed.cs index 6553a6433fc..8d3e0f0fd2a 100644 --- a/src/Nethermind/Nethermind.Runner/Monitoring/DataFeed.cs +++ b/src/Nethermind/Nethermind.Runner/Monitoring/DataFeed.cs @@ -57,7 +57,7 @@ public DataFeed( CancellationToken lifetime) { ArgumentNullException.ThrowIfNull(txPool); - ArgumentNullException.ThrowIfNull(syncPeerPool); + ArgumentNullException.ThrowIfNull(specProvider); ArgumentNullException.ThrowIfNull(receiptFinder); ArgumentNullException.ThrowIfNull(blockTree); ArgumentNullException.ThrowIfNull(syncPeerPool); diff --git a/src/Nethermind/Nethermind.Runner/Monitoring/DataFeedExtensions.cs b/src/Nethermind/Nethermind.Runner/Monitoring/DataFeedExtensions.cs index c5f8dd28d08..cb3a27d243f 100644 --- a/src/Nethermind/Nethermind.Runner/Monitoring/DataFeedExtensions.cs +++ b/src/Nethermind/Nethermind.Runner/Monitoring/DataFeedExtensions.cs @@ -36,6 +36,6 @@ public static void MapDataFeeds(this IEndpointRouteBuilder endpoints, Applicatio _dataFeed = new DataFeed(txPool, specProvider, receiptFinder, blockTree, syncPeerPool, mainProcessingContext, logManager, lifetime.ApplicationStopped); - endpoints.MapGet("/data/events", _dataFeed.ProcessingFeedAsync); + endpoints.MapGet("data/events", _dataFeed.ProcessingFeedAsync); } } diff --git a/src/Nethermind/Nethermind.Runner/NLog.config b/src/Nethermind/Nethermind.Runner/NLog.config index 1dd703dcb03..17d3b6cd2a8 100644 --- a/src/Nethermind/Nethermind.Runner/NLog.config +++ b/src/Nethermind/Nethermind.Runner/NLog.config @@ -59,7 +59,7 @@ - + @@ -78,7 +78,7 @@ - + @@ -137,7 +137,7 @@ - You can also use wilcards + You can also use wildcards --> @@ -176,6 +176,9 @@ + + + diff --git a/src/Nethermind/Nethermind.Runner/Program.cs b/src/Nethermind/Nethermind.Runner/Program.cs index 54fce7573e1..f6c063b77f7 100644 --- a/src/Nethermind/Nethermind.Runner/Program.cs +++ b/src/Nethermind/Nethermind.Runner/Program.cs @@ -213,10 +213,13 @@ async Task RunAsync(ParseResult parseResult, PluginLoader pluginLoader, Can void AddConfigurationOptions(Command command) { - static Option CreateOption(string name, Type configType) => - new Option( - $"--{ConfigExtensions.GetCategoryName(configType)}.{name}", - $"--{ConfigExtensions.GetCategoryName(configType)}-{name}".ToLowerInvariant()); + static Option CreateOption(Type configType, string name, string? alias) + { + var category = ConfigExtensions.GetCategoryName(configType); + alias = string.IsNullOrWhiteSpace(alias) ? name : alias; + + return new Option($"--{category}.{name}", $"--{category}-{alias}".ToLowerInvariant()); + } IEnumerable configTypes = TypeDiscovery .FindNethermindBasedTypes(typeof(IConfig)) @@ -238,19 +241,19 @@ static Option CreateOption(string name, Type configType) => foreach (PropertyInfo prop in configType.GetProperties(BindingFlags.Public | BindingFlags.Instance).OrderBy(p => p.Name)) { - ConfigItemAttribute? configItemAttribute = prop.GetCustomAttribute(); + ConfigItemAttribute? configItemAttr = prop.GetCustomAttribute(); - if (configItemAttribute?.DisabledForCli != true) + if (configItemAttr?.DisabledForCli != true) { Option option = prop.PropertyType == typeof(bool) - ? CreateOption(prop.Name, configType) - : CreateOption(prop.Name, configType); + ? CreateOption(configType, prop.Name, configItemAttr?.CliOptionAlias) + : CreateOption(configType, prop.Name, configItemAttr?.CliOptionAlias); - string? description = configItemAttribute?.Description; + string? description = configItemAttr?.Description; - if (!string.IsNullOrEmpty(configItemAttribute?.DefaultValue)) + if (!string.IsNullOrEmpty(configItemAttr?.DefaultValue)) { - string defaultValue = $"Defaults to `{configItemAttribute.DefaultValue}`."; + string defaultValue = $"Defaults to `{configItemAttr.DefaultValue}`."; description = string.IsNullOrEmpty(description) ? defaultValue @@ -259,12 +262,12 @@ static Option CreateOption(string name, Type configType) => option.Description = description; option.HelpName = "value"; - option.Hidden = categoryHidden || configItemAttribute?.HiddenFromDocs == true; + option.Hidden = categoryHidden || configItemAttr?.HiddenFromDocs == true; command.Add(option); } - if (configItemAttribute?.IsPortOption == true) + if (configItemAttr?.IsPortOption == true) ConfigExtensions.AddPortOptionName(configType, prop.Name); } } diff --git a/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json index 8e78db7b6bf..346fc1ec959 100644 --- a/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/base-mainnet.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 38520000, - "PivotHash": "0x2917da7d4e067e563b7fc3ef3137477a24b4f59e09be3b9d6e1277b044bd326d" + "PivotNumber": 40940000, + "PivotHash": "0x778ef345ff357d99252bd3db6a7f51c8d3b131b07f3edc1569d035c13e71df65" }, "Discovery": { "DiscoveryVersion": "V5" @@ -36,4 +36,4 @@ "Optimism": { "SequencerUrl": "https://mainnet-sequencer.base.org" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/base-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/base-sepolia.json index ada93cbfccc..2f76ab467e7 100644 --- a/src/Nethermind/Nethermind.Runner/configs/base-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/base-sepolia.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 34030000, - "PivotHash": "0x5ee6cfb55a38c0c97a9b7dce826efd38bad5a4224b3e348d5ecfe3739c700994" + "PivotNumber": 36450000, + "PivotHash": "0x856c031d90a52d7a3244710a436f68bb2b517e4cf44bcb4a34324c5324db7c14" }, "Discovery": { "DiscoveryVersion": "V5" @@ -36,4 +36,4 @@ "Optimism": { "SequencerUrl": "https://sepolia-sequencer.base.org" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/celo-sep-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/celo-sep-sepolia.json new file mode 100644 index 00000000000..6452392575c --- /dev/null +++ b/src/Nethermind/Nethermind.Runner/configs/celo-sep-sepolia.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://raw.githubusercontent.com/NethermindEth/core-scripts/refs/heads/main/schemas/config.json", + "Init": { + "ChainSpecPath": "chainspec/celo-sep-sepolia.json.zst", + "GenesisHash": "0x1b65cd292881564a4ba788e0822ef07f6dd558c76466ee9d1f07b57065b392f4", + "BaseDbPath": "nethermind_db/celo-sep-sepolia", + "LogFileName": "celo-sep-sepolia.log" + }, + "TxPool": { + "BlobsSupport": "Disabled" + }, + "Sync": { + "FastSync": true, + "SnapSync": true, + "FastSyncCatchUpHeightDelta": "10000000000" + }, + "Discovery": { + "DiscoveryVersion": "V5" + }, + "JsonRpc": { + "Enabled": true, + "Port": 8545, + "EnginePort": 8551 + }, + "Pruning": { + "PruningBoundary": 256 + }, + "Blocks": { + "SecondsPerSlot": 1 + }, + "Merge": { + "Enabled": true + }, + "Optimism": { + "SequencerUrl": "https://forno.celo-sepolia.celo-testnet.org" + } +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/chiado.json b/src/Nethermind/Nethermind.Runner/configs/chiado.json index bc8d57d9e0c..53b11f6bd47 100644 --- a/src/Nethermind/Nethermind.Runner/configs/chiado.json +++ b/src/Nethermind/Nethermind.Runner/configs/chiado.json @@ -18,8 +18,8 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 18890000, - "PivotHash": "0xa2bdaed82c6a81efc4f29b716ac6bd6d39308c52d68690e8be848091daafabb0", + "PivotNumber": 19700000, + "PivotHash": "0xfab06a40c27488d573945f6c492955f1aa6e4fcb8fc30c2e8df8a58072fe00f4", "PivotTotalDifficulty": "231708131825107706987652208063906496124457284", "FastSyncCatchUpHeightDelta": 10000000000, "UseGethLimitsInFastBlocks": false diff --git a/src/Nethermind/Nethermind.Runner/configs/gnosis.json b/src/Nethermind/Nethermind.Runner/configs/gnosis.json index cd9b4a3280e..d6d8d79a24d 100644 --- a/src/Nethermind/Nethermind.Runner/configs/gnosis.json +++ b/src/Nethermind/Nethermind.Runner/configs/gnosis.json @@ -14,8 +14,8 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 43270000, - "PivotHash": "0x52b1d954c69cfcc675394247ecb51113aa7ddaa1efdba34de5ef18369786bb3c", + "PivotNumber": 44210000, + "PivotHash": "0x2f75566e7b27504a0587119e3fdeb148a6b8c03fcd97bec0f803baea595d718b", "PivotTotalDifficulty": "8626000110427538733349499292577475819600160930", "UseGethLimitsInFastBlocks": false, "FastSyncCatchUpHeightDelta": 10000000000, diff --git a/src/Nethermind/Nethermind.Runner/configs/hoodi.json b/src/Nethermind/Nethermind.Runner/configs/hoodi.json index f0529fbb6c8..4c38c876d55 100644 --- a/src/Nethermind/Nethermind.Runner/configs/hoodi.json +++ b/src/Nethermind/Nethermind.Runner/configs/hoodi.json @@ -12,7 +12,7 @@ "Sync": { "FastSync": true, "SnapSync": true, - "FastSyncCatchUpHeightDelta": "10000000000" + "FastSyncCatchUpHeightDelta": 10000000000 }, "Metrics": { "NodeName": "Hoodi" diff --git a/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json index e761d672e00..055812824e6 100644 --- a/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/joc-mainnet.json @@ -12,9 +12,9 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 20840000, - "PivotHash": "0xc55b678aa86b32fa800d46c9862ef50c2890b4f023c4b8213cdcac43f8de0433", - "PivotTotalDifficulty": "37764460" + "PivotNumber": 21800000, + "PivotHash": "0xb1696386d4c8a573ad9eca043ced6590cce1ff422929fd5b0b5d8f1861cf10a2", + "PivotTotalDifficulty": "39190823" }, "Metrics": { "NodeName": "JOC-Mainnet" diff --git a/src/Nethermind/Nethermind.Runner/configs/joc-testnet.json b/src/Nethermind/Nethermind.Runner/configs/joc-testnet.json index c99f634ed1f..23aba507fa0 100644 --- a/src/Nethermind/Nethermind.Runner/configs/joc-testnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/joc-testnet.json @@ -12,9 +12,9 @@ "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 14440000, - "PivotHash": "0xe62450ea0d1dec45020d590f1a906f96c334d0b304f148e465f38f5d45c4ec09", - "PivotTotalDifficulty": "24123062" + "PivotNumber": 15410000, + "PivotHash": "0xacb1fa4763fcb53b8bee2e259008df4872cf507c8977874b74ad9f88d826630b", + "PivotTotalDifficulty": "25623910" }, "Metrics": { "NodeName": "JOC-Testnet" diff --git a/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json index cbbc7df4776..f380e362ae1 100644 --- a/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/linea-mainnet.json @@ -17,8 +17,8 @@ }, "Sync": { "SnapSync": true, - "PivotNumber": 25910000, - "PivotHash": "0xe809fbbdc4eddd1044a0847b2207f7d6619653a3a1c7fe8c95ba03d7b0531c6e", + "PivotNumber": 27970000, + "PivotHash": "0xc2ffe62fe8f65c8f4fef9591d3674695368e3df6d361d81157bd3f1dd6a2e2b2", "PivotTotalDifficulty": "0", "HeaderStateDistance": 6 }, diff --git a/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json index 86abf95447b..5932fce1cfa 100644 --- a/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/linea-sepolia.json @@ -17,8 +17,8 @@ }, "Sync": { "SnapSync": true, - "PivotNumber": 21080000, - "PivotHash": "0x37045b9032c2cc2a4e9041ce1276580c3d38a20b9272e156918b5f8234ed59b5", + "PivotNumber": 23480000, + "PivotHash": "0x84228950a8e7b465dfd3985a687e8e90295f640ab129b9aed6fa50c6abd4ee1e", "PivotTotalDifficulty": "37331807", "HeaderStateDistance": 6 }, diff --git a/src/Nethermind/Nethermind.Runner/configs/mainnet.json b/src/Nethermind/Nethermind.Runner/configs/mainnet.json index 6a92c67988c..f399e05da9f 100644 --- a/src/Nethermind/Nethermind.Runner/configs/mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/mainnet.json @@ -5,15 +5,15 @@ "GenesisHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", "BaseDbPath": "nethermind_db/mainnet", "LogFileName": "mainnet.log", - "MemoryHint": 2048000000 + "MemoryHint": 1024000000 }, "Sync": { "FastSync": true, "SnapSync": true, - "PivotNumber": 23857000, - "PivotHash": "0x05aff3fbd91d0faa080e195b1870c5e7925bb551b7db6951e7c698117fc8afb3", + "PivotNumber": 24257000, + "PivotHash": "0x3a3aa93772ec060df348b5824ab9458704873215cc5a4abd354a7c4f83379efe", "PivotTotalDifficulty": "58750003716598352816469", - "FastSyncCatchUpHeightDelta": "10000000000", + "FastSyncCatchUpHeightDelta": 10000000000, "AncientReceiptsBarrier": 15537394, "AncientBodiesBarrier": 15537394 }, @@ -38,4 +38,4 @@ "Merge": { "Enabled": true } -} \ No newline at end of file +} diff --git a/src/Nethermind/Nethermind.Runner/configs/op-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/op-mainnet.json index 4c3d98525e4..2fc85092c3d 100644 --- a/src/Nethermind/Nethermind.Runner/configs/op-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/op-mainnet.json @@ -15,8 +15,8 @@ "FastSyncCatchUpHeightDelta": "10000000000", "AncientBodiesBarrier": 105235063, "AncientReceiptsBarrier": 105235063, - "PivotNumber": 144120000, - "PivotHash": "0x3b727d82d92cf02e2fb04961b582f6a514c25e319260ac01b91e34f3ddcd44eb" + "PivotNumber": 146540000, + "PivotHash": "0x1e8f249641e4863081989fa9a860022caf67f6b759b8369c49d0c919be6edbdb" }, "Discovery": { "DiscoveryVersion": "V5" @@ -38,4 +38,4 @@ "Optimism": { "SequencerUrl": "https://mainnet-sequencer.optimism.io" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/op-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/op-sepolia.json index ff92f07904d..02ba96655f9 100644 --- a/src/Nethermind/Nethermind.Runner/configs/op-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/op-sepolia.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 36010000, - "PivotHash": "0x510e0bc26a218acf7959f2156cf50dd9875477dd169c19a84168e6181f8a0de2" + "PivotNumber": 38430000, + "PivotHash": "0x543e91629191a9e2e1dcc36e8b06f92f561b59d01049436078ef66aa77e83c6b" }, "Discovery": { "DiscoveryVersion": "V5" @@ -36,4 +36,4 @@ "Optimism": { "SequencerUrl": "https://sepolia-sequencer.optimism.io" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/sepolia.json b/src/Nethermind/Nethermind.Runner/configs/sepolia.json index ed2e0a1d35b..616f47c76e3 100644 --- a/src/Nethermind/Nethermind.Runner/configs/sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/sepolia.json @@ -17,8 +17,8 @@ "FastSync": true, "SnapSync": true, "UseGethLimitsInFastBlocks": true, - "PivotNumber": 9685000, - "PivotHash": "0x4aaa98fd5699adc0fdc6ea7bf826c118210463dfd4934da0ca2c8c5f95289e4f", + "PivotNumber": 10066000, + "PivotHash": "0xde60cbbff240f3b93b4aad26f3255938eb4b2624dadfd3b58dd2ce4dbc21f601", "PivotTotalDifficulty": "17000018015853232", "FastSyncCatchUpHeightDelta": 10000000000, "AncientReceiptsBarrier": 1450409, diff --git a/src/Nethermind/Nethermind.Runner/configs/surge-hoodi.json b/src/Nethermind/Nethermind.Runner/configs/surge-hoodi.json index 6ea551e391c..c05e1cafcdf 100644 --- a/src/Nethermind/Nethermind.Runner/configs/surge-hoodi.json +++ b/src/Nethermind/Nethermind.Runner/configs/surge-hoodi.json @@ -13,7 +13,7 @@ "Sync": { "FastSync": true, "SnapSync": true, - "FastSyncCatchUpHeightDelta": "10000000000" + "FastSyncCatchUpHeightDelta": 10000000000 }, "Pruning": { "PruningBoundary": 1000 diff --git a/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json b/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json index f48d853d191..828bc23b74b 100644 --- a/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json +++ b/src/Nethermind/Nethermind.Runner/configs/worldchain-mainnet.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 22250000, - "PivotHash": "0xcccd653577d55d4fab46c8b196e6d3765bd745ae9068703593367ebdf7afceb4" + "PivotNumber": 24670000, + "PivotHash": "0x842231fe10f01c5322a4410d5a07664cca94e34a9e6794efcf76897b658b285a" }, "Discovery": { "DiscoveryVersion": "V5" @@ -36,4 +36,4 @@ "Optimism": { "SequencerUrl": "https://worldchain-mainnet-sequencer.g.alchemy.com" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/configs/worldchain-sepolia.json b/src/Nethermind/Nethermind.Runner/configs/worldchain-sepolia.json index a5dc130b7e7..c82feb5a3d1 100644 --- a/src/Nethermind/Nethermind.Runner/configs/worldchain-sepolia.json +++ b/src/Nethermind/Nethermind.Runner/configs/worldchain-sepolia.json @@ -13,8 +13,8 @@ "FastSync": true, "SnapSync": true, "FastSyncCatchUpHeightDelta": "10000000000", - "PivotNumber": 21640000, - "PivotHash": "0x7d6c13f48501b2b44180820a213dea4457f91ec8538550c3e1a131ff913b909f" + "PivotNumber": 24060000, + "PivotHash": "0xe368ce8e466e95d1888b8201590bdf12acd4f3d062ed3bd785b5f581e58b5037" }, "Discovery": { "DiscoveryVersion": "V5" @@ -36,4 +36,4 @@ "Optimism": { "SequencerUrl": "https://worldchain-sepolia-sequencer.g.alchemy.com" } -} +} \ No newline at end of file diff --git a/src/Nethermind/Nethermind.Runner/packages.lock.json b/src/Nethermind/Nethermind.Runner/packages.lock.json index 683dbaa0f14..e87c9544f20 100644 --- a/src/Nethermind/Nethermind.Runner/packages.lock.json +++ b/src/Nethermind/Nethermind.Runner/packages.lock.json @@ -19,9 +19,9 @@ }, "Microsoft.Extensions.FileProviders.Embedded": { "type": "Direct", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "ECaTMB4NdV9W1es9J6tN0yoXRPUHKMi5+2L7hcVZ5k9zVdxccIx6+vMllwEYcdTaO0mCETEmdH4F0KxCqgnPaw==" + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "PkrDqw6uwm4Y7IucI3PjqwhCeCoHno3hzOeEt0hUrFI+ccYBC0X3NfQbhkFG46TgclnCyUDStmgJzpie9T9ZlQ==" }, "Microsoft.VisualStudio.Azure.Containers.Tools.Targets": { "type": "Direct", @@ -46,9 +46,9 @@ }, "System.CommandLine": { "type": "Direct", - "requested": "[2.0.0, )", - "resolved": "2.0.0", - "contentHash": "Bjklzc5NoxqAGFi7BcGlY2TWAdB06Bq3a5sfRr3ubMRU80Mf98eyq3Y2UgR6xRV0TLznZmfe5T7mUjOunRNcdA==" + "requested": "[2.0.1, )", + "resolved": "2.0.1", + "contentHash": "GLc43eDFq8KbpxIb7UhTwV0vC5CzB0NspJvfFbfhoW4O057xCJXuO18KLpVn9x3JykQn2mRske6+I6JXHwqmDg==" }, "AspNetCore.HealthChecks.UI.Core": { "type": "Transitive", @@ -123,19 +123,6 @@ "resolved": "2.14.1", "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" }, - "IdentityModel": { - "type": "Transitive", - "resolved": "5.2.0", - "contentHash": "nuhkbaDH9l5QzNJp2MtP3qio57MPtiRneUN8Ocr7od0JvSYaIe3gBj/vxllr11S/Qvu1AG4GZXoyv5469ewYDA==" - }, - "IdentityModel.OidcClient": { - "type": "Transitive", - "resolved": "5.2.1", - "contentHash": "OuPhDNAw/EoJVEmYO6/ChZUBcug4OGoGKTKLUyBCsGhlKegxJk25LYQ0EL7GCBMgkEL+BYNJukNZyaJ+JNaWog==", - "dependencies": { - "IdentityModel": "5.2.0" - } - }, "IPNetwork2": { "type": "Transitive", "resolved": "2.1.2", @@ -149,17 +136,6 @@ "resolved": "1.0.0", "contentHash": "duyRtj4I3+yZZZC7Ma5S/cxzWn5CLPRcXeXtmBcLS3TpjwLm74afQEGzfYEWma8H/dbpUiHl2ozYszKuQ8QpEg==" }, - "KubernetesClient": { - "type": "Transitive", - "resolved": "15.0.1", - "contentHash": "IOsMJaBpiHELr7ZeiJQypdtLDbc/HqxbEh9UMaDvLpBvGIzS+KhjA0LJVEbGgvubmhWHxLPfgHAL0le1zr2RwA==", - "dependencies": { - "Fractions": "7.3.0", - "IdentityModel.OidcClient": "5.2.1", - "System.IdentityModel.Tokens.Jwt": "7.1.2", - "YamlDotNet": "16.0.0" - } - }, "libsodium": { "type": "Transitive", "resolved": "1.0.20", @@ -534,25 +510,25 @@ }, "PierTwo.Lantern.Discv5.Enr": { "type": "Transitive", - "resolved": "1.0.0-preview.6", - "contentHash": "rHuVBANEsOdf+5p1uU35mrdJfb+2z9EtDU5LeHKWDWx5nfGMRK4LtcrbfvDnrdsTLQbxmV88PoBuS23FMLrpYQ==", + "resolved": "1.0.0-preview.7", + "contentHash": "oNF8cPIbYt+8xWoCqPCDfKOEsxhlFUWEXmoV45/XTKipU5ZqvmdTsESCv0o97TP2sNZaZrFrvpovf7aNk3BUKw==", "dependencies": { "Keccak256": "1.0.0", "Multiformats.Base": "2.0.2", "Multiformats.Hash": "1.5.0", "NBitcoin.Secp256k1": "3.1.5", - "PierTwo.Lantern.Discv5.Rlp": "1.0.0-preview.6" + "PierTwo.Lantern.Discv5.Rlp": "1.0.0-preview.7" } }, "PierTwo.Lantern.Discv5.Rlp": { "type": "Transitive", - "resolved": "1.0.0-preview.6", - "contentHash": "X1mOhKZytX60uZwh9aCex0BVidN0hVlf5jKZsufacWpfsN/yMstc/fh3aRM9JWm8x7PQVeY300JXVFhDk4roqA==" + "resolved": "1.0.0-preview.7", + "contentHash": "tAwonG4x8SWFBxd06JvzYNo0xvTsDoM9xfk2tnwIcFzCvY7PORvpOiy9AQcyjqomFQmCNqF4ezwZoRZJV32iQg==" }, "Polly.Core": { "type": "Transitive", - "resolved": "8.6.4", - "contentHash": "4AWqYnQ2TME0E+Mzovt1Uu+VyvpR84ymUldMcPw7Mbj799Phaag14CKrMtlJGx5jsvYP+S3oR1QmysgmXoD5cw==" + "resolved": "8.6.5", + "contentHash": "t+sUVrIwvo7UmsgHGgOG9F0GDZSRIm47u2ylH17Gvcv1q5hNEwgD5GoBlFyc0kh/pebmPyrAgvGsR/65ZBaXlg==" }, "Portable.BouncyCastle": { "type": "Transitive", @@ -622,15 +598,6 @@ "System.Composition.Runtime": "6.0.0" } }, - "System.IdentityModel.Tokens.Jwt": { - "type": "Transitive", - "resolved": "7.1.2", - "contentHash": "Thhbe1peAmtSBFaV/ohtykXiZSOkx59Da44hvtWfIMFofDA3M3LaVyjstACf2rKGn4dEDR2cUpRAZ0Xs/zB+7Q==", - "dependencies": { - "Microsoft.IdentityModel.JsonWebTokens": "7.1.2", - "Microsoft.IdentityModel.Tokens": "7.1.2" - } - }, "System.Reactive": { "type": "Transitive", "resolved": "6.0.0", @@ -638,8 +605,8 @@ }, "Testably.Abstractions.FileSystem.Interface": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "uksk86YlnzAdyfVNu3wICU0X5iXVe9LF7Q3UkngNliHWEvM5gvAlOUr+jmd9JwmbJWISH5+i1vyXE02lEVz7WQ==" + "resolved": "10.0.0", + "contentHash": "tZOXFLGjkh8TxgMgKeEcM2HAlz9DwndGl6TFLo6ISHcszFX3FkuPMrtVbmqVjhooWNXrgJ/a9cH9ym5MZL1LAg==" }, "Tmds.LibC": { "type": "Transitive", @@ -648,27 +615,27 @@ }, "YamlDotNet": { "type": "Transitive", - "resolved": "16.0.0", - "contentHash": "kZ4jR5ltFhnjaUqK9x81zXRIUTH4PTXTTEmJDNQdkDLQhcv+2Nl19r0dCSvPW1mstOYBfXTnjdieRbUO6gHMDw==" + "resolved": "16.3.0", + "contentHash": "SgMOdxbz8X65z8hraIs6hOEdnkH6hESTAIUa7viEngHOYaH+6q5XJmwr1+yb9vJpNQ19hCQY69xbFsLtXpobQA==" }, "nethermind.abi": { "type": "Project", "dependencies": { "MathNet.Numerics.FSharp": "[5.0.0, )", - "Nethermind.Core": "[1.36.0-unstable, )" + "Nethermind.Core": "[1.37.0-unstable, )" } }, "nethermind.api": { "type": "Project", "dependencies": { - "Nethermind.Blockchain": "[1.36.0-unstable, )", - "Nethermind.Facade": "[1.36.0-unstable, )", - "Nethermind.Grpc": "[1.36.0-unstable, )", - "Nethermind.History": "[1.36.0-unstable, )", - "Nethermind.JsonRpc": "[1.36.0-unstable, )", - "Nethermind.Monitoring": "[1.36.0-unstable, )", - "Nethermind.Network": "[1.36.0-unstable, )", - "Nethermind.Sockets": "[1.36.0-unstable, )" + "Nethermind.Blockchain": "[1.37.0-unstable, )", + "Nethermind.Facade": "[1.37.0-unstable, )", + "Nethermind.Grpc": "[1.37.0-unstable, )", + "Nethermind.History": "[1.37.0-unstable, )", + "Nethermind.JsonRpc": "[1.37.0-unstable, )", + "Nethermind.Monitoring": "[1.37.0-unstable, )", + "Nethermind.Network": "[1.37.0-unstable, )", + "Nethermind.Sockets": "[1.37.0-unstable, )" } }, "nethermind.blockchain": { @@ -679,70 +646,70 @@ "Microsoft.ClearScript.V8.Native.osx-arm64": "[7.5.0, )", "Microsoft.ClearScript.V8.Native.osx-x64": "[7.5.0, )", "Microsoft.ClearScript.V8.Native.win-x64": "[7.5.0, )", - "Nethermind.Abi": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Db": "[1.36.0-unstable, )", - "Nethermind.Evm": "[1.36.0-unstable, )", - "Nethermind.Evm.Precompiles": "[1.36.0-unstable, )", - "Nethermind.Network.Stats": "[1.36.0-unstable, )", - "Nethermind.Specs": "[1.36.0-unstable, )", - "Nethermind.State": "[1.36.0-unstable, )", - "Nethermind.TxPool": "[1.36.0-unstable, )", - "Polly": "[8.6.4, )" + "Nethermind.Abi": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Db": "[1.37.0-unstable, )", + "Nethermind.Evm": "[1.37.0-unstable, )", + "Nethermind.Evm.Precompiles": "[1.37.0-unstable, )", + "Nethermind.Network.Stats": "[1.37.0-unstable, )", + "Nethermind.Specs": "[1.37.0-unstable, )", + "Nethermind.State": "[1.37.0-unstable, )", + "Nethermind.TxPool": "[1.37.0-unstable, )", + "Polly": "[8.6.5, )" } }, "nethermind.config": { "type": "Project", "dependencies": { - "Nethermind.Core": "[1.36.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", "NonBlocking": "[2.1.2, )", - "System.Configuration.ConfigurationManager": "[10.0.0, )" + "System.Configuration.ConfigurationManager": "[10.0.1, )" } }, "nethermind.consensus": { "type": "Project", "dependencies": { - "Nethermind.Blockchain": "[1.36.0-unstable, )", - "Nethermind.Config": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Crypto": "[1.36.0-unstable, )", - "Nethermind.Evm": "[1.36.0-unstable, )", - "Nethermind.TxPool": "[1.36.0-unstable, )" + "Nethermind.Blockchain": "[1.37.0-unstable, )", + "Nethermind.Config": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Crypto": "[1.37.0-unstable, )", + "Nethermind.Evm": "[1.37.0-unstable, )", + "Nethermind.TxPool": "[1.37.0-unstable, )" } }, "nethermind.consensus.aura": { "type": "Project", "dependencies": { "BouncyCastle.Cryptography": "[2.6.2, )", - "Nethermind.Abi": "[1.36.0-unstable, )", - "Nethermind.Api": "[1.36.0-unstable, )", - "Nethermind.Blockchain": "[1.36.0-unstable, )", - "Nethermind.Facade": "[1.36.0-unstable, )", - "Nethermind.Init": "[1.36.0-unstable, )", - "Nethermind.Specs": "[1.36.0-unstable, )", - "Nethermind.Synchronization": "[1.36.0-unstable, )", + "Nethermind.Abi": "[1.37.0-unstable, )", + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.Blockchain": "[1.37.0-unstable, )", + "Nethermind.Facade": "[1.37.0-unstable, )", + "Nethermind.Init": "[1.37.0-unstable, )", + "Nethermind.Specs": "[1.37.0-unstable, )", + "Nethermind.Synchronization": "[1.37.0-unstable, )", "Nito.Collections.Deque": "[1.2.1, )" } }, "nethermind.consensus.clique": { "type": "Project", "dependencies": { - "Nethermind.Api": "[1.36.0-unstable, )", - "Nethermind.Blockchain": "[1.36.0-unstable, )", - "Nethermind.Consensus": "[1.36.0-unstable, )", - "Nethermind.JsonRpc": "[1.36.0-unstable, )" + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.Blockchain": "[1.37.0-unstable, )", + "Nethermind.Consensus": "[1.37.0-unstable, )", + "Nethermind.JsonRpc": "[1.37.0-unstable, )" } }, "nethermind.consensus.ethash": { "type": "Project", "dependencies": { - "Nethermind.Api": "[1.36.0-unstable, )", - "Nethermind.Blockchain": "[1.36.0-unstable, )", - "Nethermind.Consensus": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Crypto": "[1.36.0-unstable, )", - "Nethermind.Serialization.Rlp": "[1.36.0-unstable, )", - "Nethermind.Specs": "[1.36.0-unstable, )" + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.Blockchain": "[1.37.0-unstable, )", + "Nethermind.Consensus": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Crypto": "[1.37.0-unstable, )", + "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", + "Nethermind.Specs": "[1.37.0-unstable, )" } }, "nethermind.core": { @@ -754,28 +721,28 @@ "Microsoft.IO.RecyclableMemoryStream": "[3.0.1, )", "Microsoft.IdentityModel.JsonWebTokens": "[8.15.0, )", "Nethermind.Crypto.SecP256k1": "[1.5.0, )", - "Nethermind.Logging": "[1.36.0-unstable, )", - "Nethermind.Numerics.Int256": "[1.3.6, )", + "Nethermind.Logging": "[1.37.0-unstable, )", + "Nethermind.Numerics.Int256": "[1.4.0, )", "NonBlocking": "[2.1.2, )", - "TestableIO.System.IO.Abstractions.Wrappers": "[22.0.16, )" + "TestableIO.System.IO.Abstractions.Wrappers": "[22.1.0, )" } }, "nethermind.crypto": { "type": "Project", "dependencies": { "BouncyCastle.Cryptography": "[2.6.2, )", - "Ckzg.Bindings": "[2.1.5.1542, )", - "Nethermind.Core": "[1.36.0-unstable, )", + "Ckzg.Bindings": "[2.1.5.1544, )", + "Nethermind.Core": "[1.37.0-unstable, )", "Nethermind.Crypto.Bls": "[1.0.5, )", - "Nethermind.Serialization.Rlp": "[1.36.0-unstable, )", - "System.Security.Cryptography.ProtectedData": "[10.0.0, )" + "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", + "System.Security.Cryptography.ProtectedData": "[10.0.1, )" } }, "nethermind.db": { "type": "Project", "dependencies": { - "Nethermind.Config": "[1.36.0-unstable, )", - "Nethermind.Serialization.Rlp": "[1.36.0-unstable, )", + "Nethermind.Config": "[1.37.0-unstable, )", + "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", "NonBlocking": "[2.1.2, )" } }, @@ -783,8 +750,8 @@ "type": "Project", "dependencies": { "ConcurrentHashSet": "[1.3.0, )", - "Nethermind.Api": "[1.36.0-unstable, )", - "Nethermind.Db": "[1.36.0-unstable, )", + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.Db": "[1.37.0-unstable, )", "NonBlocking": "[2.1.2, )", "RocksDB": "[10.4.2.62659, 10.4.2.62659]" } @@ -792,97 +759,97 @@ "nethermind.db.rpc": { "type": "Project", "dependencies": { - "Nethermind.Db": "[1.36.0-unstable, )", - "Nethermind.JsonRpc": "[1.36.0-unstable, )", - "Nethermind.Serialization.Json": "[1.36.0-unstable, )", - "Nethermind.State": "[1.36.0-unstable, )" + "Nethermind.Db": "[1.37.0-unstable, )", + "Nethermind.JsonRpc": "[1.37.0-unstable, )", + "Nethermind.Serialization.Json": "[1.37.0-unstable, )", + "Nethermind.State": "[1.37.0-unstable, )" } }, "nethermind.era1": { "type": "Project", "dependencies": { "CommunityToolkit.HighPerformance": "[8.4.0, )", - "Nethermind.Api": "[1.36.0-unstable, )", - "Nethermind.Blockchain": "[1.36.0-unstable, )", - "Nethermind.Consensus": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.JsonRpc": "[1.36.0-unstable, )", - "Nethermind.Merkleization": "[1.36.0-unstable, )", - "Nethermind.Serialization.Rlp": "[1.36.0-unstable, )", - "Nethermind.Serialization.Ssz": "[1.36.0-unstable, )", - "Nethermind.State": "[1.36.0-unstable, )", + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.Blockchain": "[1.37.0-unstable, )", + "Nethermind.Consensus": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.JsonRpc": "[1.37.0-unstable, )", + "Nethermind.Merkleization": "[1.37.0-unstable, )", + "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", + "Nethermind.Serialization.Ssz": "[1.37.0-unstable, )", + "Nethermind.State": "[1.37.0-unstable, )", "Snappier": "[1.2.0, )" } }, "nethermind.ethstats": { "type": "Project", "dependencies": { - "Nethermind.Api": "[1.36.0-unstable, )", - "Nethermind.Blockchain": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Init": "[1.36.0-unstable, )", - "Nethermind.JsonRpc": "[1.36.0-unstable, )", - "Nethermind.Logging": "[1.36.0-unstable, )", - "Nethermind.Network": "[1.36.0-unstable, )", + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.Blockchain": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Init": "[1.37.0-unstable, )", + "Nethermind.JsonRpc": "[1.37.0-unstable, )", + "Nethermind.Logging": "[1.37.0-unstable, )", + "Nethermind.Network": "[1.37.0-unstable, )", "Websocket.Client": "[5.3.0, )" } }, "nethermind.evm": { "type": "Project", "dependencies": { - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Crypto": "[1.36.0-unstable, )", - "Nethermind.Serialization.Rlp": "[1.36.0-unstable, )", - "Nethermind.Specs": "[1.36.0-unstable, )", - "Nethermind.Trie": "[1.36.0-unstable, )" + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Crypto": "[1.37.0-unstable, )", + "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", + "Nethermind.Specs": "[1.37.0-unstable, )", + "Nethermind.Trie": "[1.37.0-unstable, )" } }, "nethermind.evm.precompiles": { "type": "Project", "dependencies": { - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Crypto": "[1.36.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Crypto": "[1.37.0-unstable, )", "Nethermind.Crypto.Bls": "[1.0.5, )", "Nethermind.Crypto.SecP256r1": "[1.0.0-preview.6, )", - "Nethermind.Evm": "[1.36.0-unstable, )", + "Nethermind.Evm": "[1.37.0-unstable, )", "Nethermind.GmpBindings": "[1.0.3, )", - "Nethermind.MclBindings": "[1.0.3, )", - "Nethermind.Serialization.Rlp": "[1.36.0-unstable, )", - "Nethermind.Specs": "[1.36.0-unstable, )" + "Nethermind.MclBindings": "[1.0.4, )", + "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", + "Nethermind.Specs": "[1.37.0-unstable, )" } }, "nethermind.externalsigner.plugin": { "type": "Project", "dependencies": { - "Nethermind.Api": "[1.36.0-unstable, )", - "Nethermind.JsonRpc": "[1.36.0-unstable, )" + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.JsonRpc": "[1.37.0-unstable, )" } }, "nethermind.facade": { "type": "Project", "dependencies": { - "Nethermind.Consensus": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Crypto": "[1.36.0-unstable, )", - "Nethermind.Synchronization": "[1.36.0-unstable, )", + "Nethermind.Consensus": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Crypto": "[1.37.0-unstable, )", + "Nethermind.Synchronization": "[1.37.0-unstable, )", "NonBlocking": "[2.1.2, )" } }, "nethermind.flashbots": { "type": "Project", "dependencies": { - "Nethermind.Merge.Plugin": "[1.36.0-unstable, )" + "Nethermind.Merge.Plugin": "[1.37.0-unstable, )" } }, "nethermind.grpc": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.33.1, )", - "Google.Protobuf.Tools": "[3.33.1, )", + "Google.Protobuf": "[3.33.2, )", + "Google.Protobuf.Tools": "[3.33.2, )", "Grpc": "[2.46.6, )", - "Nethermind.Config": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Serialization.Json": "[1.36.0-unstable, )" + "Nethermind.Config": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Serialization.Json": "[1.37.0-unstable, )" } }, "nethermind.healthchecks": { @@ -890,74 +857,75 @@ "dependencies": { "AspNetCore.HealthChecks.UI": "[9.0.0, )", "AspNetCore.HealthChecks.UI.InMemory.Storage": "[9.0.0, )", - "Nethermind.Api": "[1.36.0-unstable, )", - "Nethermind.Merge.Plugin": "[1.36.0-unstable, )" + "KubernetesClient": "[18.0.13, )", + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.Merge.Plugin": "[1.37.0-unstable, )" } }, "nethermind.history": { "type": "Project", "dependencies": { - "Nethermind.Consensus": "[1.36.0-unstable, )" + "Nethermind.Consensus": "[1.37.0-unstable, )" } }, "nethermind.hive": { "type": "Project", "dependencies": { - "Nethermind.Api": "[1.36.0-unstable, )", - "Nethermind.Init": "[1.36.0-unstable, )" + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.Init": "[1.37.0-unstable, )" } }, "nethermind.init": { "type": "Project", "dependencies": { - "Nethermind.Api": "[1.36.0-unstable, )", - "Nethermind.Db.Rocks": "[1.36.0-unstable, )", - "Nethermind.Db.Rpc": "[1.36.0-unstable, )", - "Nethermind.Era1": "[1.36.0-unstable, )", - "Nethermind.Network.Discovery": "[1.36.0-unstable, )", - "Nethermind.Network.Dns": "[1.36.0-unstable, )", - "Nethermind.Network.Enr": "[1.36.0-unstable, )", - "Nethermind.Specs": "[1.36.0-unstable, )" + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.Db.Rocks": "[1.37.0-unstable, )", + "Nethermind.Db.Rpc": "[1.37.0-unstable, )", + "Nethermind.Era1": "[1.37.0-unstable, )", + "Nethermind.Network.Discovery": "[1.37.0-unstable, )", + "Nethermind.Network.Dns": "[1.37.0-unstable, )", + "Nethermind.Network.Enr": "[1.37.0-unstable, )", + "Nethermind.Specs": "[1.37.0-unstable, )" } }, "nethermind.init.snapshot": { "type": "Project", "dependencies": { - "Nethermind.Api": "[1.36.0-unstable, )", - "Nethermind.Init": "[1.36.0-unstable, )" + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.Init": "[1.37.0-unstable, )" } }, "nethermind.jsonrpc": { "type": "Project", "dependencies": { - "Nethermind.Abi": "[1.36.0-unstable, )", - "Nethermind.Blockchain": "[1.36.0-unstable, )", - "Nethermind.Config": "[1.36.0-unstable, )", - "Nethermind.Consensus": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Crypto": "[1.36.0-unstable, )", - "Nethermind.Evm": "[1.36.0-unstable, )", - "Nethermind.Facade": "[1.36.0-unstable, )", - "Nethermind.Network.Dns": "[1.36.0-unstable, )", - "Nethermind.Sockets": "[1.36.0-unstable, )", - "Nethermind.Synchronization": "[1.36.0-unstable, )", - "Nethermind.Wallet": "[1.36.0-unstable, )" + "Nethermind.Abi": "[1.37.0-unstable, )", + "Nethermind.Blockchain": "[1.37.0-unstable, )", + "Nethermind.Config": "[1.37.0-unstable, )", + "Nethermind.Consensus": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Crypto": "[1.37.0-unstable, )", + "Nethermind.Evm": "[1.37.0-unstable, )", + "Nethermind.Facade": "[1.37.0-unstable, )", + "Nethermind.Network.Dns": "[1.37.0-unstable, )", + "Nethermind.Sockets": "[1.37.0-unstable, )", + "Nethermind.Synchronization": "[1.37.0-unstable, )", + "Nethermind.Wallet": "[1.37.0-unstable, )" } }, "nethermind.jsonrpc.tracestore": { "type": "Project", "dependencies": { - "Nethermind.Api": "[1.36.0-unstable, )", - "Nethermind.Init": "[1.36.0-unstable, )" + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.Init": "[1.37.0-unstable, )" } }, "nethermind.keystore": { "type": "Project", "dependencies": { - "Nethermind.Config": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Crypto": "[1.36.0-unstable, )", - "Nethermind.Serialization.Json": "[1.36.0-unstable, )", + "Nethermind.Config": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Crypto": "[1.37.0-unstable, )", + "Nethermind.Serialization.Json": "[1.37.0-unstable, )", "SCrypt": "[2.0.0.2, )" } }, @@ -968,41 +936,41 @@ "type": "Project", "dependencies": { "NLog": "[5.5.1, )", - "Nethermind.Logging": "[1.36.0-unstable, )" + "Nethermind.Logging": "[1.37.0-unstable, )" } }, "nethermind.merge.aura": { "type": "Project", "dependencies": { - "Nethermind.Blockchain": "[1.36.0-unstable, )", - "Nethermind.Consensus": "[1.36.0-unstable, )", - "Nethermind.Consensus.AuRa": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Db": "[1.36.0-unstable, )", - "Nethermind.Evm": "[1.36.0-unstable, )", - "Nethermind.Merge.Plugin": "[1.36.0-unstable, )", - "Nethermind.Specs": "[1.36.0-unstable, )", - "Nethermind.State": "[1.36.0-unstable, )" + "Nethermind.Blockchain": "[1.37.0-unstable, )", + "Nethermind.Consensus": "[1.37.0-unstable, )", + "Nethermind.Consensus.AuRa": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Db": "[1.37.0-unstable, )", + "Nethermind.Evm": "[1.37.0-unstable, )", + "Nethermind.Merge.Plugin": "[1.37.0-unstable, )", + "Nethermind.Specs": "[1.37.0-unstable, )", + "Nethermind.State": "[1.37.0-unstable, )" } }, "nethermind.merge.plugin": { "type": "Project", "dependencies": { - "Nethermind.Api": "[1.36.0-unstable, )" + "Nethermind.Api": "[1.37.0-unstable, )" } }, "nethermind.merkleization": { "type": "Project", "dependencies": { - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Serialization.Ssz": "[1.36.0-unstable, )" + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Serialization.Ssz": "[1.37.0-unstable, )" } }, "nethermind.monitoring": { "type": "Project", "dependencies": { - "Nethermind.Config": "[1.36.0-unstable, )", - "Nethermind.Logging": "[1.36.0-unstable, )", + "Nethermind.Config": "[1.37.0-unstable, )", + "Nethermind.Logging": "[1.37.0-unstable, )", "prometheus-net.AspNetCore": "[8.2.1, )" } }, @@ -1010,219 +978,219 @@ "type": "Project", "dependencies": { "Crc32.NET": "[1.2.0, )", - "Nethermind.Blockchain": "[1.36.0-unstable, )", - "Nethermind.Config": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Crypto": "[1.36.0-unstable, )", + "Nethermind.Blockchain": "[1.37.0-unstable, )", + "Nethermind.Config": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Crypto": "[1.37.0-unstable, )", "Nethermind.DotNetty.Handlers": "[1.0.2.76, )", - "Nethermind.Network.Contract": "[1.36.0-unstable, )", - "Nethermind.Network.Stats": "[1.36.0-unstable, )", - "Nethermind.Synchronization": "[1.36.0-unstable, )", + "Nethermind.Network.Contract": "[1.37.0-unstable, )", + "Nethermind.Network.Stats": "[1.37.0-unstable, )", + "Nethermind.Synchronization": "[1.37.0-unstable, )", "Snappier": "[1.2.0, )" } }, "nethermind.network.contract": { "type": "Project", "dependencies": { - "Nethermind.Config": "[1.36.0-unstable, )" + "Nethermind.Config": "[1.37.0-unstable, )" } }, "nethermind.network.discovery": { "type": "Project", "dependencies": { - "Nethermind.Api": "[1.36.0-unstable, )", - "Nethermind.Crypto": "[1.36.0-unstable, )", - "Nethermind.Facade": "[1.36.0-unstable, )", - "Nethermind.Network": "[1.36.0-unstable, )", - "Nethermind.Network.Enr": "[1.36.0-unstable, )", - "PierTwo.Lantern.Discv5.WireProtocol": "[1.0.0-preview.6, )" + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.Crypto": "[1.37.0-unstable, )", + "Nethermind.Facade": "[1.37.0-unstable, )", + "Nethermind.Network": "[1.37.0-unstable, )", + "Nethermind.Network.Enr": "[1.37.0-unstable, )", + "PierTwo.Lantern.Discv5.WireProtocol": "[1.0.0-preview.7, )" } }, "nethermind.network.dns": { "type": "Project", "dependencies": { "DnsClient": "[1.8.0, )", - "Nethermind.Network": "[1.36.0-unstable, )", - "Nethermind.Network.Enr": "[1.36.0-unstable, )" + "Nethermind.Network": "[1.37.0-unstable, )", + "Nethermind.Network.Enr": "[1.37.0-unstable, )" } }, "nethermind.network.enr": { "type": "Project", "dependencies": { - "Nethermind.Crypto": "[1.36.0-unstable, )", - "Nethermind.Network": "[1.36.0-unstable, )" + "Nethermind.Crypto": "[1.37.0-unstable, )", + "Nethermind.Network": "[1.37.0-unstable, )" } }, "nethermind.network.stats": { "type": "Project", "dependencies": { - "Nethermind.Config": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Logging": "[1.36.0-unstable, )", - "Nethermind.Network.Contract": "[1.36.0-unstable, )" + "Nethermind.Config": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Logging": "[1.37.0-unstable, )", + "Nethermind.Network.Contract": "[1.37.0-unstable, )" } }, "nethermind.optimism": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.33.1, )", - "Nethermind.Api": "[1.36.0-unstable, )", - "Nethermind.Blockchain": "[1.36.0-unstable, )", - "Nethermind.Consensus": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Init": "[1.36.0-unstable, )", - "Nethermind.JsonRpc": "[1.36.0-unstable, )", + "Google.Protobuf": "[3.33.2, )", + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.Blockchain": "[1.37.0-unstable, )", + "Nethermind.Consensus": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Init": "[1.37.0-unstable, )", + "Nethermind.JsonRpc": "[1.37.0-unstable, )", "Nethermind.Libp2p": "[1.0.0-preview.45, )", "Nethermind.Libp2p.Protocols.PubsubPeerDiscovery": "[1.0.0-preview.45, )", - "Nethermind.Merge.Plugin": "[1.36.0-unstable, )", + "Nethermind.Merge.Plugin": "[1.37.0-unstable, )", "Snappier": "[1.2.0, )" } }, "nethermind.seq": { "type": "Project", "dependencies": { - "Nethermind.Config": "[1.36.0-unstable, )" + "Nethermind.Config": "[1.37.0-unstable, )" } }, "nethermind.serialization.json": { "type": "Project", "dependencies": { "Microsoft.ClearScript.V8": "[7.5.0, )", - "Nethermind.Core": "[1.36.0-unstable, )" + "Nethermind.Core": "[1.37.0-unstable, )" } }, "nethermind.serialization.rlp": { "type": "Project", "dependencies": { - "Nethermind.Core": "[1.36.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", "Nethermind.DotNetty.Buffers": "[1.0.2.76, )" } }, "nethermind.serialization.ssz": { "type": "Project", "dependencies": { - "Nethermind.Core": "[1.36.0-unstable, )" + "Nethermind.Core": "[1.37.0-unstable, )" } }, "nethermind.shutter": { "type": "Project", "dependencies": { - "Google.Protobuf": "[3.33.1, )", - "Nethermind.Blockchain": "[1.36.0-unstable, )", - "Nethermind.Consensus": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Crypto": "[1.36.0-unstable, )", - "Nethermind.Init": "[1.36.0-unstable, )", + "Google.Protobuf": "[3.33.2, )", + "Nethermind.Blockchain": "[1.37.0-unstable, )", + "Nethermind.Consensus": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Crypto": "[1.37.0-unstable, )", + "Nethermind.Init": "[1.37.0-unstable, )", "Nethermind.Libp2p": "[1.0.0-preview.45, )", "Nethermind.Libp2p.Protocols.PubsubPeerDiscovery": "[1.0.0-preview.45, )", - "Nethermind.Merge.Plugin": "[1.36.0-unstable, )", - "Nethermind.Merkleization": "[1.36.0-unstable, )", - "Nethermind.Network.Discovery": "[1.36.0-unstable, )", - "Nethermind.Serialization.Ssz": "[1.36.0-unstable, )", - "Nethermind.Specs": "[1.36.0-unstable, )" + "Nethermind.Merge.Plugin": "[1.37.0-unstable, )", + "Nethermind.Merkleization": "[1.37.0-unstable, )", + "Nethermind.Network.Discovery": "[1.37.0-unstable, )", + "Nethermind.Serialization.Ssz": "[1.37.0-unstable, )", + "Nethermind.Specs": "[1.37.0-unstable, )" } }, "nethermind.sockets": { "type": "Project", "dependencies": { - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Logging": "[1.36.0-unstable, )", - "Nethermind.Serialization.Json": "[1.36.0-unstable, )" + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Logging": "[1.37.0-unstable, )", + "Nethermind.Serialization.Json": "[1.37.0-unstable, )" } }, "nethermind.specs": { "type": "Project", "dependencies": { - "Nethermind.Config": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Serialization.Json": "[1.36.0-unstable, )", + "Nethermind.Config": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Serialization.Json": "[1.37.0-unstable, )", "ZstdSharp.Port": "[0.8.6, )" } }, "nethermind.state": { "type": "Project", "dependencies": { - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Db": "[1.36.0-unstable, )", - "Nethermind.Evm": "[1.36.0-unstable, )", - "Nethermind.Serialization.Rlp": "[1.36.0-unstable, )", - "Nethermind.Trie": "[1.36.0-unstable, )" + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Db": "[1.37.0-unstable, )", + "Nethermind.Evm": "[1.37.0-unstable, )", + "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", + "Nethermind.Trie": "[1.37.0-unstable, )" } }, "nethermind.synchronization": { "type": "Project", "dependencies": { "ConcurrentHashSet": "[1.3.0, )", - "Nethermind.Consensus": "[1.36.0-unstable, )", - "Nethermind.History": "[1.36.0-unstable, )", - "Nethermind.Logging": "[1.36.0-unstable, )", - "Nethermind.Network.Contract": "[1.36.0-unstable, )", - "Nethermind.Trie": "[1.36.0-unstable, )", + "Nethermind.Consensus": "[1.37.0-unstable, )", + "Nethermind.History": "[1.37.0-unstable, )", + "Nethermind.Logging": "[1.37.0-unstable, )", + "Nethermind.Network.Contract": "[1.37.0-unstable, )", + "Nethermind.Trie": "[1.37.0-unstable, )", "NonBlocking": "[2.1.2, )" } }, "nethermind.taiko": { "type": "Project", "dependencies": { - "Nethermind.Api": "[1.36.0-unstable, )", - "Nethermind.Blockchain": "[1.36.0-unstable, )", - "Nethermind.Consensus": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Evm": "[1.36.0-unstable, )", - "Nethermind.Evm.Precompiles": "[1.36.0-unstable, )", - "Nethermind.Init": "[1.36.0-unstable, )", - "Nethermind.JsonRpc": "[1.36.0-unstable, )", - "Nethermind.Logging": "[1.36.0-unstable, )", - "Nethermind.Merge.Plugin": "[1.36.0-unstable, )", - "Nethermind.Serialization.Json": "[1.36.0-unstable, )" + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.Blockchain": "[1.37.0-unstable, )", + "Nethermind.Consensus": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Evm": "[1.37.0-unstable, )", + "Nethermind.Evm.Precompiles": "[1.37.0-unstable, )", + "Nethermind.Init": "[1.37.0-unstable, )", + "Nethermind.JsonRpc": "[1.37.0-unstable, )", + "Nethermind.Logging": "[1.37.0-unstable, )", + "Nethermind.Merge.Plugin": "[1.37.0-unstable, )", + "Nethermind.Serialization.Json": "[1.37.0-unstable, )" } }, "nethermind.trie": { "type": "Project", "dependencies": { - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Db": "[1.36.0-unstable, )", - "Nethermind.Serialization.Rlp": "[1.36.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Db": "[1.37.0-unstable, )", + "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", "NonBlocking": "[2.1.2, )" } }, "nethermind.txpool": { "type": "Project", "dependencies": { - "Collections.Pooled": "[1.0.82, )", - "Nethermind.Config": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Crypto": "[1.36.0-unstable, )", - "Nethermind.Db": "[1.36.0-unstable, )", - "Nethermind.Evm": "[1.36.0-unstable, )", - "Nethermind.Network.Contract": "[1.36.0-unstable, )", - "Nethermind.State": "[1.36.0-unstable, )", + "ConcurrentHashSet": "[1.3.0, )", + "Nethermind.Config": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Crypto": "[1.37.0-unstable, )", + "Nethermind.Db": "[1.37.0-unstable, )", + "Nethermind.Evm": "[1.37.0-unstable, )", + "Nethermind.Network.Contract": "[1.37.0-unstable, )", + "Nethermind.State": "[1.37.0-unstable, )", "NonBlocking": "[2.1.2, )" } }, "nethermind.upnp.plugin": { "type": "Project", "dependencies": { - "Nethermind.Api": "[1.36.0-unstable, )", + "Nethermind.Api": "[1.37.0-unstable, )", "Open.NAT.Core": "[2.1.0.5, )" } }, "nethermind.wallet": { "type": "Project", "dependencies": { - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.KeyStore": "[1.36.0-unstable, )", - "Nethermind.Serialization.Rlp": "[1.36.0-unstable, )", - "Nethermind.TxPool": "[1.36.0-unstable, )" + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.KeyStore": "[1.37.0-unstable, )", + "Nethermind.Serialization.Rlp": "[1.37.0-unstable, )", + "Nethermind.TxPool": "[1.37.0-unstable, )" } }, "nethermind.xdc": { "type": "Project", "dependencies": { - "Nethermind.Api": "[1.36.0-unstable, )", - "Nethermind.Consensus": "[1.36.0-unstable, )", - "Nethermind.Core": "[1.36.0-unstable, )", - "Nethermind.Init": "[1.36.0-unstable, )" + "Nethermind.Api": "[1.37.0-unstable, )", + "Nethermind.Consensus": "[1.37.0-unstable, )", + "Nethermind.Core": "[1.37.0-unstable, )", + "Nethermind.Init": "[1.37.0-unstable, )" } }, "AspNetCore.HealthChecks.UI": { @@ -1269,15 +1237,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1542, )", - "resolved": "2.1.5.1542", - "contentHash": "GFXmOjg5fZ8s+836/HdKiyXbJ+J73wVX6hNmUE6Isb1rA8dI2SMLeW1m48s+ZNIPN9ENJb8Daq1GwmMjBGjUVQ==" - }, - "Collections.Pooled": { - "type": "CentralTransitive", - "requested": "[1.0.82, )", - "resolved": "1.0.82", - "contentHash": "o14V0k8bg+EPMwaVJcFbLNrkyDPaqNx6fWTTjJLgVWJLu939kVIAAWKaV5lCwZ7LM/tv59igi65rVWRyjILc0Q==" + "requested": "[2.1.5.1544, )", + "resolved": "2.1.5.1544", + "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" }, "CommunityToolkit.HighPerformance": { "type": "CentralTransitive", @@ -1318,15 +1280,15 @@ }, "Google.Protobuf": { "type": "CentralTransitive", - "requested": "[3.33.1, )", - "resolved": "3.33.1", - "contentHash": "RztiFmX9aOWDwfhxFzx/nS4fjs4DX7ZC7/XBEBl56k15lPGBftvbSwZkeo1mMCJHrNp1kK5iQYrFXB4MqCmBCA==" + "requested": "[3.33.2, )", + "resolved": "3.33.2", + "contentHash": "vZXVbrZgBqUkP5iWQi0CS6pucIS2MQdEYPS1duWCo8fGrrt4th6HTiHfLFX2RmAWAQl1oUnzGgyDBsfq7fHQJA==" }, "Google.Protobuf.Tools": { "type": "CentralTransitive", - "requested": "[3.33.1, )", - "resolved": "3.33.1", - "contentHash": "GxkbdNAc0NQVqwiUpaK30FIi3kBs131AoIiaC7cCSkK+KmCKlwGJOmrEv/iJTA+W3um+QEXGI6Fp1roOIGyXuA==" + "requested": "[3.33.2, )", + "resolved": "3.33.2", + "contentHash": "3YFiSs39mhBiAfeQ9u27JniqVNunVrYomNnSb8Rx6D3dJqC9Uwdpm5Xu2f2ZOGvUzkB114NAvU44KySOplgCzw==" }, "Grpc": { "type": "CentralTransitive", @@ -1337,6 +1299,16 @@ "Grpc.Core": "2.46.6" } }, + "KubernetesClient": { + "type": "CentralTransitive", + "requested": "[18.0.13, )", + "resolved": "18.0.13", + "contentHash": "X5IuxmydftB148XeULtc7rD5/RvqLuW5SzkIjFovPgJpvV4RAoRqNPruVB7GEFu1Xg+zHVIk88WqdV8JjbgHbA==", + "dependencies": { + "Fractions": "7.3.0", + "YamlDotNet": "16.3.0" + } + }, "MathNet.Numerics.FSharp": { "type": "CentralTransitive", "requested": "[5.0.0, )", @@ -1506,15 +1478,15 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.3, )", - "resolved": "1.0.3", - "contentHash": "hgT2oiMFqItNXv5vzIbhhlgqPJK4qnOHaYmUiR4jJsaWiqDRH05YtqYeMQq2+oyBOf8REtuGOW5RZ7+agRSEbg==" + "requested": "[1.0.4, )", + "resolved": "1.0.4", + "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" }, "Nethermind.Numerics.Int256": { "type": "CentralTransitive", - "requested": "[1.3.6, )", - "resolved": "1.3.6", - "contentHash": "Sk/CakMkQZuCUflJxYlj9gVTfxbTPJKsgDS5fsRWdzYA6hHVQipNJfyD6xnrF3u1X6XJadeyTTa3Rs80hJCMvQ==" + "requested": "[1.4.0, )", + "resolved": "1.4.0", + "contentHash": "w8HRMsdpX9fG9kcELJeJPEKIZgOUTCe47ebtejCvfBYQVlabA9blqba6QWIt5oG8cRSgnVlQ24DsdGsLzqFM+Q==" }, "Nito.Collections.Deque": { "type": "CentralTransitive", @@ -1542,23 +1514,23 @@ }, "PierTwo.Lantern.Discv5.WireProtocol": { "type": "CentralTransitive", - "requested": "[1.0.0-preview.6, )", - "resolved": "1.0.0-preview.6", - "contentHash": "WjVYiDxyZ3z00kuJJXuWJwRqkWfTrZF1v7qWz4mMASRP6AEhDCF4jMdCuAWkH1uPj00kkluONOc426Z//FcjDw==", + "requested": "[1.0.0-preview.7, )", + "resolved": "1.0.0-preview.7", + "contentHash": "wfa8Drf8r8Ty8r6cebobxANFmM2h0ckA/fWIKkQCnC+Af91IKFTAtiVhtu5oCjRxY21MLuWxqObV8r+JkKSYrg==", "dependencies": { "BouncyCastle.Cryptography": "2.4.0", "NBitcoin.Secp256k1": "3.1.5", - "PierTwo.Lantern.Discv5.Enr": "1.0.0-preview.6", - "PierTwo.Lantern.Discv5.Rlp": "1.0.0-preview.6" + "PierTwo.Lantern.Discv5.Enr": "1.0.0-preview.7", + "PierTwo.Lantern.Discv5.Rlp": "1.0.0-preview.7" } }, "Polly": { "type": "CentralTransitive", - "requested": "[8.6.4, )", - "resolved": "8.6.4", - "contentHash": "uuBsDoBw0oYrMe3uTWRjkT2sIkKh+ZZnnDrLb4Z+QANfeA4+7FJacx6E8CY5GAxXRoSgFrvUADEAQ7DPF6fGiw==", + "requested": "[8.6.5, )", + "resolved": "8.6.5", + "contentHash": "VqtW2ZE/ALvQMAH1cQY3qZ2cF2OXa3oe/HKMdOv6Q02HCoEW0rsFNfcBONXlHBe1TnjWW1vdRxBEkPeq0/2FHA==", "dependencies": { - "Polly.Core": "8.6.4" + "Polly.Core": "8.6.5" } }, "prometheus-net.AspNetCore": { @@ -1590,26 +1562,26 @@ }, "System.Configuration.ConfigurationManager": { "type": "CentralTransitive", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "0B+BzJ6pPMrRzJrVsttKf9MfDj6Syw9xoY+agcS9VssYQali1446+jTf5v1K94AMFUBxLXqDZlaTjO5edaI3jA==", + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "HfOAIlSA8OuaxBZD6xjsUWhtB0KdKSWEfRId8gSGveLUjuP6G8IxfiFgJNxaiRIEC1kx4pSvz3Em5xW/J6LLxA==", "dependencies": { - "System.Security.Cryptography.ProtectedData": "10.0.0" + "System.Security.Cryptography.ProtectedData": "10.0.1" } }, "System.Security.Cryptography.ProtectedData": { "type": "CentralTransitive", - "requested": "[10.0.0, )", - "resolved": "10.0.0", - "contentHash": "qy6C3gQRTrfhFfuiIYU1jNwWfFdJpBVo1BARW7jgMm1zTEm3LRzLLyOLJGaAuKE+nZCGkEP71y4rj9NWjqLGdQ==" + "requested": "[10.0.1, )", + "resolved": "10.0.1", + "contentHash": "9SqHNq+lAjZeyPcm69FTQEjr+wsRYvkS3aW8yxoEndVYwDRkCrsP/44QPqpWHwzevoX26rkOoQ6kr7GZWngw2A==" }, "TestableIO.System.IO.Abstractions.Wrappers": { "type": "CentralTransitive", - "requested": "[22.0.16, )", - "resolved": "22.0.16", - "contentHash": "QUX0TLMvnRLEgvuMRotrZKN9eCdX4yzK7HJCaPj17T4jvUf+G4XifdLpB5wmRct2zKlscYzpWMOmHjKabse3yw==", + "requested": "[22.1.0, )", + "resolved": "22.1.0", + "contentHash": "IsW3jQqIiTN4GwdWFx+dzgRL5XR75UDTFVGuuIackPf2d7eH0KKyrx4wuIoASa1XnS9zhgLP39FKwJq6nbbx1w==", "dependencies": { - "Testably.Abstractions.FileSystem.Interface": "9.0.0" + "Testably.Abstractions.FileSystem.Interface": "10.0.0" } }, "Websocket.Client": { @@ -1650,9 +1622,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1542, )", - "resolved": "2.1.5.1542", - "contentHash": "GFXmOjg5fZ8s+836/HdKiyXbJ+J73wVX6hNmUE6Isb1rA8dI2SMLeW1m48s+ZNIPN9ENJb8Daq1GwmMjBGjUVQ==" + "requested": "[2.1.5.1544, )", + "resolved": "2.1.5.1544", + "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -1710,9 +1682,9 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.3, )", - "resolved": "1.0.3", - "contentHash": "hgT2oiMFqItNXv5vzIbhhlgqPJK4qnOHaYmUiR4jJsaWiqDRH05YtqYeMQq2+oyBOf8REtuGOW5RZ7+agRSEbg==" + "requested": "[1.0.4, )", + "resolved": "1.0.4", + "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" }, "RocksDB": { "type": "CentralTransitive", @@ -1742,9 +1714,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1542, )", - "resolved": "2.1.5.1542", - "contentHash": "GFXmOjg5fZ8s+836/HdKiyXbJ+J73wVX6hNmUE6Isb1rA8dI2SMLeW1m48s+ZNIPN9ENJb8Daq1GwmMjBGjUVQ==" + "requested": "[2.1.5.1544, )", + "resolved": "2.1.5.1544", + "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -1802,9 +1774,9 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.3, )", - "resolved": "1.0.3", - "contentHash": "hgT2oiMFqItNXv5vzIbhhlgqPJK4qnOHaYmUiR4jJsaWiqDRH05YtqYeMQq2+oyBOf8REtuGOW5RZ7+agRSEbg==" + "requested": "[1.0.4, )", + "resolved": "1.0.4", + "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" }, "RocksDB": { "type": "CentralTransitive", @@ -1834,9 +1806,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1542, )", - "resolved": "2.1.5.1542", - "contentHash": "GFXmOjg5fZ8s+836/HdKiyXbJ+J73wVX6hNmUE6Isb1rA8dI2SMLeW1m48s+ZNIPN9ENJb8Daq1GwmMjBGjUVQ==" + "requested": "[2.1.5.1544, )", + "resolved": "2.1.5.1544", + "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -1894,9 +1866,9 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.3, )", - "resolved": "1.0.3", - "contentHash": "hgT2oiMFqItNXv5vzIbhhlgqPJK4qnOHaYmUiR4jJsaWiqDRH05YtqYeMQq2+oyBOf8REtuGOW5RZ7+agRSEbg==" + "requested": "[1.0.4, )", + "resolved": "1.0.4", + "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" }, "RocksDB": { "type": "CentralTransitive", @@ -1926,9 +1898,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1542, )", - "resolved": "2.1.5.1542", - "contentHash": "GFXmOjg5fZ8s+836/HdKiyXbJ+J73wVX6hNmUE6Isb1rA8dI2SMLeW1m48s+ZNIPN9ENJb8Daq1GwmMjBGjUVQ==" + "requested": "[2.1.5.1544, )", + "resolved": "2.1.5.1544", + "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -1986,9 +1958,9 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.3, )", - "resolved": "1.0.3", - "contentHash": "hgT2oiMFqItNXv5vzIbhhlgqPJK4qnOHaYmUiR4jJsaWiqDRH05YtqYeMQq2+oyBOf8REtuGOW5RZ7+agRSEbg==" + "requested": "[1.0.4, )", + "resolved": "1.0.4", + "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" }, "RocksDB": { "type": "CentralTransitive", @@ -2018,9 +1990,9 @@ }, "Ckzg.Bindings": { "type": "CentralTransitive", - "requested": "[2.1.5.1542, )", - "resolved": "2.1.5.1542", - "contentHash": "GFXmOjg5fZ8s+836/HdKiyXbJ+J73wVX6hNmUE6Isb1rA8dI2SMLeW1m48s+ZNIPN9ENJb8Daq1GwmMjBGjUVQ==" + "requested": "[2.1.5.1544, )", + "resolved": "2.1.5.1544", + "contentHash": "xKg/UV2QpE7A2Oe8Ipry2BLRxDSM0sRsKL5INwGkrNmrRkZbdVCjfbSg7Nubvtw2/c7OTGHeMh4gg35rd9MqOA==" }, "Microsoft.ClearScript.V8.Native.linux-arm64": { "type": "CentralTransitive", @@ -2078,9 +2050,9 @@ }, "Nethermind.MclBindings": { "type": "CentralTransitive", - "requested": "[1.0.3, )", - "resolved": "1.0.3", - "contentHash": "hgT2oiMFqItNXv5vzIbhhlgqPJK4qnOHaYmUiR4jJsaWiqDRH05YtqYeMQq2+oyBOf8REtuGOW5RZ7+agRSEbg==" + "requested": "[1.0.4, )", + "resolved": "1.0.4", + "contentHash": "F/HdZV0bWJs8HB5kR+00ljMRQr+SA5csTGkk/ZBawlcMvvIsXxkF7nOilGlrLlEDO3rPjilvoIzp8NDKUO36Ow==" }, "RocksDB": { "type": "CentralTransitive", diff --git a/src/Nethermind/Nethermind.Runner/wwwroot/index.html b/src/Nethermind/Nethermind.Runner/wwwroot/index.html index c9e34b49e66..7f826e8faca 100644 --- a/src/Nethermind/Nethermind.Runner/wwwroot/index.html +++ b/src/Nethermind/Nethermind.Runner/wwwroot/index.html @@ -90,8 +90,8 @@
Max: MB
-
-
Lastest Block
+
+
Latest Block
Not Synced
Gas Target
diff --git a/src/Nethermind/Nethermind.Runner/wwwroot/js/bundle.js b/src/Nethermind/Nethermind.Runner/wwwroot/js/bundle.js index 05cbff79ae1..4c493e6bdf5 100644 --- a/src/Nethermind/Nethermind.Runner/wwwroot/js/bundle.js +++ b/src/Nethermind/Nethermind.Runner/wwwroot/js/bundle.js @@ -1,6 +1,6 @@ (()=>{var Ct=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var ai=Ct((KS,mh)=>{mh.exports={Aacute:"\xC1",aacute:"\xE1",Abreve:"\u0102",abreve:"\u0103",ac:"\u223E",acd:"\u223F",acE:"\u223E\u0333",Acirc:"\xC2",acirc:"\xE2",acute:"\xB4",Acy:"\u0410",acy:"\u0430",AElig:"\xC6",aelig:"\xE6",af:"\u2061",Afr:"\u{1D504}",afr:"\u{1D51E}",Agrave:"\xC0",agrave:"\xE0",alefsym:"\u2135",aleph:"\u2135",Alpha:"\u0391",alpha:"\u03B1",Amacr:"\u0100",amacr:"\u0101",amalg:"\u2A3F",amp:"&",AMP:"&",andand:"\u2A55",And:"\u2A53",and:"\u2227",andd:"\u2A5C",andslope:"\u2A58",andv:"\u2A5A",ang:"\u2220",ange:"\u29A4",angle:"\u2220",angmsdaa:"\u29A8",angmsdab:"\u29A9",angmsdac:"\u29AA",angmsdad:"\u29AB",angmsdae:"\u29AC",angmsdaf:"\u29AD",angmsdag:"\u29AE",angmsdah:"\u29AF",angmsd:"\u2221",angrt:"\u221F",angrtvb:"\u22BE",angrtvbd:"\u299D",angsph:"\u2222",angst:"\xC5",angzarr:"\u237C",Aogon:"\u0104",aogon:"\u0105",Aopf:"\u{1D538}",aopf:"\u{1D552}",apacir:"\u2A6F",ap:"\u2248",apE:"\u2A70",ape:"\u224A",apid:"\u224B",apos:"'",ApplyFunction:"\u2061",approx:"\u2248",approxeq:"\u224A",Aring:"\xC5",aring:"\xE5",Ascr:"\u{1D49C}",ascr:"\u{1D4B6}",Assign:"\u2254",ast:"*",asymp:"\u2248",asympeq:"\u224D",Atilde:"\xC3",atilde:"\xE3",Auml:"\xC4",auml:"\xE4",awconint:"\u2233",awint:"\u2A11",backcong:"\u224C",backepsilon:"\u03F6",backprime:"\u2035",backsim:"\u223D",backsimeq:"\u22CD",Backslash:"\u2216",Barv:"\u2AE7",barvee:"\u22BD",barwed:"\u2305",Barwed:"\u2306",barwedge:"\u2305",bbrk:"\u23B5",bbrktbrk:"\u23B6",bcong:"\u224C",Bcy:"\u0411",bcy:"\u0431",bdquo:"\u201E",becaus:"\u2235",because:"\u2235",Because:"\u2235",bemptyv:"\u29B0",bepsi:"\u03F6",bernou:"\u212C",Bernoullis:"\u212C",Beta:"\u0392",beta:"\u03B2",beth:"\u2136",between:"\u226C",Bfr:"\u{1D505}",bfr:"\u{1D51F}",bigcap:"\u22C2",bigcirc:"\u25EF",bigcup:"\u22C3",bigodot:"\u2A00",bigoplus:"\u2A01",bigotimes:"\u2A02",bigsqcup:"\u2A06",bigstar:"\u2605",bigtriangledown:"\u25BD",bigtriangleup:"\u25B3",biguplus:"\u2A04",bigvee:"\u22C1",bigwedge:"\u22C0",bkarow:"\u290D",blacklozenge:"\u29EB",blacksquare:"\u25AA",blacktriangle:"\u25B4",blacktriangledown:"\u25BE",blacktriangleleft:"\u25C2",blacktriangleright:"\u25B8",blank:"\u2423",blk12:"\u2592",blk14:"\u2591",blk34:"\u2593",block:"\u2588",bne:"=\u20E5",bnequiv:"\u2261\u20E5",bNot:"\u2AED",bnot:"\u2310",Bopf:"\u{1D539}",bopf:"\u{1D553}",bot:"\u22A5",bottom:"\u22A5",bowtie:"\u22C8",boxbox:"\u29C9",boxdl:"\u2510",boxdL:"\u2555",boxDl:"\u2556",boxDL:"\u2557",boxdr:"\u250C",boxdR:"\u2552",boxDr:"\u2553",boxDR:"\u2554",boxh:"\u2500",boxH:"\u2550",boxhd:"\u252C",boxHd:"\u2564",boxhD:"\u2565",boxHD:"\u2566",boxhu:"\u2534",boxHu:"\u2567",boxhU:"\u2568",boxHU:"\u2569",boxminus:"\u229F",boxplus:"\u229E",boxtimes:"\u22A0",boxul:"\u2518",boxuL:"\u255B",boxUl:"\u255C",boxUL:"\u255D",boxur:"\u2514",boxuR:"\u2558",boxUr:"\u2559",boxUR:"\u255A",boxv:"\u2502",boxV:"\u2551",boxvh:"\u253C",boxvH:"\u256A",boxVh:"\u256B",boxVH:"\u256C",boxvl:"\u2524",boxvL:"\u2561",boxVl:"\u2562",boxVL:"\u2563",boxvr:"\u251C",boxvR:"\u255E",boxVr:"\u255F",boxVR:"\u2560",bprime:"\u2035",breve:"\u02D8",Breve:"\u02D8",brvbar:"\xA6",bscr:"\u{1D4B7}",Bscr:"\u212C",bsemi:"\u204F",bsim:"\u223D",bsime:"\u22CD",bsolb:"\u29C5",bsol:"\\",bsolhsub:"\u27C8",bull:"\u2022",bullet:"\u2022",bump:"\u224E",bumpE:"\u2AAE",bumpe:"\u224F",Bumpeq:"\u224E",bumpeq:"\u224F",Cacute:"\u0106",cacute:"\u0107",capand:"\u2A44",capbrcup:"\u2A49",capcap:"\u2A4B",cap:"\u2229",Cap:"\u22D2",capcup:"\u2A47",capdot:"\u2A40",CapitalDifferentialD:"\u2145",caps:"\u2229\uFE00",caret:"\u2041",caron:"\u02C7",Cayleys:"\u212D",ccaps:"\u2A4D",Ccaron:"\u010C",ccaron:"\u010D",Ccedil:"\xC7",ccedil:"\xE7",Ccirc:"\u0108",ccirc:"\u0109",Cconint:"\u2230",ccups:"\u2A4C",ccupssm:"\u2A50",Cdot:"\u010A",cdot:"\u010B",cedil:"\xB8",Cedilla:"\xB8",cemptyv:"\u29B2",cent:"\xA2",centerdot:"\xB7",CenterDot:"\xB7",cfr:"\u{1D520}",Cfr:"\u212D",CHcy:"\u0427",chcy:"\u0447",check:"\u2713",checkmark:"\u2713",Chi:"\u03A7",chi:"\u03C7",circ:"\u02C6",circeq:"\u2257",circlearrowleft:"\u21BA",circlearrowright:"\u21BB",circledast:"\u229B",circledcirc:"\u229A",circleddash:"\u229D",CircleDot:"\u2299",circledR:"\xAE",circledS:"\u24C8",CircleMinus:"\u2296",CirclePlus:"\u2295",CircleTimes:"\u2297",cir:"\u25CB",cirE:"\u29C3",cire:"\u2257",cirfnint:"\u2A10",cirmid:"\u2AEF",cirscir:"\u29C2",ClockwiseContourIntegral:"\u2232",CloseCurlyDoubleQuote:"\u201D",CloseCurlyQuote:"\u2019",clubs:"\u2663",clubsuit:"\u2663",colon:":",Colon:"\u2237",Colone:"\u2A74",colone:"\u2254",coloneq:"\u2254",comma:",",commat:"@",comp:"\u2201",compfn:"\u2218",complement:"\u2201",complexes:"\u2102",cong:"\u2245",congdot:"\u2A6D",Congruent:"\u2261",conint:"\u222E",Conint:"\u222F",ContourIntegral:"\u222E",copf:"\u{1D554}",Copf:"\u2102",coprod:"\u2210",Coproduct:"\u2210",copy:"\xA9",COPY:"\xA9",copysr:"\u2117",CounterClockwiseContourIntegral:"\u2233",crarr:"\u21B5",cross:"\u2717",Cross:"\u2A2F",Cscr:"\u{1D49E}",cscr:"\u{1D4B8}",csub:"\u2ACF",csube:"\u2AD1",csup:"\u2AD0",csupe:"\u2AD2",ctdot:"\u22EF",cudarrl:"\u2938",cudarrr:"\u2935",cuepr:"\u22DE",cuesc:"\u22DF",cularr:"\u21B6",cularrp:"\u293D",cupbrcap:"\u2A48",cupcap:"\u2A46",CupCap:"\u224D",cup:"\u222A",Cup:"\u22D3",cupcup:"\u2A4A",cupdot:"\u228D",cupor:"\u2A45",cups:"\u222A\uFE00",curarr:"\u21B7",curarrm:"\u293C",curlyeqprec:"\u22DE",curlyeqsucc:"\u22DF",curlyvee:"\u22CE",curlywedge:"\u22CF",curren:"\xA4",curvearrowleft:"\u21B6",curvearrowright:"\u21B7",cuvee:"\u22CE",cuwed:"\u22CF",cwconint:"\u2232",cwint:"\u2231",cylcty:"\u232D",dagger:"\u2020",Dagger:"\u2021",daleth:"\u2138",darr:"\u2193",Darr:"\u21A1",dArr:"\u21D3",dash:"\u2010",Dashv:"\u2AE4",dashv:"\u22A3",dbkarow:"\u290F",dblac:"\u02DD",Dcaron:"\u010E",dcaron:"\u010F",Dcy:"\u0414",dcy:"\u0434",ddagger:"\u2021",ddarr:"\u21CA",DD:"\u2145",dd:"\u2146",DDotrahd:"\u2911",ddotseq:"\u2A77",deg:"\xB0",Del:"\u2207",Delta:"\u0394",delta:"\u03B4",demptyv:"\u29B1",dfisht:"\u297F",Dfr:"\u{1D507}",dfr:"\u{1D521}",dHar:"\u2965",dharl:"\u21C3",dharr:"\u21C2",DiacriticalAcute:"\xB4",DiacriticalDot:"\u02D9",DiacriticalDoubleAcute:"\u02DD",DiacriticalGrave:"`",DiacriticalTilde:"\u02DC",diam:"\u22C4",diamond:"\u22C4",Diamond:"\u22C4",diamondsuit:"\u2666",diams:"\u2666",die:"\xA8",DifferentialD:"\u2146",digamma:"\u03DD",disin:"\u22F2",div:"\xF7",divide:"\xF7",divideontimes:"\u22C7",divonx:"\u22C7",DJcy:"\u0402",djcy:"\u0452",dlcorn:"\u231E",dlcrop:"\u230D",dollar:"$",Dopf:"\u{1D53B}",dopf:"\u{1D555}",Dot:"\xA8",dot:"\u02D9",DotDot:"\u20DC",doteq:"\u2250",doteqdot:"\u2251",DotEqual:"\u2250",dotminus:"\u2238",dotplus:"\u2214",dotsquare:"\u22A1",doublebarwedge:"\u2306",DoubleContourIntegral:"\u222F",DoubleDot:"\xA8",DoubleDownArrow:"\u21D3",DoubleLeftArrow:"\u21D0",DoubleLeftRightArrow:"\u21D4",DoubleLeftTee:"\u2AE4",DoubleLongLeftArrow:"\u27F8",DoubleLongLeftRightArrow:"\u27FA",DoubleLongRightArrow:"\u27F9",DoubleRightArrow:"\u21D2",DoubleRightTee:"\u22A8",DoubleUpArrow:"\u21D1",DoubleUpDownArrow:"\u21D5",DoubleVerticalBar:"\u2225",DownArrowBar:"\u2913",downarrow:"\u2193",DownArrow:"\u2193",Downarrow:"\u21D3",DownArrowUpArrow:"\u21F5",DownBreve:"\u0311",downdownarrows:"\u21CA",downharpoonleft:"\u21C3",downharpoonright:"\u21C2",DownLeftRightVector:"\u2950",DownLeftTeeVector:"\u295E",DownLeftVectorBar:"\u2956",DownLeftVector:"\u21BD",DownRightTeeVector:"\u295F",DownRightVectorBar:"\u2957",DownRightVector:"\u21C1",DownTeeArrow:"\u21A7",DownTee:"\u22A4",drbkarow:"\u2910",drcorn:"\u231F",drcrop:"\u230C",Dscr:"\u{1D49F}",dscr:"\u{1D4B9}",DScy:"\u0405",dscy:"\u0455",dsol:"\u29F6",Dstrok:"\u0110",dstrok:"\u0111",dtdot:"\u22F1",dtri:"\u25BF",dtrif:"\u25BE",duarr:"\u21F5",duhar:"\u296F",dwangle:"\u29A6",DZcy:"\u040F",dzcy:"\u045F",dzigrarr:"\u27FF",Eacute:"\xC9",eacute:"\xE9",easter:"\u2A6E",Ecaron:"\u011A",ecaron:"\u011B",Ecirc:"\xCA",ecirc:"\xEA",ecir:"\u2256",ecolon:"\u2255",Ecy:"\u042D",ecy:"\u044D",eDDot:"\u2A77",Edot:"\u0116",edot:"\u0117",eDot:"\u2251",ee:"\u2147",efDot:"\u2252",Efr:"\u{1D508}",efr:"\u{1D522}",eg:"\u2A9A",Egrave:"\xC8",egrave:"\xE8",egs:"\u2A96",egsdot:"\u2A98",el:"\u2A99",Element:"\u2208",elinters:"\u23E7",ell:"\u2113",els:"\u2A95",elsdot:"\u2A97",Emacr:"\u0112",emacr:"\u0113",empty:"\u2205",emptyset:"\u2205",EmptySmallSquare:"\u25FB",emptyv:"\u2205",EmptyVerySmallSquare:"\u25AB",emsp13:"\u2004",emsp14:"\u2005",emsp:"\u2003",ENG:"\u014A",eng:"\u014B",ensp:"\u2002",Eogon:"\u0118",eogon:"\u0119",Eopf:"\u{1D53C}",eopf:"\u{1D556}",epar:"\u22D5",eparsl:"\u29E3",eplus:"\u2A71",epsi:"\u03B5",Epsilon:"\u0395",epsilon:"\u03B5",epsiv:"\u03F5",eqcirc:"\u2256",eqcolon:"\u2255",eqsim:"\u2242",eqslantgtr:"\u2A96",eqslantless:"\u2A95",Equal:"\u2A75",equals:"=",EqualTilde:"\u2242",equest:"\u225F",Equilibrium:"\u21CC",equiv:"\u2261",equivDD:"\u2A78",eqvparsl:"\u29E5",erarr:"\u2971",erDot:"\u2253",escr:"\u212F",Escr:"\u2130",esdot:"\u2250",Esim:"\u2A73",esim:"\u2242",Eta:"\u0397",eta:"\u03B7",ETH:"\xD0",eth:"\xF0",Euml:"\xCB",euml:"\xEB",euro:"\u20AC",excl:"!",exist:"\u2203",Exists:"\u2203",expectation:"\u2130",exponentiale:"\u2147",ExponentialE:"\u2147",fallingdotseq:"\u2252",Fcy:"\u0424",fcy:"\u0444",female:"\u2640",ffilig:"\uFB03",fflig:"\uFB00",ffllig:"\uFB04",Ffr:"\u{1D509}",ffr:"\u{1D523}",filig:"\uFB01",FilledSmallSquare:"\u25FC",FilledVerySmallSquare:"\u25AA",fjlig:"fj",flat:"\u266D",fllig:"\uFB02",fltns:"\u25B1",fnof:"\u0192",Fopf:"\u{1D53D}",fopf:"\u{1D557}",forall:"\u2200",ForAll:"\u2200",fork:"\u22D4",forkv:"\u2AD9",Fouriertrf:"\u2131",fpartint:"\u2A0D",frac12:"\xBD",frac13:"\u2153",frac14:"\xBC",frac15:"\u2155",frac16:"\u2159",frac18:"\u215B",frac23:"\u2154",frac25:"\u2156",frac34:"\xBE",frac35:"\u2157",frac38:"\u215C",frac45:"\u2158",frac56:"\u215A",frac58:"\u215D",frac78:"\u215E",frasl:"\u2044",frown:"\u2322",fscr:"\u{1D4BB}",Fscr:"\u2131",gacute:"\u01F5",Gamma:"\u0393",gamma:"\u03B3",Gammad:"\u03DC",gammad:"\u03DD",gap:"\u2A86",Gbreve:"\u011E",gbreve:"\u011F",Gcedil:"\u0122",Gcirc:"\u011C",gcirc:"\u011D",Gcy:"\u0413",gcy:"\u0433",Gdot:"\u0120",gdot:"\u0121",ge:"\u2265",gE:"\u2267",gEl:"\u2A8C",gel:"\u22DB",geq:"\u2265",geqq:"\u2267",geqslant:"\u2A7E",gescc:"\u2AA9",ges:"\u2A7E",gesdot:"\u2A80",gesdoto:"\u2A82",gesdotol:"\u2A84",gesl:"\u22DB\uFE00",gesles:"\u2A94",Gfr:"\u{1D50A}",gfr:"\u{1D524}",gg:"\u226B",Gg:"\u22D9",ggg:"\u22D9",gimel:"\u2137",GJcy:"\u0403",gjcy:"\u0453",gla:"\u2AA5",gl:"\u2277",glE:"\u2A92",glj:"\u2AA4",gnap:"\u2A8A",gnapprox:"\u2A8A",gne:"\u2A88",gnE:"\u2269",gneq:"\u2A88",gneqq:"\u2269",gnsim:"\u22E7",Gopf:"\u{1D53E}",gopf:"\u{1D558}",grave:"`",GreaterEqual:"\u2265",GreaterEqualLess:"\u22DB",GreaterFullEqual:"\u2267",GreaterGreater:"\u2AA2",GreaterLess:"\u2277",GreaterSlantEqual:"\u2A7E",GreaterTilde:"\u2273",Gscr:"\u{1D4A2}",gscr:"\u210A",gsim:"\u2273",gsime:"\u2A8E",gsiml:"\u2A90",gtcc:"\u2AA7",gtcir:"\u2A7A",gt:">",GT:">",Gt:"\u226B",gtdot:"\u22D7",gtlPar:"\u2995",gtquest:"\u2A7C",gtrapprox:"\u2A86",gtrarr:"\u2978",gtrdot:"\u22D7",gtreqless:"\u22DB",gtreqqless:"\u2A8C",gtrless:"\u2277",gtrsim:"\u2273",gvertneqq:"\u2269\uFE00",gvnE:"\u2269\uFE00",Hacek:"\u02C7",hairsp:"\u200A",half:"\xBD",hamilt:"\u210B",HARDcy:"\u042A",hardcy:"\u044A",harrcir:"\u2948",harr:"\u2194",hArr:"\u21D4",harrw:"\u21AD",Hat:"^",hbar:"\u210F",Hcirc:"\u0124",hcirc:"\u0125",hearts:"\u2665",heartsuit:"\u2665",hellip:"\u2026",hercon:"\u22B9",hfr:"\u{1D525}",Hfr:"\u210C",HilbertSpace:"\u210B",hksearow:"\u2925",hkswarow:"\u2926",hoarr:"\u21FF",homtht:"\u223B",hookleftarrow:"\u21A9",hookrightarrow:"\u21AA",hopf:"\u{1D559}",Hopf:"\u210D",horbar:"\u2015",HorizontalLine:"\u2500",hscr:"\u{1D4BD}",Hscr:"\u210B",hslash:"\u210F",Hstrok:"\u0126",hstrok:"\u0127",HumpDownHump:"\u224E",HumpEqual:"\u224F",hybull:"\u2043",hyphen:"\u2010",Iacute:"\xCD",iacute:"\xED",ic:"\u2063",Icirc:"\xCE",icirc:"\xEE",Icy:"\u0418",icy:"\u0438",Idot:"\u0130",IEcy:"\u0415",iecy:"\u0435",iexcl:"\xA1",iff:"\u21D4",ifr:"\u{1D526}",Ifr:"\u2111",Igrave:"\xCC",igrave:"\xEC",ii:"\u2148",iiiint:"\u2A0C",iiint:"\u222D",iinfin:"\u29DC",iiota:"\u2129",IJlig:"\u0132",ijlig:"\u0133",Imacr:"\u012A",imacr:"\u012B",image:"\u2111",ImaginaryI:"\u2148",imagline:"\u2110",imagpart:"\u2111",imath:"\u0131",Im:"\u2111",imof:"\u22B7",imped:"\u01B5",Implies:"\u21D2",incare:"\u2105",in:"\u2208",infin:"\u221E",infintie:"\u29DD",inodot:"\u0131",intcal:"\u22BA",int:"\u222B",Int:"\u222C",integers:"\u2124",Integral:"\u222B",intercal:"\u22BA",Intersection:"\u22C2",intlarhk:"\u2A17",intprod:"\u2A3C",InvisibleComma:"\u2063",InvisibleTimes:"\u2062",IOcy:"\u0401",iocy:"\u0451",Iogon:"\u012E",iogon:"\u012F",Iopf:"\u{1D540}",iopf:"\u{1D55A}",Iota:"\u0399",iota:"\u03B9",iprod:"\u2A3C",iquest:"\xBF",iscr:"\u{1D4BE}",Iscr:"\u2110",isin:"\u2208",isindot:"\u22F5",isinE:"\u22F9",isins:"\u22F4",isinsv:"\u22F3",isinv:"\u2208",it:"\u2062",Itilde:"\u0128",itilde:"\u0129",Iukcy:"\u0406",iukcy:"\u0456",Iuml:"\xCF",iuml:"\xEF",Jcirc:"\u0134",jcirc:"\u0135",Jcy:"\u0419",jcy:"\u0439",Jfr:"\u{1D50D}",jfr:"\u{1D527}",jmath:"\u0237",Jopf:"\u{1D541}",jopf:"\u{1D55B}",Jscr:"\u{1D4A5}",jscr:"\u{1D4BF}",Jsercy:"\u0408",jsercy:"\u0458",Jukcy:"\u0404",jukcy:"\u0454",Kappa:"\u039A",kappa:"\u03BA",kappav:"\u03F0",Kcedil:"\u0136",kcedil:"\u0137",Kcy:"\u041A",kcy:"\u043A",Kfr:"\u{1D50E}",kfr:"\u{1D528}",kgreen:"\u0138",KHcy:"\u0425",khcy:"\u0445",KJcy:"\u040C",kjcy:"\u045C",Kopf:"\u{1D542}",kopf:"\u{1D55C}",Kscr:"\u{1D4A6}",kscr:"\u{1D4C0}",lAarr:"\u21DA",Lacute:"\u0139",lacute:"\u013A",laemptyv:"\u29B4",lagran:"\u2112",Lambda:"\u039B",lambda:"\u03BB",lang:"\u27E8",Lang:"\u27EA",langd:"\u2991",langle:"\u27E8",lap:"\u2A85",Laplacetrf:"\u2112",laquo:"\xAB",larrb:"\u21E4",larrbfs:"\u291F",larr:"\u2190",Larr:"\u219E",lArr:"\u21D0",larrfs:"\u291D",larrhk:"\u21A9",larrlp:"\u21AB",larrpl:"\u2939",larrsim:"\u2973",larrtl:"\u21A2",latail:"\u2919",lAtail:"\u291B",lat:"\u2AAB",late:"\u2AAD",lates:"\u2AAD\uFE00",lbarr:"\u290C",lBarr:"\u290E",lbbrk:"\u2772",lbrace:"{",lbrack:"[",lbrke:"\u298B",lbrksld:"\u298F",lbrkslu:"\u298D",Lcaron:"\u013D",lcaron:"\u013E",Lcedil:"\u013B",lcedil:"\u013C",lceil:"\u2308",lcub:"{",Lcy:"\u041B",lcy:"\u043B",ldca:"\u2936",ldquo:"\u201C",ldquor:"\u201E",ldrdhar:"\u2967",ldrushar:"\u294B",ldsh:"\u21B2",le:"\u2264",lE:"\u2266",LeftAngleBracket:"\u27E8",LeftArrowBar:"\u21E4",leftarrow:"\u2190",LeftArrow:"\u2190",Leftarrow:"\u21D0",LeftArrowRightArrow:"\u21C6",leftarrowtail:"\u21A2",LeftCeiling:"\u2308",LeftDoubleBracket:"\u27E6",LeftDownTeeVector:"\u2961",LeftDownVectorBar:"\u2959",LeftDownVector:"\u21C3",LeftFloor:"\u230A",leftharpoondown:"\u21BD",leftharpoonup:"\u21BC",leftleftarrows:"\u21C7",leftrightarrow:"\u2194",LeftRightArrow:"\u2194",Leftrightarrow:"\u21D4",leftrightarrows:"\u21C6",leftrightharpoons:"\u21CB",leftrightsquigarrow:"\u21AD",LeftRightVector:"\u294E",LeftTeeArrow:"\u21A4",LeftTee:"\u22A3",LeftTeeVector:"\u295A",leftthreetimes:"\u22CB",LeftTriangleBar:"\u29CF",LeftTriangle:"\u22B2",LeftTriangleEqual:"\u22B4",LeftUpDownVector:"\u2951",LeftUpTeeVector:"\u2960",LeftUpVectorBar:"\u2958",LeftUpVector:"\u21BF",LeftVectorBar:"\u2952",LeftVector:"\u21BC",lEg:"\u2A8B",leg:"\u22DA",leq:"\u2264",leqq:"\u2266",leqslant:"\u2A7D",lescc:"\u2AA8",les:"\u2A7D",lesdot:"\u2A7F",lesdoto:"\u2A81",lesdotor:"\u2A83",lesg:"\u22DA\uFE00",lesges:"\u2A93",lessapprox:"\u2A85",lessdot:"\u22D6",lesseqgtr:"\u22DA",lesseqqgtr:"\u2A8B",LessEqualGreater:"\u22DA",LessFullEqual:"\u2266",LessGreater:"\u2276",lessgtr:"\u2276",LessLess:"\u2AA1",lesssim:"\u2272",LessSlantEqual:"\u2A7D",LessTilde:"\u2272",lfisht:"\u297C",lfloor:"\u230A",Lfr:"\u{1D50F}",lfr:"\u{1D529}",lg:"\u2276",lgE:"\u2A91",lHar:"\u2962",lhard:"\u21BD",lharu:"\u21BC",lharul:"\u296A",lhblk:"\u2584",LJcy:"\u0409",ljcy:"\u0459",llarr:"\u21C7",ll:"\u226A",Ll:"\u22D8",llcorner:"\u231E",Lleftarrow:"\u21DA",llhard:"\u296B",lltri:"\u25FA",Lmidot:"\u013F",lmidot:"\u0140",lmoustache:"\u23B0",lmoust:"\u23B0",lnap:"\u2A89",lnapprox:"\u2A89",lne:"\u2A87",lnE:"\u2268",lneq:"\u2A87",lneqq:"\u2268",lnsim:"\u22E6",loang:"\u27EC",loarr:"\u21FD",lobrk:"\u27E6",longleftarrow:"\u27F5",LongLeftArrow:"\u27F5",Longleftarrow:"\u27F8",longleftrightarrow:"\u27F7",LongLeftRightArrow:"\u27F7",Longleftrightarrow:"\u27FA",longmapsto:"\u27FC",longrightarrow:"\u27F6",LongRightArrow:"\u27F6",Longrightarrow:"\u27F9",looparrowleft:"\u21AB",looparrowright:"\u21AC",lopar:"\u2985",Lopf:"\u{1D543}",lopf:"\u{1D55D}",loplus:"\u2A2D",lotimes:"\u2A34",lowast:"\u2217",lowbar:"_",LowerLeftArrow:"\u2199",LowerRightArrow:"\u2198",loz:"\u25CA",lozenge:"\u25CA",lozf:"\u29EB",lpar:"(",lparlt:"\u2993",lrarr:"\u21C6",lrcorner:"\u231F",lrhar:"\u21CB",lrhard:"\u296D",lrm:"\u200E",lrtri:"\u22BF",lsaquo:"\u2039",lscr:"\u{1D4C1}",Lscr:"\u2112",lsh:"\u21B0",Lsh:"\u21B0",lsim:"\u2272",lsime:"\u2A8D",lsimg:"\u2A8F",lsqb:"[",lsquo:"\u2018",lsquor:"\u201A",Lstrok:"\u0141",lstrok:"\u0142",ltcc:"\u2AA6",ltcir:"\u2A79",lt:"<",LT:"<",Lt:"\u226A",ltdot:"\u22D6",lthree:"\u22CB",ltimes:"\u22C9",ltlarr:"\u2976",ltquest:"\u2A7B",ltri:"\u25C3",ltrie:"\u22B4",ltrif:"\u25C2",ltrPar:"\u2996",lurdshar:"\u294A",luruhar:"\u2966",lvertneqq:"\u2268\uFE00",lvnE:"\u2268\uFE00",macr:"\xAF",male:"\u2642",malt:"\u2720",maltese:"\u2720",Map:"\u2905",map:"\u21A6",mapsto:"\u21A6",mapstodown:"\u21A7",mapstoleft:"\u21A4",mapstoup:"\u21A5",marker:"\u25AE",mcomma:"\u2A29",Mcy:"\u041C",mcy:"\u043C",mdash:"\u2014",mDDot:"\u223A",measuredangle:"\u2221",MediumSpace:"\u205F",Mellintrf:"\u2133",Mfr:"\u{1D510}",mfr:"\u{1D52A}",mho:"\u2127",micro:"\xB5",midast:"*",midcir:"\u2AF0",mid:"\u2223",middot:"\xB7",minusb:"\u229F",minus:"\u2212",minusd:"\u2238",minusdu:"\u2A2A",MinusPlus:"\u2213",mlcp:"\u2ADB",mldr:"\u2026",mnplus:"\u2213",models:"\u22A7",Mopf:"\u{1D544}",mopf:"\u{1D55E}",mp:"\u2213",mscr:"\u{1D4C2}",Mscr:"\u2133",mstpos:"\u223E",Mu:"\u039C",mu:"\u03BC",multimap:"\u22B8",mumap:"\u22B8",nabla:"\u2207",Nacute:"\u0143",nacute:"\u0144",nang:"\u2220\u20D2",nap:"\u2249",napE:"\u2A70\u0338",napid:"\u224B\u0338",napos:"\u0149",napprox:"\u2249",natural:"\u266E",naturals:"\u2115",natur:"\u266E",nbsp:"\xA0",nbump:"\u224E\u0338",nbumpe:"\u224F\u0338",ncap:"\u2A43",Ncaron:"\u0147",ncaron:"\u0148",Ncedil:"\u0145",ncedil:"\u0146",ncong:"\u2247",ncongdot:"\u2A6D\u0338",ncup:"\u2A42",Ncy:"\u041D",ncy:"\u043D",ndash:"\u2013",nearhk:"\u2924",nearr:"\u2197",neArr:"\u21D7",nearrow:"\u2197",ne:"\u2260",nedot:"\u2250\u0338",NegativeMediumSpace:"\u200B",NegativeThickSpace:"\u200B",NegativeThinSpace:"\u200B",NegativeVeryThinSpace:"\u200B",nequiv:"\u2262",nesear:"\u2928",nesim:"\u2242\u0338",NestedGreaterGreater:"\u226B",NestedLessLess:"\u226A",NewLine:` `,nexist:"\u2204",nexists:"\u2204",Nfr:"\u{1D511}",nfr:"\u{1D52B}",ngE:"\u2267\u0338",nge:"\u2271",ngeq:"\u2271",ngeqq:"\u2267\u0338",ngeqslant:"\u2A7E\u0338",nges:"\u2A7E\u0338",nGg:"\u22D9\u0338",ngsim:"\u2275",nGt:"\u226B\u20D2",ngt:"\u226F",ngtr:"\u226F",nGtv:"\u226B\u0338",nharr:"\u21AE",nhArr:"\u21CE",nhpar:"\u2AF2",ni:"\u220B",nis:"\u22FC",nisd:"\u22FA",niv:"\u220B",NJcy:"\u040A",njcy:"\u045A",nlarr:"\u219A",nlArr:"\u21CD",nldr:"\u2025",nlE:"\u2266\u0338",nle:"\u2270",nleftarrow:"\u219A",nLeftarrow:"\u21CD",nleftrightarrow:"\u21AE",nLeftrightarrow:"\u21CE",nleq:"\u2270",nleqq:"\u2266\u0338",nleqslant:"\u2A7D\u0338",nles:"\u2A7D\u0338",nless:"\u226E",nLl:"\u22D8\u0338",nlsim:"\u2274",nLt:"\u226A\u20D2",nlt:"\u226E",nltri:"\u22EA",nltrie:"\u22EC",nLtv:"\u226A\u0338",nmid:"\u2224",NoBreak:"\u2060",NonBreakingSpace:"\xA0",nopf:"\u{1D55F}",Nopf:"\u2115",Not:"\u2AEC",not:"\xAC",NotCongruent:"\u2262",NotCupCap:"\u226D",NotDoubleVerticalBar:"\u2226",NotElement:"\u2209",NotEqual:"\u2260",NotEqualTilde:"\u2242\u0338",NotExists:"\u2204",NotGreater:"\u226F",NotGreaterEqual:"\u2271",NotGreaterFullEqual:"\u2267\u0338",NotGreaterGreater:"\u226B\u0338",NotGreaterLess:"\u2279",NotGreaterSlantEqual:"\u2A7E\u0338",NotGreaterTilde:"\u2275",NotHumpDownHump:"\u224E\u0338",NotHumpEqual:"\u224F\u0338",notin:"\u2209",notindot:"\u22F5\u0338",notinE:"\u22F9\u0338",notinva:"\u2209",notinvb:"\u22F7",notinvc:"\u22F6",NotLeftTriangleBar:"\u29CF\u0338",NotLeftTriangle:"\u22EA",NotLeftTriangleEqual:"\u22EC",NotLess:"\u226E",NotLessEqual:"\u2270",NotLessGreater:"\u2278",NotLessLess:"\u226A\u0338",NotLessSlantEqual:"\u2A7D\u0338",NotLessTilde:"\u2274",NotNestedGreaterGreater:"\u2AA2\u0338",NotNestedLessLess:"\u2AA1\u0338",notni:"\u220C",notniva:"\u220C",notnivb:"\u22FE",notnivc:"\u22FD",NotPrecedes:"\u2280",NotPrecedesEqual:"\u2AAF\u0338",NotPrecedesSlantEqual:"\u22E0",NotReverseElement:"\u220C",NotRightTriangleBar:"\u29D0\u0338",NotRightTriangle:"\u22EB",NotRightTriangleEqual:"\u22ED",NotSquareSubset:"\u228F\u0338",NotSquareSubsetEqual:"\u22E2",NotSquareSuperset:"\u2290\u0338",NotSquareSupersetEqual:"\u22E3",NotSubset:"\u2282\u20D2",NotSubsetEqual:"\u2288",NotSucceeds:"\u2281",NotSucceedsEqual:"\u2AB0\u0338",NotSucceedsSlantEqual:"\u22E1",NotSucceedsTilde:"\u227F\u0338",NotSuperset:"\u2283\u20D2",NotSupersetEqual:"\u2289",NotTilde:"\u2241",NotTildeEqual:"\u2244",NotTildeFullEqual:"\u2247",NotTildeTilde:"\u2249",NotVerticalBar:"\u2224",nparallel:"\u2226",npar:"\u2226",nparsl:"\u2AFD\u20E5",npart:"\u2202\u0338",npolint:"\u2A14",npr:"\u2280",nprcue:"\u22E0",nprec:"\u2280",npreceq:"\u2AAF\u0338",npre:"\u2AAF\u0338",nrarrc:"\u2933\u0338",nrarr:"\u219B",nrArr:"\u21CF",nrarrw:"\u219D\u0338",nrightarrow:"\u219B",nRightarrow:"\u21CF",nrtri:"\u22EB",nrtrie:"\u22ED",nsc:"\u2281",nsccue:"\u22E1",nsce:"\u2AB0\u0338",Nscr:"\u{1D4A9}",nscr:"\u{1D4C3}",nshortmid:"\u2224",nshortparallel:"\u2226",nsim:"\u2241",nsime:"\u2244",nsimeq:"\u2244",nsmid:"\u2224",nspar:"\u2226",nsqsube:"\u22E2",nsqsupe:"\u22E3",nsub:"\u2284",nsubE:"\u2AC5\u0338",nsube:"\u2288",nsubset:"\u2282\u20D2",nsubseteq:"\u2288",nsubseteqq:"\u2AC5\u0338",nsucc:"\u2281",nsucceq:"\u2AB0\u0338",nsup:"\u2285",nsupE:"\u2AC6\u0338",nsupe:"\u2289",nsupset:"\u2283\u20D2",nsupseteq:"\u2289",nsupseteqq:"\u2AC6\u0338",ntgl:"\u2279",Ntilde:"\xD1",ntilde:"\xF1",ntlg:"\u2278",ntriangleleft:"\u22EA",ntrianglelefteq:"\u22EC",ntriangleright:"\u22EB",ntrianglerighteq:"\u22ED",Nu:"\u039D",nu:"\u03BD",num:"#",numero:"\u2116",numsp:"\u2007",nvap:"\u224D\u20D2",nvdash:"\u22AC",nvDash:"\u22AD",nVdash:"\u22AE",nVDash:"\u22AF",nvge:"\u2265\u20D2",nvgt:">\u20D2",nvHarr:"\u2904",nvinfin:"\u29DE",nvlArr:"\u2902",nvle:"\u2264\u20D2",nvlt:"<\u20D2",nvltrie:"\u22B4\u20D2",nvrArr:"\u2903",nvrtrie:"\u22B5\u20D2",nvsim:"\u223C\u20D2",nwarhk:"\u2923",nwarr:"\u2196",nwArr:"\u21D6",nwarrow:"\u2196",nwnear:"\u2927",Oacute:"\xD3",oacute:"\xF3",oast:"\u229B",Ocirc:"\xD4",ocirc:"\xF4",ocir:"\u229A",Ocy:"\u041E",ocy:"\u043E",odash:"\u229D",Odblac:"\u0150",odblac:"\u0151",odiv:"\u2A38",odot:"\u2299",odsold:"\u29BC",OElig:"\u0152",oelig:"\u0153",ofcir:"\u29BF",Ofr:"\u{1D512}",ofr:"\u{1D52C}",ogon:"\u02DB",Ograve:"\xD2",ograve:"\xF2",ogt:"\u29C1",ohbar:"\u29B5",ohm:"\u03A9",oint:"\u222E",olarr:"\u21BA",olcir:"\u29BE",olcross:"\u29BB",oline:"\u203E",olt:"\u29C0",Omacr:"\u014C",omacr:"\u014D",Omega:"\u03A9",omega:"\u03C9",Omicron:"\u039F",omicron:"\u03BF",omid:"\u29B6",ominus:"\u2296",Oopf:"\u{1D546}",oopf:"\u{1D560}",opar:"\u29B7",OpenCurlyDoubleQuote:"\u201C",OpenCurlyQuote:"\u2018",operp:"\u29B9",oplus:"\u2295",orarr:"\u21BB",Or:"\u2A54",or:"\u2228",ord:"\u2A5D",order:"\u2134",orderof:"\u2134",ordf:"\xAA",ordm:"\xBA",origof:"\u22B6",oror:"\u2A56",orslope:"\u2A57",orv:"\u2A5B",oS:"\u24C8",Oscr:"\u{1D4AA}",oscr:"\u2134",Oslash:"\xD8",oslash:"\xF8",osol:"\u2298",Otilde:"\xD5",otilde:"\xF5",otimesas:"\u2A36",Otimes:"\u2A37",otimes:"\u2297",Ouml:"\xD6",ouml:"\xF6",ovbar:"\u233D",OverBar:"\u203E",OverBrace:"\u23DE",OverBracket:"\u23B4",OverParenthesis:"\u23DC",para:"\xB6",parallel:"\u2225",par:"\u2225",parsim:"\u2AF3",parsl:"\u2AFD",part:"\u2202",PartialD:"\u2202",Pcy:"\u041F",pcy:"\u043F",percnt:"%",period:".",permil:"\u2030",perp:"\u22A5",pertenk:"\u2031",Pfr:"\u{1D513}",pfr:"\u{1D52D}",Phi:"\u03A6",phi:"\u03C6",phiv:"\u03D5",phmmat:"\u2133",phone:"\u260E",Pi:"\u03A0",pi:"\u03C0",pitchfork:"\u22D4",piv:"\u03D6",planck:"\u210F",planckh:"\u210E",plankv:"\u210F",plusacir:"\u2A23",plusb:"\u229E",pluscir:"\u2A22",plus:"+",plusdo:"\u2214",plusdu:"\u2A25",pluse:"\u2A72",PlusMinus:"\xB1",plusmn:"\xB1",plussim:"\u2A26",plustwo:"\u2A27",pm:"\xB1",Poincareplane:"\u210C",pointint:"\u2A15",popf:"\u{1D561}",Popf:"\u2119",pound:"\xA3",prap:"\u2AB7",Pr:"\u2ABB",pr:"\u227A",prcue:"\u227C",precapprox:"\u2AB7",prec:"\u227A",preccurlyeq:"\u227C",Precedes:"\u227A",PrecedesEqual:"\u2AAF",PrecedesSlantEqual:"\u227C",PrecedesTilde:"\u227E",preceq:"\u2AAF",precnapprox:"\u2AB9",precneqq:"\u2AB5",precnsim:"\u22E8",pre:"\u2AAF",prE:"\u2AB3",precsim:"\u227E",prime:"\u2032",Prime:"\u2033",primes:"\u2119",prnap:"\u2AB9",prnE:"\u2AB5",prnsim:"\u22E8",prod:"\u220F",Product:"\u220F",profalar:"\u232E",profline:"\u2312",profsurf:"\u2313",prop:"\u221D",Proportional:"\u221D",Proportion:"\u2237",propto:"\u221D",prsim:"\u227E",prurel:"\u22B0",Pscr:"\u{1D4AB}",pscr:"\u{1D4C5}",Psi:"\u03A8",psi:"\u03C8",puncsp:"\u2008",Qfr:"\u{1D514}",qfr:"\u{1D52E}",qint:"\u2A0C",qopf:"\u{1D562}",Qopf:"\u211A",qprime:"\u2057",Qscr:"\u{1D4AC}",qscr:"\u{1D4C6}",quaternions:"\u210D",quatint:"\u2A16",quest:"?",questeq:"\u225F",quot:'"',QUOT:'"',rAarr:"\u21DB",race:"\u223D\u0331",Racute:"\u0154",racute:"\u0155",radic:"\u221A",raemptyv:"\u29B3",rang:"\u27E9",Rang:"\u27EB",rangd:"\u2992",range:"\u29A5",rangle:"\u27E9",raquo:"\xBB",rarrap:"\u2975",rarrb:"\u21E5",rarrbfs:"\u2920",rarrc:"\u2933",rarr:"\u2192",Rarr:"\u21A0",rArr:"\u21D2",rarrfs:"\u291E",rarrhk:"\u21AA",rarrlp:"\u21AC",rarrpl:"\u2945",rarrsim:"\u2974",Rarrtl:"\u2916",rarrtl:"\u21A3",rarrw:"\u219D",ratail:"\u291A",rAtail:"\u291C",ratio:"\u2236",rationals:"\u211A",rbarr:"\u290D",rBarr:"\u290F",RBarr:"\u2910",rbbrk:"\u2773",rbrace:"}",rbrack:"]",rbrke:"\u298C",rbrksld:"\u298E",rbrkslu:"\u2990",Rcaron:"\u0158",rcaron:"\u0159",Rcedil:"\u0156",rcedil:"\u0157",rceil:"\u2309",rcub:"}",Rcy:"\u0420",rcy:"\u0440",rdca:"\u2937",rdldhar:"\u2969",rdquo:"\u201D",rdquor:"\u201D",rdsh:"\u21B3",real:"\u211C",realine:"\u211B",realpart:"\u211C",reals:"\u211D",Re:"\u211C",rect:"\u25AD",reg:"\xAE",REG:"\xAE",ReverseElement:"\u220B",ReverseEquilibrium:"\u21CB",ReverseUpEquilibrium:"\u296F",rfisht:"\u297D",rfloor:"\u230B",rfr:"\u{1D52F}",Rfr:"\u211C",rHar:"\u2964",rhard:"\u21C1",rharu:"\u21C0",rharul:"\u296C",Rho:"\u03A1",rho:"\u03C1",rhov:"\u03F1",RightAngleBracket:"\u27E9",RightArrowBar:"\u21E5",rightarrow:"\u2192",RightArrow:"\u2192",Rightarrow:"\u21D2",RightArrowLeftArrow:"\u21C4",rightarrowtail:"\u21A3",RightCeiling:"\u2309",RightDoubleBracket:"\u27E7",RightDownTeeVector:"\u295D",RightDownVectorBar:"\u2955",RightDownVector:"\u21C2",RightFloor:"\u230B",rightharpoondown:"\u21C1",rightharpoonup:"\u21C0",rightleftarrows:"\u21C4",rightleftharpoons:"\u21CC",rightrightarrows:"\u21C9",rightsquigarrow:"\u219D",RightTeeArrow:"\u21A6",RightTee:"\u22A2",RightTeeVector:"\u295B",rightthreetimes:"\u22CC",RightTriangleBar:"\u29D0",RightTriangle:"\u22B3",RightTriangleEqual:"\u22B5",RightUpDownVector:"\u294F",RightUpTeeVector:"\u295C",RightUpVectorBar:"\u2954",RightUpVector:"\u21BE",RightVectorBar:"\u2953",RightVector:"\u21C0",ring:"\u02DA",risingdotseq:"\u2253",rlarr:"\u21C4",rlhar:"\u21CC",rlm:"\u200F",rmoustache:"\u23B1",rmoust:"\u23B1",rnmid:"\u2AEE",roang:"\u27ED",roarr:"\u21FE",robrk:"\u27E7",ropar:"\u2986",ropf:"\u{1D563}",Ropf:"\u211D",roplus:"\u2A2E",rotimes:"\u2A35",RoundImplies:"\u2970",rpar:")",rpargt:"\u2994",rppolint:"\u2A12",rrarr:"\u21C9",Rrightarrow:"\u21DB",rsaquo:"\u203A",rscr:"\u{1D4C7}",Rscr:"\u211B",rsh:"\u21B1",Rsh:"\u21B1",rsqb:"]",rsquo:"\u2019",rsquor:"\u2019",rthree:"\u22CC",rtimes:"\u22CA",rtri:"\u25B9",rtrie:"\u22B5",rtrif:"\u25B8",rtriltri:"\u29CE",RuleDelayed:"\u29F4",ruluhar:"\u2968",rx:"\u211E",Sacute:"\u015A",sacute:"\u015B",sbquo:"\u201A",scap:"\u2AB8",Scaron:"\u0160",scaron:"\u0161",Sc:"\u2ABC",sc:"\u227B",sccue:"\u227D",sce:"\u2AB0",scE:"\u2AB4",Scedil:"\u015E",scedil:"\u015F",Scirc:"\u015C",scirc:"\u015D",scnap:"\u2ABA",scnE:"\u2AB6",scnsim:"\u22E9",scpolint:"\u2A13",scsim:"\u227F",Scy:"\u0421",scy:"\u0441",sdotb:"\u22A1",sdot:"\u22C5",sdote:"\u2A66",searhk:"\u2925",searr:"\u2198",seArr:"\u21D8",searrow:"\u2198",sect:"\xA7",semi:";",seswar:"\u2929",setminus:"\u2216",setmn:"\u2216",sext:"\u2736",Sfr:"\u{1D516}",sfr:"\u{1D530}",sfrown:"\u2322",sharp:"\u266F",SHCHcy:"\u0429",shchcy:"\u0449",SHcy:"\u0428",shcy:"\u0448",ShortDownArrow:"\u2193",ShortLeftArrow:"\u2190",shortmid:"\u2223",shortparallel:"\u2225",ShortRightArrow:"\u2192",ShortUpArrow:"\u2191",shy:"\xAD",Sigma:"\u03A3",sigma:"\u03C3",sigmaf:"\u03C2",sigmav:"\u03C2",sim:"\u223C",simdot:"\u2A6A",sime:"\u2243",simeq:"\u2243",simg:"\u2A9E",simgE:"\u2AA0",siml:"\u2A9D",simlE:"\u2A9F",simne:"\u2246",simplus:"\u2A24",simrarr:"\u2972",slarr:"\u2190",SmallCircle:"\u2218",smallsetminus:"\u2216",smashp:"\u2A33",smeparsl:"\u29E4",smid:"\u2223",smile:"\u2323",smt:"\u2AAA",smte:"\u2AAC",smtes:"\u2AAC\uFE00",SOFTcy:"\u042C",softcy:"\u044C",solbar:"\u233F",solb:"\u29C4",sol:"/",Sopf:"\u{1D54A}",sopf:"\u{1D564}",spades:"\u2660",spadesuit:"\u2660",spar:"\u2225",sqcap:"\u2293",sqcaps:"\u2293\uFE00",sqcup:"\u2294",sqcups:"\u2294\uFE00",Sqrt:"\u221A",sqsub:"\u228F",sqsube:"\u2291",sqsubset:"\u228F",sqsubseteq:"\u2291",sqsup:"\u2290",sqsupe:"\u2292",sqsupset:"\u2290",sqsupseteq:"\u2292",square:"\u25A1",Square:"\u25A1",SquareIntersection:"\u2293",SquareSubset:"\u228F",SquareSubsetEqual:"\u2291",SquareSuperset:"\u2290",SquareSupersetEqual:"\u2292",SquareUnion:"\u2294",squarf:"\u25AA",squ:"\u25A1",squf:"\u25AA",srarr:"\u2192",Sscr:"\u{1D4AE}",sscr:"\u{1D4C8}",ssetmn:"\u2216",ssmile:"\u2323",sstarf:"\u22C6",Star:"\u22C6",star:"\u2606",starf:"\u2605",straightepsilon:"\u03F5",straightphi:"\u03D5",strns:"\xAF",sub:"\u2282",Sub:"\u22D0",subdot:"\u2ABD",subE:"\u2AC5",sube:"\u2286",subedot:"\u2AC3",submult:"\u2AC1",subnE:"\u2ACB",subne:"\u228A",subplus:"\u2ABF",subrarr:"\u2979",subset:"\u2282",Subset:"\u22D0",subseteq:"\u2286",subseteqq:"\u2AC5",SubsetEqual:"\u2286",subsetneq:"\u228A",subsetneqq:"\u2ACB",subsim:"\u2AC7",subsub:"\u2AD5",subsup:"\u2AD3",succapprox:"\u2AB8",succ:"\u227B",succcurlyeq:"\u227D",Succeeds:"\u227B",SucceedsEqual:"\u2AB0",SucceedsSlantEqual:"\u227D",SucceedsTilde:"\u227F",succeq:"\u2AB0",succnapprox:"\u2ABA",succneqq:"\u2AB6",succnsim:"\u22E9",succsim:"\u227F",SuchThat:"\u220B",sum:"\u2211",Sum:"\u2211",sung:"\u266A",sup1:"\xB9",sup2:"\xB2",sup3:"\xB3",sup:"\u2283",Sup:"\u22D1",supdot:"\u2ABE",supdsub:"\u2AD8",supE:"\u2AC6",supe:"\u2287",supedot:"\u2AC4",Superset:"\u2283",SupersetEqual:"\u2287",suphsol:"\u27C9",suphsub:"\u2AD7",suplarr:"\u297B",supmult:"\u2AC2",supnE:"\u2ACC",supne:"\u228B",supplus:"\u2AC0",supset:"\u2283",Supset:"\u22D1",supseteq:"\u2287",supseteqq:"\u2AC6",supsetneq:"\u228B",supsetneqq:"\u2ACC",supsim:"\u2AC8",supsub:"\u2AD4",supsup:"\u2AD6",swarhk:"\u2926",swarr:"\u2199",swArr:"\u21D9",swarrow:"\u2199",swnwar:"\u292A",szlig:"\xDF",Tab:" ",target:"\u2316",Tau:"\u03A4",tau:"\u03C4",tbrk:"\u23B4",Tcaron:"\u0164",tcaron:"\u0165",Tcedil:"\u0162",tcedil:"\u0163",Tcy:"\u0422",tcy:"\u0442",tdot:"\u20DB",telrec:"\u2315",Tfr:"\u{1D517}",tfr:"\u{1D531}",there4:"\u2234",therefore:"\u2234",Therefore:"\u2234",Theta:"\u0398",theta:"\u03B8",thetasym:"\u03D1",thetav:"\u03D1",thickapprox:"\u2248",thicksim:"\u223C",ThickSpace:"\u205F\u200A",ThinSpace:"\u2009",thinsp:"\u2009",thkap:"\u2248",thksim:"\u223C",THORN:"\xDE",thorn:"\xFE",tilde:"\u02DC",Tilde:"\u223C",TildeEqual:"\u2243",TildeFullEqual:"\u2245",TildeTilde:"\u2248",timesbar:"\u2A31",timesb:"\u22A0",times:"\xD7",timesd:"\u2A30",tint:"\u222D",toea:"\u2928",topbot:"\u2336",topcir:"\u2AF1",top:"\u22A4",Topf:"\u{1D54B}",topf:"\u{1D565}",topfork:"\u2ADA",tosa:"\u2929",tprime:"\u2034",trade:"\u2122",TRADE:"\u2122",triangle:"\u25B5",triangledown:"\u25BF",triangleleft:"\u25C3",trianglelefteq:"\u22B4",triangleq:"\u225C",triangleright:"\u25B9",trianglerighteq:"\u22B5",tridot:"\u25EC",trie:"\u225C",triminus:"\u2A3A",TripleDot:"\u20DB",triplus:"\u2A39",trisb:"\u29CD",tritime:"\u2A3B",trpezium:"\u23E2",Tscr:"\u{1D4AF}",tscr:"\u{1D4C9}",TScy:"\u0426",tscy:"\u0446",TSHcy:"\u040B",tshcy:"\u045B",Tstrok:"\u0166",tstrok:"\u0167",twixt:"\u226C",twoheadleftarrow:"\u219E",twoheadrightarrow:"\u21A0",Uacute:"\xDA",uacute:"\xFA",uarr:"\u2191",Uarr:"\u219F",uArr:"\u21D1",Uarrocir:"\u2949",Ubrcy:"\u040E",ubrcy:"\u045E",Ubreve:"\u016C",ubreve:"\u016D",Ucirc:"\xDB",ucirc:"\xFB",Ucy:"\u0423",ucy:"\u0443",udarr:"\u21C5",Udblac:"\u0170",udblac:"\u0171",udhar:"\u296E",ufisht:"\u297E",Ufr:"\u{1D518}",ufr:"\u{1D532}",Ugrave:"\xD9",ugrave:"\xF9",uHar:"\u2963",uharl:"\u21BF",uharr:"\u21BE",uhblk:"\u2580",ulcorn:"\u231C",ulcorner:"\u231C",ulcrop:"\u230F",ultri:"\u25F8",Umacr:"\u016A",umacr:"\u016B",uml:"\xA8",UnderBar:"_",UnderBrace:"\u23DF",UnderBracket:"\u23B5",UnderParenthesis:"\u23DD",Union:"\u22C3",UnionPlus:"\u228E",Uogon:"\u0172",uogon:"\u0173",Uopf:"\u{1D54C}",uopf:"\u{1D566}",UpArrowBar:"\u2912",uparrow:"\u2191",UpArrow:"\u2191",Uparrow:"\u21D1",UpArrowDownArrow:"\u21C5",updownarrow:"\u2195",UpDownArrow:"\u2195",Updownarrow:"\u21D5",UpEquilibrium:"\u296E",upharpoonleft:"\u21BF",upharpoonright:"\u21BE",uplus:"\u228E",UpperLeftArrow:"\u2196",UpperRightArrow:"\u2197",upsi:"\u03C5",Upsi:"\u03D2",upsih:"\u03D2",Upsilon:"\u03A5",upsilon:"\u03C5",UpTeeArrow:"\u21A5",UpTee:"\u22A5",upuparrows:"\u21C8",urcorn:"\u231D",urcorner:"\u231D",urcrop:"\u230E",Uring:"\u016E",uring:"\u016F",urtri:"\u25F9",Uscr:"\u{1D4B0}",uscr:"\u{1D4CA}",utdot:"\u22F0",Utilde:"\u0168",utilde:"\u0169",utri:"\u25B5",utrif:"\u25B4",uuarr:"\u21C8",Uuml:"\xDC",uuml:"\xFC",uwangle:"\u29A7",vangrt:"\u299C",varepsilon:"\u03F5",varkappa:"\u03F0",varnothing:"\u2205",varphi:"\u03D5",varpi:"\u03D6",varpropto:"\u221D",varr:"\u2195",vArr:"\u21D5",varrho:"\u03F1",varsigma:"\u03C2",varsubsetneq:"\u228A\uFE00",varsubsetneqq:"\u2ACB\uFE00",varsupsetneq:"\u228B\uFE00",varsupsetneqq:"\u2ACC\uFE00",vartheta:"\u03D1",vartriangleleft:"\u22B2",vartriangleright:"\u22B3",vBar:"\u2AE8",Vbar:"\u2AEB",vBarv:"\u2AE9",Vcy:"\u0412",vcy:"\u0432",vdash:"\u22A2",vDash:"\u22A8",Vdash:"\u22A9",VDash:"\u22AB",Vdashl:"\u2AE6",veebar:"\u22BB",vee:"\u2228",Vee:"\u22C1",veeeq:"\u225A",vellip:"\u22EE",verbar:"|",Verbar:"\u2016",vert:"|",Vert:"\u2016",VerticalBar:"\u2223",VerticalLine:"|",VerticalSeparator:"\u2758",VerticalTilde:"\u2240",VeryThinSpace:"\u200A",Vfr:"\u{1D519}",vfr:"\u{1D533}",vltri:"\u22B2",vnsub:"\u2282\u20D2",vnsup:"\u2283\u20D2",Vopf:"\u{1D54D}",vopf:"\u{1D567}",vprop:"\u221D",vrtri:"\u22B3",Vscr:"\u{1D4B1}",vscr:"\u{1D4CB}",vsubnE:"\u2ACB\uFE00",vsubne:"\u228A\uFE00",vsupnE:"\u2ACC\uFE00",vsupne:"\u228B\uFE00",Vvdash:"\u22AA",vzigzag:"\u299A",Wcirc:"\u0174",wcirc:"\u0175",wedbar:"\u2A5F",wedge:"\u2227",Wedge:"\u22C0",wedgeq:"\u2259",weierp:"\u2118",Wfr:"\u{1D51A}",wfr:"\u{1D534}",Wopf:"\u{1D54E}",wopf:"\u{1D568}",wp:"\u2118",wr:"\u2240",wreath:"\u2240",Wscr:"\u{1D4B2}",wscr:"\u{1D4CC}",xcap:"\u22C2",xcirc:"\u25EF",xcup:"\u22C3",xdtri:"\u25BD",Xfr:"\u{1D51B}",xfr:"\u{1D535}",xharr:"\u27F7",xhArr:"\u27FA",Xi:"\u039E",xi:"\u03BE",xlarr:"\u27F5",xlArr:"\u27F8",xmap:"\u27FC",xnis:"\u22FB",xodot:"\u2A00",Xopf:"\u{1D54F}",xopf:"\u{1D569}",xoplus:"\u2A01",xotime:"\u2A02",xrarr:"\u27F6",xrArr:"\u27F9",Xscr:"\u{1D4B3}",xscr:"\u{1D4CD}",xsqcup:"\u2A06",xuplus:"\u2A04",xutri:"\u25B3",xvee:"\u22C1",xwedge:"\u22C0",Yacute:"\xDD",yacute:"\xFD",YAcy:"\u042F",yacy:"\u044F",Ycirc:"\u0176",ycirc:"\u0177",Ycy:"\u042B",ycy:"\u044B",yen:"\xA5",Yfr:"\u{1D51C}",yfr:"\u{1D536}",YIcy:"\u0407",yicy:"\u0457",Yopf:"\u{1D550}",yopf:"\u{1D56A}",Yscr:"\u{1D4B4}",yscr:"\u{1D4CE}",YUcy:"\u042E",yucy:"\u044E",yuml:"\xFF",Yuml:"\u0178",Zacute:"\u0179",zacute:"\u017A",Zcaron:"\u017D",zcaron:"\u017E",Zcy:"\u0417",zcy:"\u0437",Zdot:"\u017B",zdot:"\u017C",zeetrf:"\u2128",ZeroWidthSpace:"\u200B",Zeta:"\u0396",zeta:"\u03B6",zfr:"\u{1D537}",Zfr:"\u2128",ZHcy:"\u0416",zhcy:"\u0436",zigrarr:"\u21DD",zopf:"\u{1D56B}",Zopf:"\u2124",Zscr:"\u{1D4B5}",zscr:"\u{1D4CF}",zwj:"\u200D",zwnj:"\u200C"}});var Sl=Ct((t2,dh)=>{dh.exports={Aacute:"\xC1",aacute:"\xE1",Acirc:"\xC2",acirc:"\xE2",acute:"\xB4",AElig:"\xC6",aelig:"\xE6",Agrave:"\xC0",agrave:"\xE0",amp:"&",AMP:"&",Aring:"\xC5",aring:"\xE5",Atilde:"\xC3",atilde:"\xE3",Auml:"\xC4",auml:"\xE4",brvbar:"\xA6",Ccedil:"\xC7",ccedil:"\xE7",cedil:"\xB8",cent:"\xA2",copy:"\xA9",COPY:"\xA9",curren:"\xA4",deg:"\xB0",divide:"\xF7",Eacute:"\xC9",eacute:"\xE9",Ecirc:"\xCA",ecirc:"\xEA",Egrave:"\xC8",egrave:"\xE8",ETH:"\xD0",eth:"\xF0",Euml:"\xCB",euml:"\xEB",frac12:"\xBD",frac14:"\xBC",frac34:"\xBE",gt:">",GT:">",Iacute:"\xCD",iacute:"\xED",Icirc:"\xCE",icirc:"\xEE",iexcl:"\xA1",Igrave:"\xCC",igrave:"\xEC",iquest:"\xBF",Iuml:"\xCF",iuml:"\xEF",laquo:"\xAB",lt:"<",LT:"<",macr:"\xAF",micro:"\xB5",middot:"\xB7",nbsp:"\xA0",not:"\xAC",Ntilde:"\xD1",ntilde:"\xF1",Oacute:"\xD3",oacute:"\xF3",Ocirc:"\xD4",ocirc:"\xF4",Ograve:"\xD2",ograve:"\xF2",ordf:"\xAA",ordm:"\xBA",Oslash:"\xD8",oslash:"\xF8",Otilde:"\xD5",otilde:"\xF5",Ouml:"\xD6",ouml:"\xF6",para:"\xB6",plusmn:"\xB1",pound:"\xA3",quot:'"',QUOT:'"',raquo:"\xBB",reg:"\xAE",REG:"\xAE",sect:"\xA7",shy:"\xAD",sup1:"\xB9",sup2:"\xB2",sup3:"\xB3",szlig:"\xDF",THORN:"\xDE",thorn:"\xFE",times:"\xD7",Uacute:"\xDA",uacute:"\xFA",Ucirc:"\xDB",ucirc:"\xFB",Ugrave:"\xD9",ugrave:"\xF9",uml:"\xA8",Uuml:"\xDC",uuml:"\xFC",Yacute:"\xDD",yacute:"\xFD",yen:"\xA5",yuml:"\xFF"}});var si=Ct((e2,hh)=>{hh.exports={amp:"&",apos:"'",gt:">",lt:"<",quot:'"'}});var Ll=Ct((r2,gh)=>{gh.exports={"0":65533,"128":8364,"130":8218,"131":402,"132":8222,"133":8230,"134":8224,"135":8225,"136":710,"137":8240,"138":352,"139":8249,"140":338,"142":381,"145":8216,"146":8217,"147":8220,"148":8221,"149":8226,"150":8211,"151":8212,"152":732,"153":8482,"154":353,"155":8250,"156":339,"158":382,"159":376}});var Al=Ct(Ir=>{"use strict";var xh=Ir&&Ir.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(Ir,"__esModule",{value:!0});var El=xh(Ll()),yh=String.fromCodePoint||function(t){var e="";return t>65535&&(t-=65536,e+=String.fromCharCode(t>>>10&1023|55296),t=56320|t&1023),e+=String.fromCharCode(t),e};function vh(t){return t>=55296&&t<=57343||t>1114111?"\uFFFD":(t in El.default&&(t=El.default[t]),yh(t))}Ir.default=vh});var li=Ct(At=>{"use strict";var In=At&&At.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(At,"__esModule",{value:!0});At.decodeHTML=At.decodeHTMLStrict=At.decodeXML=void 0;var ui=In(ai()),bh=In(Sl()),wh=In(si()),Dl=In(Al()),kh=/&(?:[a-zA-Z0-9]+|#[xX][\da-fA-F]+|#\d+);/g;At.decodeXML=ql(wh.default);At.decodeHTMLStrict=ql(ui.default);function ql(t){var e=Il(t);return function(r){return String(r).replace(kh,e)}}var Cl=function(t,e){return t{"use strict";var Nl=ut&&ut.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(ut,"__esModule",{value:!0});ut.escapeUTF8=ut.escape=ut.encodeNonAsciiHTML=ut.encodeHTML=ut.encodeXML=void 0;var _h=Nl(si()),Bl=Hl(_h.default),Rl=Fl(Bl);ut.encodeXML=$l(Bl);var Th=Nl(ai()),ci=Hl(Th.default),Mh=Fl(ci);ut.encodeHTML=Lh(ci,Mh);ut.encodeNonAsciiHTML=$l(ci);function Hl(t){return Object.keys(t).sort().reduce(function(e,r){return e[t[r]]="&"+r+";",e},{})}function Fl(t){for(var e=[],r=[],n=0,o=Object.keys(t);n1?Sh(t):t.charCodeAt(0)).toString(16).toUpperCase()+";"}function Lh(t,e){return function(r){return r.replace(e,function(n){return t[n]}).replace(Ol,Nn)}}var Pl=new RegExp(Rl.source+"|"+Ol.source,"g");function Eh(t){return t.replace(Pl,Nn)}ut.escape=Eh;function Ah(t){return t.replace(Rl,Nn)}ut.escapeUTF8=Ah;function $l(t){return function(e){return e.replace(Pl,function(r){return t[r]||Nn(r)})}}});var Gl=Ct(O=>{"use strict";Object.defineProperty(O,"__esModule",{value:!0});O.decodeXMLStrict=O.decodeHTML5Strict=O.decodeHTML4Strict=O.decodeHTML5=O.decodeHTML4=O.decodeHTMLStrict=O.decodeHTML=O.decodeXML=O.encodeHTML5=O.encodeHTML4=O.escapeUTF8=O.escape=O.encodeNonAsciiHTML=O.encodeHTML=O.encodeXML=O.encode=O.decodeStrict=O.decode=void 0;var Bn=li(),Ul=fi();function Dh(t,e){return(!e||e<=0?Bn.decodeXML:Bn.decodeHTML)(t)}O.decode=Dh;function Ch(t,e){return(!e||e<=0?Bn.decodeXML:Bn.decodeHTMLStrict)(t)}O.decodeStrict=Ch;function qh(t,e){return(!e||e<=0?Ul.encodeXML:Ul.encodeHTML)(t)}O.encode=qh;var _e=fi();Object.defineProperty(O,"encodeXML",{enumerable:!0,get:function(){return _e.encodeXML}});Object.defineProperty(O,"encodeHTML",{enumerable:!0,get:function(){return _e.encodeHTML}});Object.defineProperty(O,"encodeNonAsciiHTML",{enumerable:!0,get:function(){return _e.encodeNonAsciiHTML}});Object.defineProperty(O,"escape",{enumerable:!0,get:function(){return _e.escape}});Object.defineProperty(O,"escapeUTF8",{enumerable:!0,get:function(){return _e.escapeUTF8}});Object.defineProperty(O,"encodeHTML4",{enumerable:!0,get:function(){return _e.encodeHTML}});Object.defineProperty(O,"encodeHTML5",{enumerable:!0,get:function(){return _e.encodeHTML}});var jt=li();Object.defineProperty(O,"decodeXML",{enumerable:!0,get:function(){return jt.decodeXML}});Object.defineProperty(O,"decodeHTML",{enumerable:!0,get:function(){return jt.decodeHTML}});Object.defineProperty(O,"decodeHTMLStrict",{enumerable:!0,get:function(){return jt.decodeHTMLStrict}});Object.defineProperty(O,"decodeHTML4",{enumerable:!0,get:function(){return jt.decodeHTML}});Object.defineProperty(O,"decodeHTML5",{enumerable:!0,get:function(){return jt.decodeHTML}});Object.defineProperty(O,"decodeHTML4Strict",{enumerable:!0,get:function(){return jt.decodeHTMLStrict}});Object.defineProperty(O,"decodeHTML5Strict",{enumerable:!0,get:function(){return jt.decodeHTMLStrict}});Object.defineProperty(O,"decodeXMLStrict",{enumerable:!0,get:function(){return jt.decodeXML}})});var tc=Ct((s2,Kl)=>{"use strict";function Ih(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function Vl(t,e){for(var r=0;r=t.length?{done:!0}:{done:!1,value:t[n++]}},e:function(c){throw c},f:o}}throw new TypeError(`Invalid attempt to iterate non-iterable instance. -In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}var i=!0,a=!1,s;return{s:function(){r=r.call(t)},n:function(){var c=r.next();return i=c.done,c},e:function(c){a=!0,s=c},f:function(){try{!i&&r.return!=null&&r.return()}finally{if(a)throw s}}}}function Bh(t,e){if(t){if(typeof t=="string")return zl(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);if(r==="Object"&&t.constructor&&(r=t.constructor.name),r==="Map"||r==="Set")return Array.from(t);if(r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return zl(t,e)}}function zl(t,e){(e==null||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r0?t*40+55:0,a=e>0?e*40+55:0,s=r>0?r*40+55:0;n[o]=Oh([i,a,s])}function Jl(t){for(var e=t.toString(16);e.length<2;)e="0"+e;return e}function Oh(t){var e=[],r=Zl(t),n;try{for(r.s();!(n=r.n()).done;){var o=n.value;e.push(Jl(o))}}catch(i){r.e(i)}finally{r.f()}return"#"+e.join("")}function Wl(t,e,r,n){var o;return e==="text"?o=Gh(r,n):e==="display"?o=$h(t,r,n):e==="xterm256Foreground"?o=Fn(t,n.colors[r]):e==="xterm256Background"?o=On(t,n.colors[r]):e==="rgb"&&(o=Ph(t,r)),o}function Ph(t,e){e=e.substring(2).slice(0,-1);var r=+e.substr(0,2),n=e.substring(5).split(";"),o=n.map(function(i){return("0"+Number(i).toString(16)).substr(-2)}).join("");return Hn(t,(r===38?"color:#":"background-color:#")+o)}function $h(t,e,r){e=parseInt(e,10);var n={"-1":function(){return"
"},0:function(){return t.length&&Ql(t)},1:function(){return Zt(t,"b")},3:function(){return Zt(t,"i")},4:function(){return Zt(t,"u")},8:function(){return Hn(t,"display:none")},9:function(){return Zt(t,"strike")},22:function(){return Hn(t,"font-weight:normal;text-decoration:none;font-style:normal")},23:function(){return jl(t,"i")},24:function(){return jl(t,"u")},39:function(){return Fn(t,r.fg)},49:function(){return On(t,r.bg)},53:function(){return Hn(t,"text-decoration:overline")}},o;return n[e]?o=n[e]():4"}).join("")}function Rn(t,e){for(var r=[],n=t;n<=e;n++)r.push(n);return r}function Uh(t){return function(e){return(t===null||e.category!==t)&&t!=="all"}}function Xl(t){t=parseInt(t,10);var e=null;return t===0?e="all":t===1?e="bold":2")}function Hn(t,e){return Zt(t,"span",e)}function Fn(t,e){return Zt(t,"span","color:"+e)}function On(t,e){return Zt(t,"span","background-color:"+e)}function jl(t,e){var r;if(t.slice(-1)[0]===e&&(r=t.pop()),r)return""}function Vh(t,e,r){var n=!1,o=3;function i(){return""}function a(L,b){return r("xterm256Foreground",b),""}function s(L,b){return r("xterm256Background",b),""}function u(L){return e.newline?r("display",-1):r("text",L),""}function c(L,b){n=!0,b.trim().length===0&&(b="0"),b=b.trimRight(";").split(";");var N=Zl(b),H;try{for(N.s();!(H=N.n()).done;){var P=H.value;r("display",P)}}catch(I){N.e(I)}finally{N.f()}return""}function l(L){return r("text",L),""}function p(L){return r("rgb",L),""}var f=[{pattern:/^\x08+/,sub:i},{pattern:/^\x1b\[[012]?K/,sub:i},{pattern:/^\x1b\[\(B/,sub:i},{pattern:/^\x1b\[[34]8;2;\d+;\d+;\d+m/,sub:p},{pattern:/^\x1b\[38;5;(\d+)m/,sub:a},{pattern:/^\x1b\[48;5;(\d+)m/,sub:s},{pattern:/^\n/,sub:u},{pattern:/^\r+\n/,sub:u},{pattern:/^\r/,sub:u},{pattern:/^\x1b\[((?:\d{1,3};?)+|)m/,sub:c},{pattern:/^\x1b\[\d?J/,sub:i},{pattern:/^\x1b\[\d{0,3};\d{0,3}f/,sub:i},{pattern:/^\x1b\[?[\d;]{0,3}/,sub:i},{pattern:/^(([^\x1b\x08\r\n])+)/,sub:l}];function m(L,b){b>o&&n||(n=!1,t=t.replace(L.pattern,L.sub))}var d=[],y=t,k=y.length;t:for(;k>0;){for(var _=0,q=0,A=f.length;qe?1:t>=e?0:NaN}function Gn(t,e){return t==null||e==null?NaN:et?1:e>=t?0:NaN}function Kt(t){let e,r,n;t.length!==2?(e=ft,r=(s,u)=>ft(t(s),u),n=(s,u)=>t(s)-u):(e=t===ft||t===Gn?t:yc,r=t,n=t);function o(s,u,c=0,l=s.length){if(c>>1;r(s[p],u)<0?c=p+1:l=p}while(c>>1;r(s[p],u)<=0?c=p+1:l=p}while(cc&&n(s[p-1],u)>-n(s[p],u)?p-1:p}return{left:o,center:a,right:i}}function yc(){return 0}function Nr(t){return t===null?NaN:+t}function*Si(t,e){if(e===void 0)for(let r of t)r!=null&&(r=+r)>=r&&(yield r);else{let r=-1;for(let n of t)(n=e(n,++r,t))!=null&&(n=+n)>=n&&(yield n)}}var Li=Kt(ft),Ei=Li.right,vc=Li.left,bc=Kt(Nr).center,Vn=Ei;function Me(t,e){let r,n;if(e===void 0)for(let o of t)o!=null&&(r===void 0?o>=o&&(r=n=o):(r>o&&(r=o),n=i&&(r=n=i):(r>i&&(r=i),n{let n=t(e,r);return n||n===0?n:(t(r,r)===0)-(t(e,e)===0)}}function zn(t,e){return(t==null||!(t>=t))-(e==null||!(e>=e))||(te?1:0)}var Tc=Math.sqrt(50),Mc=Math.sqrt(10),Sc=Math.sqrt(2);function Br(t,e,r){let n=(e-t)/Math.max(0,r),o=Math.floor(Math.log10(n)),i=n/Math.pow(10,o),a=i>=Tc?10:i>=Mc?5:i>=Sc?2:1,s,u,c;return o<0?(c=Math.pow(10,-o)/a,s=Math.round(t*c),u=Math.round(e*c),s/ce&&--u,c=-c):(c=Math.pow(10,o)*a,s=Math.round(t/c),u=Math.round(e/c),s*ce&&--u),u0))return[];if(t===e)return[t];let n=e=o))return[];let s=i-o+1,u=new Array(s);if(n)if(a<0)for(let c=0;c=n)&&(r=n);else{let n=-1;for(let o of t)(o=e(o,++n,t))!=null&&(r=o)&&(r=o)}return r}function Rr(t,e){let r;if(e===void 0)for(let n of t)n!=null&&(r>n||r===void 0&&n>=n)&&(r=n);else{let n=-1;for(let o of t)(o=e(o,++n,t))!=null&&(r>o||r===void 0&&o>=o)&&(r=o)}return r}function Hr(t,e,r=0,n=1/0,o){if(e=Math.floor(e),r=Math.floor(Math.max(0,r)),n=Math.floor(Math.min(t.length-1,n)),!(r<=e&&e<=n))return t;for(o=o===void 0?zn:Di(o);n>r;){if(n-r>600){let u=n-r+1,c=e-r+1,l=Math.log(u),p=.5*Math.exp(2*l/3),f=.5*Math.sqrt(l*p*(u-p)/u)*(c-u/2<0?-1:1),m=Math.max(r,Math.floor(e-c*p/u+f)),d=Math.min(n,Math.floor(e+(u-c)*p/u+f));Hr(t,e,m,d,o)}let i=t[e],a=r,s=n;for(Ke(t,r,e),o(t[n],i)>0&&Ke(t,r,n);a0;)--s}o(t[r],i)===0?Ke(t,r,s):(++s,Ke(t,s,n)),s<=e&&(r=s+1),e<=s&&(n=s-1)}return t}function Ke(t,e,r){let n=t[e];t[e]=t[r],t[r]=n}function Ee(t,e,r){if(t=Float64Array.from(Si(t,r)),!(!(n=t.length)||isNaN(e=+e))){if(e<=0||n<2)return Rr(t);if(e>=1)return ee(t);var n,o=(n-1)*e,i=Math.floor(o),a=ee(Hr(t,i).subarray(0,i+1)),s=Rr(t.subarray(i+1));return a+(s-a)*(o-i)}}function Ae(t,e,r){t=+t,e=+e,r=(o=arguments.length)<2?(e=t,t=0,1):o<3?1:+r;for(var n=-1,o=Math.max(0,Math.ceil((e-t)/r))|0,i=new Array(o);++n+t(e)}function Dc(t,e){return e=Math.max(0,t.bandwidth()-e*2)/2,t.round()&&(e=Math.round(e)),r=>+t(r)+e}function Cc(){return!this.__axis}function Ii(t,e){var r=[],n=null,o=null,i=6,a=6,s=3,u=typeof window<"u"&&window.devicePixelRatio>1?0:.5,c=t===Yn||t===tr?-1:1,l=t===tr||t===Wn?"x":"y",p=t===Yn||t===Xn?Lc:Ec;function f(m){var d=n??(e.ticks?e.ticks.apply(e,r):e.domain()),y=o??(e.tickFormat?e.tickFormat.apply(e,r):Ci),k=Math.max(i,0)+s,_=e.range(),q=+_[0]+u,A=+_[_.length-1]+u,D=(e.bandwidth?Dc:Ac)(e.copy(),u),L=m.selection?m.selection():m,b=L.selectAll(".domain").data([null]),N=L.selectAll(".tick").data(d,e).order(),H=N.exit(),P=N.enter().append("g").attr("class","tick"),I=N.select("line"),h=N.select("text");b=b.merge(b.enter().insert("path",".tick").attr("class","domain").attr("stroke","currentColor")),N=N.merge(P),I=I.merge(P.append("line").attr("stroke","currentColor").attr(l+"2",c*i)),h=h.merge(P.append("text").attr("fill","currentColor").attr(l,c*k).attr("dy",t===Yn?"0em":t===Xn?"0.71em":"0.32em")),m!==L&&(b=b.transition(m),N=N.transition(m),I=I.transition(m),h=h.transition(m),H=H.transition(m).attr("opacity",qi).attr("transform",function(T){return isFinite(T=D(T))?p(T+u):this.getAttribute("transform")}),P.attr("opacity",qi).attr("transform",function(T){var E=this.parentNode.__axis;return p((E&&isFinite(E=E(T))?E:D(T))+u)})),H.remove(),b.attr("d",t===tr||t===Wn?a?"M"+c*a+","+q+"H"+u+"V"+A+"H"+c*a:"M"+u+","+q+"V"+A:a?"M"+q+","+c*a+"V"+u+"H"+A+"V"+c*a:"M"+q+","+u+"H"+A),N.attr("opacity",1).attr("transform",function(T){return p(D(T)+u)}),I.attr(l+"2",c*i),h.attr(l,c*k).text(y),L.filter(Cc).attr("fill","none").attr("font-size",10).attr("font-family","sans-serif").attr("text-anchor",t===Wn?"start":t===tr?"end":"middle"),L.each(function(){this.__axis=D})}return f.scale=function(m){return arguments.length?(e=m,f):e},f.ticks=function(){return r=Array.from(arguments),f},f.tickArguments=function(m){return arguments.length?(r=m==null?[]:Array.from(m),f):r.slice()},f.tickValues=function(m){return arguments.length?(n=m==null?null:Array.from(m),f):n&&n.slice()},f.tickFormat=function(m){return arguments.length?(o=m,f):o},f.tickSize=function(m){return arguments.length?(i=a=+m,f):i},f.tickSizeInner=function(m){return arguments.length?(i=+m,f):i},f.tickSizeOuter=function(m){return arguments.length?(a=+m,f):a},f.tickPadding=function(m){return arguments.length?(s=+m,f):s},f.offset=function(m){return arguments.length?(u=+m,f):u},f}function Fr(t){return Ii(Xn,t)}function Or(t){return Ii(tr,t)}var qc={value:()=>{}};function Bi(){for(var t=0,e=arguments.length,r={},n;t=0&&(n=r.slice(o+1),r=r.slice(0,o)),r&&!e.hasOwnProperty(r))throw new Error("unknown type: "+r);return{type:r,name:n}})}Pr.prototype=Bi.prototype={constructor:Pr,on:function(t,e){var r=this._,n=Ic(t+"",r),o,i=-1,a=n.length;if(arguments.length<2){for(;++i0)for(var r=new Array(o),n=0,o,i;n=0&&(e=t.slice(0,r))!=="xmlns"&&(t=t.slice(r+1)),Zn.hasOwnProperty(e)?{space:Zn[e],local:t}:t}function Bc(t){return function(){var e=this.ownerDocument,r=this.namespaceURI;return r===$r&&e.documentElement.namespaceURI===$r?e.createElement(t):e.createElementNS(r,t)}}function Rc(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function Ur(t){var e=qt(t);return(e.local?Rc:Bc)(e)}function Hc(){}function re(t){return t==null?Hc:function(){return this.querySelector(t)}}function Ri(t){typeof t!="function"&&(t=re(t));for(var e=this._groups,r=e.length,n=new Array(r),o=0;o=A&&(A=q+1);!(L=k[A])&&++A=0;)(a=n[o])&&(i&&a.compareDocumentPosition(i)^4&&i.parentNode.insertBefore(a,i),i=a);return this}function Xi(t){t||(t=Zc);function e(p,f){return p&&f?t(p.__data__,f.__data__):!p-!f}for(var r=this._groups,n=r.length,o=new Array(n),i=0;ie?1:t>=e?0:NaN}function ji(){var t=arguments[0];return arguments[0]=this,t.apply(null,arguments),this}function Zi(){return Array.from(this)}function Ji(){for(var t=this._groups,e=0,r=t.length;e1?this.each((e==null?nf:typeof e=="function"?af:of)(t,e,r??"")):Ft(this.node(),t)}function Ft(t,e){return t.style.getPropertyValue(e)||zr(t).getComputedStyle(t,null).getPropertyValue(e)}function sf(t){return function(){delete this[t]}}function uf(t,e){return function(){this[t]=e}}function lf(t,e){return function(){var r=e.apply(this,arguments);r==null?delete this[t]:this[t]=r}}function na(t,e){return arguments.length>1?this.each((e==null?sf:typeof e=="function"?lf:uf)(t,e)):this.node()[t]}function oa(t){return t.trim().split(/^|\s+/)}function Qn(t){return t.classList||new ia(t)}function ia(t){this._node=t,this._names=oa(t.getAttribute("class")||"")}ia.prototype={add:function(t){var e=this._names.indexOf(t);e<0&&(this._names.push(t),this._node.setAttribute("class",this._names.join(" ")))},remove:function(t){var e=this._names.indexOf(t);e>=0&&(this._names.splice(e,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};function aa(t,e){for(var r=Qn(t),n=-1,o=e.length;++n=0&&(r=e.slice(n+1),e=e.slice(0,n)),{type:e,name:r}})}function Lf(t){return function(){var e=this.__on;if(e){for(var r=0,n=-1,o=e.length,i;r>8&15|e>>4&240,e>>4&15|e&240,(e&15)<<4|e&15,1):r===8?Yr(e>>24&255,e>>16&255,e>>8&255,(e&255)/255):r===4?Yr(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|e&240,((e&15)<<4|e&15)/255):null):(e=If.exec(t))?new et(e[1],e[2],e[3],1):(e=Nf.exec(t))?new et(e[1]*255/100,e[2]*255/100,e[3]*255/100,1):(e=Bf.exec(t))?Yr(e[1],e[2],e[3],e[4]):(e=Rf.exec(t))?Yr(e[1]*255/100,e[2]*255/100,e[3]*255/100,e[4]):(e=Hf.exec(t))?Aa(e[1],e[2]/100,e[3]/100,1):(e=Ff.exec(t))?Aa(e[1],e[2]/100,e[3]/100,e[4]):_a.hasOwnProperty(t)?Sa(_a[t]):t==="transparent"?new et(NaN,NaN,NaN,0):null}function Sa(t){return new et(t>>16&255,t>>8&255,t&255,1)}function Yr(t,e,r,n){return n<=0&&(t=e=r=NaN),new et(t,e,r,n)}function eo(t){return t instanceof ae||(t=yt(t)),t?(t=t.rgb(),new et(t.r,t.g,t.b,t.opacity)):new et}function qe(t,e,r,n){return arguments.length===1?eo(t):new et(t,e,r,n??1)}function et(t,e,r,n){this.r=+t,this.g=+e,this.b=+r,this.opacity=+n}De(et,qe,or(ae,{brighter(t){return t=t==null?ie:Math.pow(ie,t),new et(this.r*t,this.g*t,this.b*t,this.opacity)},darker(t){return t=t==null?Ot:Math.pow(Ot,t),new et(this.r*t,this.g*t,this.b*t,this.opacity)},rgb(){return this},clamp(){return new et(oe(this.r),oe(this.g),oe(this.b),Xr(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:La,formatHex:La,formatHex8:$f,formatRgb:Ea,toString:Ea}));function La(){return`#${ne(this.r)}${ne(this.g)}${ne(this.b)}`}function $f(){return`#${ne(this.r)}${ne(this.g)}${ne(this.b)}${ne((isNaN(this.opacity)?1:this.opacity)*255)}`}function Ea(){let t=Xr(this.opacity);return`${t===1?"rgb(":"rgba("}${oe(this.r)}, ${oe(this.g)}, ${oe(this.b)}${t===1?")":`, ${t})`}`}function Xr(t){return isNaN(t)?1:Math.max(0,Math.min(1,t))}function oe(t){return Math.max(0,Math.min(255,Math.round(t)||0))}function ne(t){return t=oe(t),(t<16?"0":"")+t.toString(16)}function Aa(t,e,r,n){return n<=0?t=e=r=NaN:r<=0||r>=1?t=e=NaN:e<=0&&(t=NaN),new xt(t,e,r,n)}function Ca(t){if(t instanceof xt)return new xt(t.h,t.s,t.l,t.opacity);if(t instanceof ae||(t=yt(t)),!t)return new xt;if(t instanceof xt)return t;t=t.rgb();var e=t.r/255,r=t.g/255,n=t.b/255,o=Math.min(e,r,n),i=Math.max(e,r,n),a=NaN,s=i-o,u=(i+o)/2;return s?(e===i?a=(r-n)/s+(r0&&u<1?0:a,new xt(a,s,u,t.opacity)}function qa(t,e,r,n){return arguments.length===1?Ca(t):new xt(t,e,r,n??1)}function xt(t,e,r,n){this.h=+t,this.s=+e,this.l=+r,this.opacity=+n}De(xt,qa,or(ae,{brighter(t){return t=t==null?ie:Math.pow(ie,t),new xt(this.h,this.s,this.l*t,this.opacity)},darker(t){return t=t==null?Ot:Math.pow(Ot,t),new xt(this.h,this.s,this.l*t,this.opacity)},rgb(){var t=this.h%360+(this.h<0)*360,e=isNaN(t)||isNaN(this.s)?0:this.s,r=this.l,n=r+(r<.5?r:1-r)*e,o=2*r-n;return new et(to(t>=240?t-240:t+120,o,n),to(t,o,n),to(t<120?t+240:t-120,o,n),this.opacity)},clamp(){return new xt(Da(this.h),Wr(this.s),Wr(this.l),Xr(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){let t=Xr(this.opacity);return`${t===1?"hsl(":"hsla("}${Da(this.h)}, ${Wr(this.s)*100}%, ${Wr(this.l)*100}%${t===1?")":`, ${t})`}`}}));function Da(t){return t=(t||0)%360,t<0?t+360:t}function Wr(t){return Math.max(0,Math.min(1,t||0))}function to(t,e,r){return(t<60?e+(r-e)*t/60:t<180?r:t<240?e+(r-e)*(240-t)/60:e)*255}var Ia=Math.PI/180,Na=180/Math.PI;var Fa=-.14861,ro=1.78277,no=-.29227,jr=-.90649,ar=1.97294,Ba=ar*jr,Ra=ar*ro,Ha=ro*no-jr*Fa;function Uf(t){if(t instanceof se)return new se(t.h,t.s,t.l,t.opacity);t instanceof et||(t=eo(t));var e=t.r/255,r=t.g/255,n=t.b/255,o=(Ha*n+Ba*e-Ra*r)/(Ha+Ba-Ra),i=n-o,a=(ar*(r-o)-no*i)/jr,s=Math.sqrt(a*a+i*i)/(ar*o*(1-o)),u=s?Math.atan2(a,i)*Na-120:NaN;return new se(u<0?u+360:u,s,o,t.opacity)}function mt(t,e,r,n){return arguments.length===1?Uf(t):new se(t,e,r,n??1)}function se(t,e,r,n){this.h=+t,this.s=+e,this.l=+r,this.opacity=+n}De(se,mt,or(ae,{brighter(t){return t=t==null?ie:Math.pow(ie,t),new se(this.h,this.s,this.l*t,this.opacity)},darker(t){return t=t==null?Ot:Math.pow(Ot,t),new se(this.h,this.s,this.l*t,this.opacity)},rgb(){var t=isNaN(this.h)?0:(this.h+120)*Ia,e=+this.l,r=isNaN(this.s)?0:this.s*e*(1-e),n=Math.cos(t),o=Math.sin(t);return new et(255*(e+r*(Fa*n+ro*o)),255*(e+r*(no*n+jr*o)),255*(e+r*(ar*n)),this.opacity)}}));function oo(t,e,r,n,o){var i=t*t,a=i*t;return((1-3*t+3*i-a)*e+(4-6*i+3*a)*r+(1+3*t+3*i-3*a)*n+a*o)/6}function Oa(t){var e=t.length-1;return function(r){var n=r<=0?r=0:r>=1?(r=1,e-1):Math.floor(r*e),o=t[n],i=t[n+1],a=n>0?t[n-1]:2*o-i,s=n()=>t;function $a(t,e){return function(r){return t+r*e}}function Gf(t,e,r){return t=Math.pow(t,r),e=Math.pow(e,r)-t,r=1/r,function(n){return Math.pow(t+n*e,r)}}function Ua(t,e){var r=e-t;return r?$a(t,r>180||r<-180?r-360*Math.round(r/360):r):Ie(isNaN(t)?e:t)}function Ga(t){return(t=+t)==1?Nt:function(e,r){return r-e?Gf(e,r,t):Ie(isNaN(e)?r:e)}}function Nt(t,e){var r=e-t;return r?$a(t,r):Ie(isNaN(t)?e:t)}var ue=function t(e){var r=Ga(e);function n(o,i){var a=r((o=qe(o)).r,(i=qe(i)).r),s=r(o.g,i.g),u=r(o.b,i.b),c=Nt(o.opacity,i.opacity);return function(l){return o.r=a(l),o.g=s(l),o.b=u(l),o.opacity=c(l),o+""}}return n.gamma=t,n}(1);function Va(t){return function(e){var r=e.length,n=new Array(r),o=new Array(r),i=new Array(r),a,s;for(a=0;ar&&(i=e.slice(r,i),s[a]?s[a]+=i:s[++a]=i),(n=n[0])===(o=o[0])?s[a]?s[a]+=o:s[++a]=o:(s[++a]=null,u.push({i:a,x:Z(n,o)})),r=io.lastIndex;return r180?l+=360:l-c>180&&(c+=360),f.push({i:p.push(o(p)+"rotate(",null,n)-2,x:Z(c,l)})):l&&p.push(o(p)+"rotate("+l+n)}function s(c,l,p,f){c!==l?f.push({i:p.push(o(p)+"skewX(",null,n)-2,x:Z(c,l)}):l&&p.push(o(p)+"skewX("+l+n)}function u(c,l,p,f,m,d){if(c!==p||l!==f){var y=m.push(o(m)+"scale(",null,",",null,")");d.push({i:y-4,x:Z(c,p)},{i:y-2,x:Z(l,f)})}else(p!==1||f!==1)&&m.push(o(m)+"scale("+p+","+f+")")}return function(c,l){var p=[],f=[];return c=t(c),l=t(l),i(c.translateX,c.translateY,l.translateX,l.translateY,p,f),a(c.rotate,l.rotate,p,f),s(c.skewX,l.skewX,p,f),u(c.scaleX,c.scaleY,l.scaleX,l.scaleY,p,f),c=l=null,function(m){for(var d=-1,y=f.length,k;++d=0&&t._call.call(void 0,e),t=t._next;--Ne}function es(){le=(tn=pr.now())+en,Ne=cr=0;try{os()}finally{Ne=0,Jf(),le=0}}function Zf(){var t=pr.now(),e=t-tn;e>rs&&(en-=e,tn=t)}function Jf(){for(var t,e=Kr,r,n=1/0;e;)e._call?(n>e._time&&(n=e._time),t=e,e=e._next):(r=e._next,e._next=null,e=t?t._next=r:Kr=r);fr=t,co(n)}function co(t){if(!Ne){cr&&(cr=clearTimeout(cr));var e=t-le;e>24?(t<1/0&&(cr=setTimeout(es,t-pr.now()-en)),lr&&(lr=clearInterval(lr))):(lr||(tn=pr.now(),lr=setInterval(Zf,rs)),Ne=1,ns(es))}}function nn(t,e,r){var n=new mr;return e=e==null?0:+e,n.restart(o=>{n.stop(),t(o+e)},e,r),n}var Qf=jn("start","end","cancel","interrupt"),Kf=[],ss=0,is=1,an=2,on=3,as=4,sn=5,hr=6;function Pt(t,e,r,n,o,i){var a=t.__transition;if(!a)t.__transition={};else if(r in a)return;tp(t,r,{name:e,index:n,group:o,on:Qf,tween:Kf,time:i.time,delay:i.delay,duration:i.duration,ease:i.ease,timer:null,state:ss})}function gr(t,e){var r=J(t,e);if(r.state>ss)throw new Error("too late; already scheduled");return r}function rt(t,e){var r=J(t,e);if(r.state>on)throw new Error("too late; already running");return r}function J(t,e){var r=t.__transition;if(!r||!(r=r[e]))throw new Error("transition not found");return r}function tp(t,e,r){var n=t.__transition,o;n[e]=r,r.timer=rn(i,0,r.time);function i(c){r.state=is,r.timer.restart(a,r.delay,r.time),r.delay<=c&&a(c-r.delay)}function a(c){var l,p,f,m;if(r.state!==is)return u();for(l in n)if(m=n[l],m.name===r.name){if(m.state===on)return nn(a);m.state===as?(m.state=hr,m.timer.stop(),m.on.call("interrupt",t,t.__data__,m.index,m.group),delete n[l]):+lan&&n.state=0&&(e=e.slice(0,r)),!e||e==="start"})}function bp(t,e,r){var n,o,i=vp(e)?gr:rt;return function(){var a=i(this,t),s=a.on;s!==n&&(o=(n=s).copy()).on(e,r),a.on=o}}function ys(t,e){var r=this._id;return arguments.length<2?J(this.node(),r).on.on(t):this.each(bp(r,t,e))}function wp(t){return function(){var e=this.parentNode;for(var r in this.__transition)if(+r!==t)return;e&&e.removeChild(this)}}function vs(){return this.on("end.remove",wp(this._id))}function bs(t){var e=this._name,r=this._id;typeof t!="function"&&(t=re(t));for(var n=this._groups,o=n.length,i=new Array(o),a=0;a=0))throw new Error(`invalid digits: ${t}`);if(e>15)return qs;let r=10**e;return function(n){this._+=n[0];for(let o=1,i=n.length;oce)if(!(Math.abs(p*u-c*l)>ce)||!i)this._append`L${this._x1=e},${this._y1=r}`;else{let m=n-a,d=o-s,y=u*u+c*c,k=m*m+d*d,_=Math.sqrt(y),q=Math.sqrt(f),A=i*Math.tan((po-Math.acos((y+f-k)/(2*_*q)))/2),D=A/q,L=A/_;Math.abs(D-1)>ce&&this._append`L${e+D*l},${r+D*p}`,this._append`A${i},${i},0,0,${+(p*m>l*d)},${this._x1=e+L*u},${this._y1=r+L*c}`}}arc(e,r,n,o,i,a){if(e=+e,r=+r,n=+n,a=!!a,n<0)throw new Error(`negative radius: ${n}`);let s=n*Math.cos(o),u=n*Math.sin(o),c=e+s,l=r+u,p=1^a,f=a?o-i:i-o;this._x1===null?this._append`M${c},${l}`:(Math.abs(this._x1-c)>ce||Math.abs(this._y1-l)>ce)&&this._append`L${c},${l}`,n&&(f<0&&(f=f%mo+mo),f>Hp?this._append`A${n},${n},0,1,${p},${e-s},${r-u}A${n},${n},0,1,${p},${this._x1=c},${this._y1=l}`:f>ce&&this._append`A${n},${n},0,${+(f>=po)},${p},${this._x1=e+n*Math.cos(i)},${this._y1=r+n*Math.sin(i)}`)}rect(e,r,n,o){this._append`M${this._x0=this._x1=+e},${this._y0=this._y1=+r}h${n=+n}v${+o}h${-n}Z`}toString(){return this._}};function Is(){return new fe}Is.prototype=fe.prototype;function Ns(t){return Math.abs(t=Math.round(t))>=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)}function pe(t,e){if((r=(t=e?t.toExponential(e-1):t.toExponential()).indexOf("e"))<0)return null;var r,n=t.slice(0,r);return[n.length>1?n[0]+n.slice(2):n,+t.slice(r+1)]}function Mt(t){return t=pe(Math.abs(t)),t?t[1]:NaN}function Bs(t,e){return function(r,n){for(var o=r.length,i=[],a=0,s=t[0],u=0;o>0&&s>0&&(u+s+1>n&&(s=Math.max(1,n-u)),i.push(r.substring(o-=s,o+s)),!((u+=s+1)>n));)s=t[a=(a+1)%t.length];return i.reverse().join(e)}}function Rs(t){return function(e){return e.replace(/[0-9]/g,function(r){return t[+r]})}}var Op=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function St(t){if(!(e=Op.exec(t)))throw new Error("invalid format: "+t);var e;return new pn({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}St.prototype=pn.prototype;function pn(t){this.fill=t.fill===void 0?" ":t.fill+"",this.align=t.align===void 0?">":t.align+"",this.sign=t.sign===void 0?"-":t.sign+"",this.symbol=t.symbol===void 0?"":t.symbol+"",this.zero=!!t.zero,this.width=t.width===void 0?void 0:+t.width,this.comma=!!t.comma,this.precision=t.precision===void 0?void 0:+t.precision,this.trim=!!t.trim,this.type=t.type===void 0?"":t.type+""}pn.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(this.width===void 0?"":Math.max(1,this.width|0))+(this.comma?",":"")+(this.precision===void 0?"":"."+Math.max(0,this.precision|0))+(this.trim?"~":"")+this.type};function Hs(t){t:for(var e=t.length,r=1,n=-1,o;r0&&(n=0);break}return n>0?t.slice(0,n)+t.slice(o+1):t}var ho;function Fs(t,e){var r=pe(t,e);if(!r)return t+"";var n=r[0],o=r[1],i=o-(ho=Math.max(-8,Math.min(8,Math.floor(o/3)))*3)+1,a=n.length;return i===a?n:i>a?n+new Array(i-a+1).join("0"):i>0?n.slice(0,i)+"."+n.slice(i):"0."+new Array(1-i).join("0")+pe(t,Math.max(0,e+i-1))[0]}function go(t,e){var r=pe(t,e);if(!r)return t+"";var n=r[0],o=r[1];return o<0?"0."+new Array(-o).join("0")+n:n.length>o+1?n.slice(0,o+1)+"."+n.slice(o+1):n+new Array(o-n.length+2).join("0")}var xo={"%":(t,e)=>(t*100).toFixed(e),b:t=>Math.round(t).toString(2),c:t=>t+"",d:Ns,e:(t,e)=>t.toExponential(e),f:(t,e)=>t.toFixed(e),g:(t,e)=>t.toPrecision(e),o:t=>Math.round(t).toString(8),p:(t,e)=>go(t*100,e),r:go,s:Fs,X:t=>Math.round(t).toString(16).toUpperCase(),x:t=>Math.round(t).toString(16)};function yo(t){return t}var Os=Array.prototype.map,Ps=["y","z","a","f","p","n","\xB5","m","","k","M","G","T","P","E","Z","Y"];function $s(t){var e=t.grouping===void 0||t.thousands===void 0?yo:Bs(Os.call(t.grouping,Number),t.thousands+""),r=t.currency===void 0?"":t.currency[0]+"",n=t.currency===void 0?"":t.currency[1]+"",o=t.decimal===void 0?".":t.decimal+"",i=t.numerals===void 0?yo:Rs(Os.call(t.numerals,String)),a=t.percent===void 0?"%":t.percent+"",s=t.minus===void 0?"\u2212":t.minus+"",u=t.nan===void 0?"NaN":t.nan+"";function c(p){p=St(p);var f=p.fill,m=p.align,d=p.sign,y=p.symbol,k=p.zero,_=p.width,q=p.comma,A=p.precision,D=p.trim,L=p.type;L==="n"?(q=!0,L="g"):xo[L]||(A===void 0&&(A=12),D=!0,L="g"),(k||f==="0"&&m==="=")&&(k=!0,f="0",m="=");var b=y==="$"?r:y==="#"&&/[boxX]/.test(L)?"0"+L.toLowerCase():"",N=y==="$"?n:/[%p]/.test(L)?a:"",H=xo[L],P=/[defgprs%]/.test(L);A=A===void 0?6:/[gprs]/.test(L)?Math.max(1,Math.min(21,A)):Math.max(0,Math.min(20,A));function I(h){var T=b,E=N,R,g,x;if(L==="c")E=H(h)+E,h="";else{h=+h;var M=h<0||1/h<0;if(h=isNaN(h)?u:H(Math.abs(h),A),D&&(h=Hs(h)),M&&+h==0&&d!=="+"&&(M=!1),T=(M?d==="("?d:s:d==="-"||d==="("?"":d)+T,E=(L==="s"?Ps[8+ho/3]:"")+E+(M&&d==="("?")":""),P){for(R=-1,g=h.length;++Rx||x>57){E=(x===46?o+h.slice(R+1):h.slice(R))+E,h=h.slice(0,R);break}}}q&&!k&&(h=e(h,1/0));var S=T.length+h.length+E.length,v=S<_?new Array(_-S+1).join(f):"";switch(q&&k&&(h=e(v+h,v.length?_-E.length:1/0),v=""),m){case"<":h=T+h+E+v;break;case"=":h=T+v+h+E;break;case"^":h=v.slice(0,S=v.length>>1)+T+h+E+v.slice(S);break;default:h=v+T+h+E;break}return i(h)}return I.toString=function(){return p+""},I}function l(p,f){var m=c((p=St(p),p.type="f",p)),d=Math.max(-8,Math.min(8,Math.floor(Mt(f)/3)))*3,y=Math.pow(10,-d),k=Ps[8+d/3];return function(_){return m(y*_)+k}}return{format:c,formatPrefix:l}}var mn,bt,dn;vo({thousands:",",grouping:[3],currency:["$",""]});function vo(t){return mn=$s(t),bt=mn.format,dn=mn.formatPrefix,mn}function bo(t){return Math.max(0,-Mt(Math.abs(t)))}function wo(t,e){return Math.max(0,Math.max(-8,Math.min(8,Math.floor(Mt(e)/3)))*3-Mt(Math.abs(t)))}function ko(t,e){return t=Math.abs(t),e=Math.abs(e)-t,Math.max(0,Mt(e)-Mt(t))+1}function Pp(t){var e=0,r=t.children,n=r&&r.length;if(!n)e=1;else for(;--n>=0;)e+=r[n].value;t.value=e}function Us(){return this.eachAfter(Pp)}function Gs(t,e){let r=-1;for(let n of this)t.call(e,n,++r,this);return this}function Vs(t,e){for(var r=this,n=[r],o,i,a=-1;r=n.pop();)if(t.call(e,r,++a,this),o=r.children)for(i=o.length-1;i>=0;--i)n.push(o[i]);return this}function zs(t,e){for(var r=this,n=[r],o=[],i,a,s,u=-1;r=n.pop();)if(o.push(r),i=r.children)for(a=0,s=i.length;a=0;)r+=n[o].value;e.value=r})}function Xs(t){return this.eachBefore(function(e){e.children&&e.children.sort(t)})}function js(t){for(var e=this,r=$p(e,t),n=[e];e!==r;)e=e.parent,n.push(e);for(var o=n.length;t!==r;)n.splice(o,0,t),t=t.parent;return n}function $p(t,e){if(t===e)return t;var r=t.ancestors(),n=e.ancestors(),o=null;for(t=r.pop(),e=n.pop();t===e;)o=t,t=r.pop(),e=n.pop();return o}function Zs(){for(var t=this,e=[t];t=t.parent;)e.push(t);return e}function Js(){return Array.from(this)}function Qs(){var t=[];return this.eachBefore(function(e){e.children||t.push(e)}),t}function Ks(){var t=this,e=[];return t.each(function(r){r!==t&&e.push({source:r.parent,target:r})}),e}function*tu(){var t=this,e,r=[t],n,o,i;do for(e=r.reverse(),r=[];t=e.pop();)if(yield t,n=t.children)for(o=0,i=n.length;o=0;--s)o.push(i=a[s]=new yr(a[s])),i.parent=n,i.depth=n.depth+1;return r.eachBefore(Yp)}function Up(){return Re(this).eachBefore(zp)}function Gp(t){return t.children}function Vp(t){return Array.isArray(t)?t[1]:null}function zp(t){t.data.value!==void 0&&(t.value=t.data.value),t.data=t.data.data}function Yp(t){var e=0;do t.height=e;while((t=t.parent)&&t.height<++e)}function yr(t){this.data=t,this.depth=this.height=0,this.parent=null}yr.prototype=Re.prototype={constructor:yr,count:Us,each:Gs,eachAfter:zs,eachBefore:Vs,find:Ys,sum:Ws,sort:Xs,path:js,ancestors:Zs,descendants:Js,leaves:Qs,links:Ks,copy:Up,[Symbol.iterator]:tu};function eu(t){if(typeof t!="function")throw new Error;return t}function He(){return 0}function Fe(t){return function(){return t}}function ru(t){t.x0=Math.round(t.x0),t.y0=Math.round(t.y0),t.x1=Math.round(t.x1),t.y1=Math.round(t.y1)}function nu(t,e,r,n,o){for(var i=t.children,a,s=-1,u=i.length,c=t.value&&(n-e)/t.value;++sq&&(q=c),b=k*k*L,A=Math.max(q/b,b/_),A>D){k-=c;break}D=A}a.push(u={value:k,dice:m1?n:1)},r}(Wp);function _o(){var t=vr,e=!1,r=1,n=1,o=[0],i=He,a=He,s=He,u=He,c=He;function l(f){return f.x0=f.y0=0,f.x1=r,f.y1=n,f.eachBefore(p),o=[0],e&&f.eachBefore(ru),f}function p(f){var m=o[f.depth],d=f.x0+m,y=f.y0+m,k=f.x1-m,_=f.y1-m;ke&&(r=t,t=e,e=r),function(n){return Math.max(t,Math.min(e,n))}}function Zp(t,e,r){var n=t[0],o=t[1],i=e[0],a=e[1];return o2?Jp:Zp,u=c=null,p}function p(f){return f==null||isNaN(f=+f)?i:(u||(u=s(t.map(n),e,r)))(n(a(f)))}return p.invert=function(f){return a(o((c||(c=s(e,t.map(n),Z)))(f)))},p.domain=function(f){return arguments.length?(t=Array.from(f,So),l()):t.slice()},p.range=function(f){return arguments.length?(e=Array.from(f),l()):e.slice()},p.rangeRound=function(f){return e=Array.from(f),r=ur,l()},p.clamp=function(f){return arguments.length?(a=f?!0:Ut,l()):a!==Ut},p.interpolate=function(f){return arguments.length?(r=f,l()):r},p.unknown=function(f){return arguments.length?(i=f,p):i},function(f,m){return n=f,o=m,l()}}function wr(){return Qp()(Ut,Ut)}function Eo(t,e,r,n){var o=Le(t,e,r),i;switch(n=St(n??",f"),n.type){case"s":{var a=Math.max(Math.abs(t),Math.abs(e));return n.precision==null&&!isNaN(i=wo(o,a))&&(n.precision=i),dn(n,a)}case"":case"e":case"g":case"p":case"r":{n.precision==null&&!isNaN(i=ko(o,Math.max(Math.abs(t),Math.abs(e))))&&(n.precision=i-(n.type==="e"));break}case"f":case"%":{n.precision==null&&!isNaN(i=bo(o))&&(n.precision=i-(n.type==="%")*2);break}}return bt(n)}function Kp(t){var e=t.domain;return t.ticks=function(r){var n=e();return te(n[0],n[n.length-1],r??10)},t.tickFormat=function(r,n){var o=e();return Eo(o[0],o[o.length-1],r??10,n)},t.nice=function(r){r==null&&(r=10);var n=e(),o=0,i=n.length-1,a=n[o],s=n[i],u,c,l=10;for(s0;){if(c=Qe(a,s,r),c===u)return n[o]=a,n[i]=s,e(n);if(c>0)a=Math.floor(a/c)*c,s=Math.ceil(s/c)*c;else if(c<0)a=Math.ceil(a*c)/c,s=Math.floor(s*c)/c;else break;u=c}return t},t}function de(){var t=wr();return t.copy=function(){return hn(t,de())},$t.apply(t,arguments),Kp(t)}function kr(t,e){t=t.slice();var r=0,n=t.length-1,o=t[r],i=t[n],a;return iMath.pow(t,e)}function om(t){return t===Math.E?Math.log:t===10&&Math.log10||t===2&&Math.log2||(t=Math.log(t),e=>Math.log(e)/t)}function lu(t){return(e,r)=>-t(-e,r)}function cu(t){let e=t(su,uu),r=e.domain,n=10,o,i;function a(){return o=om(n),i=nm(n),r()[0]<0?(o=lu(o),i=lu(i),t(tm,em)):t(su,uu),e}return e.base=function(s){return arguments.length?(n=+s,a()):n},e.domain=function(s){return arguments.length?(r(s),a()):r()},e.ticks=s=>{let u=r(),c=u[0],l=u[u.length-1],p=l0){for(;f<=m;++f)for(d=1;dl)break;_.push(y)}}else for(;f<=m;++f)for(d=n-1;d>=1;--d)if(y=f>0?d/i(-f):d*i(f),!(yl)break;_.push(y)}_.length*2{if(s==null&&(s=10),u==null&&(u=n===10?"s":","),typeof u!="function"&&(!(n%1)&&(u=St(u)).precision==null&&(u.trim=!0),u=bt(u)),s===1/0)return u;let c=Math.max(1,n*s/e.ticks().length);return l=>{let p=l/i(Math.round(o(l)));return p*nr(kr(r(),{floor:s=>i(Math.floor(o(s))),ceil:s=>i(Math.ceil(o(s)))})),e}var Ao=new Date,Do=new Date;function z(t,e,r,n){function o(i){return t(i=arguments.length===0?new Date:new Date(+i)),i}return o.floor=i=>(t(i=new Date(+i)),i),o.ceil=i=>(t(i=new Date(i-1)),e(i,1),t(i),i),o.round=i=>{let a=o(i),s=o.ceil(i);return i-a(e(i=new Date(+i),a==null?1:Math.floor(a)),i),o.range=(i,a,s)=>{let u=[];if(i=o.ceil(i),s=s==null?1:Math.floor(s),!(i0))return u;let c;do u.push(c=new Date(+i)),e(i,s),t(i);while(cz(a=>{if(a>=a)for(;t(a),!i(a);)a.setTime(a-1)},(a,s)=>{if(a>=a)if(s<0)for(;++s<=0;)for(;e(a,-1),!i(a););else for(;--s>=0;)for(;e(a,1),!i(a););}),r&&(o.count=(i,a)=>(Ao.setTime(+i),Do.setTime(+a),t(Ao),t(Do),Math.floor(r(Ao,Do))),o.every=i=>(i=Math.floor(i),!isFinite(i)||!(i>0)?null:i>1?o.filter(n?a=>n(a)%i===0:a=>o.count(0,a)%i===0):o)),o}var _r=z(()=>{},(t,e)=>{t.setTime(+t+e)},(t,e)=>e-t);_r.every=t=>(t=Math.floor(t),!isFinite(t)||!(t>0)?null:t>1?z(e=>{e.setTime(Math.floor(e/t)*t)},(e,r)=>{e.setTime(+e+r*t)},(e,r)=>(r-e)/t):_r);var m_=_r.range;var Lt=z(t=>{t.setTime(t-t.getMilliseconds())},(t,e)=>{t.setTime(+t+e*1e3)},(t,e)=>(e-t)/1e3,t=>t.getUTCSeconds()),fu=Lt.range;var Oe=z(t=>{t.setTime(t-t.getMilliseconds()-t.getSeconds()*1e3)},(t,e)=>{t.setTime(+t+e*6e4)},(t,e)=>(e-t)/6e4,t=>t.getMinutes()),im=Oe.range,gn=z(t=>{t.setUTCSeconds(0,0)},(t,e)=>{t.setTime(+t+e*6e4)},(t,e)=>(e-t)/6e4,t=>t.getUTCMinutes()),am=gn.range;var Pe=z(t=>{t.setTime(t-t.getMilliseconds()-t.getSeconds()*1e3-t.getMinutes()*6e4)},(t,e)=>{t.setTime(+t+e*36e5)},(t,e)=>(e-t)/36e5,t=>t.getHours()),sm=Pe.range,xn=z(t=>{t.setUTCMinutes(0,0,0)},(t,e)=>{t.setTime(+t+e*36e5)},(t,e)=>(e-t)/36e5,t=>t.getUTCHours()),um=xn.range;var Rt=z(t=>t.setHours(0,0,0,0),(t,e)=>t.setDate(t.getDate()+e),(t,e)=>(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*6e4)/864e5,t=>t.getDate()-1),lm=Rt.range,Mr=z(t=>{t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+e)},(t,e)=>(e-t)/864e5,t=>t.getUTCDate()-1),cm=Mr.range,yn=z(t=>{t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+e)},(t,e)=>(e-t)/864e5,t=>Math.floor(t/864e5)),fm=yn.range;function xe(t){return z(e=>{e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)},(e,r)=>{e.setDate(e.getDate()+r*7)},(e,r)=>(r-e-(r.getTimezoneOffset()-e.getTimezoneOffset())*6e4)/6048e5)}var Ht=xe(0),$e=xe(1),mu=xe(2),du=xe(3),Gt=xe(4),hu=xe(5),gu=xe(6),xu=Ht.range,pm=$e.range,mm=mu.range,dm=du.range,hm=Gt.range,gm=hu.range,xm=gu.range;function ye(t){return z(e=>{e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)},(e,r)=>{e.setUTCDate(e.getUTCDate()+r*7)},(e,r)=>(r-e)/6048e5)}var ve=ye(0),Ue=ye(1),yu=ye(2),vu=ye(3),Vt=ye(4),bu=ye(5),wu=ye(6),ku=ve.range,ym=Ue.range,vm=yu.range,bm=vu.range,wm=Vt.range,km=bu.range,_m=wu.range;var Ge=z(t=>{t.setDate(1),t.setHours(0,0,0,0)},(t,e)=>{t.setMonth(t.getMonth()+e)},(t,e)=>e.getMonth()-t.getMonth()+(e.getFullYear()-t.getFullYear())*12,t=>t.getMonth()),Tm=Ge.range,vn=z(t=>{t.setUTCDate(1),t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCMonth(t.getUTCMonth()+e)},(t,e)=>e.getUTCMonth()-t.getUTCMonth()+(e.getUTCFullYear()-t.getUTCFullYear())*12,t=>t.getUTCMonth()),Mm=vn.range;var pt=z(t=>{t.setMonth(0,1),t.setHours(0,0,0,0)},(t,e)=>{t.setFullYear(t.getFullYear()+e)},(t,e)=>e.getFullYear()-t.getFullYear(),t=>t.getFullYear());pt.every=t=>!isFinite(t=Math.floor(t))||!(t>0)?null:z(e=>{e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)},(e,r)=>{e.setFullYear(e.getFullYear()+r*t)});var Sm=pt.range,wt=z(t=>{t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCFullYear(t.getUTCFullYear()+e)},(t,e)=>e.getUTCFullYear()-t.getUTCFullYear(),t=>t.getUTCFullYear());wt.every=t=>!isFinite(t=Math.floor(t))||!(t>0)?null:z(e=>{e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)},(e,r)=>{e.setUTCFullYear(e.getUTCFullYear()+r*t)});var Lm=wt.range;function Tu(t,e,r,n,o,i){let a=[[Lt,1,1e3],[Lt,5,5*1e3],[Lt,15,15*1e3],[Lt,30,30*1e3],[i,1,6e4],[i,5,5*6e4],[i,15,15*6e4],[i,30,30*6e4],[o,1,36e5],[o,3,3*36e5],[o,6,6*36e5],[o,12,12*36e5],[n,1,864e5],[n,2,2*864e5],[r,1,6048e5],[e,1,2592e6],[e,3,3*2592e6],[t,1,31536e6]];function s(c,l,p){let f=lk).right(a,f);if(m===a.length)return t.every(Le(c/31536e6,l/31536e6,p));if(m===0)return _r.every(Math.max(Le(c,l,p),1));let[d,y]=a[f/a[m-1][2]53)return null;"w"in w||(w.w=1),"Z"in w?(Y=No(Sr(w.y,0,1)),lt=Y.getUTCDay(),Y=lt>4||lt===0?Ue.ceil(Y):Ue(Y),Y=Mr.offset(Y,(w.V-1)*7),w.y=Y.getUTCFullYear(),w.m=Y.getUTCMonth(),w.d=Y.getUTCDate()+(w.w+6)%7):(Y=Io(Sr(w.y,0,1)),lt=Y.getDay(),Y=lt>4||lt===0?$e.ceil(Y):$e(Y),Y=Rt.offset(Y,(w.V-1)*7),w.y=Y.getFullYear(),w.m=Y.getMonth(),w.d=Y.getDate()+(w.w+6)%7)}else("W"in w||"U"in w)&&("w"in w||(w.w="u"in w?w.u%7:"W"in w?1:0),lt="Z"in w?No(Sr(w.y,0,1)).getUTCDay():Io(Sr(w.y,0,1)).getDay(),w.m=0,w.d="W"in w?(w.w+6)%7+w.W*7-(lt+5)%7:w.w+w.U*7-(lt+6)%7);return"Z"in w?(w.H+=w.Z/100|0,w.M+=w.Z%100,No(w)):Io(w)}}function H(C,F,U,w){for(var st=0,Y=F.length,lt=U.length,ct,Qt;st=lt)return-1;if(ct=F.charCodeAt(st++),ct===37){if(ct=F.charAt(st++),Qt=L[ct in Mu?F.charAt(st++):ct],!Qt||(w=Qt(C,U,w))<0)return-1}else if(ct!=U.charCodeAt(w++))return-1}return w}function P(C,F,U){var w=c.exec(F.slice(U));return w?(C.p=l.get(w[0].toLowerCase()),U+w[0].length):-1}function I(C,F,U){var w=m.exec(F.slice(U));return w?(C.w=d.get(w[0].toLowerCase()),U+w[0].length):-1}function h(C,F,U){var w=p.exec(F.slice(U));return w?(C.w=f.get(w[0].toLowerCase()),U+w[0].length):-1}function T(C,F,U){var w=_.exec(F.slice(U));return w?(C.m=q.get(w[0].toLowerCase()),U+w[0].length):-1}function E(C,F,U){var w=y.exec(F.slice(U));return w?(C.m=k.get(w[0].toLowerCase()),U+w[0].length):-1}function R(C,F,U){return H(C,e,F,U)}function g(C,F,U){return H(C,r,F,U)}function x(C,F,U){return H(C,n,F,U)}function M(C){return a[C.getDay()]}function S(C){return i[C.getDay()]}function v(C){return u[C.getMonth()]}function B(C){return s[C.getMonth()]}function G(C){return o[+(C.getHours()>=12)]}function K(C){return 1+~~(C.getMonth()/3)}function ot(C){return a[C.getUTCDay()]}function ht(C){return i[C.getUTCDay()]}function tt(C){return u[C.getUTCMonth()]}function Dt(C){return s[C.getUTCMonth()]}function gt(C){return o[+(C.getUTCHours()>=12)]}function Je(C){return 1+~~(C.getUTCMonth()/3)}return{format:function(C){var F=b(C+="",A);return F.toString=function(){return C},F},parse:function(C){var F=N(C+="",!1);return F.toString=function(){return C},F},utcFormat:function(C){var F=b(C+="",D);return F.toString=function(){return C},F},utcParse:function(C){var F=N(C+="",!0);return F.toString=function(){return C},F}}}var Mu={"-":"",_:" ",0:"0"},Q=/^\s*\d+/,Cm=/^%/,qm=/[\\^$*+?|[\]().{}]/g;function V(t,e,r){var n=t<0?"-":"",o=(n?-t:t)+"",i=o.length;return n+(i[e.toLowerCase(),r]))}function Nm(t,e,r){var n=Q.exec(e.slice(r,r+1));return n?(t.w=+n[0],r+n[0].length):-1}function Bm(t,e,r){var n=Q.exec(e.slice(r,r+1));return n?(t.u=+n[0],r+n[0].length):-1}function Rm(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.U=+n[0],r+n[0].length):-1}function Hm(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.V=+n[0],r+n[0].length):-1}function Fm(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.W=+n[0],r+n[0].length):-1}function Su(t,e,r){var n=Q.exec(e.slice(r,r+4));return n?(t.y=+n[0],r+n[0].length):-1}function Lu(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.y=+n[0]+(+n[0]>68?1900:2e3),r+n[0].length):-1}function Om(t,e,r){var n=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(r,r+6));return n?(t.Z=n[1]?0:-(n[2]+(n[3]||"00")),r+n[0].length):-1}function Pm(t,e,r){var n=Q.exec(e.slice(r,r+1));return n?(t.q=n[0]*3-3,r+n[0].length):-1}function $m(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.m=n[0]-1,r+n[0].length):-1}function Eu(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.d=+n[0],r+n[0].length):-1}function Um(t,e,r){var n=Q.exec(e.slice(r,r+3));return n?(t.m=0,t.d=+n[0],r+n[0].length):-1}function Au(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.H=+n[0],r+n[0].length):-1}function Gm(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.M=+n[0],r+n[0].length):-1}function Vm(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.S=+n[0],r+n[0].length):-1}function zm(t,e,r){var n=Q.exec(e.slice(r,r+3));return n?(t.L=+n[0],r+n[0].length):-1}function Ym(t,e,r){var n=Q.exec(e.slice(r,r+6));return n?(t.L=Math.floor(n[0]/1e3),r+n[0].length):-1}function Wm(t,e,r){var n=Cm.exec(e.slice(r,r+1));return n?r+n[0].length:-1}function Xm(t,e,r){var n=Q.exec(e.slice(r));return n?(t.Q=+n[0],r+n[0].length):-1}function jm(t,e,r){var n=Q.exec(e.slice(r));return n?(t.s=+n[0],r+n[0].length):-1}function Du(t,e){return V(t.getDate(),e,2)}function Zm(t,e){return V(t.getHours(),e,2)}function Jm(t,e){return V(t.getHours()%12||12,e,2)}function Qm(t,e){return V(1+Rt.count(pt(t),t),e,3)}function Bu(t,e){return V(t.getMilliseconds(),e,3)}function Km(t,e){return Bu(t,e)+"000"}function td(t,e){return V(t.getMonth()+1,e,2)}function ed(t,e){return V(t.getMinutes(),e,2)}function rd(t,e){return V(t.getSeconds(),e,2)}function nd(t){var e=t.getDay();return e===0?7:e}function od(t,e){return V(Ht.count(pt(t)-1,t),e,2)}function Ru(t){var e=t.getDay();return e>=4||e===0?Gt(t):Gt.ceil(t)}function id(t,e){return t=Ru(t),V(Gt.count(pt(t),t)+(pt(t).getDay()===4),e,2)}function ad(t){return t.getDay()}function sd(t,e){return V($e.count(pt(t)-1,t),e,2)}function ud(t,e){return V(t.getFullYear()%100,e,2)}function ld(t,e){return t=Ru(t),V(t.getFullYear()%100,e,2)}function cd(t,e){return V(t.getFullYear()%1e4,e,4)}function fd(t,e){var r=t.getDay();return t=r>=4||r===0?Gt(t):Gt.ceil(t),V(t.getFullYear()%1e4,e,4)}function pd(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+V(e/60|0,"0",2)+V(e%60,"0",2)}function Cu(t,e){return V(t.getUTCDate(),e,2)}function md(t,e){return V(t.getUTCHours(),e,2)}function dd(t,e){return V(t.getUTCHours()%12||12,e,2)}function hd(t,e){return V(1+Mr.count(wt(t),t),e,3)}function Hu(t,e){return V(t.getUTCMilliseconds(),e,3)}function gd(t,e){return Hu(t,e)+"000"}function xd(t,e){return V(t.getUTCMonth()+1,e,2)}function yd(t,e){return V(t.getUTCMinutes(),e,2)}function vd(t,e){return V(t.getUTCSeconds(),e,2)}function bd(t){var e=t.getUTCDay();return e===0?7:e}function wd(t,e){return V(ve.count(wt(t)-1,t),e,2)}function Fu(t){var e=t.getUTCDay();return e>=4||e===0?Vt(t):Vt.ceil(t)}function kd(t,e){return t=Fu(t),V(Vt.count(wt(t),t)+(wt(t).getUTCDay()===4),e,2)}function _d(t){return t.getUTCDay()}function Td(t,e){return V(Ue.count(wt(t)-1,t),e,2)}function Md(t,e){return V(t.getUTCFullYear()%100,e,2)}function Sd(t,e){return t=Fu(t),V(t.getUTCFullYear()%100,e,2)}function Ld(t,e){return V(t.getUTCFullYear()%1e4,e,4)}function Ed(t,e){var r=t.getUTCDay();return t=r>=4||r===0?Vt(t):Vt.ceil(t),V(t.getUTCFullYear()%1e4,e,4)}function Ad(){return"+0000"}function qu(){return"%"}function Iu(t){return+t}function Nu(t){return Math.floor(+t/1e3)}var Ve,bn,Ou,Pu,$u;Ro({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});function Ro(t){return Ve=Bo(t),bn=Ve.format,Ou=Ve.parse,Pu=Ve.utcFormat,$u=Ve.utcParse,Ve}function Dd(t){return new Date(t)}function Cd(t){return t instanceof Date?+t:+new Date(+t)}function Uu(t,e,r,n,o,i,a,s,u,c){var l=wr(),p=l.invert,f=l.domain,m=c(".%L"),d=c(":%S"),y=c("%I:%M"),k=c("%I %p"),_=c("%a %d"),q=c("%b %d"),A=c("%B"),D=c("%Y");function L(b){return(u(b)1?0:t<-1?ze:Math.acos(t)}function Po(t){return t>=1?Ar:t<=-1?-Ar:Math.asin(t)}function Tn(t){let e=3;return t.digits=function(r){if(!arguments.length)return e;if(r==null)e=null;else{let n=Math.floor(r);if(!(n>=0))throw new RangeError(`invalid digits: ${r}`);e=n}return t},()=>new fe(e)}function Bd(t){return t.innerRadius}function Rd(t){return t.outerRadius}function Hd(t){return t.startAngle}function Fd(t){return t.endAngle}function Od(t){return t&&t.padAngle}function Pd(t,e,r,n,o,i,a,s){var u=r-t,c=n-e,l=a-o,p=s-i,f=p*u-l*c;if(!(f*fR*R+g*g&&(H=I,P=h),{cx:H,cy:P,x01:-l,y01:-p,x11:H*(o/L-1),y11:P*(o/L-1)}}function $o(){var t=Bd,e=Rd,r=W(0),n=null,o=Hd,i=Fd,a=Od,s=null,u=Tn(c);function c(){var l,p,f=+t.apply(this,arguments),m=+e.apply(this,arguments),d=o.apply(this,arguments)-Ar,y=i.apply(this,arguments)-Ar,k=Oo(y-d),_=y>d;if(s||(s=l=u()),mat))s.moveTo(0,0);else if(k>Ye-at)s.moveTo(m*zt(d),m*kt(d)),s.arc(0,0,m,d,y,!_),f>at&&(s.moveTo(f*zt(y),f*kt(y)),s.arc(0,0,f,y,d,_));else{var q=d,A=y,D=d,L=y,b=k,N=k,H=a.apply(this,arguments)/2,P=H>at&&(n?+n.apply(this,arguments):be(f*f+m*m)),I=_n(Oo(m-f)/2,+r.apply(this,arguments)),h=I,T=I,E,R;if(P>at){var g=Po(P/f*kt(H)),x=Po(P/m*kt(H));(b-=g*2)>at?(g*=_?1:-1,D+=g,L-=g):(b=0,D=L=(d+y)/2),(N-=x*2)>at?(x*=_?1:-1,q+=x,A-=x):(N=0,q=A=(d+y)/2)}var M=m*zt(q),S=m*kt(q),v=f*zt(L),B=f*kt(L);if(I>at){var G=m*zt(A),K=m*kt(A),ot=f*zt(D),ht=f*kt(D),tt;if(kat?T>at?(E=Mn(ot,ht,M,S,m,T,_),R=Mn(G,K,v,B,m,T,_),s.moveTo(E.cx+E.x01,E.cy+E.y01),Tat)||!(b>at)?s.lineTo(v,B):h>at?(E=Mn(v,B,G,K,f,-h,_),R=Mn(M,S,ot,ht,f,-h,_),s.lineTo(E.cx+E.x01,E.cy+E.y01),ht?1:e>=t?0:NaN}function Ju(t){return t}function Uo(){var t=Ju,e=Zu,r=null,n=W(0),o=W(Ye),i=W(0);function a(s){var u,c=(s=Sn(s)).length,l,p,f=0,m=new Array(c),d=new Array(c),y=+n.apply(this,arguments),k=Math.min(Ye,Math.max(-Ye,o.apply(this,arguments)-y)),_,q=Math.min(Math.abs(k)/c,i.apply(this,arguments)),A=q*(k<0?-1:1),D;for(u=0;u0&&(f+=D);for(e!=null?m.sort(function(L,b){return e(d[L],d[b])}):r!=null&&m.sort(function(L,b){return r(s[L],s[b])}),u=0,p=f?(k-c*A)/f:0;u0?D*p:0)+A,d[l]={data:s[l],index:u,value:D,startAngle:y,endAngle:_,padAngle:q};return d}return a.value=function(s){return arguments.length?(t=typeof s=="function"?s:W(+s),a):t},a.sortValues=function(s){return arguments.length?(e=s,r=null,a):e},a.sort=function(s){return arguments.length?(r=s,e=null,a):r},a.startAngle=function(s){return arguments.length?(n=typeof s=="function"?s:W(+s),a):n},a.endAngle=function(s){return arguments.length?(o=typeof s=="function"?s:W(+s),a):o},a.padAngle=function(s){return arguments.length?(i=typeof s=="function"?s:W(+s),a):i},a}function Qu(t){return t<0?-1:1}function Ku(t,e,r){var n=t._x1-t._x0,o=e-t._x1,i=(t._y1-t._y0)/(n||o<0&&-0),a=(r-t._y1)/(o||n<0&&-0),s=(i*o+a*n)/(n+o);return(Qu(i)+Qu(a))*Math.min(Math.abs(i),Math.abs(a),.5*Math.abs(s))||0}function tl(t,e){var r=t._x1-t._x0;return r?(3*(t._y1-t._y0)/r-e)/2:e}function Go(t,e,r){var n=t._x0,o=t._y0,i=t._x1,a=t._y1,s=(i-n)/3;t._context.bezierCurveTo(n+s,o+s*e,i-s,a-s*r,i,a)}function Ln(t){this._context=t}Ln.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=this._t0=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x1,this._y1);break;case 3:Go(this,this._t0,tl(this,this._t0));break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){var r=NaN;if(t=+t,e=+e,!(t===this._x1&&e===this._y1)){switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,Go(this,tl(this,r=Ku(this,t,e)),r);break;default:Go(this,this._t0,r=Ku(this,t,e));break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e,this._t0=r}}};function $d(t){this._context=new el(t)}($d.prototype=Object.create(Ln.prototype)).point=function(t,e){Ln.prototype.point.call(this,e,t)};function el(t){this._context=t}el.prototype={moveTo:function(t,e){this._context.moveTo(e,t)},closePath:function(){this._context.closePath()},lineTo:function(t,e){this._context.lineTo(e,t)},bezierCurveTo:function(t,e,r,n,o,i){this._context.bezierCurveTo(e,t,n,r,i,o)}};function En(t){return new Ln(t)}function Yt(t,e,r){this.k=t,this.x=e,this.y=r}Yt.prototype={constructor:Yt,scale:function(t){return t===1?this:new Yt(this.k*t,this.x,this.y)},translate:function(t,e){return t===0&e===0?this:new Yt(this.k,this.x+this.k*t,this.y+this.k*e)},apply:function(t){return[t[0]*this.k+this.x,t[1]*this.k+this.y]},applyX:function(t){return t*this.k+this.x},applyY:function(t){return t*this.k+this.y},invert:function(t){return[(t[0]-this.x)/this.k,(t[1]-this.y)/this.k]},invertX:function(t){return(t-this.x)/this.k},invertY:function(t){return(t-this.y)/this.k},rescaleX:function(t){return t.copy().domain(t.range().map(this.invertX,this).map(t.invert,t))},rescaleY:function(t){return t.copy().domain(t.range().map(this.invertY,this).map(t.invert,t))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var Vo=new Yt(1,0,0);zo.prototype=Yt.prototype;function zo(t){for(;!t.__zoom;)if(!(t=t.parentNode))return Vo;return t.__zoom}var dt=bt(",.0f"),Wt=bt(",.1f");function Wo(t){function e(c){return c.toString().padStart(2,"0")}let r=Math.floor(t/1e3),n=Math.floor(r/60),o=Math.floor(n/60),i=Math.floor(o/24),a=o%24,s=n%60,u=r%60;return i===0&&a===0&&s===0&&u===0?"0s":i>0?`${i}d ${e(a)}h ${e(s)}m ${e(u)}s`:a>0?`${a}h ${e(s)}m ${e(u)}s`:s>0?`${s}m ${e(u)}s`:`${u}s`}var Ud=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function nl(t){let e=new Date(t*1e3),r=String(e.getDate()).padStart(2,"0"),n=Ud[e.getMonth()],o=e.getFullYear(),i=String(e.getHours()).padStart(2,"0"),a=String(e.getMinutes()).padStart(2,"0"),s=String(e.getSeconds()).padStart(2,"0");return`${r} ${n} ${o} ${i}:${a}:${s}`}var Yo=["bytes","kB","MB","GB","TB","PB"];function ol(t){if(!Number.isFinite(t)||t<0)return"0 bytes";let e=0,r=t;for(;r>=1e3&&eGd(a.charCodeAt(0))).length>=o.length/2?`0x${e}`:o}function Gd(t){return t<32||t>=127&&t<160}function Vd(t){if(t.length===0||t.length%2!==0)return new Uint8Array;let e=new Uint8Array(t.length/2);for(let r=0;r=1e14){let o=t/1e18;n=`${rl(o,4)} ${ll}`}else if(t>=1e5){let o=t/1e9;n=`${rl(o,4)} gwei`}else n=`${t} wei`;return n}function An(t){return!t.startsWith("0x")||t.length<14?t:`${t.slice(0,8)}...${t.slice(-6)}`}function Xt(t,e,r=80,n=44,o=60){let i=e[e.length-1],a=i.t-o*1e3;e=e.filter(A=>A.t>=a);let s={top:2,right:2,bottom:2,left:2},u=r-s.left-s.right,c=n-s.top-s.bottom,l=j(t).select("svg");l.empty()&&(l=j(t).append("svg").attr("width",r).attr("height",n),l.append("g").attr("class","line-group").attr("transform",`translate(${s.left},${s.top})`).append("path").attr("class","sparkline-path").attr("fill","none").attr("stroke","#00bff2").attr("stroke-width",1.5),l.append("g").attr("class","y-axis").attr("transform",`translate(${s.left},${s.top})`)),l.attr("width",r).attr("height",n);let f=l.select("g.line-group").select("path.sparkline-path"),m=i.t,d=wn().domain([new Date(a),new Date(m)]).range([0,u]),[y,k]=Me(e,A=>A.v),_=de().domain([y,k]).range([c,0]).nice(),q=We().x(A=>d(new Date(A.t))).y(A=>_(A.v));if(f.datum(e).attr("d",q).attr("transform",null),e.length>1){let A=u/o;f.attr("transform",`translate(${A},0)`),f.transition().duration(300).attr("transform","translate(0,0)")}}function Cr(t,e){let r;if(e===void 0)for(let n of t)n!=null&&(r=n)&&(r=n);else{let n=-1;for(let o of t)(o=e(o,++n,t))!=null&&(r=o)&&(r=o)}return r}function Xe(t,e){let r;if(e===void 0)for(let n of t)n!=null&&(r>n||r===void 0&&n>=n)&&(r=n);else{let n=-1;for(let o of t)(o=e(o,++n,t))!=null&&(r>o||r===void 0&&o>=o)&&(r=o)}return r}function je(t,e){let r=0;if(e===void 0)for(let n of t)(n=+n)&&(r+=n);else{let n=-1;for(let o of t)(o=+e(o,++n,t))&&(r+=o)}return r}function Xd(t){return t.target.depth}function Xo(t,e){return t.sourceLinks.length?t.depth:e-1}function jo(t){return t.targetLinks.length?t.depth:t.sourceLinks.length?Xe(t.sourceLinks,Xd)-1:0}function Ze(t){return function(){return t}}function fl(t,e){return Dn(t.source,e.source)||t.index-e.index}function pl(t,e){return Dn(t.target,e.target)||t.index-e.index}function Dn(t,e){return t.y0-e.y0}function Zo(t){return t.value}function jd(t){return t.index}function Zd(t){return t.nodes}function Jd(t){return t.links}function ml(t,e){let r=t.get(e);if(!r)throw new Error("missing: "+e);return r}function dl({nodes:t}){for(let e of t){let r=e.y0,n=r;for(let o of e.sourceLinks)o.y0=r+o.width/2,r+=o.width;for(let o of e.targetLinks)o.y1=n+o.width/2,n+=o.width}}function Cn(){let t=0,e=0,r=1,n=1,o=24,i=8,a,s=jd,u=Xo,c,l,p=Zd,f=Jd,m=6;function d(){let g={nodes:p.apply(null,arguments),links:f.apply(null,arguments)};return y(g),k(g),_(g),q(g),L(g),dl(g),g}d.update=function(g){return dl(g),g},d.nodeId=function(g){return arguments.length?(s=typeof g=="function"?g:Ze(g),d):s},d.nodeAlign=function(g){return arguments.length?(u=typeof g=="function"?g:Ze(g),d):u},d.nodeSort=function(g){return arguments.length?(c=g,d):c},d.nodeWidth=function(g){return arguments.length?(o=+g,d):o},d.nodePadding=function(g){return arguments.length?(i=a=+g,d):i},d.nodes=function(g){return arguments.length?(p=typeof g=="function"?g:Ze(g),d):p},d.links=function(g){return arguments.length?(f=typeof g=="function"?g:Ze(g),d):f},d.linkSort=function(g){return arguments.length?(l=g,d):l},d.size=function(g){return arguments.length?(t=e=0,r=+g[0],n=+g[1],d):[r-t,n-e]},d.extent=function(g){return arguments.length?(t=+g[0][0],r=+g[1][0],e=+g[0][1],n=+g[1][1],d):[[t,e],[r,n]]},d.iterations=function(g){return arguments.length?(m=+g,d):m};function y({nodes:g,links:x}){for(let[S,v]of g.entries())v.index=S,v.sourceLinks=[],v.targetLinks=[];let M=new Map(g.map((S,v)=>[s(S,v,g),S]));for(let[S,v]of x.entries()){v.index=S;let{source:B,target:G}=v;typeof B!="object"&&(B=v.source=ml(M,B)),typeof G!="object"&&(G=v.target=ml(M,G)),B.sourceLinks.push(v),G.targetLinks.push(v)}if(l!=null)for(let{sourceLinks:S,targetLinks:v}of g)S.sort(l),v.sort(l)}function k({nodes:g}){for(let x of g)x.value=x.fixedValue===void 0?Math.max(je(x.sourceLinks,Zo),je(x.targetLinks,Zo)):x.fixedValue}function _({nodes:g}){let x=g.length,M=new Set(g),S=new Set,v=0;for(;M.size;){for(let B of M){B.depth=v;for(let{target:G}of B.sourceLinks)S.add(G)}if(++v>x)throw new Error("circular link");M=S,S=new Set}}function q({nodes:g}){let x=g.length,M=new Set(g),S=new Set,v=0;for(;M.size;){for(let B of M){B.height=v;for(let{source:G}of B.targetLinks)S.add(G)}if(++v>x)throw new Error("circular link");M=S,S=new Set}}function A({nodes:g}){let x=Cr(g,v=>v.depth)+1,M=(r-t-o)/(x-1),S=new Array(x);for(let v of g){let B=Math.max(0,Math.min(x-1,Math.floor(u.call(null,v,x))));v.layer=B,v.x0=t+B*M,v.x1=v.x0+o,S[B]?S[B].push(v):S[B]=[v]}if(c)for(let v of S)v.sort(c);return S}function D(g){let x=Xe(g,M=>(n-e-(M.length-1)*a)/je(M,Zo));for(let M of g){let S=e;for(let v of M){v.y0=S,v.y1=S+v.value*x,S=v.y1+a;for(let B of v.sourceLinks)B.width=B.value*x}S=(n-S+a)/(M.length+1);for(let v=0;vM.length)-1)),D(x);for(let M=0;M0))continue;let ht=(K/ot-G.y0)*x;G.y0+=ht,G.y1+=ht,h(G)}c===void 0&&B.sort(Dn),H(B,M)}}function N(g,x,M){for(let S=g.length,v=S-2;v>=0;--v){let B=g[v];for(let G of B){let K=0,ot=0;for(let{target:tt,value:Dt}of G.sourceLinks){let gt=Dt*(tt.layer-G.layer);K+=R(G,tt)*gt,ot+=gt}if(!(ot>0))continue;let ht=(K/ot-G.y0)*x;G.y0+=ht,G.y1+=ht,h(G)}c===void 0&&B.sort(Dn),H(B,M)}}function H(g,x){let M=g.length>>1,S=g[M];I(g,S.y0-a,M-1,x),P(g,S.y1+a,M+1,x),I(g,n,g.length-1,x),P(g,e,0,x)}function P(g,x,M,S){for(;M1e-6&&(v.y0+=B,v.y1+=B),x=v.y1+a}}function I(g,x,M,S){for(;M>=0;--M){let v=g[M],B=(v.y1-x)*S;B>1e-6&&(v.y0-=B,v.y1-=B),x=v.y0-a}}function h({sourceLinks:g,targetLinks:x}){if(l===void 0){for(let{source:{sourceLinks:M}}of x)M.sort(pl);for(let{target:{targetLinks:M}}of g)M.sort(fl)}}function T(g){if(l===void 0)for(let{sourceLinks:x,targetLinks:M}of g)x.sort(pl),M.sort(fl)}function E(g,x){let M=g.y0-(g.sourceLinks.length-1)*a/2;for(let{target:S,width:v}of g.sourceLinks){if(S===x)break;M+=v+a}for(let{source:S,width:v}of x.targetLinks){if(S===g)break;M-=v}return M}function R(g,x){let M=x.y0-(x.targetLinks.length-1)*a/2;for(let{source:S,width:v}of x.targetLinks){if(S===g)break;M+=v+a}for(let{target:S,width:v}of g.sourceLinks){if(S===x)break;M-=v}return M}return d}var Jo=Math.PI,Qo=2*Jo,ke=1e-6,Qd=Qo-ke;function Ko(){this._x0=this._y0=this._x1=this._y1=null,this._=""}function hl(){return new Ko}Ko.prototype=hl.prototype={constructor:Ko,moveTo:function(t,e){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+e)},closePath:function(){this._x1!==null&&(this._x1=this._x0,this._y1=this._y0,this._+="Z")},lineTo:function(t,e){this._+="L"+(this._x1=+t)+","+(this._y1=+e)},quadraticCurveTo:function(t,e,r,n){this._+="Q"+ +t+","+ +e+","+(this._x1=+r)+","+(this._y1=+n)},bezierCurveTo:function(t,e,r,n,o,i){this._+="C"+ +t+","+ +e+","+ +r+","+ +n+","+(this._x1=+o)+","+(this._y1=+i)},arcTo:function(t,e,r,n,o){t=+t,e=+e,r=+r,n=+n,o=+o;var i=this._x1,a=this._y1,s=r-t,u=n-e,c=i-t,l=a-e,p=c*c+l*l;if(o<0)throw new Error("negative radius: "+o);if(this._x1===null)this._+="M"+(this._x1=t)+","+(this._y1=e);else if(p>ke)if(!(Math.abs(l*s-u*c)>ke)||!o)this._+="L"+(this._x1=t)+","+(this._y1=e);else{var f=r-i,m=n-a,d=s*s+u*u,y=f*f+m*m,k=Math.sqrt(d),_=Math.sqrt(p),q=o*Math.tan((Jo-Math.acos((d+p-y)/(2*k*_)))/2),A=q/_,D=q/k;Math.abs(A-1)>ke&&(this._+="L"+(t+A*c)+","+(e+A*l)),this._+="A"+o+","+o+",0,0,"+ +(l*f>c*m)+","+(this._x1=t+D*s)+","+(this._y1=e+D*u)}},arc:function(t,e,r,n,o,i){t=+t,e=+e,r=+r,i=!!i;var a=r*Math.cos(n),s=r*Math.sin(n),u=t+a,c=e+s,l=1^i,p=i?n-o:o-n;if(r<0)throw new Error("negative radius: "+r);this._x1===null?this._+="M"+u+","+c:(Math.abs(this._x1-u)>ke||Math.abs(this._y1-c)>ke)&&(this._+="L"+u+","+c),r&&(p<0&&(p=p%Qo+Qo),p>Qd?this._+="A"+r+","+r+",0,1,"+l+","+(t-a)+","+(e-s)+"A"+r+","+r+",0,1,"+l+","+(this._x1=u)+","+(this._y1=c):p>ke&&(this._+="A"+r+","+r+",0,"+ +(p>=Jo)+","+l+","+(this._x1=t+r*Math.cos(o))+","+(this._y1=e+r*Math.sin(o))))},rect:function(t,e,r,n){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+e)+"h"+ +r+"v"+ +n+"h"+-r+"Z"},toString:function(){return this._}};var ti=hl;function ei(t){return function(){return t}}function gl(t){return t[0]}function xl(t){return t[1]}var yl=Array.prototype.slice;function Kd(t){return t.source}function th(t){return t.target}function eh(t){var e=Kd,r=th,n=gl,o=xl,i=null;function a(){var s,u=yl.call(arguments),c=e.apply(this,u),l=r.apply(this,u);if(i||(i=s=ti()),t(i,+n.apply(this,(u[0]=c,u)),+o.apply(this,u),+n.apply(this,(u[0]=l,u)),+o.apply(this,u)),s)return i=null,s+""||null}return a.source=function(s){return arguments.length?(e=s,a):e},a.target=function(s){return arguments.length?(r=s,a):r},a.x=function(s){return arguments.length?(n=typeof s=="function"?s:ei(+s),a):n},a.y=function(s){return arguments.length?(o=typeof s=="function"?s:ei(+s),a):o},a.context=function(s){return arguments.length?(i=s??null,a):i},a}function rh(t,e,r,n,o){t.moveTo(e,r),t.bezierCurveTo(e=(e+n)/2,r,e,o,n,o)}function ri(){return eh(rh)}function nh(t){return[t.source.x1,t.y0]}function oh(t){return[t.target.x0,t.y1]}function ni(){return ri().source(nh).target(oh)}var qn=class{svg;rectG;linkG;nodeG;sankeyGenerator;width=window.innerWidth;height=250;defs;blueColors=["#E1F5FE","#B3E5FC","#81D4FA","#4FC3F7","#29B6F6","#03A9F4","#039BE5","#0288D1","#0277BD","#01579B"];orangeColors=["#FFF5e1","#FFE0B2","#FFCC80","#FFB74D","#FFA726","#FF9800","#FB8C00","#F57C00","#EF6C00","#E65100"];constructor(e){this.svg=j(e).append("svg").attr("width",window.innerWidth).attr("height",this.height).attr("viewBox",[0,0,window.innerWidth,this.height]).style("max-width","100%").style("height","auto"),this.defs=this.svg.append("defs");let r=this.blueColors.slice(5,-1);r=[...r,...r,...r,...r],this.initGradient("blue-flow",r),this.rectG=this.svg.append("g").attr("stroke","#000"),this.linkG=this.svg.append("g").attr("fill","none").style("mix-blend-mode","normal"),this.nodeG=this.svg.append("g"),this.sankeyGenerator=Cn().nodeId(n=>n.name).nodeAlign(jo).nodeWidth(10).nodePadding(30).nodeSort((n,o)=>n.inclusion&&o.inclusion?n.namen.target.inclusion&&o.target.inclusion?n.source.namei/(r.length-1)).attr("stop-color",o=>o),n.append("animate").attr("attributeName","x1").attr("values","0%;200%").attr("dur","12s").attr("repeatCount","indefinite"),n.append("animate").attr("attributeName","x2").attr("values","100%;300%").attr("dur","12s").attr("repeatCount","indefinite")}isRightAligned(e){return!e.inclusion}update(e,r){this.sankeyGenerator.extent([[100,20],[window.innerWidth-100,this.height-25]]);let n=[],o={};this.width=window.innerWidth-56,this.svg.attr("width",this.width).attr("height",this.height).attr("viewBox",[0,0,this.width,this.height]);for(let l of r.links)l.value>0&&(n.push(l),o[l.source]=!0,o[l.target]=!0);let a={nodes:e.filter(l=>o[l.name]).map(l=>({...l})),links:n.map(l=>({...l}))},{nodes:s,links:u}=this.sankeyGenerator(a);this.rectG.selectAll("rect").data(s,l=>l.name).join("rect").attr("x",l=>l.x0).attr("y",l=>l.y0).attr("height",l=>l.y1-l.y0).attr("width",l=>l.x1-l.x0).attr("fill",l=>(l.name==="P2P Network"&&(l.value=r.hashesReceived),l.inclusion?l.name==="Tx Pool"||l.name==="Added To Block"?"#FFA726":"#00BFF2":"#555")),this.linkG.selectAll("path").data(u,l=>l.index).join("path").attr("d",ni()).attr("stroke",l=>l.target.inclusion?"url(#blue-flow)":"#333").attr("stroke-width",l=>Math.max(1,l.width??1));let c=this.nodeG.selectAll("text").data(s,l=>l.name).join(l=>l.append("text").attr("data-last","0"),l=>l,l=>l.remove());c.attr("data-last",function(l){return j(this).attr("data-current")||"0"}).attr("data-current",l=>(l.targetLinks||[]).reduce((f,m)=>f+(m.value||0),0)||l.value||0).attr("x",l=>this.isRightAligned(l)?l.x1+6:l.x0-6).attr("y",l=>(l.y0+l.y1)/2).attr("dy","-0.5em").attr("text-anchor",l=>this.isRightAligned(l)?"start":"end").text(l=>l.name).each(function(){j(this).selectAll("tspan.number").data([0]).join("tspan").attr("class","number").attr("x",()=>{let l=j(this).datum();return l&&l.inclusion?l.x1+6:l.x0-6}).attr("dy","1em")}),c.selectAll("tspan.number").transition().duration(500).tween("text",function(){let l=j(this),p=j(this.parentNode),f=p.empty()?0:parseFloat(p.attr("data-last")||"0"),m=p.empty()?0:parseFloat(p.attr("data-current")||"0"),d=Z(f,m);return function(y){l.text(bt(",.0f")(d(y)))}})}};function vl(t,e,r,n,o,i,a,s){let u=window.innerWidth-56,l={name:"root",children:[...n.map(h=>({name:o(h),item:h,size:a(h)}))]},p=Re(l).sum(h=>h.children?0:a(h.data?h.data.item:h.item)).sort(h=>h.children?0:i(h.data?h.data.item:h.item)),f=p.value??0,m=f>0?f/r:0,d=Math.min(u,u*m);_o().size([d,e-1]).round(!0).tile(vr.ratio(1)).paddingOuter(.5).paddingInner(2)(p);let[y,k]=Me(n,s),_=y&&y>0?y:1e-6,q=k||1,A=kn(Fo).domain([_,q]);function D(h){let T=s(h),E=T>0?T:1e-6;return A(E)}let L=j(t).select("svg");if(L.empty()){L=j(t).append("svg").attr("width",u).attr("height",e).attr("viewBox",[0,0,u,e]);let h=L.append("defs"),T=h.append("pattern").attr("id","unusedStripes").attr("patternUnits","userSpaceOnUse").attr("width",8).attr("height",8);T.append("rect").attr("class","pattern-bg").attr("width",8).attr("height",8).attr("fill","#444"),T.append("path").attr("d","M0,0 l8,8").attr("stroke","#000").attr("stroke-width",1),L.append("rect").attr("class","unused").attr("fill","url(#unusedStripes)").attr("opacity",1).attr("width",u).attr("height",e).attr("stroke","#fff").attr("stroke-width",1),h.append("marker").attr("id","arrowStart").attr("markerWidth",10).attr("markerHeight",10).attr("refX",5).attr("refY",5).attr("orient","auto").attr("markerUnits","strokeWidth").append("path").attr("d","M10,0 L0,5 L10,10").attr("fill","#ccc"),h.append("marker").attr("id","arrowEnd").attr("markerWidth",10).attr("markerHeight",10).attr("refX",5).attr("refY",5).attr("orient","auto").attr("markerUnits","strokeWidth").append("path").attr("d","M0,0 L10,5 L0,10").attr("fill","#ccc")}L.attr("width",u).attr("height",e).attr("viewBox",[0,0,u,e]),L.selectAll("rect.unused").attr("width",u);let b=p.leaves();L.selectAll("g.node").data(b,h=>h.data.name).join(h=>{let T=h.append("g").attr("class","node").attr("data-hash",E=>E.data.name).attr("transform",E=>`translate(${E.x0},${E.y0})`).attr("opacity",0);return T.append("rect").attr("stroke","#000").attr("stroke-width",.5).attr("width",0).attr("height",0).attr("fill",E=>D(E.data.item)),T.transition().duration(600).attr("opacity",1),T.select("rect").transition().duration(600).attr("width",E=>E.x1-E.x0).attr("height",E=>E.y1-E.y0),T},h=>(h.transition().duration(600).attr("transform",T=>`translate(${T.x0},${T.y0})`).attr("opacity",1),h.select("rect").transition().duration(600).attr("width",T=>T.x1-T.x0).attr("height",T=>T.y1-T.y0).attr("fill",T=>D(T.data.item)),h),h=>{h.transition().duration(600).attr("opacity",0).remove()});let H=(r-f)/r,I=H>=.1?[{key:"unused-label",x:d,w:u-d,ratio:H}]:[];L.selectAll("line.unused-arrow").data(I,h=>h.key).join(h=>h.append("line").attr("class","unused-arrow").attr("x1",T=>T.x+10).attr("x2",T=>T.x+T.w-10).attr("y1",145).attr("y2",145).attr("stroke","#ccc").attr("stroke-width",2).attr("marker-start","url(#arrowStart)").attr("marker-end","url(#arrowEnd)").attr("opacity",0).call(T=>T.transition().duration(600).attr("opacity",1)),h=>h.transition().duration(600).attr("x1",T=>T.x+10).attr("x2",T=>T.x+T.w-10),h=>h.transition().duration(600).attr("opacity",0).remove()),L.selectAll("text.unused-label").data(I,h=>h.key).join(h=>h.append("text").attr("class","unused-label").attr("x",T=>T.x+T.w/2).attr("y",135).attr("fill","#ccc").attr("font-size",14).attr("text-anchor","middle").attr("opacity",0).text(T=>`${(T.ratio*100).toFixed(1)}% available`).call(T=>T.transition().duration(600).attr("opacity",1)),h=>h.transition().duration(600).attr("x",T=>T.x+T.w/2).text(T=>`${(T.ratio*100).toFixed(1)}% available`),h=>h.transition().duration(600).attr("opacity",0).remove())}function bl(t,e,r=36){let n={top:4,right:4,bottom:20,left:20},o=t.getBoundingClientRect().width,i=100,a=j(t).append("svg").style("display","block").style("background","black").attr("width",o).attr("height",i),s=a.append("g").attr("transform",`translate(${n.left},${n.top})`);function u(){return o-n.left-n.right}let c=i-n.top-n.bottom,l=[],p,f=br().domain(Ae(r)).range([0,u()]).paddingInner(.3).paddingOuter(.1),m=de().range([c,0]);function d(){return xr().duration(750)}function y(A){if(!A.length)return{min:0,q1:0,median:0,q3:0,max:0,whiskerLow:0,whiskerHigh:0};let D=A.slice().sort(ft),L=D[0],b=D[D.length-1],N=Ee(D,.25),H=Ee(D,.5),P=Ee(D,.75),I=P-N,h=Math.max(L,N-1.5*I),T=Math.min(b,P+1.5*I);return{min:L,q1:N,median:H,q3:P,max:b,whiskerLow:h,whiskerHigh:T}}function k(A,D){let L=A.map(e).filter(x=>Number.isFinite(x)&&x>=0),b=y(L);if(l.length{x.xIndex-=1});l.length&&l[0].xIndex<0;){let x=l.shift();x&&(p=x.stats.median)}l.push({xIndex:r-1,blockNumber:D,stats:b,values:L})}let N=ee(l,x=>x.stats.whiskerHigh)||1;m.domain([0,N]);let H=s.selectAll(".box-group").data(l,x=>x.xIndex);H.exit().transition(d()).attr("transform",x=>`translate(${f(x.xIndex)||0},${c}) scale(0.001,0.001)`).remove();let P=H.enter().append("g").attr("class","box-group").attr("stroke-width",1).attr("transform",x=>`translate(${f(x.xIndex)||0}, ${c}) scale(0.001,0.001)`);P.append("line").attr("class","whisker-line").attr("stroke","#aaa"),P.append("rect").attr("class","box-rect").attr("stroke","white").attr("fill","gray"),P.append("line").attr("class","median-line").attr("stroke","#ccc").attr("stroke-width",1),P.append("line").attr("class","whisker-cap lower").attr("stroke","#aaa"),P.append("line").attr("class","whisker-cap upper").attr("stroke","#aaa"),P.append("g").attr("class","points-group"),H=P.merge(H),H.transition(d()).attr("transform",x=>`translate(${f(x.xIndex)||0},0) scale(1,1)`),H.each(function(x){let M=j(this),S=x.stats,v=f.bandwidth(),B,G=l.find(ot=>ot.xIndex===x.xIndex-1);G?B=G.stats.median:x.xIndex===0&&p!==void 0&&(B=p);let K="gray";B!==void 0&&(S.median>B?K="rgb(127, 63, 63)":S.median{x.selectAll("text").attr("fill","white"),x.selectAll("line,path").attr("stroke","white")});let h=Fr(f).tickFormat(x=>{let M=l.find(S=>S.xIndex===x);return M?String(M.blockNumber):""}),T=s.append("g").attr("class","x-axis").attr("transform",`translate(0,${c})`).call(h),E=f.bandwidth();E<25&&T.selectAll(".tick").each(function(x,M){M/2%2!==0&&j(this).remove()}),T.call(x=>{x.selectAll("text").attr("fill","white"),x.selectAll("line,path").attr("stroke","white")}),s.selectAll(".median-trend").remove();let R=l.filter(x=>x.xIndex>=0).sort((x,M)=>x.xIndex-M.xIndex),g=We().x(x=>(f(x.xIndex)??0)+E/2).y(x=>m(x.stats.median)).curve(En);s.append("path").attr("class","median-trend").datum(R).attr("fill","none").attr("stroke","#FFA726").attr("stroke-width",2).transition(d()).attr("d",g)}function _(){o=t.getBoundingClientRect().width,a.attr("width",o),f.range([0,u()]),q()}function q(){s.selectAll(".box-group").transition().duration(0).attr("transform",I=>`translate(${f(I.xIndex)||0},0)`),s.selectAll(".y-axis").remove(),s.selectAll(".x-axis").remove();let A=Or(m).ticks(3);s.append("g").attr("class","y-axis").call(A).call(I=>{I.selectAll("text").attr("fill","white"),I.selectAll("line,path").attr("stroke","white")});let D=Fr(f).tickFormat(I=>{let h=l.find(T=>T.xIndex===I);return h?String(h.blockNumber):""}),L=s.append("g").attr("class","x-axis").attr("transform",`translate(0,${c})`).call(D);f.bandwidth()<25&&L.selectAll(".tick").each(function(I,h){h%2!==0&&j(this).remove()}),L.call(I=>{I.selectAll("text").attr("fill","white"),I.selectAll("line,path").attr("stroke","white")}),s.selectAll(".median-trend").remove();let N=l.filter(I=>I.xIndex>=0).sort((I,h)=>I.xIndex-h.xIndex),H=f.bandwidth(),P=We().x(I=>(f(I.xIndex)??0)+H/2).y(I=>m(I.stats.median)).curve(En);s.append("path").attr("class","median-trend").datum(N).attr("fill","none").attr("stroke","#FFA726").attr("stroke-width",2).attr("d",P)}return{update:k,resize:_}}var qr=100,ih=120,ah=qr+ih,sh=qr,wl=qr/2,uh=j("#pie-chart").append("svg").attr("width",ah).attr("height",sh).attr("overflow","visible"),kl=uh.append("g").attr("transform",`translate(${qr/2}, ${qr/2})`),lh=me().range(Ho),ch=Uo().sort(null).value(t=>t.count),oi=$o().innerRadius(0).outerRadius(wl),fh=kl.append("g").attr("class","slice-layer"),ph=kl.append("g").attr("class","label-layer");function _l(t){let e=ch(t),r=e.map(m=>{let[d,y]=oi.centroid(m);return{...m,centroidY:y}}),n=r.slice().sort((m,d)=>m.centroidY-d.centroidY),o=18,i=-((n.length-1)*o)/2,a=wl+20,s={};n.forEach((m,d)=>{s[m.data.type]={x:a,y:i+d*o}});let u=fh.selectAll("path").data(e,m=>m.data.type);u.exit().remove(),u.enter().append("path").attr("stroke","#222").style("stroke-width","1px").attr("fill",m=>lh(m.data.type)).each(function(m){this._current=m}).merge(u).transition().duration(750).attrTween("d",function(m){let d=vt(this._current,m);return this._current=d(0),y=>oi(d(y))});let l=ph.selectAll("g.label").data(r,m=>m.data.type);l.exit().remove();let p=l.enter().append("g").attr("class","label").style("pointer-events","none");p.append("polyline").attr("fill","none").attr("stroke","#fff"),p.append("text").style("alignment-baseline","middle").style("text-anchor","start").style("fill","#fff");let f=p.merge(l);f.select("polyline").transition().duration(750).attr("points",m=>{let[d,y]=oi.centroid(m),{x:k,y:_}=s[m.data.type],q=k*.6;return`${d},${y} ${q},${_} ${k},${_}`}),f.select("text").transition().duration(750).attr("transform",m=>{let d=s[m.data.type];return`translate(${d.x},${d.y})`}).tween("label",function(m){return()=>{this.textContent=`${m.data.type} (${m.data.count})`}})}function Tl(t){switch(t){case 0:return"Legacy";case 1:return"AccessList";case 2:return"Eip1559";case 3:return"Blob";case 4:return"SetCode";case 5:return"TxCreate";default:return"Unknown"}}function ii(t){switch(t){case"0x095ea7b3":return"approve";case"0xa9059cbb":return"transfer";case"0x23b872dd":return"transferFrom";case"0xd0e30db0":return"deposit";case"0xe8e33700":case"0xf305d719":return"addLiquidity";case"0xbaa2abde":case"0x02751cec":case"0xaf2979eb":case"0xded9382a":case"0x5b0d5984":case"0x2195995c":return"removeLiquidity";case"0xfb3bdb41":case"0x7ff36ab5":case"0xb6f9de95":case"0x18cbafe5":case"0x791ac947":case"0x38ed1739":case"0x5c11d795":case"0x4a25d94a":case"0x5f575529":case"0x6b68764c":case"0x845a101f":case"0x8803dbee":return"swap";case"0x24856bc3":case"0x3593564c":return"dex";case"0x":return"ETH transfer";default:return t}}function Ml(t){switch(t){case"0xdac17f958d2ee523a2206206994597c13d831ec7":return"usdt";case"0x4ecaba5870353805a9f068101a40e0f32ed605c6":return"usdt";case"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48":return"usdc";case"0xddafbb505ad214d7b80b1f830fccc89b60fb7a83":return"usdc";case"0x6b175474e89094c44da98b954eedeac495271d0f":return"dai";case"0x44fa8e6f47987339850636f88629646662444217":return"dai";case"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2":return"weth";case"0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1":return"weth";case"0x7a250d5630b4cf539739df2c5dacb4c659f2488d":return"uniswap v2";case"0x66a9893cc07d91d95644aedd05d03f95e1dba8af":return"uniswap v4";case"0x2f848984984d6c3c036174ce627703edaf780479":return"xen minter";case"0x0a252663dbcc0b073063d6420a40319e438cfa59":return"xen";case"0x881d40237659c251811cec9c364ef91dc08d300c":return"metamask";default:return t}}var Wh=tc(),Pn=class{nodeLog;ansiConvert=new Wh;logs=[];constructor(e){this.nodeLog=document.getElementById(e)}receivedLog(e){let r=JSON.parse(e.data);for(let n of r){let o=this.ansiConvert.toHtml(n);this.logs.length>=100&&this.logs.shift(),this.logs.push(o)}}appendLogs(){if(this.logs.length>0){let e=!1;(this.nodeLog.scrollHeight<500||this.nodeLog.scrollTop250;)this.nodeLog.firstChild.remove(),n--;e&&window.setTimeout(()=>this.scrollLogs(),17)}}resize(){let e=document.body.getBoundingClientRect();var n=this.nodeLog.getBoundingClientRect().top-e.top,o=window.innerHeight-n-16;o>0&&(this.nodeLog.style.height=`${o}px`)}scrollLogs(){this.nodeLog.scrollTop=this.nodeLog.scrollHeight}};function $(t,e){t.innerText!==e&&(t.innerText=e)}var $n=class{lastGasLimit=36e6;minGas;medianGas;aveGas;maxGas;gasLimit;gasLimitDelta;constructor(e,r,n,o,i,a){this.minGas=document.getElementById(e),this.medianGas=document.getElementById(r),this.aveGas=document.getElementById(n),this.maxGas=document.getElementById(o),this.gasLimit=document.getElementById(i),this.gasLimitDelta=document.getElementById(a)}parseEvent(e){if(document.hidden)return;let r=JSON.parse(e.data);$(this.minGas,r.minGas.toFixed(2)),$(this.medianGas,r.medianGas.toFixed(2)),$(this.aveGas,r.aveGas.toFixed(2)),$(this.maxGas,r.maxGas.toFixed(2)),$(this.gasLimit,dt(r.gasLimit)),$(this.gasLimitDelta,r.gasLimit>this.lastGasLimit?"\u{1F446}":r.gasLimitparseInt(t.effectiveGasPrice,16)/1e9,36);function dc(t,e){do if(!(t.matches===void 0||!t.matches(e)))return t;while(t=t.parentElement);return null}function kg(t,e,r,n,o){t.addEventListener(e,function(i){try{let a=dc(i.target,r);a!==null&&n.apply(a,arguments)}catch{}},o)}var ec=[],rc=[],nc=[],oc=[],ic=[],ac=[],sc=[],pi=0,mi=0,di=0,hi=0,gi=0,Un=0;function Te(t,e){t.push(e),t.length>60&&t.shift()}var _g=new qn("#txPoolFlow"),bi=null,uc=!1;function Tg(t){if(!uc){if(t.pooledTx==0){document.getElementById("txPoolFlow").classList.add("not-active");return}setTimeout(Ti,10),document.getElementById("txPoolFlow").classList.remove("not-active"),uc=!0}let e=performance.now(),r=e/1e3,n=r-Un,o=0,i=0,a=0,s=0;for(let c of t.links)c.target==="Received Txs"&&(o+=c.value),c.target==="Tx Pool"&&(i+=c.value),c.target==="Added To Block"&&(a+=c.value),c.target==="Duplicate"&&(s+=c.value);let u=t.hashesReceived;if(Un!==0&&(Te(ec,{t:e,v:u-gi}),Te(rc,{t:e,v:o-pi}),Te(ic,{t:e,v:s-hi}),Te(nc,{t:e,v:i-mi}),Te(oc,{t:e,v:a-di})),!document.hidden){if(!bi)return;_g.update(bi,t),$(Xh,dt(t.pooledTx)),$(jh,dt(t.pooledBlobTx)),$(Zh,dt(t.pooledTx+t.pooledBlobTx)),Un!==0&&(Xt(document.getElementById("sparkHashesTps"),ec),Xt(document.getElementById("sparkReceivedTps"),rc),Xt(document.getElementById("sparkDuplicateTps"),ic),Xt(document.getElementById("sparkTxPoolTps"),nc),Xt(document.getElementById("sparkBlockTps"),oc),$(Jh,Wt((a-di)/n)),$(Qh,Wt((o-pi)/n)),$(Kh,Wt((i-mi)/n)),$(tg,Wt((s-hi)/n)),$(eg,Wt((u-gi)/n)))}Un=r,pi=o,mi=i,di=a,hi=s,gi=u}var _i=new Pn("nodeLog"),Mg=new $n("minGas","medianGas","aveGas","maxGas","gasLimit","gasLimitDelta"),Jt=new EventSource("/data/events");Jt.addEventListener("log",t=>_i.receivedLog(t));Jt.addEventListener("processed",t=>Mg.parseEvent(t));Jt.addEventListener("nodeData",t=>{let e=JSON.parse(t.data),r=al(e.network),n=`Nethermind [${r}]${e.instance?" - "+e.instance:""}`;document.title!=n&&(document.title=n),$(rg,e.version),$(ng,r),document.getElementById("network-logo").src=`logos/${sl(e.network)}`,$(pc,Wo(e.uptime)),cl(e.gasToken)});Jt.addEventListener("txNodes",t=>{bi=JSON.parse(t.data)});Jt.addEventListener("txLinks",t=>{if(document.hidden)return;let e=JSON.parse(t.data);Tg(e)});var wi=0,hc=0,lc=!1;Jt.addEventListener("forkChoice",t=>{let e=performance.now();if(hc=e-wi,wi=e,document.hidden)return;let r=JSON.parse(t.data),n=parseInt(r.head.number,16);!lc&&n!==0&&(lc=!0,document.getElementById("lastestBlock").classList.remove("not-active"),setTimeout(Ti,10));let o=parseInt(r.safe,16),i=parseInt(r.finalized,16);$(og,n.toFixed(0)),$(ig,o.toFixed(0)),$(ag,i.toFixed(0)),$(sg,`(${(o-n).toFixed(0)})`),$(ug,`(${(i-n).toFixed(0)})`);let a=r.head;if(a.tx.length===0)return;let s=a.tx.map((u,c)=>{let l=a.receipts[c];return{block:a.number,order:c,...u,...l}});$(hg,il(a.extraData)),$(gg,dt(parseInt(a.gasUsed,16))),$(xg,dt(parseInt(a.gasLimit,16))),$(yg,ol(parseInt(a.size,16))),$(vg,nl(parseInt(a.timestamp,16))),$(bg,dt(a.tx.length)),$(wg,dt(a.tx.reduce((u,c)=>u+c.blobs,0))),vl(document.getElementById("block"),160,parseInt(r.head.gasLimit,16),s,u=>u.hash,u=>u.order,u=>parseInt(u.gasUsed,16),u=>parseInt(u.effectiveGasPrice,16)*parseInt(u.gasUsed,16)),mc.update(s,n),_t.push(...s),ki=_t.length,_t.length>25e4&&_t.slice(_t.length-25e3),gc=Lg(s)});var gc,Sg="";kg(document.getElementById("block"),"pointermove","g.node",t=>{let r=dc(t.target,"g.node").dataset.hash,n=gc[r];if(!n)return;let o=document.getElementById("txDetails");return Sg!==r&&(o.innerHTML=` +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}var i=!0,a=!1,s;return{s:function(){r=r.call(t)},n:function(){var c=r.next();return i=c.done,c},e:function(c){a=!0,s=c},f:function(){try{!i&&r.return!=null&&r.return()}finally{if(a)throw s}}}}function Bh(t,e){if(t){if(typeof t=="string")return zl(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);if(r==="Object"&&t.constructor&&(r=t.constructor.name),r==="Map"||r==="Set")return Array.from(t);if(r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return zl(t,e)}}function zl(t,e){(e==null||e>t.length)&&(e=t.length);for(var r=0,n=new Array(e);r0?t*40+55:0,a=e>0?e*40+55:0,s=r>0?r*40+55:0;n[o]=Oh([i,a,s])}function Jl(t){for(var e=t.toString(16);e.length<2;)e="0"+e;return e}function Oh(t){var e=[],r=Zl(t),n;try{for(r.s();!(n=r.n()).done;){var o=n.value;e.push(Jl(o))}}catch(i){r.e(i)}finally{r.f()}return"#"+e.join("")}function Wl(t,e,r,n){var o;return e==="text"?o=Gh(r,n):e==="display"?o=$h(t,r,n):e==="xterm256Foreground"?o=Fn(t,n.colors[r]):e==="xterm256Background"?o=On(t,n.colors[r]):e==="rgb"&&(o=Ph(t,r)),o}function Ph(t,e){e=e.substring(2).slice(0,-1);var r=+e.substr(0,2),n=e.substring(5).split(";"),o=n.map(function(i){return("0"+Number(i).toString(16)).substr(-2)}).join("");return Hn(t,(r===38?"color:#":"background-color:#")+o)}function $h(t,e,r){e=parseInt(e,10);var n={"-1":function(){return"
"},0:function(){return t.length&&Ql(t)},1:function(){return Zt(t,"b")},3:function(){return Zt(t,"i")},4:function(){return Zt(t,"u")},8:function(){return Hn(t,"display:none")},9:function(){return Zt(t,"strike")},22:function(){return Hn(t,"font-weight:normal;text-decoration:none;font-style:normal")},23:function(){return jl(t,"i")},24:function(){return jl(t,"u")},39:function(){return Fn(t,r.fg)},49:function(){return On(t,r.bg)},53:function(){return Hn(t,"text-decoration:overline")}},o;return n[e]?o=n[e]():4"}).join("")}function Rn(t,e){for(var r=[],n=t;n<=e;n++)r.push(n);return r}function Uh(t){return function(e){return(t===null||e.category!==t)&&t!=="all"}}function Xl(t){t=parseInt(t,10);var e=null;return t===0?e="all":t===1?e="bold":2")}function Hn(t,e){return Zt(t,"span",e)}function Fn(t,e){return Zt(t,"span","color:"+e)}function On(t,e){return Zt(t,"span","background-color:"+e)}function jl(t,e){var r;if(t.slice(-1)[0]===e&&(r=t.pop()),r)return""}function Vh(t,e,r){var n=!1,o=3;function i(){return""}function a(L,b){return r("xterm256Foreground",b),""}function s(L,b){return r("xterm256Background",b),""}function u(L){return e.newline?r("display",-1):r("text",L),""}function c(L,b){n=!0,b.trim().length===0&&(b="0"),b=b.trimRight(";").split(";");var N=Zl(b),H;try{for(N.s();!(H=N.n()).done;){var P=H.value;r("display",P)}}catch(I){N.e(I)}finally{N.f()}return""}function l(L){return r("text",L),""}function p(L){return r("rgb",L),""}var f=[{pattern:/^\x08+/,sub:i},{pattern:/^\x1b\[[012]?K/,sub:i},{pattern:/^\x1b\[\(B/,sub:i},{pattern:/^\x1b\[[34]8;2;\d+;\d+;\d+m/,sub:p},{pattern:/^\x1b\[38;5;(\d+)m/,sub:a},{pattern:/^\x1b\[48;5;(\d+)m/,sub:s},{pattern:/^\n/,sub:u},{pattern:/^\r+\n/,sub:u},{pattern:/^\r/,sub:u},{pattern:/^\x1b\[((?:\d{1,3};?)+|)m/,sub:c},{pattern:/^\x1b\[\d?J/,sub:i},{pattern:/^\x1b\[\d{0,3};\d{0,3}f/,sub:i},{pattern:/^\x1b\[?[\d;]{0,3}/,sub:i},{pattern:/^(([^\x1b\x08\r\n])+)/,sub:l}];function m(L,b){b>o&&n||(n=!1,t=t.replace(L.pattern,L.sub))}var d=[],y=t,k=y.length;t:for(;k>0;){for(var _=0,q=0,A=f.length;qe?1:t>=e?0:NaN}function Gn(t,e){return t==null||e==null?NaN:et?1:e>=t?0:NaN}function Kt(t){let e,r,n;t.length!==2?(e=ft,r=(s,u)=>ft(t(s),u),n=(s,u)=>t(s)-u):(e=t===ft||t===Gn?t:yc,r=t,n=t);function o(s,u,c=0,l=s.length){if(c>>1;r(s[p],u)<0?c=p+1:l=p}while(c>>1;r(s[p],u)<=0?c=p+1:l=p}while(cc&&n(s[p-1],u)>-n(s[p],u)?p-1:p}return{left:o,center:a,right:i}}function yc(){return 0}function Nr(t){return t===null?NaN:+t}function*Si(t,e){if(e===void 0)for(let r of t)r!=null&&(r=+r)>=r&&(yield r);else{let r=-1;for(let n of t)(n=e(n,++r,t))!=null&&(n=+n)>=n&&(yield n)}}var Li=Kt(ft),Ei=Li.right,vc=Li.left,bc=Kt(Nr).center,Vn=Ei;function Me(t,e){let r,n;if(e===void 0)for(let o of t)o!=null&&(r===void 0?o>=o&&(r=n=o):(r>o&&(r=o),n=i&&(r=n=i):(r>i&&(r=i),n{let n=t(e,r);return n||n===0?n:(t(r,r)===0)-(t(e,e)===0)}}function zn(t,e){return(t==null||!(t>=t))-(e==null||!(e>=e))||(te?1:0)}var Tc=Math.sqrt(50),Mc=Math.sqrt(10),Sc=Math.sqrt(2);function Br(t,e,r){let n=(e-t)/Math.max(0,r),o=Math.floor(Math.log10(n)),i=n/Math.pow(10,o),a=i>=Tc?10:i>=Mc?5:i>=Sc?2:1,s,u,c;return o<0?(c=Math.pow(10,-o)/a,s=Math.round(t*c),u=Math.round(e*c),s/ce&&--u,c=-c):(c=Math.pow(10,o)*a,s=Math.round(t/c),u=Math.round(e/c),s*ce&&--u),u0))return[];if(t===e)return[t];let n=e=o))return[];let s=i-o+1,u=new Array(s);if(n)if(a<0)for(let c=0;c=n)&&(r=n);else{let n=-1;for(let o of t)(o=e(o,++n,t))!=null&&(r=o)&&(r=o)}return r}function Rr(t,e){let r;if(e===void 0)for(let n of t)n!=null&&(r>n||r===void 0&&n>=n)&&(r=n);else{let n=-1;for(let o of t)(o=e(o,++n,t))!=null&&(r>o||r===void 0&&o>=o)&&(r=o)}return r}function Hr(t,e,r=0,n=1/0,o){if(e=Math.floor(e),r=Math.floor(Math.max(0,r)),n=Math.floor(Math.min(t.length-1,n)),!(r<=e&&e<=n))return t;for(o=o===void 0?zn:Di(o);n>r;){if(n-r>600){let u=n-r+1,c=e-r+1,l=Math.log(u),p=.5*Math.exp(2*l/3),f=.5*Math.sqrt(l*p*(u-p)/u)*(c-u/2<0?-1:1),m=Math.max(r,Math.floor(e-c*p/u+f)),d=Math.min(n,Math.floor(e+(u-c)*p/u+f));Hr(t,e,m,d,o)}let i=t[e],a=r,s=n;for(Ke(t,r,e),o(t[n],i)>0&&Ke(t,r,n);a0;)--s}o(t[r],i)===0?Ke(t,r,s):(++s,Ke(t,s,n)),s<=e&&(r=s+1),e<=s&&(n=s-1)}return t}function Ke(t,e,r){let n=t[e];t[e]=t[r],t[r]=n}function Ee(t,e,r){if(t=Float64Array.from(Si(t,r)),!(!(n=t.length)||isNaN(e=+e))){if(e<=0||n<2)return Rr(t);if(e>=1)return ee(t);var n,o=(n-1)*e,i=Math.floor(o),a=ee(Hr(t,i).subarray(0,i+1)),s=Rr(t.subarray(i+1));return a+(s-a)*(o-i)}}function Ae(t,e,r){t=+t,e=+e,r=(o=arguments.length)<2?(e=t,t=0,1):o<3?1:+r;for(var n=-1,o=Math.max(0,Math.ceil((e-t)/r))|0,i=new Array(o);++n+t(e)}function Dc(t,e){return e=Math.max(0,t.bandwidth()-e*2)/2,t.round()&&(e=Math.round(e)),r=>+t(r)+e}function Cc(){return!this.__axis}function Ii(t,e){var r=[],n=null,o=null,i=6,a=6,s=3,u=typeof window<"u"&&window.devicePixelRatio>1?0:.5,c=t===Yn||t===tr?-1:1,l=t===tr||t===Wn?"x":"y",p=t===Yn||t===Xn?Lc:Ec;function f(m){var d=n??(e.ticks?e.ticks.apply(e,r):e.domain()),y=o??(e.tickFormat?e.tickFormat.apply(e,r):Ci),k=Math.max(i,0)+s,_=e.range(),q=+_[0]+u,A=+_[_.length-1]+u,D=(e.bandwidth?Dc:Ac)(e.copy(),u),L=m.selection?m.selection():m,b=L.selectAll(".domain").data([null]),N=L.selectAll(".tick").data(d,e).order(),H=N.exit(),P=N.enter().append("g").attr("class","tick"),I=N.select("line"),h=N.select("text");b=b.merge(b.enter().insert("path",".tick").attr("class","domain").attr("stroke","currentColor")),N=N.merge(P),I=I.merge(P.append("line").attr("stroke","currentColor").attr(l+"2",c*i)),h=h.merge(P.append("text").attr("fill","currentColor").attr(l,c*k).attr("dy",t===Yn?"0em":t===Xn?"0.71em":"0.32em")),m!==L&&(b=b.transition(m),N=N.transition(m),I=I.transition(m),h=h.transition(m),H=H.transition(m).attr("opacity",qi).attr("transform",function(T){return isFinite(T=D(T))?p(T+u):this.getAttribute("transform")}),P.attr("opacity",qi).attr("transform",function(T){var E=this.parentNode.__axis;return p((E&&isFinite(E=E(T))?E:D(T))+u)})),H.remove(),b.attr("d",t===tr||t===Wn?a?"M"+c*a+","+q+"H"+u+"V"+A+"H"+c*a:"M"+u+","+q+"V"+A:a?"M"+q+","+c*a+"V"+u+"H"+A+"V"+c*a:"M"+q+","+u+"H"+A),N.attr("opacity",1).attr("transform",function(T){return p(D(T)+u)}),I.attr(l+"2",c*i),h.attr(l,c*k).text(y),L.filter(Cc).attr("fill","none").attr("font-size",10).attr("font-family","sans-serif").attr("text-anchor",t===Wn?"start":t===tr?"end":"middle"),L.each(function(){this.__axis=D})}return f.scale=function(m){return arguments.length?(e=m,f):e},f.ticks=function(){return r=Array.from(arguments),f},f.tickArguments=function(m){return arguments.length?(r=m==null?[]:Array.from(m),f):r.slice()},f.tickValues=function(m){return arguments.length?(n=m==null?null:Array.from(m),f):n&&n.slice()},f.tickFormat=function(m){return arguments.length?(o=m,f):o},f.tickSize=function(m){return arguments.length?(i=a=+m,f):i},f.tickSizeInner=function(m){return arguments.length?(i=+m,f):i},f.tickSizeOuter=function(m){return arguments.length?(a=+m,f):a},f.tickPadding=function(m){return arguments.length?(s=+m,f):s},f.offset=function(m){return arguments.length?(u=+m,f):u},f}function Fr(t){return Ii(Xn,t)}function Or(t){return Ii(tr,t)}var qc={value:()=>{}};function Bi(){for(var t=0,e=arguments.length,r={},n;t=0&&(n=r.slice(o+1),r=r.slice(0,o)),r&&!e.hasOwnProperty(r))throw new Error("unknown type: "+r);return{type:r,name:n}})}Pr.prototype=Bi.prototype={constructor:Pr,on:function(t,e){var r=this._,n=Ic(t+"",r),o,i=-1,a=n.length;if(arguments.length<2){for(;++i0)for(var r=new Array(o),n=0,o,i;n=0&&(e=t.slice(0,r))!=="xmlns"&&(t=t.slice(r+1)),Zn.hasOwnProperty(e)?{space:Zn[e],local:t}:t}function Bc(t){return function(){var e=this.ownerDocument,r=this.namespaceURI;return r===$r&&e.documentElement.namespaceURI===$r?e.createElement(t):e.createElementNS(r,t)}}function Rc(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function Ur(t){var e=qt(t);return(e.local?Rc:Bc)(e)}function Hc(){}function re(t){return t==null?Hc:function(){return this.querySelector(t)}}function Ri(t){typeof t!="function"&&(t=re(t));for(var e=this._groups,r=e.length,n=new Array(r),o=0;o=A&&(A=q+1);!(L=k[A])&&++A=0;)(a=n[o])&&(i&&a.compareDocumentPosition(i)^4&&i.parentNode.insertBefore(a,i),i=a);return this}function Xi(t){t||(t=Zc);function e(p,f){return p&&f?t(p.__data__,f.__data__):!p-!f}for(var r=this._groups,n=r.length,o=new Array(n),i=0;ie?1:t>=e?0:NaN}function ji(){var t=arguments[0];return arguments[0]=this,t.apply(null,arguments),this}function Zi(){return Array.from(this)}function Ji(){for(var t=this._groups,e=0,r=t.length;e1?this.each((e==null?nf:typeof e=="function"?af:of)(t,e,r??"")):Ft(this.node(),t)}function Ft(t,e){return t.style.getPropertyValue(e)||zr(t).getComputedStyle(t,null).getPropertyValue(e)}function sf(t){return function(){delete this[t]}}function uf(t,e){return function(){this[t]=e}}function lf(t,e){return function(){var r=e.apply(this,arguments);r==null?delete this[t]:this[t]=r}}function na(t,e){return arguments.length>1?this.each((e==null?sf:typeof e=="function"?lf:uf)(t,e)):this.node()[t]}function oa(t){return t.trim().split(/^|\s+/)}function Qn(t){return t.classList||new ia(t)}function ia(t){this._node=t,this._names=oa(t.getAttribute("class")||"")}ia.prototype={add:function(t){var e=this._names.indexOf(t);e<0&&(this._names.push(t),this._node.setAttribute("class",this._names.join(" ")))},remove:function(t){var e=this._names.indexOf(t);e>=0&&(this._names.splice(e,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};function aa(t,e){for(var r=Qn(t),n=-1,o=e.length;++n=0&&(r=e.slice(n+1),e=e.slice(0,n)),{type:e,name:r}})}function Lf(t){return function(){var e=this.__on;if(e){for(var r=0,n=-1,o=e.length,i;r>8&15|e>>4&240,e>>4&15|e&240,(e&15)<<4|e&15,1):r===8?Yr(e>>24&255,e>>16&255,e>>8&255,(e&255)/255):r===4?Yr(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|e&240,((e&15)<<4|e&15)/255):null):(e=If.exec(t))?new et(e[1],e[2],e[3],1):(e=Nf.exec(t))?new et(e[1]*255/100,e[2]*255/100,e[3]*255/100,1):(e=Bf.exec(t))?Yr(e[1],e[2],e[3],e[4]):(e=Rf.exec(t))?Yr(e[1]*255/100,e[2]*255/100,e[3]*255/100,e[4]):(e=Hf.exec(t))?Aa(e[1],e[2]/100,e[3]/100,1):(e=Ff.exec(t))?Aa(e[1],e[2]/100,e[3]/100,e[4]):_a.hasOwnProperty(t)?Sa(_a[t]):t==="transparent"?new et(NaN,NaN,NaN,0):null}function Sa(t){return new et(t>>16&255,t>>8&255,t&255,1)}function Yr(t,e,r,n){return n<=0&&(t=e=r=NaN),new et(t,e,r,n)}function eo(t){return t instanceof ae||(t=yt(t)),t?(t=t.rgb(),new et(t.r,t.g,t.b,t.opacity)):new et}function qe(t,e,r,n){return arguments.length===1?eo(t):new et(t,e,r,n??1)}function et(t,e,r,n){this.r=+t,this.g=+e,this.b=+r,this.opacity=+n}De(et,qe,or(ae,{brighter(t){return t=t==null?ie:Math.pow(ie,t),new et(this.r*t,this.g*t,this.b*t,this.opacity)},darker(t){return t=t==null?Ot:Math.pow(Ot,t),new et(this.r*t,this.g*t,this.b*t,this.opacity)},rgb(){return this},clamp(){return new et(oe(this.r),oe(this.g),oe(this.b),Xr(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:La,formatHex:La,formatHex8:$f,formatRgb:Ea,toString:Ea}));function La(){return`#${ne(this.r)}${ne(this.g)}${ne(this.b)}`}function $f(){return`#${ne(this.r)}${ne(this.g)}${ne(this.b)}${ne((isNaN(this.opacity)?1:this.opacity)*255)}`}function Ea(){let t=Xr(this.opacity);return`${t===1?"rgb(":"rgba("}${oe(this.r)}, ${oe(this.g)}, ${oe(this.b)}${t===1?")":`, ${t})`}`}function Xr(t){return isNaN(t)?1:Math.max(0,Math.min(1,t))}function oe(t){return Math.max(0,Math.min(255,Math.round(t)||0))}function ne(t){return t=oe(t),(t<16?"0":"")+t.toString(16)}function Aa(t,e,r,n){return n<=0?t=e=r=NaN:r<=0||r>=1?t=e=NaN:e<=0&&(t=NaN),new xt(t,e,r,n)}function Ca(t){if(t instanceof xt)return new xt(t.h,t.s,t.l,t.opacity);if(t instanceof ae||(t=yt(t)),!t)return new xt;if(t instanceof xt)return t;t=t.rgb();var e=t.r/255,r=t.g/255,n=t.b/255,o=Math.min(e,r,n),i=Math.max(e,r,n),a=NaN,s=i-o,u=(i+o)/2;return s?(e===i?a=(r-n)/s+(r0&&u<1?0:a,new xt(a,s,u,t.opacity)}function qa(t,e,r,n){return arguments.length===1?Ca(t):new xt(t,e,r,n??1)}function xt(t,e,r,n){this.h=+t,this.s=+e,this.l=+r,this.opacity=+n}De(xt,qa,or(ae,{brighter(t){return t=t==null?ie:Math.pow(ie,t),new xt(this.h,this.s,this.l*t,this.opacity)},darker(t){return t=t==null?Ot:Math.pow(Ot,t),new xt(this.h,this.s,this.l*t,this.opacity)},rgb(){var t=this.h%360+(this.h<0)*360,e=isNaN(t)||isNaN(this.s)?0:this.s,r=this.l,n=r+(r<.5?r:1-r)*e,o=2*r-n;return new et(to(t>=240?t-240:t+120,o,n),to(t,o,n),to(t<120?t+240:t-120,o,n),this.opacity)},clamp(){return new xt(Da(this.h),Wr(this.s),Wr(this.l),Xr(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){let t=Xr(this.opacity);return`${t===1?"hsl(":"hsla("}${Da(this.h)}, ${Wr(this.s)*100}%, ${Wr(this.l)*100}%${t===1?")":`, ${t})`}`}}));function Da(t){return t=(t||0)%360,t<0?t+360:t}function Wr(t){return Math.max(0,Math.min(1,t||0))}function to(t,e,r){return(t<60?e+(r-e)*t/60:t<180?r:t<240?e+(r-e)*(240-t)/60:e)*255}var Ia=Math.PI/180,Na=180/Math.PI;var Fa=-.14861,ro=1.78277,no=-.29227,jr=-.90649,ar=1.97294,Ba=ar*jr,Ra=ar*ro,Ha=ro*no-jr*Fa;function Uf(t){if(t instanceof se)return new se(t.h,t.s,t.l,t.opacity);t instanceof et||(t=eo(t));var e=t.r/255,r=t.g/255,n=t.b/255,o=(Ha*n+Ba*e-Ra*r)/(Ha+Ba-Ra),i=n-o,a=(ar*(r-o)-no*i)/jr,s=Math.sqrt(a*a+i*i)/(ar*o*(1-o)),u=s?Math.atan2(a,i)*Na-120:NaN;return new se(u<0?u+360:u,s,o,t.opacity)}function mt(t,e,r,n){return arguments.length===1?Uf(t):new se(t,e,r,n??1)}function se(t,e,r,n){this.h=+t,this.s=+e,this.l=+r,this.opacity=+n}De(se,mt,or(ae,{brighter(t){return t=t==null?ie:Math.pow(ie,t),new se(this.h,this.s,this.l*t,this.opacity)},darker(t){return t=t==null?Ot:Math.pow(Ot,t),new se(this.h,this.s,this.l*t,this.opacity)},rgb(){var t=isNaN(this.h)?0:(this.h+120)*Ia,e=+this.l,r=isNaN(this.s)?0:this.s*e*(1-e),n=Math.cos(t),o=Math.sin(t);return new et(255*(e+r*(Fa*n+ro*o)),255*(e+r*(no*n+jr*o)),255*(e+r*(ar*n)),this.opacity)}}));function oo(t,e,r,n,o){var i=t*t,a=i*t;return((1-3*t+3*i-a)*e+(4-6*i+3*a)*r+(1+3*t+3*i-3*a)*n+a*o)/6}function Oa(t){var e=t.length-1;return function(r){var n=r<=0?r=0:r>=1?(r=1,e-1):Math.floor(r*e),o=t[n],i=t[n+1],a=n>0?t[n-1]:2*o-i,s=n()=>t;function $a(t,e){return function(r){return t+r*e}}function Gf(t,e,r){return t=Math.pow(t,r),e=Math.pow(e,r)-t,r=1/r,function(n){return Math.pow(t+n*e,r)}}function Ua(t,e){var r=e-t;return r?$a(t,r>180||r<-180?r-360*Math.round(r/360):r):Ie(isNaN(t)?e:t)}function Ga(t){return(t=+t)==1?Nt:function(e,r){return r-e?Gf(e,r,t):Ie(isNaN(e)?r:e)}}function Nt(t,e){var r=e-t;return r?$a(t,r):Ie(isNaN(t)?e:t)}var ue=function t(e){var r=Ga(e);function n(o,i){var a=r((o=qe(o)).r,(i=qe(i)).r),s=r(o.g,i.g),u=r(o.b,i.b),c=Nt(o.opacity,i.opacity);return function(l){return o.r=a(l),o.g=s(l),o.b=u(l),o.opacity=c(l),o+""}}return n.gamma=t,n}(1);function Va(t){return function(e){var r=e.length,n=new Array(r),o=new Array(r),i=new Array(r),a,s;for(a=0;ar&&(i=e.slice(r,i),s[a]?s[a]+=i:s[++a]=i),(n=n[0])===(o=o[0])?s[a]?s[a]+=o:s[++a]=o:(s[++a]=null,u.push({i:a,x:Z(n,o)})),r=io.lastIndex;return r180?l+=360:l-c>180&&(c+=360),f.push({i:p.push(o(p)+"rotate(",null,n)-2,x:Z(c,l)})):l&&p.push(o(p)+"rotate("+l+n)}function s(c,l,p,f){c!==l?f.push({i:p.push(o(p)+"skewX(",null,n)-2,x:Z(c,l)}):l&&p.push(o(p)+"skewX("+l+n)}function u(c,l,p,f,m,d){if(c!==p||l!==f){var y=m.push(o(m)+"scale(",null,",",null,")");d.push({i:y-4,x:Z(c,p)},{i:y-2,x:Z(l,f)})}else(p!==1||f!==1)&&m.push(o(m)+"scale("+p+","+f+")")}return function(c,l){var p=[],f=[];return c=t(c),l=t(l),i(c.translateX,c.translateY,l.translateX,l.translateY,p,f),a(c.rotate,l.rotate,p,f),s(c.skewX,l.skewX,p,f),u(c.scaleX,c.scaleY,l.scaleX,l.scaleY,p,f),c=l=null,function(m){for(var d=-1,y=f.length,k;++d=0&&t._call.call(void 0,e),t=t._next;--Ne}function es(){le=(tn=pr.now())+en,Ne=cr=0;try{os()}finally{Ne=0,Jf(),le=0}}function Zf(){var t=pr.now(),e=t-tn;e>rs&&(en-=e,tn=t)}function Jf(){for(var t,e=Kr,r,n=1/0;e;)e._call?(n>e._time&&(n=e._time),t=e,e=e._next):(r=e._next,e._next=null,e=t?t._next=r:Kr=r);fr=t,co(n)}function co(t){if(!Ne){cr&&(cr=clearTimeout(cr));var e=t-le;e>24?(t<1/0&&(cr=setTimeout(es,t-pr.now()-en)),lr&&(lr=clearInterval(lr))):(lr||(tn=pr.now(),lr=setInterval(Zf,rs)),Ne=1,ns(es))}}function nn(t,e,r){var n=new mr;return e=e==null?0:+e,n.restart(o=>{n.stop(),t(o+e)},e,r),n}var Qf=jn("start","end","cancel","interrupt"),Kf=[],ss=0,is=1,an=2,on=3,as=4,sn=5,hr=6;function Pt(t,e,r,n,o,i){var a=t.__transition;if(!a)t.__transition={};else if(r in a)return;tp(t,r,{name:e,index:n,group:o,on:Qf,tween:Kf,time:i.time,delay:i.delay,duration:i.duration,ease:i.ease,timer:null,state:ss})}function gr(t,e){var r=J(t,e);if(r.state>ss)throw new Error("too late; already scheduled");return r}function rt(t,e){var r=J(t,e);if(r.state>on)throw new Error("too late; already running");return r}function J(t,e){var r=t.__transition;if(!r||!(r=r[e]))throw new Error("transition not found");return r}function tp(t,e,r){var n=t.__transition,o;n[e]=r,r.timer=rn(i,0,r.time);function i(c){r.state=is,r.timer.restart(a,r.delay,r.time),r.delay<=c&&a(c-r.delay)}function a(c){var l,p,f,m;if(r.state!==is)return u();for(l in n)if(m=n[l],m.name===r.name){if(m.state===on)return nn(a);m.state===as?(m.state=hr,m.timer.stop(),m.on.call("interrupt",t,t.__data__,m.index,m.group),delete n[l]):+lan&&n.state=0&&(e=e.slice(0,r)),!e||e==="start"})}function bp(t,e,r){var n,o,i=vp(e)?gr:rt;return function(){var a=i(this,t),s=a.on;s!==n&&(o=(n=s).copy()).on(e,r),a.on=o}}function ys(t,e){var r=this._id;return arguments.length<2?J(this.node(),r).on.on(t):this.each(bp(r,t,e))}function wp(t){return function(){var e=this.parentNode;for(var r in this.__transition)if(+r!==t)return;e&&e.removeChild(this)}}function vs(){return this.on("end.remove",wp(this._id))}function bs(t){var e=this._name,r=this._id;typeof t!="function"&&(t=re(t));for(var n=this._groups,o=n.length,i=new Array(o),a=0;a=0))throw new Error(`invalid digits: ${t}`);if(e>15)return qs;let r=10**e;return function(n){this._+=n[0];for(let o=1,i=n.length;oce)if(!(Math.abs(p*u-c*l)>ce)||!i)this._append`L${this._x1=e},${this._y1=r}`;else{let m=n-a,d=o-s,y=u*u+c*c,k=m*m+d*d,_=Math.sqrt(y),q=Math.sqrt(f),A=i*Math.tan((po-Math.acos((y+f-k)/(2*_*q)))/2),D=A/q,L=A/_;Math.abs(D-1)>ce&&this._append`L${e+D*l},${r+D*p}`,this._append`A${i},${i},0,0,${+(p*m>l*d)},${this._x1=e+L*u},${this._y1=r+L*c}`}}arc(e,r,n,o,i,a){if(e=+e,r=+r,n=+n,a=!!a,n<0)throw new Error(`negative radius: ${n}`);let s=n*Math.cos(o),u=n*Math.sin(o),c=e+s,l=r+u,p=1^a,f=a?o-i:i-o;this._x1===null?this._append`M${c},${l}`:(Math.abs(this._x1-c)>ce||Math.abs(this._y1-l)>ce)&&this._append`L${c},${l}`,n&&(f<0&&(f=f%mo+mo),f>Hp?this._append`A${n},${n},0,1,${p},${e-s},${r-u}A${n},${n},0,1,${p},${this._x1=c},${this._y1=l}`:f>ce&&this._append`A${n},${n},0,${+(f>=po)},${p},${this._x1=e+n*Math.cos(i)},${this._y1=r+n*Math.sin(i)}`)}rect(e,r,n,o){this._append`M${this._x0=this._x1=+e},${this._y0=this._y1=+r}h${n=+n}v${+o}h${-n}Z`}toString(){return this._}};function Is(){return new fe}Is.prototype=fe.prototype;function Ns(t){return Math.abs(t=Math.round(t))>=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)}function pe(t,e){if((r=(t=e?t.toExponential(e-1):t.toExponential()).indexOf("e"))<0)return null;var r,n=t.slice(0,r);return[n.length>1?n[0]+n.slice(2):n,+t.slice(r+1)]}function Mt(t){return t=pe(Math.abs(t)),t?t[1]:NaN}function Bs(t,e){return function(r,n){for(var o=r.length,i=[],a=0,s=t[0],u=0;o>0&&s>0&&(u+s+1>n&&(s=Math.max(1,n-u)),i.push(r.substring(o-=s,o+s)),!((u+=s+1)>n));)s=t[a=(a+1)%t.length];return i.reverse().join(e)}}function Rs(t){return function(e){return e.replace(/[0-9]/g,function(r){return t[+r]})}}var Op=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function St(t){if(!(e=Op.exec(t)))throw new Error("invalid format: "+t);var e;return new pn({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}St.prototype=pn.prototype;function pn(t){this.fill=t.fill===void 0?" ":t.fill+"",this.align=t.align===void 0?">":t.align+"",this.sign=t.sign===void 0?"-":t.sign+"",this.symbol=t.symbol===void 0?"":t.symbol+"",this.zero=!!t.zero,this.width=t.width===void 0?void 0:+t.width,this.comma=!!t.comma,this.precision=t.precision===void 0?void 0:+t.precision,this.trim=!!t.trim,this.type=t.type===void 0?"":t.type+""}pn.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(this.width===void 0?"":Math.max(1,this.width|0))+(this.comma?",":"")+(this.precision===void 0?"":"."+Math.max(0,this.precision|0))+(this.trim?"~":"")+this.type};function Hs(t){t:for(var e=t.length,r=1,n=-1,o;r0&&(n=0);break}return n>0?t.slice(0,n)+t.slice(o+1):t}var ho;function Fs(t,e){var r=pe(t,e);if(!r)return t+"";var n=r[0],o=r[1],i=o-(ho=Math.max(-8,Math.min(8,Math.floor(o/3)))*3)+1,a=n.length;return i===a?n:i>a?n+new Array(i-a+1).join("0"):i>0?n.slice(0,i)+"."+n.slice(i):"0."+new Array(1-i).join("0")+pe(t,Math.max(0,e+i-1))[0]}function go(t,e){var r=pe(t,e);if(!r)return t+"";var n=r[0],o=r[1];return o<0?"0."+new Array(-o).join("0")+n:n.length>o+1?n.slice(0,o+1)+"."+n.slice(o+1):n+new Array(o-n.length+2).join("0")}var xo={"%":(t,e)=>(t*100).toFixed(e),b:t=>Math.round(t).toString(2),c:t=>t+"",d:Ns,e:(t,e)=>t.toExponential(e),f:(t,e)=>t.toFixed(e),g:(t,e)=>t.toPrecision(e),o:t=>Math.round(t).toString(8),p:(t,e)=>go(t*100,e),r:go,s:Fs,X:t=>Math.round(t).toString(16).toUpperCase(),x:t=>Math.round(t).toString(16)};function yo(t){return t}var Os=Array.prototype.map,Ps=["y","z","a","f","p","n","\xB5","m","","k","M","G","T","P","E","Z","Y"];function $s(t){var e=t.grouping===void 0||t.thousands===void 0?yo:Bs(Os.call(t.grouping,Number),t.thousands+""),r=t.currency===void 0?"":t.currency[0]+"",n=t.currency===void 0?"":t.currency[1]+"",o=t.decimal===void 0?".":t.decimal+"",i=t.numerals===void 0?yo:Rs(Os.call(t.numerals,String)),a=t.percent===void 0?"%":t.percent+"",s=t.minus===void 0?"\u2212":t.minus+"",u=t.nan===void 0?"NaN":t.nan+"";function c(p){p=St(p);var f=p.fill,m=p.align,d=p.sign,y=p.symbol,k=p.zero,_=p.width,q=p.comma,A=p.precision,D=p.trim,L=p.type;L==="n"?(q=!0,L="g"):xo[L]||(A===void 0&&(A=12),D=!0,L="g"),(k||f==="0"&&m==="=")&&(k=!0,f="0",m="=");var b=y==="$"?r:y==="#"&&/[boxX]/.test(L)?"0"+L.toLowerCase():"",N=y==="$"?n:/[%p]/.test(L)?a:"",H=xo[L],P=/[defgprs%]/.test(L);A=A===void 0?6:/[gprs]/.test(L)?Math.max(1,Math.min(21,A)):Math.max(0,Math.min(20,A));function I(h){var T=b,E=N,R,g,x;if(L==="c")E=H(h)+E,h="";else{h=+h;var M=h<0||1/h<0;if(h=isNaN(h)?u:H(Math.abs(h),A),D&&(h=Hs(h)),M&&+h==0&&d!=="+"&&(M=!1),T=(M?d==="("?d:s:d==="-"||d==="("?"":d)+T,E=(L==="s"?Ps[8+ho/3]:"")+E+(M&&d==="("?")":""),P){for(R=-1,g=h.length;++Rx||x>57){E=(x===46?o+h.slice(R+1):h.slice(R))+E,h=h.slice(0,R);break}}}q&&!k&&(h=e(h,1/0));var S=T.length+h.length+E.length,v=S<_?new Array(_-S+1).join(f):"";switch(q&&k&&(h=e(v+h,v.length?_-E.length:1/0),v=""),m){case"<":h=T+h+E+v;break;case"=":h=T+v+h+E;break;case"^":h=v.slice(0,S=v.length>>1)+T+h+E+v.slice(S);break;default:h=v+T+h+E;break}return i(h)}return I.toString=function(){return p+""},I}function l(p,f){var m=c((p=St(p),p.type="f",p)),d=Math.max(-8,Math.min(8,Math.floor(Mt(f)/3)))*3,y=Math.pow(10,-d),k=Ps[8+d/3];return function(_){return m(y*_)+k}}return{format:c,formatPrefix:l}}var mn,bt,dn;vo({thousands:",",grouping:[3],currency:["$",""]});function vo(t){return mn=$s(t),bt=mn.format,dn=mn.formatPrefix,mn}function bo(t){return Math.max(0,-Mt(Math.abs(t)))}function wo(t,e){return Math.max(0,Math.max(-8,Math.min(8,Math.floor(Mt(e)/3)))*3-Mt(Math.abs(t)))}function ko(t,e){return t=Math.abs(t),e=Math.abs(e)-t,Math.max(0,Mt(e)-Mt(t))+1}function Pp(t){var e=0,r=t.children,n=r&&r.length;if(!n)e=1;else for(;--n>=0;)e+=r[n].value;t.value=e}function Us(){return this.eachAfter(Pp)}function Gs(t,e){let r=-1;for(let n of this)t.call(e,n,++r,this);return this}function Vs(t,e){for(var r=this,n=[r],o,i,a=-1;r=n.pop();)if(t.call(e,r,++a,this),o=r.children)for(i=o.length-1;i>=0;--i)n.push(o[i]);return this}function zs(t,e){for(var r=this,n=[r],o=[],i,a,s,u=-1;r=n.pop();)if(o.push(r),i=r.children)for(a=0,s=i.length;a=0;)r+=n[o].value;e.value=r})}function Xs(t){return this.eachBefore(function(e){e.children&&e.children.sort(t)})}function js(t){for(var e=this,r=$p(e,t),n=[e];e!==r;)e=e.parent,n.push(e);for(var o=n.length;t!==r;)n.splice(o,0,t),t=t.parent;return n}function $p(t,e){if(t===e)return t;var r=t.ancestors(),n=e.ancestors(),o=null;for(t=r.pop(),e=n.pop();t===e;)o=t,t=r.pop(),e=n.pop();return o}function Zs(){for(var t=this,e=[t];t=t.parent;)e.push(t);return e}function Js(){return Array.from(this)}function Qs(){var t=[];return this.eachBefore(function(e){e.children||t.push(e)}),t}function Ks(){var t=this,e=[];return t.each(function(r){r!==t&&e.push({source:r.parent,target:r})}),e}function*tu(){var t=this,e,r=[t],n,o,i;do for(e=r.reverse(),r=[];t=e.pop();)if(yield t,n=t.children)for(o=0,i=n.length;o=0;--s)o.push(i=a[s]=new yr(a[s])),i.parent=n,i.depth=n.depth+1;return r.eachBefore(Yp)}function Up(){return Re(this).eachBefore(zp)}function Gp(t){return t.children}function Vp(t){return Array.isArray(t)?t[1]:null}function zp(t){t.data.value!==void 0&&(t.value=t.data.value),t.data=t.data.data}function Yp(t){var e=0;do t.height=e;while((t=t.parent)&&t.height<++e)}function yr(t){this.data=t,this.depth=this.height=0,this.parent=null}yr.prototype=Re.prototype={constructor:yr,count:Us,each:Gs,eachAfter:zs,eachBefore:Vs,find:Ys,sum:Ws,sort:Xs,path:js,ancestors:Zs,descendants:Js,leaves:Qs,links:Ks,copy:Up,[Symbol.iterator]:tu};function eu(t){if(typeof t!="function")throw new Error;return t}function He(){return 0}function Fe(t){return function(){return t}}function ru(t){t.x0=Math.round(t.x0),t.y0=Math.round(t.y0),t.x1=Math.round(t.x1),t.y1=Math.round(t.y1)}function nu(t,e,r,n,o){for(var i=t.children,a,s=-1,u=i.length,c=t.value&&(n-e)/t.value;++sq&&(q=c),b=k*k*L,A=Math.max(q/b,b/_),A>D){k-=c;break}D=A}a.push(u={value:k,dice:m1?n:1)},r}(Wp);function _o(){var t=vr,e=!1,r=1,n=1,o=[0],i=He,a=He,s=He,u=He,c=He;function l(f){return f.x0=f.y0=0,f.x1=r,f.y1=n,f.eachBefore(p),o=[0],e&&f.eachBefore(ru),f}function p(f){var m=o[f.depth],d=f.x0+m,y=f.y0+m,k=f.x1-m,_=f.y1-m;ke&&(r=t,t=e,e=r),function(n){return Math.max(t,Math.min(e,n))}}function Zp(t,e,r){var n=t[0],o=t[1],i=e[0],a=e[1];return o2?Jp:Zp,u=c=null,p}function p(f){return f==null||isNaN(f=+f)?i:(u||(u=s(t.map(n),e,r)))(n(a(f)))}return p.invert=function(f){return a(o((c||(c=s(e,t.map(n),Z)))(f)))},p.domain=function(f){return arguments.length?(t=Array.from(f,So),l()):t.slice()},p.range=function(f){return arguments.length?(e=Array.from(f),l()):e.slice()},p.rangeRound=function(f){return e=Array.from(f),r=ur,l()},p.clamp=function(f){return arguments.length?(a=f?!0:Ut,l()):a!==Ut},p.interpolate=function(f){return arguments.length?(r=f,l()):r},p.unknown=function(f){return arguments.length?(i=f,p):i},function(f,m){return n=f,o=m,l()}}function wr(){return Qp()(Ut,Ut)}function Eo(t,e,r,n){var o=Le(t,e,r),i;switch(n=St(n??",f"),n.type){case"s":{var a=Math.max(Math.abs(t),Math.abs(e));return n.precision==null&&!isNaN(i=wo(o,a))&&(n.precision=i),dn(n,a)}case"":case"e":case"g":case"p":case"r":{n.precision==null&&!isNaN(i=ko(o,Math.max(Math.abs(t),Math.abs(e))))&&(n.precision=i-(n.type==="e"));break}case"f":case"%":{n.precision==null&&!isNaN(i=bo(o))&&(n.precision=i-(n.type==="%")*2);break}}return bt(n)}function Kp(t){var e=t.domain;return t.ticks=function(r){var n=e();return te(n[0],n[n.length-1],r??10)},t.tickFormat=function(r,n){var o=e();return Eo(o[0],o[o.length-1],r??10,n)},t.nice=function(r){r==null&&(r=10);var n=e(),o=0,i=n.length-1,a=n[o],s=n[i],u,c,l=10;for(s0;){if(c=Qe(a,s,r),c===u)return n[o]=a,n[i]=s,e(n);if(c>0)a=Math.floor(a/c)*c,s=Math.ceil(s/c)*c;else if(c<0)a=Math.ceil(a*c)/c,s=Math.floor(s*c)/c;else break;u=c}return t},t}function de(){var t=wr();return t.copy=function(){return hn(t,de())},$t.apply(t,arguments),Kp(t)}function kr(t,e){t=t.slice();var r=0,n=t.length-1,o=t[r],i=t[n],a;return iMath.pow(t,e)}function om(t){return t===Math.E?Math.log:t===10&&Math.log10||t===2&&Math.log2||(t=Math.log(t),e=>Math.log(e)/t)}function lu(t){return(e,r)=>-t(-e,r)}function cu(t){let e=t(su,uu),r=e.domain,n=10,o,i;function a(){return o=om(n),i=nm(n),r()[0]<0?(o=lu(o),i=lu(i),t(tm,em)):t(su,uu),e}return e.base=function(s){return arguments.length?(n=+s,a()):n},e.domain=function(s){return arguments.length?(r(s),a()):r()},e.ticks=s=>{let u=r(),c=u[0],l=u[u.length-1],p=l0){for(;f<=m;++f)for(d=1;dl)break;_.push(y)}}else for(;f<=m;++f)for(d=n-1;d>=1;--d)if(y=f>0?d/i(-f):d*i(f),!(yl)break;_.push(y)}_.length*2{if(s==null&&(s=10),u==null&&(u=n===10?"s":","),typeof u!="function"&&(!(n%1)&&(u=St(u)).precision==null&&(u.trim=!0),u=bt(u)),s===1/0)return u;let c=Math.max(1,n*s/e.ticks().length);return l=>{let p=l/i(Math.round(o(l)));return p*nr(kr(r(),{floor:s=>i(Math.floor(o(s))),ceil:s=>i(Math.ceil(o(s)))})),e}var Ao=new Date,Do=new Date;function z(t,e,r,n){function o(i){return t(i=arguments.length===0?new Date:new Date(+i)),i}return o.floor=i=>(t(i=new Date(+i)),i),o.ceil=i=>(t(i=new Date(i-1)),e(i,1),t(i),i),o.round=i=>{let a=o(i),s=o.ceil(i);return i-a(e(i=new Date(+i),a==null?1:Math.floor(a)),i),o.range=(i,a,s)=>{let u=[];if(i=o.ceil(i),s=s==null?1:Math.floor(s),!(i0))return u;let c;do u.push(c=new Date(+i)),e(i,s),t(i);while(cz(a=>{if(a>=a)for(;t(a),!i(a);)a.setTime(a-1)},(a,s)=>{if(a>=a)if(s<0)for(;++s<=0;)for(;e(a,-1),!i(a););else for(;--s>=0;)for(;e(a,1),!i(a););}),r&&(o.count=(i,a)=>(Ao.setTime(+i),Do.setTime(+a),t(Ao),t(Do),Math.floor(r(Ao,Do))),o.every=i=>(i=Math.floor(i),!isFinite(i)||!(i>0)?null:i>1?o.filter(n?a=>n(a)%i===0:a=>o.count(0,a)%i===0):o)),o}var _r=z(()=>{},(t,e)=>{t.setTime(+t+e)},(t,e)=>e-t);_r.every=t=>(t=Math.floor(t),!isFinite(t)||!(t>0)?null:t>1?z(e=>{e.setTime(Math.floor(e/t)*t)},(e,r)=>{e.setTime(+e+r*t)},(e,r)=>(r-e)/t):_r);var m_=_r.range;var Lt=z(t=>{t.setTime(t-t.getMilliseconds())},(t,e)=>{t.setTime(+t+e*1e3)},(t,e)=>(e-t)/1e3,t=>t.getUTCSeconds()),fu=Lt.range;var Oe=z(t=>{t.setTime(t-t.getMilliseconds()-t.getSeconds()*1e3)},(t,e)=>{t.setTime(+t+e*6e4)},(t,e)=>(e-t)/6e4,t=>t.getMinutes()),im=Oe.range,gn=z(t=>{t.setUTCSeconds(0,0)},(t,e)=>{t.setTime(+t+e*6e4)},(t,e)=>(e-t)/6e4,t=>t.getUTCMinutes()),am=gn.range;var Pe=z(t=>{t.setTime(t-t.getMilliseconds()-t.getSeconds()*1e3-t.getMinutes()*6e4)},(t,e)=>{t.setTime(+t+e*36e5)},(t,e)=>(e-t)/36e5,t=>t.getHours()),sm=Pe.range,xn=z(t=>{t.setUTCMinutes(0,0,0)},(t,e)=>{t.setTime(+t+e*36e5)},(t,e)=>(e-t)/36e5,t=>t.getUTCHours()),um=xn.range;var Rt=z(t=>t.setHours(0,0,0,0),(t,e)=>t.setDate(t.getDate()+e),(t,e)=>(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*6e4)/864e5,t=>t.getDate()-1),lm=Rt.range,Mr=z(t=>{t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+e)},(t,e)=>(e-t)/864e5,t=>t.getUTCDate()-1),cm=Mr.range,yn=z(t=>{t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+e)},(t,e)=>(e-t)/864e5,t=>Math.floor(t/864e5)),fm=yn.range;function xe(t){return z(e=>{e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)},(e,r)=>{e.setDate(e.getDate()+r*7)},(e,r)=>(r-e-(r.getTimezoneOffset()-e.getTimezoneOffset())*6e4)/6048e5)}var Ht=xe(0),$e=xe(1),mu=xe(2),du=xe(3),Gt=xe(4),hu=xe(5),gu=xe(6),xu=Ht.range,pm=$e.range,mm=mu.range,dm=du.range,hm=Gt.range,gm=hu.range,xm=gu.range;function ye(t){return z(e=>{e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)},(e,r)=>{e.setUTCDate(e.getUTCDate()+r*7)},(e,r)=>(r-e)/6048e5)}var ve=ye(0),Ue=ye(1),yu=ye(2),vu=ye(3),Vt=ye(4),bu=ye(5),wu=ye(6),ku=ve.range,ym=Ue.range,vm=yu.range,bm=vu.range,wm=Vt.range,km=bu.range,_m=wu.range;var Ge=z(t=>{t.setDate(1),t.setHours(0,0,0,0)},(t,e)=>{t.setMonth(t.getMonth()+e)},(t,e)=>e.getMonth()-t.getMonth()+(e.getFullYear()-t.getFullYear())*12,t=>t.getMonth()),Tm=Ge.range,vn=z(t=>{t.setUTCDate(1),t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCMonth(t.getUTCMonth()+e)},(t,e)=>e.getUTCMonth()-t.getUTCMonth()+(e.getUTCFullYear()-t.getUTCFullYear())*12,t=>t.getUTCMonth()),Mm=vn.range;var pt=z(t=>{t.setMonth(0,1),t.setHours(0,0,0,0)},(t,e)=>{t.setFullYear(t.getFullYear()+e)},(t,e)=>e.getFullYear()-t.getFullYear(),t=>t.getFullYear());pt.every=t=>!isFinite(t=Math.floor(t))||!(t>0)?null:z(e=>{e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)},(e,r)=>{e.setFullYear(e.getFullYear()+r*t)});var Sm=pt.range,wt=z(t=>{t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCFullYear(t.getUTCFullYear()+e)},(t,e)=>e.getUTCFullYear()-t.getUTCFullYear(),t=>t.getUTCFullYear());wt.every=t=>!isFinite(t=Math.floor(t))||!(t>0)?null:z(e=>{e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)},(e,r)=>{e.setUTCFullYear(e.getUTCFullYear()+r*t)});var Lm=wt.range;function Tu(t,e,r,n,o,i){let a=[[Lt,1,1e3],[Lt,5,5*1e3],[Lt,15,15*1e3],[Lt,30,30*1e3],[i,1,6e4],[i,5,5*6e4],[i,15,15*6e4],[i,30,30*6e4],[o,1,36e5],[o,3,3*36e5],[o,6,6*36e5],[o,12,12*36e5],[n,1,864e5],[n,2,2*864e5],[r,1,6048e5],[e,1,2592e6],[e,3,3*2592e6],[t,1,31536e6]];function s(c,l,p){let f=lk).right(a,f);if(m===a.length)return t.every(Le(c/31536e6,l/31536e6,p));if(m===0)return _r.every(Math.max(Le(c,l,p),1));let[d,y]=a[f/a[m-1][2]53)return null;"w"in w||(w.w=1),"Z"in w?(Y=No(Sr(w.y,0,1)),lt=Y.getUTCDay(),Y=lt>4||lt===0?Ue.ceil(Y):Ue(Y),Y=Mr.offset(Y,(w.V-1)*7),w.y=Y.getUTCFullYear(),w.m=Y.getUTCMonth(),w.d=Y.getUTCDate()+(w.w+6)%7):(Y=Io(Sr(w.y,0,1)),lt=Y.getDay(),Y=lt>4||lt===0?$e.ceil(Y):$e(Y),Y=Rt.offset(Y,(w.V-1)*7),w.y=Y.getFullYear(),w.m=Y.getMonth(),w.d=Y.getDate()+(w.w+6)%7)}else("W"in w||"U"in w)&&("w"in w||(w.w="u"in w?w.u%7:"W"in w?1:0),lt="Z"in w?No(Sr(w.y,0,1)).getUTCDay():Io(Sr(w.y,0,1)).getDay(),w.m=0,w.d="W"in w?(w.w+6)%7+w.W*7-(lt+5)%7:w.w+w.U*7-(lt+6)%7);return"Z"in w?(w.H+=w.Z/100|0,w.M+=w.Z%100,No(w)):Io(w)}}function H(C,F,U,w){for(var st=0,Y=F.length,lt=U.length,ct,Qt;st=lt)return-1;if(ct=F.charCodeAt(st++),ct===37){if(ct=F.charAt(st++),Qt=L[ct in Mu?F.charAt(st++):ct],!Qt||(w=Qt(C,U,w))<0)return-1}else if(ct!=U.charCodeAt(w++))return-1}return w}function P(C,F,U){var w=c.exec(F.slice(U));return w?(C.p=l.get(w[0].toLowerCase()),U+w[0].length):-1}function I(C,F,U){var w=m.exec(F.slice(U));return w?(C.w=d.get(w[0].toLowerCase()),U+w[0].length):-1}function h(C,F,U){var w=p.exec(F.slice(U));return w?(C.w=f.get(w[0].toLowerCase()),U+w[0].length):-1}function T(C,F,U){var w=_.exec(F.slice(U));return w?(C.m=q.get(w[0].toLowerCase()),U+w[0].length):-1}function E(C,F,U){var w=y.exec(F.slice(U));return w?(C.m=k.get(w[0].toLowerCase()),U+w[0].length):-1}function R(C,F,U){return H(C,e,F,U)}function g(C,F,U){return H(C,r,F,U)}function x(C,F,U){return H(C,n,F,U)}function M(C){return a[C.getDay()]}function S(C){return i[C.getDay()]}function v(C){return u[C.getMonth()]}function B(C){return s[C.getMonth()]}function G(C){return o[+(C.getHours()>=12)]}function K(C){return 1+~~(C.getMonth()/3)}function ot(C){return a[C.getUTCDay()]}function ht(C){return i[C.getUTCDay()]}function tt(C){return u[C.getUTCMonth()]}function Dt(C){return s[C.getUTCMonth()]}function gt(C){return o[+(C.getUTCHours()>=12)]}function Je(C){return 1+~~(C.getUTCMonth()/3)}return{format:function(C){var F=b(C+="",A);return F.toString=function(){return C},F},parse:function(C){var F=N(C+="",!1);return F.toString=function(){return C},F},utcFormat:function(C){var F=b(C+="",D);return F.toString=function(){return C},F},utcParse:function(C){var F=N(C+="",!0);return F.toString=function(){return C},F}}}var Mu={"-":"",_:" ",0:"0"},Q=/^\s*\d+/,Cm=/^%/,qm=/[\\^$*+?|[\]().{}]/g;function V(t,e,r){var n=t<0?"-":"",o=(n?-t:t)+"",i=o.length;return n+(i[e.toLowerCase(),r]))}function Nm(t,e,r){var n=Q.exec(e.slice(r,r+1));return n?(t.w=+n[0],r+n[0].length):-1}function Bm(t,e,r){var n=Q.exec(e.slice(r,r+1));return n?(t.u=+n[0],r+n[0].length):-1}function Rm(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.U=+n[0],r+n[0].length):-1}function Hm(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.V=+n[0],r+n[0].length):-1}function Fm(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.W=+n[0],r+n[0].length):-1}function Su(t,e,r){var n=Q.exec(e.slice(r,r+4));return n?(t.y=+n[0],r+n[0].length):-1}function Lu(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.y=+n[0]+(+n[0]>68?1900:2e3),r+n[0].length):-1}function Om(t,e,r){var n=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(r,r+6));return n?(t.Z=n[1]?0:-(n[2]+(n[3]||"00")),r+n[0].length):-1}function Pm(t,e,r){var n=Q.exec(e.slice(r,r+1));return n?(t.q=n[0]*3-3,r+n[0].length):-1}function $m(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.m=n[0]-1,r+n[0].length):-1}function Eu(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.d=+n[0],r+n[0].length):-1}function Um(t,e,r){var n=Q.exec(e.slice(r,r+3));return n?(t.m=0,t.d=+n[0],r+n[0].length):-1}function Au(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.H=+n[0],r+n[0].length):-1}function Gm(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.M=+n[0],r+n[0].length):-1}function Vm(t,e,r){var n=Q.exec(e.slice(r,r+2));return n?(t.S=+n[0],r+n[0].length):-1}function zm(t,e,r){var n=Q.exec(e.slice(r,r+3));return n?(t.L=+n[0],r+n[0].length):-1}function Ym(t,e,r){var n=Q.exec(e.slice(r,r+6));return n?(t.L=Math.floor(n[0]/1e3),r+n[0].length):-1}function Wm(t,e,r){var n=Cm.exec(e.slice(r,r+1));return n?r+n[0].length:-1}function Xm(t,e,r){var n=Q.exec(e.slice(r));return n?(t.Q=+n[0],r+n[0].length):-1}function jm(t,e,r){var n=Q.exec(e.slice(r));return n?(t.s=+n[0],r+n[0].length):-1}function Du(t,e){return V(t.getDate(),e,2)}function Zm(t,e){return V(t.getHours(),e,2)}function Jm(t,e){return V(t.getHours()%12||12,e,2)}function Qm(t,e){return V(1+Rt.count(pt(t),t),e,3)}function Bu(t,e){return V(t.getMilliseconds(),e,3)}function Km(t,e){return Bu(t,e)+"000"}function td(t,e){return V(t.getMonth()+1,e,2)}function ed(t,e){return V(t.getMinutes(),e,2)}function rd(t,e){return V(t.getSeconds(),e,2)}function nd(t){var e=t.getDay();return e===0?7:e}function od(t,e){return V(Ht.count(pt(t)-1,t),e,2)}function Ru(t){var e=t.getDay();return e>=4||e===0?Gt(t):Gt.ceil(t)}function id(t,e){return t=Ru(t),V(Gt.count(pt(t),t)+(pt(t).getDay()===4),e,2)}function ad(t){return t.getDay()}function sd(t,e){return V($e.count(pt(t)-1,t),e,2)}function ud(t,e){return V(t.getFullYear()%100,e,2)}function ld(t,e){return t=Ru(t),V(t.getFullYear()%100,e,2)}function cd(t,e){return V(t.getFullYear()%1e4,e,4)}function fd(t,e){var r=t.getDay();return t=r>=4||r===0?Gt(t):Gt.ceil(t),V(t.getFullYear()%1e4,e,4)}function pd(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+V(e/60|0,"0",2)+V(e%60,"0",2)}function Cu(t,e){return V(t.getUTCDate(),e,2)}function md(t,e){return V(t.getUTCHours(),e,2)}function dd(t,e){return V(t.getUTCHours()%12||12,e,2)}function hd(t,e){return V(1+Mr.count(wt(t),t),e,3)}function Hu(t,e){return V(t.getUTCMilliseconds(),e,3)}function gd(t,e){return Hu(t,e)+"000"}function xd(t,e){return V(t.getUTCMonth()+1,e,2)}function yd(t,e){return V(t.getUTCMinutes(),e,2)}function vd(t,e){return V(t.getUTCSeconds(),e,2)}function bd(t){var e=t.getUTCDay();return e===0?7:e}function wd(t,e){return V(ve.count(wt(t)-1,t),e,2)}function Fu(t){var e=t.getUTCDay();return e>=4||e===0?Vt(t):Vt.ceil(t)}function kd(t,e){return t=Fu(t),V(Vt.count(wt(t),t)+(wt(t).getUTCDay()===4),e,2)}function _d(t){return t.getUTCDay()}function Td(t,e){return V(Ue.count(wt(t)-1,t),e,2)}function Md(t,e){return V(t.getUTCFullYear()%100,e,2)}function Sd(t,e){return t=Fu(t),V(t.getUTCFullYear()%100,e,2)}function Ld(t,e){return V(t.getUTCFullYear()%1e4,e,4)}function Ed(t,e){var r=t.getUTCDay();return t=r>=4||r===0?Vt(t):Vt.ceil(t),V(t.getUTCFullYear()%1e4,e,4)}function Ad(){return"+0000"}function qu(){return"%"}function Iu(t){return+t}function Nu(t){return Math.floor(+t/1e3)}var Ve,bn,Ou,Pu,$u;Ro({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});function Ro(t){return Ve=Bo(t),bn=Ve.format,Ou=Ve.parse,Pu=Ve.utcFormat,$u=Ve.utcParse,Ve}function Dd(t){return new Date(t)}function Cd(t){return t instanceof Date?+t:+new Date(+t)}function Uu(t,e,r,n,o,i,a,s,u,c){var l=wr(),p=l.invert,f=l.domain,m=c(".%L"),d=c(":%S"),y=c("%I:%M"),k=c("%I %p"),_=c("%a %d"),q=c("%b %d"),A=c("%B"),D=c("%Y");function L(b){return(u(b)1?0:t<-1?ze:Math.acos(t)}function Po(t){return t>=1?Ar:t<=-1?-Ar:Math.asin(t)}function Tn(t){let e=3;return t.digits=function(r){if(!arguments.length)return e;if(r==null)e=null;else{let n=Math.floor(r);if(!(n>=0))throw new RangeError(`invalid digits: ${r}`);e=n}return t},()=>new fe(e)}function Bd(t){return t.innerRadius}function Rd(t){return t.outerRadius}function Hd(t){return t.startAngle}function Fd(t){return t.endAngle}function Od(t){return t&&t.padAngle}function Pd(t,e,r,n,o,i,a,s){var u=r-t,c=n-e,l=a-o,p=s-i,f=p*u-l*c;if(!(f*fR*R+g*g&&(H=I,P=h),{cx:H,cy:P,x01:-l,y01:-p,x11:H*(o/L-1),y11:P*(o/L-1)}}function $o(){var t=Bd,e=Rd,r=W(0),n=null,o=Hd,i=Fd,a=Od,s=null,u=Tn(c);function c(){var l,p,f=+t.apply(this,arguments),m=+e.apply(this,arguments),d=o.apply(this,arguments)-Ar,y=i.apply(this,arguments)-Ar,k=Oo(y-d),_=y>d;if(s||(s=l=u()),mat))s.moveTo(0,0);else if(k>Ye-at)s.moveTo(m*zt(d),m*kt(d)),s.arc(0,0,m,d,y,!_),f>at&&(s.moveTo(f*zt(y),f*kt(y)),s.arc(0,0,f,y,d,_));else{var q=d,A=y,D=d,L=y,b=k,N=k,H=a.apply(this,arguments)/2,P=H>at&&(n?+n.apply(this,arguments):be(f*f+m*m)),I=_n(Oo(m-f)/2,+r.apply(this,arguments)),h=I,T=I,E,R;if(P>at){var g=Po(P/f*kt(H)),x=Po(P/m*kt(H));(b-=g*2)>at?(g*=_?1:-1,D+=g,L-=g):(b=0,D=L=(d+y)/2),(N-=x*2)>at?(x*=_?1:-1,q+=x,A-=x):(N=0,q=A=(d+y)/2)}var M=m*zt(q),S=m*kt(q),v=f*zt(L),B=f*kt(L);if(I>at){var G=m*zt(A),K=m*kt(A),ot=f*zt(D),ht=f*kt(D),tt;if(kat?T>at?(E=Mn(ot,ht,M,S,m,T,_),R=Mn(G,K,v,B,m,T,_),s.moveTo(E.cx+E.x01,E.cy+E.y01),Tat)||!(b>at)?s.lineTo(v,B):h>at?(E=Mn(v,B,G,K,f,-h,_),R=Mn(M,S,ot,ht,f,-h,_),s.lineTo(E.cx+E.x01,E.cy+E.y01),ht?1:e>=t?0:NaN}function Ju(t){return t}function Uo(){var t=Ju,e=Zu,r=null,n=W(0),o=W(Ye),i=W(0);function a(s){var u,c=(s=Sn(s)).length,l,p,f=0,m=new Array(c),d=new Array(c),y=+n.apply(this,arguments),k=Math.min(Ye,Math.max(-Ye,o.apply(this,arguments)-y)),_,q=Math.min(Math.abs(k)/c,i.apply(this,arguments)),A=q*(k<0?-1:1),D;for(u=0;u0&&(f+=D);for(e!=null?m.sort(function(L,b){return e(d[L],d[b])}):r!=null&&m.sort(function(L,b){return r(s[L],s[b])}),u=0,p=f?(k-c*A)/f:0;u0?D*p:0)+A,d[l]={data:s[l],index:u,value:D,startAngle:y,endAngle:_,padAngle:q};return d}return a.value=function(s){return arguments.length?(t=typeof s=="function"?s:W(+s),a):t},a.sortValues=function(s){return arguments.length?(e=s,r=null,a):e},a.sort=function(s){return arguments.length?(r=s,e=null,a):r},a.startAngle=function(s){return arguments.length?(n=typeof s=="function"?s:W(+s),a):n},a.endAngle=function(s){return arguments.length?(o=typeof s=="function"?s:W(+s),a):o},a.padAngle=function(s){return arguments.length?(i=typeof s=="function"?s:W(+s),a):i},a}function Qu(t){return t<0?-1:1}function Ku(t,e,r){var n=t._x1-t._x0,o=e-t._x1,i=(t._y1-t._y0)/(n||o<0&&-0),a=(r-t._y1)/(o||n<0&&-0),s=(i*o+a*n)/(n+o);return(Qu(i)+Qu(a))*Math.min(Math.abs(i),Math.abs(a),.5*Math.abs(s))||0}function tl(t,e){var r=t._x1-t._x0;return r?(3*(t._y1-t._y0)/r-e)/2:e}function Go(t,e,r){var n=t._x0,o=t._y0,i=t._x1,a=t._y1,s=(i-n)/3;t._context.bezierCurveTo(n+s,o+s*e,i-s,a-s*r,i,a)}function Ln(t){this._context=t}Ln.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=this._t0=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x1,this._y1);break;case 3:Go(this,this._t0,tl(this,this._t0));break}(this._line||this._line!==0&&this._point===1)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){var r=NaN;if(t=+t,e=+e,!(t===this._x1&&e===this._y1)){switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,Go(this,tl(this,r=Ku(this,t,e)),r);break;default:Go(this,this._t0,r=Ku(this,t,e));break}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e,this._t0=r}}};function $d(t){this._context=new el(t)}($d.prototype=Object.create(Ln.prototype)).point=function(t,e){Ln.prototype.point.call(this,e,t)};function el(t){this._context=t}el.prototype={moveTo:function(t,e){this._context.moveTo(e,t)},closePath:function(){this._context.closePath()},lineTo:function(t,e){this._context.lineTo(e,t)},bezierCurveTo:function(t,e,r,n,o,i){this._context.bezierCurveTo(e,t,n,r,i,o)}};function En(t){return new Ln(t)}function Yt(t,e,r){this.k=t,this.x=e,this.y=r}Yt.prototype={constructor:Yt,scale:function(t){return t===1?this:new Yt(this.k*t,this.x,this.y)},translate:function(t,e){return t===0&e===0?this:new Yt(this.k,this.x+this.k*t,this.y+this.k*e)},apply:function(t){return[t[0]*this.k+this.x,t[1]*this.k+this.y]},applyX:function(t){return t*this.k+this.x},applyY:function(t){return t*this.k+this.y},invert:function(t){return[(t[0]-this.x)/this.k,(t[1]-this.y)/this.k]},invertX:function(t){return(t-this.x)/this.k},invertY:function(t){return(t-this.y)/this.k},rescaleX:function(t){return t.copy().domain(t.range().map(this.invertX,this).map(t.invert,t))},rescaleY:function(t){return t.copy().domain(t.range().map(this.invertY,this).map(t.invert,t))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var Vo=new Yt(1,0,0);zo.prototype=Yt.prototype;function zo(t){for(;!t.__zoom;)if(!(t=t.parentNode))return Vo;return t.__zoom}var dt=bt(",.0f"),Wt=bt(",.1f");function Wo(t){function e(c){return c.toString().padStart(2,"0")}let r=Math.floor(t/1e3),n=Math.floor(r/60),o=Math.floor(n/60),i=Math.floor(o/24),a=o%24,s=n%60,u=r%60;return i===0&&a===0&&s===0&&u===0?"0s":i>0?`${i}d ${e(a)}h ${e(s)}m ${e(u)}s`:a>0?`${a}h ${e(s)}m ${e(u)}s`:s>0?`${s}m ${e(u)}s`:`${u}s`}var Ud=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];function nl(t){let e=new Date(t*1e3),r=String(e.getDate()).padStart(2,"0"),n=Ud[e.getMonth()],o=e.getFullYear(),i=String(e.getHours()).padStart(2,"0"),a=String(e.getMinutes()).padStart(2,"0"),s=String(e.getSeconds()).padStart(2,"0");return`${r} ${n} ${o} ${i}:${a}:${s}`}var Yo=["bytes","kB","MB","GB","TB","PB"];function ol(t){if(!Number.isFinite(t)||t<0)return"0 bytes";let e=0,r=t;for(;r>=1e3&&eGd(a.charCodeAt(0))).length>=o.length/2?`0x${e}`:o}function Gd(t){return t<32||t>=127&&t<160}function Vd(t){if(t.length===0||t.length%2!==0)return new Uint8Array;let e=new Uint8Array(t.length/2);for(let r=0;r=1e14){let o=t/1e18;n=`${rl(o,4)} ${ll}`}else if(t>=1e5){let o=t/1e9;n=`${rl(o,4)} gwei`}else n=`${t} wei`;return n}function An(t){return!t.startsWith("0x")||t.length<14?t:`${t.slice(0,8)}...${t.slice(-6)}`}function Xt(t,e,r=80,n=44,o=60){let i=e[e.length-1],a=i.t-o*1e3;e=e.filter(A=>A.t>=a);let s={top:2,right:2,bottom:2,left:2},u=r-s.left-s.right,c=n-s.top-s.bottom,l=j(t).select("svg");l.empty()&&(l=j(t).append("svg").attr("width",r).attr("height",n),l.append("g").attr("class","line-group").attr("transform",`translate(${s.left},${s.top})`).append("path").attr("class","sparkline-path").attr("fill","none").attr("stroke","#00bff2").attr("stroke-width",1.5),l.append("g").attr("class","y-axis").attr("transform",`translate(${s.left},${s.top})`)),l.attr("width",r).attr("height",n);let f=l.select("g.line-group").select("path.sparkline-path"),m=i.t,d=wn().domain([new Date(a),new Date(m)]).range([0,u]),[y,k]=Me(e,A=>A.v),_=de().domain([y,k]).range([c,0]).nice(),q=We().x(A=>d(new Date(A.t))).y(A=>_(A.v));if(f.datum(e).attr("d",q).attr("transform",null),e.length>1){let A=u/o;f.attr("transform",`translate(${A},0)`),f.transition().duration(300).attr("transform","translate(0,0)")}}function Cr(t,e){let r;if(e===void 0)for(let n of t)n!=null&&(r=n)&&(r=n);else{let n=-1;for(let o of t)(o=e(o,++n,t))!=null&&(r=o)&&(r=o)}return r}function Xe(t,e){let r;if(e===void 0)for(let n of t)n!=null&&(r>n||r===void 0&&n>=n)&&(r=n);else{let n=-1;for(let o of t)(o=e(o,++n,t))!=null&&(r>o||r===void 0&&o>=o)&&(r=o)}return r}function je(t,e){let r=0;if(e===void 0)for(let n of t)(n=+n)&&(r+=n);else{let n=-1;for(let o of t)(o=+e(o,++n,t))&&(r+=o)}return r}function Xd(t){return t.target.depth}function Xo(t,e){return t.sourceLinks.length?t.depth:e-1}function jo(t){return t.targetLinks.length?t.depth:t.sourceLinks.length?Xe(t.sourceLinks,Xd)-1:0}function Ze(t){return function(){return t}}function fl(t,e){return Dn(t.source,e.source)||t.index-e.index}function pl(t,e){return Dn(t.target,e.target)||t.index-e.index}function Dn(t,e){return t.y0-e.y0}function Zo(t){return t.value}function jd(t){return t.index}function Zd(t){return t.nodes}function Jd(t){return t.links}function ml(t,e){let r=t.get(e);if(!r)throw new Error("missing: "+e);return r}function dl({nodes:t}){for(let e of t){let r=e.y0,n=r;for(let o of e.sourceLinks)o.y0=r+o.width/2,r+=o.width;for(let o of e.targetLinks)o.y1=n+o.width/2,n+=o.width}}function Cn(){let t=0,e=0,r=1,n=1,o=24,i=8,a,s=jd,u=Xo,c,l,p=Zd,f=Jd,m=6;function d(){let g={nodes:p.apply(null,arguments),links:f.apply(null,arguments)};return y(g),k(g),_(g),q(g),L(g),dl(g),g}d.update=function(g){return dl(g),g},d.nodeId=function(g){return arguments.length?(s=typeof g=="function"?g:Ze(g),d):s},d.nodeAlign=function(g){return arguments.length?(u=typeof g=="function"?g:Ze(g),d):u},d.nodeSort=function(g){return arguments.length?(c=g,d):c},d.nodeWidth=function(g){return arguments.length?(o=+g,d):o},d.nodePadding=function(g){return arguments.length?(i=a=+g,d):i},d.nodes=function(g){return arguments.length?(p=typeof g=="function"?g:Ze(g),d):p},d.links=function(g){return arguments.length?(f=typeof g=="function"?g:Ze(g),d):f},d.linkSort=function(g){return arguments.length?(l=g,d):l},d.size=function(g){return arguments.length?(t=e=0,r=+g[0],n=+g[1],d):[r-t,n-e]},d.extent=function(g){return arguments.length?(t=+g[0][0],r=+g[1][0],e=+g[0][1],n=+g[1][1],d):[[t,e],[r,n]]},d.iterations=function(g){return arguments.length?(m=+g,d):m};function y({nodes:g,links:x}){for(let[S,v]of g.entries())v.index=S,v.sourceLinks=[],v.targetLinks=[];let M=new Map(g.map((S,v)=>[s(S,v,g),S]));for(let[S,v]of x.entries()){v.index=S;let{source:B,target:G}=v;typeof B!="object"&&(B=v.source=ml(M,B)),typeof G!="object"&&(G=v.target=ml(M,G)),B.sourceLinks.push(v),G.targetLinks.push(v)}if(l!=null)for(let{sourceLinks:S,targetLinks:v}of g)S.sort(l),v.sort(l)}function k({nodes:g}){for(let x of g)x.value=x.fixedValue===void 0?Math.max(je(x.sourceLinks,Zo),je(x.targetLinks,Zo)):x.fixedValue}function _({nodes:g}){let x=g.length,M=new Set(g),S=new Set,v=0;for(;M.size;){for(let B of M){B.depth=v;for(let{target:G}of B.sourceLinks)S.add(G)}if(++v>x)throw new Error("circular link");M=S,S=new Set}}function q({nodes:g}){let x=g.length,M=new Set(g),S=new Set,v=0;for(;M.size;){for(let B of M){B.height=v;for(let{source:G}of B.targetLinks)S.add(G)}if(++v>x)throw new Error("circular link");M=S,S=new Set}}function A({nodes:g}){let x=Cr(g,v=>v.depth)+1,M=(r-t-o)/(x-1),S=new Array(x);for(let v of g){let B=Math.max(0,Math.min(x-1,Math.floor(u.call(null,v,x))));v.layer=B,v.x0=t+B*M,v.x1=v.x0+o,S[B]?S[B].push(v):S[B]=[v]}if(c)for(let v of S)v.sort(c);return S}function D(g){let x=Xe(g,M=>(n-e-(M.length-1)*a)/je(M,Zo));for(let M of g){let S=e;for(let v of M){v.y0=S,v.y1=S+v.value*x,S=v.y1+a;for(let B of v.sourceLinks)B.width=B.value*x}S=(n-S+a)/(M.length+1);for(let v=0;vM.length)-1)),D(x);for(let M=0;M0))continue;let ht=(K/ot-G.y0)*x;G.y0+=ht,G.y1+=ht,h(G)}c===void 0&&B.sort(Dn),H(B,M)}}function N(g,x,M){for(let S=g.length,v=S-2;v>=0;--v){let B=g[v];for(let G of B){let K=0,ot=0;for(let{target:tt,value:Dt}of G.sourceLinks){let gt=Dt*(tt.layer-G.layer);K+=R(G,tt)*gt,ot+=gt}if(!(ot>0))continue;let ht=(K/ot-G.y0)*x;G.y0+=ht,G.y1+=ht,h(G)}c===void 0&&B.sort(Dn),H(B,M)}}function H(g,x){let M=g.length>>1,S=g[M];I(g,S.y0-a,M-1,x),P(g,S.y1+a,M+1,x),I(g,n,g.length-1,x),P(g,e,0,x)}function P(g,x,M,S){for(;M1e-6&&(v.y0+=B,v.y1+=B),x=v.y1+a}}function I(g,x,M,S){for(;M>=0;--M){let v=g[M],B=(v.y1-x)*S;B>1e-6&&(v.y0-=B,v.y1-=B),x=v.y0-a}}function h({sourceLinks:g,targetLinks:x}){if(l===void 0){for(let{source:{sourceLinks:M}}of x)M.sort(pl);for(let{target:{targetLinks:M}}of g)M.sort(fl)}}function T(g){if(l===void 0)for(let{sourceLinks:x,targetLinks:M}of g)x.sort(pl),M.sort(fl)}function E(g,x){let M=g.y0-(g.sourceLinks.length-1)*a/2;for(let{target:S,width:v}of g.sourceLinks){if(S===x)break;M+=v+a}for(let{source:S,width:v}of x.targetLinks){if(S===g)break;M-=v}return M}function R(g,x){let M=x.y0-(x.targetLinks.length-1)*a/2;for(let{source:S,width:v}of x.targetLinks){if(S===g)break;M+=v+a}for(let{target:S,width:v}of g.sourceLinks){if(S===x)break;M-=v}return M}return d}var Jo=Math.PI,Qo=2*Jo,ke=1e-6,Qd=Qo-ke;function Ko(){this._x0=this._y0=this._x1=this._y1=null,this._=""}function hl(){return new Ko}Ko.prototype=hl.prototype={constructor:Ko,moveTo:function(t,e){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+e)},closePath:function(){this._x1!==null&&(this._x1=this._x0,this._y1=this._y0,this._+="Z")},lineTo:function(t,e){this._+="L"+(this._x1=+t)+","+(this._y1=+e)},quadraticCurveTo:function(t,e,r,n){this._+="Q"+ +t+","+ +e+","+(this._x1=+r)+","+(this._y1=+n)},bezierCurveTo:function(t,e,r,n,o,i){this._+="C"+ +t+","+ +e+","+ +r+","+ +n+","+(this._x1=+o)+","+(this._y1=+i)},arcTo:function(t,e,r,n,o){t=+t,e=+e,r=+r,n=+n,o=+o;var i=this._x1,a=this._y1,s=r-t,u=n-e,c=i-t,l=a-e,p=c*c+l*l;if(o<0)throw new Error("negative radius: "+o);if(this._x1===null)this._+="M"+(this._x1=t)+","+(this._y1=e);else if(p>ke)if(!(Math.abs(l*s-u*c)>ke)||!o)this._+="L"+(this._x1=t)+","+(this._y1=e);else{var f=r-i,m=n-a,d=s*s+u*u,y=f*f+m*m,k=Math.sqrt(d),_=Math.sqrt(p),q=o*Math.tan((Jo-Math.acos((d+p-y)/(2*k*_)))/2),A=q/_,D=q/k;Math.abs(A-1)>ke&&(this._+="L"+(t+A*c)+","+(e+A*l)),this._+="A"+o+","+o+",0,0,"+ +(l*f>c*m)+","+(this._x1=t+D*s)+","+(this._y1=e+D*u)}},arc:function(t,e,r,n,o,i){t=+t,e=+e,r=+r,i=!!i;var a=r*Math.cos(n),s=r*Math.sin(n),u=t+a,c=e+s,l=1^i,p=i?n-o:o-n;if(r<0)throw new Error("negative radius: "+r);this._x1===null?this._+="M"+u+","+c:(Math.abs(this._x1-u)>ke||Math.abs(this._y1-c)>ke)&&(this._+="L"+u+","+c),r&&(p<0&&(p=p%Qo+Qo),p>Qd?this._+="A"+r+","+r+",0,1,"+l+","+(t-a)+","+(e-s)+"A"+r+","+r+",0,1,"+l+","+(this._x1=u)+","+(this._y1=c):p>ke&&(this._+="A"+r+","+r+",0,"+ +(p>=Jo)+","+l+","+(this._x1=t+r*Math.cos(o))+","+(this._y1=e+r*Math.sin(o))))},rect:function(t,e,r,n){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+e)+"h"+ +r+"v"+ +n+"h"+-r+"Z"},toString:function(){return this._}};var ti=hl;function ei(t){return function(){return t}}function gl(t){return t[0]}function xl(t){return t[1]}var yl=Array.prototype.slice;function Kd(t){return t.source}function th(t){return t.target}function eh(t){var e=Kd,r=th,n=gl,o=xl,i=null;function a(){var s,u=yl.call(arguments),c=e.apply(this,u),l=r.apply(this,u);if(i||(i=s=ti()),t(i,+n.apply(this,(u[0]=c,u)),+o.apply(this,u),+n.apply(this,(u[0]=l,u)),+o.apply(this,u)),s)return i=null,s+""||null}return a.source=function(s){return arguments.length?(e=s,a):e},a.target=function(s){return arguments.length?(r=s,a):r},a.x=function(s){return arguments.length?(n=typeof s=="function"?s:ei(+s),a):n},a.y=function(s){return arguments.length?(o=typeof s=="function"?s:ei(+s),a):o},a.context=function(s){return arguments.length?(i=s??null,a):i},a}function rh(t,e,r,n,o){t.moveTo(e,r),t.bezierCurveTo(e=(e+n)/2,r,e,o,n,o)}function ri(){return eh(rh)}function nh(t){return[t.source.x1,t.y0]}function oh(t){return[t.target.x0,t.y1]}function ni(){return ri().source(nh).target(oh)}var qn=class{svg;rectG;linkG;nodeG;sankeyGenerator;width=window.innerWidth;height=250;defs;blueColors=["#E1F5FE","#B3E5FC","#81D4FA","#4FC3F7","#29B6F6","#03A9F4","#039BE5","#0288D1","#0277BD","#01579B"];orangeColors=["#FFF5e1","#FFE0B2","#FFCC80","#FFB74D","#FFA726","#FF9800","#FB8C00","#F57C00","#EF6C00","#E65100"];constructor(e){this.svg=j(e).append("svg").attr("width",window.innerWidth).attr("height",this.height).attr("viewBox",[0,0,window.innerWidth,this.height]).style("max-width","100%").style("height","auto"),this.defs=this.svg.append("defs");let r=this.blueColors.slice(5,-1);r=[...r,...r,...r,...r],this.initGradient("blue-flow",r),this.rectG=this.svg.append("g").attr("stroke","#000"),this.linkG=this.svg.append("g").attr("fill","none").style("mix-blend-mode","normal"),this.nodeG=this.svg.append("g"),this.sankeyGenerator=Cn().nodeId(n=>n.name).nodeAlign(jo).nodeWidth(10).nodePadding(30).nodeSort((n,o)=>n.inclusion&&o.inclusion?n.namen.target.inclusion&&o.target.inclusion?n.source.namei/(r.length-1)).attr("stop-color",o=>o),n.append("animate").attr("attributeName","x1").attr("values","0%;200%").attr("dur","12s").attr("repeatCount","indefinite"),n.append("animate").attr("attributeName","x2").attr("values","100%;300%").attr("dur","12s").attr("repeatCount","indefinite")}isRightAligned(e){return!e.inclusion}update(e,r){this.sankeyGenerator.extent([[100,20],[window.innerWidth-100,this.height-25]]);let n=[],o={};this.width=window.innerWidth-56,this.svg.attr("width",this.width).attr("height",this.height).attr("viewBox",[0,0,this.width,this.height]);for(let l of r.links)l.value>0&&(n.push(l),o[l.source]=!0,o[l.target]=!0);let a={nodes:e.filter(l=>o[l.name]).map(l=>({...l})),links:n.map(l=>({...l}))},{nodes:s,links:u}=this.sankeyGenerator(a);this.rectG.selectAll("rect").data(s,l=>l.name).join("rect").attr("x",l=>l.x0).attr("y",l=>l.y0).attr("height",l=>l.y1-l.y0).attr("width",l=>l.x1-l.x0).attr("fill",l=>(l.name==="P2P Network"&&(l.value=r.hashesReceived),l.inclusion?l.name==="Tx Pool"||l.name==="Added To Block"?"#FFA726":"#00BFF2":"#555")),this.linkG.selectAll("path").data(u,l=>l.index).join("path").attr("d",ni()).attr("stroke",l=>l.target.inclusion?"url(#blue-flow)":"#333").attr("stroke-width",l=>Math.max(1,l.width??1));let c=this.nodeG.selectAll("text").data(s,l=>l.name).join(l=>l.append("text").attr("data-last","0"),l=>l,l=>l.remove());c.attr("data-last",function(l){return j(this).attr("data-current")||"0"}).attr("data-current",l=>(l.targetLinks||[]).reduce((f,m)=>f+(m.value||0),0)||l.value||0).attr("x",l=>this.isRightAligned(l)?l.x1+6:l.x0-6).attr("y",l=>(l.y0+l.y1)/2).attr("dy","-0.5em").attr("text-anchor",l=>this.isRightAligned(l)?"start":"end").text(l=>l.name).each(function(){j(this).selectAll("tspan.number").data([0]).join("tspan").attr("class","number").attr("x",()=>{let l=j(this).datum();return l&&l.inclusion?l.x1+6:l.x0-6}).attr("dy","1em")}),c.selectAll("tspan.number").transition().duration(500).tween("text",function(){let l=j(this),p=j(this.parentNode),f=p.empty()?0:parseFloat(p.attr("data-last")||"0"),m=p.empty()?0:parseFloat(p.attr("data-current")||"0"),d=Z(f,m);return function(y){l.text(bt(",.0f")(d(y)))}})}};function vl(t,e,r,n,o,i,a,s){let u=window.innerWidth-56,l={name:"root",children:[...n.map(h=>({name:o(h),item:h,size:a(h)}))]},p=Re(l).sum(h=>h.children?0:a(h.data?h.data.item:h.item)).sort(h=>h.children?0:i(h.data?h.data.item:h.item)),f=p.value??0,m=f>0?f/r:0,d=Math.min(u,u*m);_o().size([d,e-1]).round(!0).tile(vr.ratio(1)).paddingOuter(.5).paddingInner(2)(p);let[y,k]=Me(n,s),_=y&&y>0?y:1e-6,q=k||1,A=kn(Fo).domain([_,q]);function D(h){let T=s(h),E=T>0?T:1e-6;return A(E)}let L=j(t).select("svg");if(L.empty()){L=j(t).append("svg").attr("width",u).attr("height",e).attr("viewBox",[0,0,u,e]);let h=L.append("defs"),T=h.append("pattern").attr("id","unusedStripes").attr("patternUnits","userSpaceOnUse").attr("width",8).attr("height",8);T.append("rect").attr("class","pattern-bg").attr("width",8).attr("height",8).attr("fill","#444"),T.append("path").attr("d","M0,0 l8,8").attr("stroke","#000").attr("stroke-width",1),L.append("rect").attr("class","unused").attr("fill","url(#unusedStripes)").attr("opacity",1).attr("width",u).attr("height",e).attr("stroke","#fff").attr("stroke-width",1),h.append("marker").attr("id","arrowStart").attr("markerWidth",10).attr("markerHeight",10).attr("refX",5).attr("refY",5).attr("orient","auto").attr("markerUnits","strokeWidth").append("path").attr("d","M10,0 L0,5 L10,10").attr("fill","#ccc"),h.append("marker").attr("id","arrowEnd").attr("markerWidth",10).attr("markerHeight",10).attr("refX",5).attr("refY",5).attr("orient","auto").attr("markerUnits","strokeWidth").append("path").attr("d","M0,0 L10,5 L0,10").attr("fill","#ccc")}L.attr("width",u).attr("height",e).attr("viewBox",[0,0,u,e]),L.selectAll("rect.unused").attr("width",u);let b=p.leaves();L.selectAll("g.node").data(b,h=>h.data.name).join(h=>{let T=h.append("g").attr("class","node").attr("data-hash",E=>E.data.name).attr("transform",E=>`translate(${E.x0},${E.y0})`).attr("opacity",0);return T.append("rect").attr("stroke","#000").attr("stroke-width",.5).attr("width",0).attr("height",0).attr("fill",E=>D(E.data.item)),T.transition().duration(600).attr("opacity",1),T.select("rect").transition().duration(600).attr("width",E=>E.x1-E.x0).attr("height",E=>E.y1-E.y0),T},h=>(h.transition().duration(600).attr("transform",T=>`translate(${T.x0},${T.y0})`).attr("opacity",1),h.select("rect").transition().duration(600).attr("width",T=>T.x1-T.x0).attr("height",T=>T.y1-T.y0).attr("fill",T=>D(T.data.item)),h),h=>{h.transition().duration(600).attr("opacity",0).remove()});let H=(r-f)/r,I=H>=.1?[{key:"unused-label",x:d,w:u-d,ratio:H}]:[];L.selectAll("line.unused-arrow").data(I,h=>h.key).join(h=>h.append("line").attr("class","unused-arrow").attr("x1",T=>T.x+10).attr("x2",T=>T.x+T.w-10).attr("y1",145).attr("y2",145).attr("stroke","#ccc").attr("stroke-width",2).attr("marker-start","url(#arrowStart)").attr("marker-end","url(#arrowEnd)").attr("opacity",0).call(T=>T.transition().duration(600).attr("opacity",1)),h=>h.transition().duration(600).attr("x1",T=>T.x+10).attr("x2",T=>T.x+T.w-10),h=>h.transition().duration(600).attr("opacity",0).remove()),L.selectAll("text.unused-label").data(I,h=>h.key).join(h=>h.append("text").attr("class","unused-label").attr("x",T=>T.x+T.w/2).attr("y",135).attr("fill","#ccc").attr("font-size",14).attr("text-anchor","middle").attr("opacity",0).text(T=>`${(T.ratio*100).toFixed(1)}% available`).call(T=>T.transition().duration(600).attr("opacity",1)),h=>h.transition().duration(600).attr("x",T=>T.x+T.w/2).text(T=>`${(T.ratio*100).toFixed(1)}% available`),h=>h.transition().duration(600).attr("opacity",0).remove())}function bl(t,e,r=36){let n={top:4,right:4,bottom:20,left:20},o=t.getBoundingClientRect().width,i=100,a=j(t).append("svg").style("display","block").style("background","black").attr("width",o).attr("height",i),s=a.append("g").attr("transform",`translate(${n.left},${n.top})`);function u(){return o-n.left-n.right}let c=i-n.top-n.bottom,l=[],p,f=br().domain(Ae(r)).range([0,u()]).paddingInner(.3).paddingOuter(.1),m=de().range([c,0]);function d(){return xr().duration(750)}function y(A){if(!A.length)return{min:0,q1:0,median:0,q3:0,max:0,whiskerLow:0,whiskerHigh:0};let D=A.slice().sort(ft),L=D[0],b=D[D.length-1],N=Ee(D,.25),H=Ee(D,.5),P=Ee(D,.75),I=P-N,h=Math.max(L,N-1.5*I),T=Math.min(b,P+1.5*I);return{min:L,q1:N,median:H,q3:P,max:b,whiskerLow:h,whiskerHigh:T}}function k(A,D){let L=A.map(e).filter(x=>Number.isFinite(x)&&x>=0),b=y(L);if(l.length{x.xIndex-=1});l.length&&l[0].xIndex<0;){let x=l.shift();x&&(p=x.stats.median)}l.push({xIndex:r-1,blockNumber:D,stats:b,values:L})}let N=ee(l,x=>x.stats.whiskerHigh)||1;m.domain([0,N]);let H=s.selectAll(".box-group").data(l,x=>x.xIndex);H.exit().transition(d()).attr("transform",x=>`translate(${f(x.xIndex)||0},${c}) scale(0.001,0.001)`).remove();let P=H.enter().append("g").attr("class","box-group").attr("stroke-width",1).attr("transform",x=>`translate(${f(x.xIndex)||0}, ${c}) scale(0.001,0.001)`);P.append("line").attr("class","whisker-line").attr("stroke","#aaa"),P.append("rect").attr("class","box-rect").attr("stroke","white").attr("fill","gray"),P.append("line").attr("class","median-line").attr("stroke","#ccc").attr("stroke-width",1),P.append("line").attr("class","whisker-cap lower").attr("stroke","#aaa"),P.append("line").attr("class","whisker-cap upper").attr("stroke","#aaa"),P.append("g").attr("class","points-group"),H=P.merge(H),H.transition(d()).attr("transform",x=>`translate(${f(x.xIndex)||0},0) scale(1,1)`),H.each(function(x){let M=j(this),S=x.stats,v=f.bandwidth(),B,G=l.find(ot=>ot.xIndex===x.xIndex-1);G?B=G.stats.median:x.xIndex===0&&p!==void 0&&(B=p);let K="gray";B!==void 0&&(S.median>B?K="rgb(127, 63, 63)":S.median{x.selectAll("text").attr("fill","white"),x.selectAll("line,path").attr("stroke","white")});let h=Fr(f).tickFormat(x=>{let M=l.find(S=>S.xIndex===x);return M?String(M.blockNumber):""}),T=s.append("g").attr("class","x-axis").attr("transform",`translate(0,${c})`).call(h),E=f.bandwidth();E<25&&T.selectAll(".tick").each(function(x,M){M/2%2!==0&&j(this).remove()}),T.call(x=>{x.selectAll("text").attr("fill","white"),x.selectAll("line,path").attr("stroke","white")}),s.selectAll(".median-trend").remove();let R=l.filter(x=>x.xIndex>=0).sort((x,M)=>x.xIndex-M.xIndex),g=We().x(x=>(f(x.xIndex)??0)+E/2).y(x=>m(x.stats.median)).curve(En);s.append("path").attr("class","median-trend").datum(R).attr("fill","none").attr("stroke","#FFA726").attr("stroke-width",2).transition(d()).attr("d",g)}function _(){o=t.getBoundingClientRect().width,a.attr("width",o),f.range([0,u()]),q()}function q(){s.selectAll(".box-group").transition().duration(0).attr("transform",I=>`translate(${f(I.xIndex)||0},0)`),s.selectAll(".y-axis").remove(),s.selectAll(".x-axis").remove();let A=Or(m).ticks(3);s.append("g").attr("class","y-axis").call(A).call(I=>{I.selectAll("text").attr("fill","white"),I.selectAll("line,path").attr("stroke","white")});let D=Fr(f).tickFormat(I=>{let h=l.find(T=>T.xIndex===I);return h?String(h.blockNumber):""}),L=s.append("g").attr("class","x-axis").attr("transform",`translate(0,${c})`).call(D);f.bandwidth()<25&&L.selectAll(".tick").each(function(I,h){h%2!==0&&j(this).remove()}),L.call(I=>{I.selectAll("text").attr("fill","white"),I.selectAll("line,path").attr("stroke","white")}),s.selectAll(".median-trend").remove();let N=l.filter(I=>I.xIndex>=0).sort((I,h)=>I.xIndex-h.xIndex),H=f.bandwidth(),P=We().x(I=>(f(I.xIndex)??0)+H/2).y(I=>m(I.stats.median)).curve(En);s.append("path").attr("class","median-trend").datum(N).attr("fill","none").attr("stroke","#FFA726").attr("stroke-width",2).attr("d",P)}return{update:k,resize:_}}var qr=100,ih=120,ah=qr+ih,sh=qr,wl=qr/2,uh=j("#pie-chart").append("svg").attr("width",ah).attr("height",sh).attr("overflow","visible"),kl=uh.append("g").attr("transform",`translate(${qr/2}, ${qr/2})`),lh=me().range(Ho),ch=Uo().sort(null).value(t=>t.count),oi=$o().innerRadius(0).outerRadius(wl),fh=kl.append("g").attr("class","slice-layer"),ph=kl.append("g").attr("class","label-layer");function _l(t){let e=ch(t),r=e.map(m=>{let[d,y]=oi.centroid(m);return{...m,centroidY:y}}),n=r.slice().sort((m,d)=>m.centroidY-d.centroidY),o=18,i=-((n.length-1)*o)/2,a=wl+20,s={};n.forEach((m,d)=>{s[m.data.type]={x:a,y:i+d*o}});let u=fh.selectAll("path").data(e,m=>m.data.type);u.exit().remove(),u.enter().append("path").attr("stroke","#222").style("stroke-width","1px").attr("fill",m=>lh(m.data.type)).each(function(m){this._current=m}).merge(u).transition().duration(750).attrTween("d",function(m){let d=vt(this._current,m);return this._current=d(0),y=>oi(d(y))});let l=ph.selectAll("g.label").data(r,m=>m.data.type);l.exit().remove();let p=l.enter().append("g").attr("class","label").style("pointer-events","none");p.append("polyline").attr("fill","none").attr("stroke","#fff"),p.append("text").style("alignment-baseline","middle").style("text-anchor","start").style("fill","#fff");let f=p.merge(l);f.select("polyline").transition().duration(750).attr("points",m=>{let[d,y]=oi.centroid(m),{x:k,y:_}=s[m.data.type],q=k*.6;return`${d},${y} ${q},${_} ${k},${_}`}),f.select("text").transition().duration(750).attr("transform",m=>{let d=s[m.data.type];return`translate(${d.x},${d.y})`}).tween("label",function(m){return()=>{this.textContent=`${m.data.type} (${m.data.count})`}})}function Tl(t){switch(t){case 0:return"Legacy";case 1:return"AccessList";case 2:return"Eip1559";case 3:return"Blob";case 4:return"SetCode";case 5:return"TxCreate";default:return"Unknown"}}function ii(t){switch(t){case"0x095ea7b3":return"approve";case"0xa9059cbb":return"transfer";case"0x23b872dd":return"transferFrom";case"0xd0e30db0":return"deposit";case"0xe8e33700":case"0xf305d719":return"addLiquidity";case"0xbaa2abde":case"0x02751cec":case"0xaf2979eb":case"0xded9382a":case"0x5b0d5984":case"0x2195995c":return"removeLiquidity";case"0xfb3bdb41":case"0x7ff36ab5":case"0xb6f9de95":case"0x18cbafe5":case"0x791ac947":case"0x38ed1739":case"0x5c11d795":case"0x4a25d94a":case"0x5f575529":case"0x6b68764c":case"0x845a101f":case"0x8803dbee":return"swap";case"0x24856bc3":case"0x3593564c":return"dex";case"0x":return"ETH transfer";default:return t}}function Ml(t){switch(t){case"0xdac17f958d2ee523a2206206994597c13d831ec7":return"usdt";case"0x4ecaba5870353805a9f068101a40e0f32ed605c6":return"usdt";case"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48":return"usdc";case"0xddafbb505ad214d7b80b1f830fccc89b60fb7a83":return"usdc";case"0x6b175474e89094c44da98b954eedeac495271d0f":return"dai";case"0x44fa8e6f47987339850636f88629646662444217":return"dai";case"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2":return"weth";case"0x6a023ccd1ff6f2045c3309768ead9e68f978f6e1":return"weth";case"0x7a250d5630b4cf539739df2c5dacb4c659f2488d":return"uniswap v2";case"0x66a9893cc07d91d95644aedd05d03f95e1dba8af":return"uniswap v4";case"0x2f848984984d6c3c036174ce627703edaf780479":return"xen minter";case"0x0a252663dbcc0b073063d6420a40319e438cfa59":return"xen";case"0x881d40237659c251811cec9c364ef91dc08d300c":return"metamask";default:return t}}var Wh=tc(),Pn=class{nodeLog;ansiConvert=new Wh;logs=[];constructor(e){this.nodeLog=document.getElementById(e)}receivedLog(e){let r=JSON.parse(e.data);for(let n of r){let o=this.ansiConvert.toHtml(n);this.logs.length>=100&&this.logs.shift(),this.logs.push(o)}}appendLogs(){if(this.logs.length>0){let e=!1;(this.nodeLog.scrollHeight<500||this.nodeLog.scrollTop250;)this.nodeLog.firstChild.remove(),n--;e&&window.setTimeout(()=>this.scrollLogs(),17)}}resize(){let e=document.body.getBoundingClientRect();var n=this.nodeLog.getBoundingClientRect().top-e.top,o=window.innerHeight-n-16;o>0&&(this.nodeLog.style.height=`${o}px`)}scrollLogs(){this.nodeLog.scrollTop=this.nodeLog.scrollHeight}};function $(t,e){t.innerText!==e&&(t.innerText=e)}var $n=class{lastGasLimit=36e6;minGas;medianGas;aveGas;maxGas;gasLimit;gasLimitDelta;constructor(e,r,n,o,i,a){this.minGas=document.getElementById(e),this.medianGas=document.getElementById(r),this.aveGas=document.getElementById(n),this.maxGas=document.getElementById(o),this.gasLimit=document.getElementById(i),this.gasLimitDelta=document.getElementById(a)}parseEvent(e){if(document.hidden)return;let r=JSON.parse(e.data);$(this.minGas,r.minGas.toFixed(2)),$(this.medianGas,r.medianGas.toFixed(2)),$(this.aveGas,r.aveGas.toFixed(2)),$(this.maxGas,r.maxGas.toFixed(2)),$(this.gasLimit,dt(r.gasLimit)),$(this.gasLimitDelta,r.gasLimit>this.lastGasLimit?"\u{1F446}":r.gasLimitparseInt(t.effectiveGasPrice,16)/1e9,36);function dc(t,e){do if(!(t.matches===void 0||!t.matches(e)))return t;while(t=t.parentElement);return null}function kg(t,e,r,n,o){t.addEventListener(e,function(i){try{let a=dc(i.target,r);a!==null&&n.apply(a,arguments)}catch{}},o)}var ec=[],rc=[],nc=[],oc=[],ic=[],ac=[],sc=[],pi=0,mi=0,di=0,hi=0,gi=0,Un=0;function Te(t,e){t.push(e),t.length>60&&t.shift()}var _g=new qn("#txPoolFlow"),bi=null,uc=!1;function Tg(t){if(!uc){if(t.pooledTx==0){document.getElementById("txPoolFlow").classList.add("not-active");return}setTimeout(Ti,10),document.getElementById("txPoolFlow").classList.remove("not-active"),uc=!0}let e=performance.now(),r=e/1e3,n=r-Un,o=0,i=0,a=0,s=0;for(let c of t.links)c.target==="Received Txs"&&(o+=c.value),c.target==="Tx Pool"&&(i+=c.value),c.target==="Added To Block"&&(a+=c.value),c.target==="Duplicate"&&(s+=c.value);let u=t.hashesReceived;if(Un!==0&&(Te(ec,{t:e,v:u-gi}),Te(rc,{t:e,v:o-pi}),Te(ic,{t:e,v:s-hi}),Te(nc,{t:e,v:i-mi}),Te(oc,{t:e,v:a-di})),!document.hidden){if(!bi)return;_g.update(bi,t),$(Xh,dt(t.pooledTx)),$(jh,dt(t.pooledBlobTx)),$(Zh,dt(t.pooledTx+t.pooledBlobTx)),Un!==0&&(Xt(document.getElementById("sparkHashesTps"),ec),Xt(document.getElementById("sparkReceivedTps"),rc),Xt(document.getElementById("sparkDuplicateTps"),ic),Xt(document.getElementById("sparkTxPoolTps"),nc),Xt(document.getElementById("sparkBlockTps"),oc),$(Jh,Wt((a-di)/n)),$(Qh,Wt((o-pi)/n)),$(Kh,Wt((i-mi)/n)),$(tg,Wt((s-hi)/n)),$(eg,Wt((u-gi)/n)))}Un=r,pi=o,mi=i,di=a,hi=s,gi=u}var _i=new Pn("nodeLog"),Mg=new $n("minGas","medianGas","aveGas","maxGas","gasLimit","gasLimitDelta"),Jt=new EventSource("data/events");Jt.addEventListener("log",t=>_i.receivedLog(t));Jt.addEventListener("processed",t=>Mg.parseEvent(t));Jt.addEventListener("nodeData",t=>{let e=JSON.parse(t.data),r=al(e.network),n=`Nethermind [${r}]${e.instance?" - "+e.instance:""}`;document.title!=n&&(document.title=n),$(rg,e.version),$(ng,r),document.getElementById("network-logo").src=`logos/${sl(e.network)}`,$(pc,Wo(e.uptime)),cl(e.gasToken)});Jt.addEventListener("txNodes",t=>{bi=JSON.parse(t.data)});Jt.addEventListener("txLinks",t=>{if(document.hidden)return;let e=JSON.parse(t.data);Tg(e)});var wi=0,hc=0,lc=!1;Jt.addEventListener("forkChoice",t=>{let e=performance.now();if(hc=e-wi,wi=e,document.hidden)return;let r=JSON.parse(t.data),n=parseInt(r.head.number,16);!lc&&n!==0&&(lc=!0,document.getElementById("latestBlock").classList.remove("not-active"),setTimeout(Ti,10));let o=parseInt(r.safe,16),i=parseInt(r.finalized,16);$(og,n.toFixed(0)),$(ig,o.toFixed(0)),$(ag,i.toFixed(0)),$(sg,`(${(o-n).toFixed(0)})`),$(ug,`(${(i-n).toFixed(0)})`);let a=r.head;if(a.tx.length===0)return;let s=a.tx.map((u,c)=>{let l=a.receipts[c];return{block:a.number,order:c,...u,...l}});$(hg,il(a.extraData)),$(gg,dt(parseInt(a.gasUsed,16))),$(xg,dt(parseInt(a.gasLimit,16))),$(yg,ol(parseInt(a.size,16))),$(vg,nl(parseInt(a.timestamp,16))),$(bg,dt(a.tx.length)),$(wg,dt(a.tx.reduce((u,c)=>u+c.blobs,0))),vl(document.getElementById("block"),160,parseInt(r.head.gasLimit,16),s,u=>u.hash,u=>u.order,u=>parseInt(u.gasUsed,16),u=>parseInt(u.effectiveGasPrice,16)*parseInt(u.gasUsed,16)),mc.update(s,n),_t.push(...s),ki=_t.length,_t.length>25e4&&_t.slice(_t.length-25e3),gc=Lg(s)});var gc,Sg="";kg(document.getElementById("block"),"pointermove","g.node",t=>{let r=dc(t.target,"g.node").dataset.hash,n=gc[r];if(!n)return;let o=document.getElementById("txDetails");return Sg!==r&&(o.innerHTML=`

Transaction ${An(n.hash)}

diff --git a/src/Nethermind/Nethermind.Serialization.Json/BigIntegerConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/BigIntegerConverter.cs index bc00e5e9d94..a7518b3d49d 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/BigIntegerConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/BigIntegerConverter.cs @@ -17,7 +17,7 @@ public override BigInteger Read(ref Utf8JsonReader reader, Type typeToConvert, J { JsonTokenType.Number => new BigInteger(reader.GetInt64()), JsonTokenType.String => BigInteger.Parse(reader.GetString()), - _ => throw new InvalidOperationException() + _ => throw new JsonException($"Cannot convert {reader.TokenType} to {nameof(BigInteger)}") }; } diff --git a/src/Nethermind/Nethermind.Serialization.Json/BooleanConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/BooleanConverter.cs index 77f5825ba96..d164de5da09 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/BooleanConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/BooleanConverter.cs @@ -44,7 +44,7 @@ public override bool Read( } } - throw new InvalidOperationException(); + throw new JsonException($"Cannot convert {reader.TokenType} to {nameof(Boolean)}"); } [SkipLocalsInit] diff --git a/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs b/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs index 787c0ad40c3..3f3e5ea4eea 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/EthereumJsonSerializer.cs @@ -94,8 +94,8 @@ private static JsonSerializerOptions CreateOptions(bool indented, IEnumerable s) { if (s.Length == 0) { - throw new JsonException("null cannot be assigned to long"); + throw new JsonException("null cannot be assigned to int"); } if (s.SequenceEqual("0x0"u8)) diff --git a/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs index 37756065e77..7b46c9e2dc3 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/LongConverter.cs @@ -79,10 +79,7 @@ public static long FromString(ReadOnlySpan s) throw new JsonException("hex to long"); } - public override long Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) + internal static long ReadCore(ref Utf8JsonReader reader) { if (reader.TokenType == JsonTokenType.Number) { @@ -103,6 +100,14 @@ public override long Read( throw new JsonException(); } + public override long Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + return ReadCore(ref reader); + } + [SkipLocalsInit] public override void Write( Utf8JsonWriter writer, diff --git a/src/Nethermind/Nethermind.Serialization.Json/LongRawJsonConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/LongRawJsonConverter.cs index df81b47e6be..aec0b06c5d7 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/LongRawJsonConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/LongRawJsonConverter.cs @@ -15,23 +15,7 @@ public override long Read( Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType == JsonTokenType.Number) - { - return reader.GetInt64(); - } - else if (reader.TokenType == JsonTokenType.String) - { - if (!reader.HasValueSequence) - { - return LongConverter.FromString(reader.ValueSpan); - } - else - { - return LongConverter.FromString(reader.ValueSequence.ToArray()); - } - } - - throw new JsonException(); + return LongConverter.ReadCore(ref reader); } public override void Write( diff --git a/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs index 08d9afa23f2..e0fb0988f5e 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ULongConverter.cs @@ -19,7 +19,7 @@ public static ulong FromString(ReadOnlySpan s) { if (s.Length == 0) { - throw new JsonException("null cannot be assigned to long"); + throw new JsonException("null cannot be assigned to ulong"); } if (s.SequenceEqual("0x0"u8)) @@ -78,10 +78,7 @@ public override void Write( } } - public override ulong Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) + internal static ulong ReadCore(ref Utf8JsonReader reader) { if (reader.TokenType == JsonTokenType.Number) { @@ -101,5 +98,13 @@ public override ulong Read( throw new JsonException(); } + + public override ulong Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + return ReadCore(ref reader); + } } } diff --git a/src/Nethermind/Nethermind.Serialization.Json/ULongRawJsonConverter.cs b/src/Nethermind/Nethermind.Serialization.Json/ULongRawJsonConverter.cs index fa172ac039c..bb1d6b92e8f 100644 --- a/src/Nethermind/Nethermind.Serialization.Json/ULongRawJsonConverter.cs +++ b/src/Nethermind/Nethermind.Serialization.Json/ULongRawJsonConverter.cs @@ -15,23 +15,7 @@ public override ulong Read( Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType == JsonTokenType.Number) - { - return reader.GetUInt64(); - } - else if (reader.TokenType == JsonTokenType.String) - { - if (!reader.HasValueSequence) - { - return ULongConverter.FromString(reader.ValueSpan); - } - else - { - return ULongConverter.FromString(reader.ValueSequence.ToArray()); - } - } - - throw new JsonException(); + return ULongConverter.ReadCore(ref reader); } public override void Write( diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/NethermindBuffers.cs b/src/Nethermind/Nethermind.Serialization.Rlp/NethermindBuffers.cs index 1b8df5ce7a6..2f9c0b87557 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/NethermindBuffers.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/NethermindBuffers.cs @@ -67,7 +67,7 @@ public static class Metrics [KeyIsLabel("allocator")] public static ConcurrentDictionary AllocatorActiveAllocationBytes { get; } = new(); - [Description("Allocatioons")] + [Description("Allocations")] [KeyIsLabel("allocator")] public static ConcurrentDictionary AllocatorAllocations { get; } = new(); } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptArrayStorageDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptArrayStorageDecoder.cs index 656d754581b..b044b4b3788 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptArrayStorageDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptArrayStorageDecoder.cs @@ -64,7 +64,7 @@ protected override TxReceipt[] DecodeInternal(RlpStream rlpStream, RlpBehaviors if (rlpStream.PeekByte() == CompactEncoding) { rlpStream.ReadByte(); - return CompactDecoder.DecodeArray(rlpStream, RlpBehaviors.Storage); + return CompactDecoder.DecodeArray(rlpStream, RlpBehaviors.Storage | RlpBehaviors.AllowExtraBytes); } else { @@ -113,7 +113,7 @@ public TxReceipt[] Decode(in Span receiptsData) if (receiptsData.Length > 0 && receiptsData[0] == CompactEncoding) { var decoderContext = new Rlp.ValueDecoderContext(receiptsData[1..]); - return CompactValueDecoder.DecodeArray(ref decoderContext, RlpBehaviors.Storage); + return CompactValueDecoder.DecodeArray(ref decoderContext, RlpBehaviors.Storage | RlpBehaviors.AllowExtraBytes); } else { @@ -148,18 +148,10 @@ public TxReceipt DeserializeReceiptObsolete(Hash256 hash, Span receiptData } } - public static bool IsCompactEncoding(Span receiptsData) - { - return receiptsData.Length > 0 && receiptsData[0] == CompactEncoding; - } + public static bool IsCompactEncoding(Span receiptsData) => receiptsData.Length > 0 && receiptsData[0] == CompactEncoding; - public IReceiptRefDecoder GetRefDecoder(Span receiptsData) - { - if (IsCompactEncoding(receiptsData)) - { - return (IReceiptRefDecoder)CompactValueDecoder; - } - - return (IReceiptRefDecoder)ValueDecoder; - } + public IReceiptRefDecoder GetRefDecoder(Span receiptsData) => + IsCompactEncoding(receiptsData) + ? (IReceiptRefDecoder)CompactValueDecoder + : (IReceiptRefDecoder)ValueDecoder; } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptMessageDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptMessageDecoder.cs index dc25bc96d35..7106903da14 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptMessageDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/ReceiptMessageDecoder.cs @@ -72,6 +72,11 @@ protected override TxReceipt DecodeInternal(ref Rlp.ValueDecoderContext ctx, Rlp } txReceipt.Logs = entries; + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + ctx.Check(lastCheck); + } + return txReceipt; } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs index 425b10a85b5..eee672e098b 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/Rlp.cs @@ -212,6 +212,11 @@ public static T[] DecodeArray(RlpStream rlpStream, IRlpStreamDecoder? rlpD result[i] = rlpDecoder.Decode(rlpStream, rlpBehaviors); } + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + rlpStream.Check(checkPosition); + } + return result; } @@ -223,7 +228,7 @@ public static ArrayPoolList DecodeArrayPool(RlpStream rlpStream, RlpBehavi : throw new RlpException($"{nameof(Rlp)} does not support decoding {typeof(T).Name}"); } - public static ArrayPoolList DecodeArrayPool(RlpStream rlpStream, IRlpStreamDecoder? rlpDecoder, RlpBehaviors rlpBehaviors = RlpBehaviors.None, RlpLimit? limit = null) + public static ArrayPoolList DecodeArrayPool(RlpStream rlpStream, IRlpStreamDecoder rlpDecoder, RlpBehaviors rlpBehaviors = RlpBehaviors.None, RlpLimit? limit = null) { int checkPosition = rlpStream.ReadSequenceLength() + rlpStream.Position; int length = rlpStream.PeekNumberOfItemsRemaining(checkPosition); @@ -234,6 +239,11 @@ public static ArrayPoolList DecodeArrayPool(RlpStream rlpStream, IRlpStrea result.Add(rlpDecoder.Decode(rlpStream, rlpBehaviors)); } + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + rlpStream.Check(checkPosition); + } + return result; } @@ -1541,7 +1551,8 @@ public byte[][] DecodeByteArrays() return []; } - int itemsCount = PeekNumberOfItemsRemaining(Position + length); + int checkPosition = Position + length; + int itemsCount = PeekNumberOfItemsRemaining(checkPosition); byte[][] result = new byte[itemsCount][]; for (int i = 0; i < itemsCount; i++) @@ -1549,6 +1560,8 @@ public byte[][] DecodeByteArrays() result[i] = DecodeByteArray(); } + Check(checkPosition); + return result; } @@ -1643,6 +1656,11 @@ public T[] DecodeArray(IRlpValueDecoder? decoder = null, bool checkPositio } } + if (checkPositions) + { + Check(positionCheck); + } + return result; } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpDecoderExtensions.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpDecoderExtensions.cs index b371f2d0d37..16202a6baf7 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/RlpDecoderExtensions.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpDecoderExtensions.cs @@ -25,6 +25,11 @@ public static T[] DecodeArray(this IRlpStreamDecoder decoder, RlpStream rl result[i] = decoder.Decode(rlpStream, rlpBehaviors); } + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + rlpStream.Check(checkPosition); + } + return result; } @@ -39,6 +44,11 @@ public static T[] DecodeArray(this IRlpValueDecoder decoder, ref Rlp.Value result[i] = decoder.Decode(ref decoderContext, rlpBehaviors); } + if ((rlpBehaviors & RlpBehaviors.AllowExtraBytes) != RlpBehaviors.AllowExtraBytes) + { + decoderContext.Check(checkPosition); + } + return result; } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/RlpLimit.cs b/src/Nethermind/Nethermind.Serialization.Rlp/RlpLimit.cs index b794d2090d1..2da1c24da2b 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/RlpLimit.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/RlpLimit.cs @@ -10,7 +10,7 @@ namespace Nethermind.Serialization.Rlp; public record struct RlpLimit(int Limit, string TypeName = "", ReadOnlyMemory PropertyName = default) { - // We shouldn't allocate any single array bigger than 1M + // We shouldn't allocate any single array bigger than 4M public static readonly RlpLimit DefaultLimit = new(); public static readonly RlpLimit Bloom = For(Core.Bloom.ByteLength); public static readonly RlpLimit L4 = new(4); @@ -20,7 +20,7 @@ public record struct RlpLimit(int Limit, string TypeName = "", ReadOnlyMemory(T?[]? items, RlpBehaviors rlpBehaviors = RlpBehaviors StartSequence(contentLength); - foreach (T item in items) + foreach (T? item in items) { decoder.Encode(this, item, rlpBehaviors); } @@ -959,6 +957,11 @@ public T[] DecodeArray(Func decodeItem, bool checkPositions = t } } + if (checkPositions) + { + Check(positionCheck); + } + return result; } @@ -981,6 +984,11 @@ public ArrayPoolList DecodeArrayPoolList(Func decodeItem, bo } } + if (checkPositions) + { + Check(positionCheck); + } + return result; } @@ -1233,7 +1241,8 @@ public byte[][] DecodeByteArrays(RlpLimit? limit = null) return []; } - int itemsCount = PeekNumberOfItemsRemaining(Position + length); + int checkPosition = Position + length; + int itemsCount = PeekNumberOfItemsRemaining(checkPosition); GuardLimit(itemsCount, limit); byte[][] result = new byte[itemsCount][]; @@ -1242,6 +1251,8 @@ public byte[][] DecodeByteArrays(RlpLimit? limit = null) result[i] = DecodeByteArray(); } + Check(checkPosition); + return result; } diff --git a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BlobTxDecoder.cs b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BlobTxDecoder.cs index 3f1fc2ea10f..dd548dd4a4a 100644 --- a/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BlobTxDecoder.cs +++ b/src/Nethermind/Nethermind.Serialization.Rlp/TxDecoders/BlobTxDecoder.cs @@ -56,9 +56,9 @@ public override void Decode(ref Transaction? transaction, int txSequenceStart, R { int networkWrapperLength = decoderContext.ReadSequenceLength(); networkWrapperCheck = decoderContext.Position + networkWrapperLength; - int rlpRength = decoderContext.PeekNextRlpLength(); + int rlpLength = decoderContext.PeekNextRlpLength(); txSequenceStart = decoderContext.Position; - transactionSequence = decoderContext.Peek(rlpRength); + transactionSequence = decoderContext.Peek(rlpLength); } base.Decode(ref transaction, txSequenceStart, transactionSequence, ref decoderContext, rlpBehaviors | RlpBehaviors.ExcludeHashes); diff --git a/src/Nethermind/Nethermind.Serialization.SszGenerator/Nethermind.Serialization.SszGenerator.csproj b/src/Nethermind/Nethermind.Serialization.SszGenerator/Nethermind.Serialization.SszGenerator.csproj index ae6ab740113..f9b4add506e 100644 --- a/src/Nethermind/Nethermind.Serialization.SszGenerator/Nethermind.Serialization.SszGenerator.csproj +++ b/src/Nethermind/Nethermind.Serialization.SszGenerator/Nethermind.Serialization.SszGenerator.csproj @@ -12,7 +12,7 @@ Generated - + diff --git a/src/Nethermind/Nethermind.Serialization.SszGenerator/SszProperty.cs b/src/Nethermind/Nethermind.Serialization.SszGenerator/SszProperty.cs index 1277e0b2805..64b4b9bf726 100644 --- a/src/Nethermind/Nethermind.Serialization.SszGenerator/SszProperty.cs +++ b/src/Nethermind/Nethermind.Serialization.SszGenerator/SszProperty.cs @@ -39,9 +39,9 @@ public static SszProperty From(SemanticModel semanticModel, List types, return array.ElementType!; } - INamedTypeSymbol? ienumerableOfT = compilation.GetTypeByMetadataName("System.Collections.Generic.IList`1"); - INamedTypeSymbol? enumerable = typeSymbol.AllInterfaces.FirstOrDefault(i => SymbolEqualityComparer.Default.Equals(i.OriginalDefinition, ienumerableOfT)); - if (ienumerableOfT != null && enumerable is not null) + INamedTypeSymbol? iListOfT = compilation.GetTypeByMetadataName("System.Collections.Generic.IList`1"); + INamedTypeSymbol? enumerable = typeSymbol.AllInterfaces.FirstOrDefault(i => SymbolEqualityComparer.Default.Equals(i.OriginalDefinition, iListOfT)); + if (iListOfT != null && enumerable is not null) { return enumerable.TypeArguments.First(); } @@ -81,16 +81,13 @@ public int StaticLength { return 4; } - try + + return Kind switch { - return Kind switch - { - Kind.Vector => Length!.Value * Type.StaticLength, - Kind.BitVector => (Length!.Value + 7) / 8, - _ => Type.StaticLength, - }; - } - catch { throw; } + Kind.Vector => Length!.Value * Type.StaticLength, + Kind.BitVector => (Length!.Value + 7) / 8, + _ => Type.StaticLength, + }; } } diff --git a/src/Nethermind/Nethermind.Shutter/ShutterBlockImprovementContext.cs b/src/Nethermind/Nethermind.Shutter/ShutterBlockImprovementContext.cs index 09617cc288c..bc213677c2e 100644 --- a/src/Nethermind/Nethermind.Shutter/ShutterBlockImprovementContext.cs +++ b/src/Nethermind/Nethermind.Shutter/ShutterBlockImprovementContext.cs @@ -118,7 +118,7 @@ public void Dispose() { (slot, offset) = _time.GetBuildingSlotAndOffset(_slotTimestampMs); } - catch (SlotTime.SlotCalulationException e) + catch (SlotTime.SlotCalculationException e) { if (_logger.IsWarn) _logger.Warn($"Could not calculate Shutter building slot: {e}"); await BuildBlock(); diff --git a/src/Nethermind/Nethermind.Shutter/ShutterCrypto.cs b/src/Nethermind/Nethermind.Shutter/ShutterCrypto.cs index b3f6275ddec..8325863d8f9 100644 --- a/src/Nethermind/Nethermind.Shutter/ShutterCrypto.cs +++ b/src/Nethermind/Nethermind.Shutter/ShutterCrypto.cs @@ -315,7 +315,7 @@ private static void ComputeR(scoped ReadOnlySpan sigma, scoped ReadOnlySpa internal static Hash256 GenerateHash(ulong instanceId, ulong eon, ulong slot, ulong txPointer, IEnumerable> identityPreimages) { - SlotDecryptionIdentites container = new() + SlotDecryptionIdentities container = new() { InstanceID = instanceId, Eon = eon, @@ -356,7 +356,7 @@ private static ValueHash256 Hash4(ReadOnlySpan bytes) return ValueKeccak.Compute(preimage); } - private readonly struct SlotDecryptionIdentites + private readonly struct SlotDecryptionIdentities { public ulong InstanceID { get; init; } public ulong Eon { get; init; } diff --git a/src/Nethermind/Nethermind.Shutter/ShutterEon.cs b/src/Nethermind/Nethermind.Shutter/ShutterEon.cs index 20e3f24b8bb..c41e3f0f037 100644 --- a/src/Nethermind/Nethermind.Shutter/ShutterEon.cs +++ b/src/Nethermind/Nethermind.Shutter/ShutterEon.cs @@ -66,7 +66,7 @@ public void Update(BlockHeader header) } else if (_logger.IsError) { - _logger.Error("Cannot use unfinalised Shutter keyper set contract."); + _logger.Error("Cannot use unfinalized Shutter keyper set contract."); } } } diff --git a/src/Nethermind/Nethermind.Shutter/ShutterTxLoader.cs b/src/Nethermind/Nethermind.Shutter/ShutterTxLoader.cs index 6c6f03813ce..7ba443fd9db 100644 --- a/src/Nethermind/Nethermind.Shutter/ShutterTxLoader.cs +++ b/src/Nethermind/Nethermind.Shutter/ShutterTxLoader.cs @@ -51,7 +51,7 @@ public ShutterTransactions LoadTransactions(Block? head, BlockHeader parentHeade long offset = time.GetCurrentOffsetMs(keys.Slot); Metrics.ShutterKeysReceivedTimeOffset = offset; - string offsetText = offset < 0 ? $"{-offset}ms before" : $"{offset}ms fter"; + string offsetText = offset < 0 ? $"{-offset}ms before" : $"{offset}ms after"; if (_logger.IsInfo) _logger.Info($"Got {sequencedTransactions.Count} encrypted transactions from Shutter sequencer contract for slot {keys.Slot} at time {offsetText} slot start..."); using ArrayPoolList<(Transaction Tx, UInt256 GasLimit)>? decrypted = DecryptSequencedTransactions(sequencedTransactions, keys.Keys); diff --git a/src/Nethermind/Nethermind.Shutter/ShutterTxSource.cs b/src/Nethermind/Nethermind.Shutter/ShutterTxSource.cs index fb02c03416d..705a9296745 100644 --- a/src/Nethermind/Nethermind.Shutter/ShutterTxSource.cs +++ b/src/Nethermind/Nethermind.Shutter/ShutterTxSource.cs @@ -43,7 +43,7 @@ public IEnumerable GetTransactions(BlockHeader parent, long gasLimi { (buildingSlot, _) = slotTime.GetBuildingSlotAndOffset(payloadAttributes!.Timestamp * 1000); } - catch (SlotTime.SlotCalulationException e) + catch (SlotTime.SlotCalculationException e) { if (_logger.IsDebug) _logger.Warn($"DEBUG/ERROR Could not calculate Shutter building slot: {e}"); return []; diff --git a/src/Nethermind/Nethermind.Shutter/SlotDecryptionIdentites.cs b/src/Nethermind/Nethermind.Shutter/SlotDecryptionIdentities.cs similarity index 93% rename from src/Nethermind/Nethermind.Shutter/SlotDecryptionIdentites.cs rename to src/Nethermind/Nethermind.Shutter/SlotDecryptionIdentities.cs index 52dfbf69fdd..e62685271cc 100644 --- a/src/Nethermind/Nethermind.Shutter/SlotDecryptionIdentites.cs +++ b/src/Nethermind/Nethermind.Shutter/SlotDecryptionIdentities.cs @@ -7,7 +7,7 @@ namespace Nethermind.Shutter; [SszSerializable] -public struct SlotDecryptionIdentites +public struct SlotDecryptionIdentities { public ulong InstanceID { get; set; } public ulong Eon { get; set; } diff --git a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs index a1f1d9606ef..f43d064c646 100644 --- a/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs +++ b/src/Nethermind/Nethermind.Specs.Test/ChainSpecStyle/ChainSpecBasedSpecProviderTests.cs @@ -56,8 +56,7 @@ public void Timestamp_activation_equal_to_genesis_timestamp_loads_correctly(long var logManager = Substitute.For(); logManager.GetClassLogger().Returns(logger); ChainSpecBasedSpecProvider provider = new(chainSpec); - ReleaseSpec expectedSpec = ((ReleaseSpec)MainnetSpecProvider - .Instance.GetSpec((MainnetSpecProvider.GrayGlacierBlockNumber, null))).Clone(); + ReleaseSpec expectedSpec = ((ReleaseSpec)MainnetSpecProvider.Instance.GetSpec((MainnetSpecProvider.GrayGlacierBlockNumber, null))).Clone(); expectedSpec.Name = "Genesis_with_non_zero_timestamp"; expectedSpec.IsEip3651Enabled = true; expectedSpec.IsEip3198Enabled = false; @@ -239,7 +238,7 @@ private static void VerifyPragueSpecificsForMainnetHoodiAndSepolia(ulong chainId expectedDepositContractAddress = Eip6110Constants.SepoliaDepositContractAddress; break; default: - Assert.Fail("Unrecognised chain id when verifying Prague specifics."); + Assert.Fail("Unrecognized chain id when verifying Prague specifics."); return; } @@ -378,10 +377,10 @@ public static IEnumerable GnosisActivations { yield return new TestCaseData((ForkActivation)0) { TestName = "Genesis" }; yield return new TestCaseData((ForkActivation)1) { TestName = "Genesis + 1" }; - yield return new TestCaseData((ForkActivation)(GnosisSpecProvider.ConstantinopoleBlockNumber - 1)) { TestName = "Before Constantinopole" }; - yield return new TestCaseData((ForkActivation)GnosisSpecProvider.ConstantinopoleBlockNumber) { TestName = "Constantinopole" }; - yield return new TestCaseData((ForkActivation)(GnosisSpecProvider.ConstantinopoleFixBlockNumber - 1)) { TestName = "Before ConstantinopoleFix" }; - yield return new TestCaseData((ForkActivation)GnosisSpecProvider.ConstantinopoleFixBlockNumber) { TestName = "ConstantinopoleFix" }; + yield return new TestCaseData((ForkActivation)(GnosisSpecProvider.ConstantinopleBlockNumber - 1)) { TestName = "Before Constantinople" }; + yield return new TestCaseData((ForkActivation)GnosisSpecProvider.ConstantinopleBlockNumber) { TestName = "Constantinople" }; + yield return new TestCaseData((ForkActivation)(GnosisSpecProvider.ConstantinopleFixBlockNumber - 1)) { TestName = "Before ConstantinopleFix" }; + yield return new TestCaseData((ForkActivation)GnosisSpecProvider.ConstantinopleFixBlockNumber) { TestName = "ConstantinopleFix" }; yield return new TestCaseData((ForkActivation)(GnosisSpecProvider.IstanbulBlockNumber - 1)) { TestName = "Before Istanbul" }; yield return new TestCaseData((ForkActivation)GnosisSpecProvider.IstanbulBlockNumber) { TestName = "Istanbul" }; yield return new TestCaseData((ForkActivation)(GnosisSpecProvider.BerlinBlockNumber - 1)) { TestName = "Before Berlin" }; @@ -506,10 +505,10 @@ private static void VerifyGnosisPreShanghaiSpecifics(ISpecProvider specProvider) using (Assert.EnterMultipleScope()) { Assert.That(specProvider.GenesisSpec.MaximumUncleCount, Is.Zero); - Assert.That(specProvider.GetSpec((ForkActivation)(GnosisSpecProvider.ConstantinopoleBlockNumber - 1)).IsEip1283Enabled, Is.False); - Assert.That(specProvider.GetSpec((ForkActivation)(GnosisSpecProvider.ConstantinopoleBlockNumber)).IsEip1283Enabled, Is.True); - Assert.That(specProvider.GetSpec((ForkActivation)(GnosisSpecProvider.ConstantinopoleBlockNumber - 1)).UseConstantinopleNetGasMetering, Is.False); - Assert.That(specProvider.GetSpec((ForkActivation)(GnosisSpecProvider.ConstantinopoleBlockNumber)).UseConstantinopleNetGasMetering, Is.True); + Assert.That(specProvider.GetSpec((ForkActivation)(GnosisSpecProvider.ConstantinopleBlockNumber - 1)).IsEip1283Enabled, Is.False); + Assert.That(specProvider.GetSpec((ForkActivation)(GnosisSpecProvider.ConstantinopleBlockNumber)).IsEip1283Enabled, Is.True); + Assert.That(specProvider.GetSpec((ForkActivation)(GnosisSpecProvider.ConstantinopleBlockNumber - 1)).UseConstantinopleNetGasMetering, Is.False); + Assert.That(specProvider.GetSpec((ForkActivation)(GnosisSpecProvider.ConstantinopleBlockNumber)).UseConstantinopleNetGasMetering, Is.True); } } @@ -661,12 +660,9 @@ private static void CompareSpecs(IReleaseSpec expectedSpec, IReleaseSpec actualS // handle gnosis specific exceptions .Where(p => !isGnosis || p.Name != nameof(IReleaseSpec.MaxCodeSize)) - .Where(p => !isGnosis || p.Name != nameof(IReleaseSpec.MaxInitCodeSize)) .Where(p => !isGnosis || p.Name != nameof(IReleaseSpec.MaximumUncleCount)) .Where(p => !isGnosis || p.Name != nameof(IReleaseSpec.IsEip170Enabled)) - .Where(p => !isGnosis || p.Name != nameof(IReleaseSpec.IsEip1283Enabled)) - .Where(p => !isGnosis || p.Name != nameof(IReleaseSpec.LimitCodeSize)) - .Where(p => !isGnosis || p.Name != nameof(IReleaseSpec.UseConstantinopleNetGasMetering))) + .Where(p => !isGnosis || p.Name != nameof(IReleaseSpec.IsEip1283Enabled))) { Assert.That(propertyInfo.GetValue(actualSpec), Is.EqualTo(propertyInfo.GetValue(expectedSpec)), activation + "." + propertyInfo.Name); diff --git a/src/Nethermind/Nethermind.Specs.Test/Nethermind.Specs.Test.csproj b/src/Nethermind/Nethermind.Specs.Test/Nethermind.Specs.Test.csproj index 79f401ccb4e..d6fc33aed5d 100644 --- a/src/Nethermind/Nethermind.Specs.Test/Nethermind.Specs.Test.csproj +++ b/src/Nethermind/Nethermind.Specs.Test/Nethermind.Specs.Test.csproj @@ -12,6 +12,7 @@ + diff --git a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs index 0b801ab6861..702e10ee5d1 100644 --- a/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs.Test/OverridableReleaseSpec.cs @@ -16,223 +16,109 @@ namespace Nethermind.Specs.Test public class OverridableReleaseSpec(IReleaseSpec spec) : IReleaseSpec { public string Name => "OverridableReleaseSpec"; - - public long MaximumExtraDataSize => spec.MaximumExtraDataSize; - - public long MaxCodeSize => spec.MaxCodeSize; - - public long MaxInitCodeSize => spec.MaxInitCodeSize; - - public long MinGasLimit => spec.MinGasLimit; - - public long MinHistoryRetentionEpochs => spec.MinHistoryRetentionEpochs; - - public long GasLimitBoundDivisor => spec.GasLimitBoundDivisor; - - private UInt256? _blockReward = spec.BlockReward; - public UInt256 BlockReward - { - get => _blockReward ?? spec.BlockReward; - set => _blockReward = value; - } - - public long DifficultyBombDelay => spec.DifficultyBombDelay; - - public long DifficultyBoundDivisor => spec.DifficultyBoundDivisor; - - public long? FixedDifficulty => spec.FixedDifficulty; - - public int MaximumUncleCount => spec.MaximumUncleCount; - - public bool IsTimeAdjustmentPostOlympic => spec.IsTimeAdjustmentPostOlympic; - - public bool IsEip2Enabled => spec.IsEip2Enabled; - - public bool IsEip7Enabled => spec.IsEip7Enabled; - - public bool IsEip100Enabled => spec.IsEip100Enabled; - - public bool IsEip140Enabled => spec.IsEip140Enabled; - - public bool IsEip150Enabled => spec.IsEip150Enabled; - - public bool IsEip155Enabled => spec.IsEip155Enabled; - - public bool IsEip158Enabled => spec.IsEip158Enabled; - - public bool IsEip160Enabled => spec.IsEip160Enabled; - - public bool IsEip170Enabled => spec.IsEip170Enabled; - - public bool IsEip196Enabled => spec.IsEip196Enabled; - - public bool IsEip197Enabled => spec.IsEip197Enabled; - - public bool IsEip198Enabled => spec.IsEip198Enabled; - - public bool IsEip211Enabled => spec.IsEip211Enabled; - - public bool IsEip214Enabled => spec.IsEip214Enabled; - - public bool IsEip649Enabled => spec.IsEip649Enabled; - - public bool IsEip658Enabled => spec.IsEip658Enabled; - - public bool IsEip145Enabled => spec.IsEip145Enabled; - - public bool IsEip1014Enabled => spec.IsEip1014Enabled; - - public bool IsEip1052Enabled => spec.IsEip1052Enabled; - - public bool IsEip1283Enabled => spec.IsEip1283Enabled; - - public bool IsEip1234Enabled => spec.IsEip1234Enabled; - - public bool IsEip1344Enabled => spec.IsEip1344Enabled; - - public bool IsEip2028Enabled => spec.IsEip2028Enabled; - - public bool IsEip152Enabled => spec.IsEip152Enabled; - - public bool IsEip1108Enabled => spec.IsEip1108Enabled; - - public bool IsEip1884Enabled => spec.IsEip1884Enabled; - - public bool IsEip2200Enabled => spec.IsEip2200Enabled; - - public bool IsEip2537Enabled => spec.IsEip2537Enabled; - - public bool IsEip2565Enabled => spec.IsEip2565Enabled; - - public bool IsEip2929Enabled => spec.IsEip2929Enabled; - - public bool IsEip2930Enabled => spec.IsEip2930Enabled; - - public bool IsEip1559Enabled => spec.IsEip1559Enabled; - public bool IsEip3198Enabled => spec.IsEip3198Enabled; - public bool IsEip3529Enabled => spec.IsEip3529Enabled; - - public bool IsEip3541Enabled => spec.IsEip3541Enabled; - public bool IsEip4844Enabled => spec.IsEip4844Enabled; - public bool IsEip7951Enabled => spec.IsEip7951Enabled; - public bool IsRip7212Enabled => spec.IsRip7212Enabled; - public bool IsOpGraniteEnabled => spec.IsOpGraniteEnabled; - public bool IsOpHoloceneEnabled => spec.IsOpHoloceneEnabled; - public bool IsOpIsthmusEnabled => spec.IsOpIsthmusEnabled; - public bool IsOpJovianEnabled => spec.IsOpJovianEnabled; - - public bool IsEip7623Enabled => spec.IsEip7623Enabled; - public bool IsEip7918Enabled => spec.IsEip7918Enabled; - - public bool IsEip7883Enabled => spec.IsEip7883Enabled; - - public bool IsEip7934Enabled => spec.IsEip7934Enabled; - public int Eip7934MaxRlpBlockSize => spec.Eip7934MaxRlpBlockSize; - + public long MaximumExtraDataSize { get; set; } = spec.MaximumExtraDataSize; + public long MaxCodeSize { get; set; } = spec.MaxCodeSize; + public long MinGasLimit { get; set; } = spec.MinGasLimit; + public long MinHistoryRetentionEpochs { get; set; } = spec.MinHistoryRetentionEpochs; + public long GasLimitBoundDivisor { get; set; } = spec.GasLimitBoundDivisor; + public UInt256 BlockReward { get; set; } = spec.BlockReward; + public long DifficultyBombDelay { get; set; } = spec.DifficultyBombDelay; + public long DifficultyBoundDivisor { get; set; } = spec.DifficultyBoundDivisor; + public long? FixedDifficulty { get; set; } = spec.FixedDifficulty; + public int MaximumUncleCount { get; set; } = spec.MaximumUncleCount; + public bool IsTimeAdjustmentPostOlympic { get; set; } = spec.IsTimeAdjustmentPostOlympic; + public bool IsEip2Enabled { get; set; } = spec.IsEip2Enabled; + public bool IsEip7Enabled { get; set; } = spec.IsEip7Enabled; + public bool IsEip100Enabled { get; set; } = spec.IsEip100Enabled; + public bool IsEip140Enabled { get; set; } = spec.IsEip140Enabled; + public bool IsEip150Enabled { get; set; } = spec.IsEip150Enabled; + public bool IsEip155Enabled { get; set; } = spec.IsEip155Enabled; + public bool IsEip158Enabled { get; set; } = spec.IsEip158Enabled; + public bool IsEip160Enabled { get; set; } = spec.IsEip160Enabled; + public bool IsEip170Enabled { get; set; } = spec.IsEip170Enabled; + public bool IsEip196Enabled { get; set; } = spec.IsEip196Enabled; + public bool IsEip197Enabled { get; set; } = spec.IsEip197Enabled; + public bool IsEip198Enabled { get; set; } = spec.IsEip198Enabled; + public bool IsEip211Enabled { get; set; } = spec.IsEip211Enabled; + public bool IsEip214Enabled { get; set; } = spec.IsEip214Enabled; + public bool IsEip649Enabled { get; set; } = spec.IsEip649Enabled; + public bool IsEip658Enabled { get; set; } = spec.IsEip658Enabled; + public bool IsEip145Enabled { get; set; } = spec.IsEip145Enabled; + public bool IsEip1014Enabled { get; set; } = spec.IsEip1014Enabled; + public bool IsEip1052Enabled { get; set; } = spec.IsEip1052Enabled; + public bool IsEip1283Enabled { get; set; } = spec.IsEip1283Enabled; + public bool IsEip1234Enabled { get; set; } = spec.IsEip1234Enabled; + public bool IsEip1344Enabled { get; set; } = spec.IsEip1344Enabled; + public bool IsEip2028Enabled { get; set; } = spec.IsEip2028Enabled; + public bool IsEip152Enabled { get; set; } = spec.IsEip152Enabled; + public bool IsEip1108Enabled { get; set; } = spec.IsEip1108Enabled; + public bool IsEip1884Enabled { get; set; } = spec.IsEip1884Enabled; + public bool IsEip2200Enabled { get; set; } = spec.IsEip2200Enabled; + public bool IsEip2537Enabled { get; set; } = spec.IsEip2537Enabled; + public bool IsEip2565Enabled { get; set; } = spec.IsEip2565Enabled; + public bool IsEip2929Enabled { get; set; } = spec.IsEip2929Enabled; + public bool IsEip2930Enabled { get; set; } = spec.IsEip2930Enabled; + public bool IsEip1559Enabled { get; set; } = spec.IsEip1559Enabled; + public bool IsEip3198Enabled { get; set; } = spec.IsEip3198Enabled; + public bool IsEip3529Enabled { get; set; } = spec.IsEip3529Enabled; + public bool IsEip3541Enabled { get; set; } = spec.IsEip3541Enabled; + public bool IsEip4844Enabled { get; set; } = spec.IsEip4844Enabled; + public bool IsEip7951Enabled { get; set; } = spec.IsEip7951Enabled; + public bool IsRip7212Enabled { get; set; } = spec.IsRip7212Enabled; + public bool IsOpGraniteEnabled { get; set; } = spec.IsOpGraniteEnabled; + public bool IsOpHoloceneEnabled { get; set; } = spec.IsOpHoloceneEnabled; + public bool IsOpIsthmusEnabled { get; set; } = spec.IsOpIsthmusEnabled; + public bool IsOpJovianEnabled { get; set; } = spec.IsOpJovianEnabled; + public bool IsEip7623Enabled { get; set; } = spec.IsEip7623Enabled; + public bool IsEip7918Enabled { get; set; } = spec.IsEip7918Enabled; + public bool IsEip7883Enabled { get; set; } = spec.IsEip7883Enabled; + public bool IsEip7934Enabled { get; set; } = spec.IsEip7934Enabled; + public int Eip7934MaxRlpBlockSize { get; set; } = spec.Eip7934MaxRlpBlockSize; + public bool ValidateChainId { get; set; } = spec.ValidateChainId; public bool IsEip3607Enabled { get; set; } = spec.IsEip3607Enabled; - public bool IsEip158IgnoredAccount(Address address) => spec.IsEip158IgnoredAccount(address); - - private long? _overridenEip1559TransitionBlock; - public long Eip1559TransitionBlock - { - get => _overridenEip1559TransitionBlock ?? spec.Eip1559TransitionBlock; - set => _overridenEip1559TransitionBlock = value; - } - - private Address? _overridenFeeCollector; - public Address? FeeCollector - { - get => _overridenFeeCollector ?? spec.FeeCollector; - set => _overridenFeeCollector = value; - } - - private ulong? _overridenEip4844TransitionTimeStamp; - - public ulong Eip4844TransitionTimestamp - { - get - { - return _overridenEip4844TransitionTimeStamp ?? spec.Eip4844TransitionTimestamp; - } - set - { - _overridenEip4844TransitionTimeStamp = value; - } - } - - private ulong? _overridenTargetBlobCount; - public ulong TargetBlobCount - { - get - { - return _overridenTargetBlobCount ?? spec.TargetBlobCount; - } - set - { - _overridenTargetBlobCount = value; - } - } - private ulong? _overridenMaxBlobCount; - public ulong MaxBlobCount - { - get - { - return _overridenMaxBlobCount ?? spec.MaxBlobCount; - } - set - { - _overridenMaxBlobCount = value; - } - } - public ulong MaxBlobsPerTx => spec.MaxBlobsPerTx; - private UInt256? _overridenBlobBaseFeeUpdateFraction; - public UInt256 BlobBaseFeeUpdateFraction - { - get - { - return _overridenBlobBaseFeeUpdateFraction ?? spec.BlobBaseFeeUpdateFraction; - } - set - { - _overridenBlobBaseFeeUpdateFraction = value; - } - } - public bool IsEip1153Enabled => spec.IsEip1153Enabled; - public bool IsEip3651Enabled => spec.IsEip3651Enabled; - public bool IsEip3855Enabled => spec.IsEip3855Enabled; - public bool IsEip3860Enabled => spec.IsEip3860Enabled; - public bool IsEip4895Enabled => spec.IsEip4895Enabled; - public ulong WithdrawalTimestamp => spec.WithdrawalTimestamp; - public bool IsEip5656Enabled => spec.IsEip5656Enabled; - public bool IsEip6780Enabled => spec.IsEip6780Enabled; - public bool IsEip4788Enabled => spec.IsEip4788Enabled; - public bool IsEip4844FeeCollectorEnabled => spec.IsEip4844FeeCollectorEnabled; - public Address? Eip4788ContractAddress => spec.Eip4788ContractAddress; - public bool IsEip7002Enabled => spec.IsEip7002Enabled; - public Address Eip7002ContractAddress => spec.Eip7002ContractAddress; - public bool IsEip7251Enabled => spec.IsEip7251Enabled; - public Address Eip7251ContractAddress => spec.Eip7251ContractAddress; - public bool IsEip2935Enabled => spec.IsEip2935Enabled; - public bool IsEip7709Enabled => spec.IsEip7709Enabled; - public Address Eip2935ContractAddress => spec.Eip2935ContractAddress; - public bool IsEip7702Enabled => spec.IsEip7702Enabled; - public bool IsEip7823Enabled => spec.IsEip7823Enabled; + public long Eip1559TransitionBlock { get; set; } = spec.Eip1559TransitionBlock; + public Address? FeeCollector { get; set; } = spec.FeeCollector; + public ulong Eip4844TransitionTimestamp { get; set; } = spec.Eip4844TransitionTimestamp; + public ulong TargetBlobCount { get; set; } = spec.TargetBlobCount; + public ulong MaxBlobCount { get; set; } = spec.MaxBlobCount; + public ulong MaxBlobsPerTx { get; set; } = spec.MaxBlobsPerTx; + public UInt256 BlobBaseFeeUpdateFraction { get; set; } = spec.BlobBaseFeeUpdateFraction; + public bool IsEip1153Enabled { get; set; } = spec.IsEip1153Enabled; + public bool IsEip3651Enabled { get; set; } = spec.IsEip3651Enabled; + public bool IsEip3855Enabled { get; set; } = spec.IsEip3855Enabled; + public bool IsEip3860Enabled { get; set; } = spec.IsEip3860Enabled; + public bool IsEip4895Enabled { get; set; } = spec.IsEip4895Enabled; + public ulong WithdrawalTimestamp { get; set; } = spec.WithdrawalTimestamp; + public bool IsEip5656Enabled { get; set; } = spec.IsEip5656Enabled; + public long Eip2935RingBufferSize { get; set; } = spec.Eip2935RingBufferSize; + public bool IsEip6780Enabled { get; set; } = spec.IsEip6780Enabled; + public bool IsEip4788Enabled { get; set; } = spec.IsEip4788Enabled; + public bool IsEip4844FeeCollectorEnabled { get; set; } = spec.IsEip4844FeeCollectorEnabled; + public Address? Eip4788ContractAddress { get; set; } = spec.Eip4788ContractAddress; + public bool IsEip7002Enabled { get; set; } = spec.IsEip7002Enabled; + public Address? Eip7002ContractAddress { get; set; } = spec.Eip7002ContractAddress; + public bool IsEip7251Enabled { get; set; } = spec.IsEip7251Enabled; + public Address? Eip7251ContractAddress { get; set; } = spec.Eip7251ContractAddress; + public bool IsEip2935Enabled { get; set; } = spec.IsEip2935Enabled; + public bool IsEip7709Enabled { get; set; } = spec.IsEip7709Enabled; + public Address? Eip2935ContractAddress { get; set; } = spec.Eip2935ContractAddress; + public bool IsEip7702Enabled { get; set; } = spec.IsEip7702Enabled; + public bool IsEip7823Enabled { get; set; } = spec.IsEip7823Enabled; public bool IsEip7825Enabled { get; set; } = spec.IsEip7825Enabled; - public UInt256 ForkBaseFee => spec.ForkBaseFee; - public UInt256 BaseFeeMaxChangeDenominator => spec.BaseFeeMaxChangeDenominator; - public long ElasticityMultiplier => spec.ElasticityMultiplier; - public IBaseFeeCalculator BaseFeeCalculator => spec.BaseFeeCalculator; - public bool IsEofEnabled => spec.IsEofEnabled; - public bool IsEip6110Enabled => spec.IsEip6110Enabled; - public Address DepositContractAddress => spec.DepositContractAddress; - public bool IsEip7594Enabled => spec.IsEip7594Enabled; - + public UInt256 ForkBaseFee { get; set; } = spec.ForkBaseFee; + public UInt256 BaseFeeMaxChangeDenominator { get; set; } = spec.BaseFeeMaxChangeDenominator; + public long ElasticityMultiplier { get; set; } = spec.ElasticityMultiplier; + public IBaseFeeCalculator BaseFeeCalculator { get; set; } = spec.BaseFeeCalculator; + public bool IsEofEnabled { get; set; } = spec.IsEofEnabled; + public bool IsEip6110Enabled { get; set; } = spec.IsEip6110Enabled; + public Address? DepositContractAddress { get; set; } = spec.DepositContractAddress; + public bool IsEip7594Enabled { get; set; } = spec.IsEip7594Enabled; Array? IReleaseSpec.EvmInstructionsNoTrace { get => spec.EvmInstructionsNoTrace; set => spec.EvmInstructionsNoTrace = value; } Array? IReleaseSpec.EvmInstructionsTraced { get => spec.EvmInstructionsTraced; set => spec.EvmInstructionsTraced = value; } - public bool IsEip7939Enabled => spec.IsEip7939Enabled; - public bool IsEip7907Enabled => spec.IsEip7907Enabled; - public bool IsRip7728Enabled => spec.IsRip7728Enabled; + public bool IsEip7939Enabled { get; set; } = spec.IsEip7939Enabled; + public bool IsEip7907Enabled { get; set; } = spec.IsEip7907Enabled; + public bool IsRip7728Enabled { get; set; } = spec.IsRip7728Enabled; public bool IsEip7928Enabled { get; set; } = spec.IsEip7928Enabled; FrozenSet IReleaseSpec.Precompiles => spec.Precompiles; } diff --git a/src/Nethermind/Nethermind.Specs.Test/OverridableSpecProvider.cs b/src/Nethermind/Nethermind.Specs.Test/OverridableSpecProvider.cs index 4a5699e4c18..14abbe55843 100644 --- a/src/Nethermind/Nethermind.Specs.Test/OverridableSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs.Test/OverridableSpecProvider.cs @@ -37,7 +37,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public IReleaseSpec GenesisSpec => _overrideAction(SpecProvider.GenesisSpec, new ForkActivation(0)); - IReleaseSpec ISpecProvider.GetSpecInternal(ForkActivation forkActivation) => _overrideAction(SpecProvider.GetSpec(forkActivation), forkActivation); + public IReleaseSpec GetSpec(ForkActivation forkActivation) => _overrideAction(SpecProvider.GetSpec(forkActivation), forkActivation); public long? DaoBlockNumber => SpecProvider.DaoBlockNumber; public ulong? BeaconChainGenesisTimestamp => SpecProvider.BeaconChainGenesisTimestamp; diff --git a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/SpecProviderBase.cs b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/SpecProviderBase.cs index 8d826557df9..4a3ef4b16b1 100644 --- a/src/Nethermind/Nethermind.Specs/ChainSpecStyle/SpecProviderBase.cs +++ b/src/Nethermind/Nethermind.Specs/ChainSpecStyle/SpecProviderBase.cs @@ -43,7 +43,7 @@ protected void LoadTransitions((ForkActivation Activation, IReleaseSpec Spec)[] public IReleaseSpec GenesisSpec { get; private set; } - public IReleaseSpec GetSpecInternal(ForkActivation activation) + public IReleaseSpec GetSpec(ForkActivation activation) { static int CompareTransitionOnActivation(ForkActivation activation, (ForkActivation Activation, IReleaseSpec Spec) transition) => activation.CompareTo(transition.Activation); diff --git a/src/Nethermind/Nethermind.Specs/ChiadoSpecProvider.cs b/src/Nethermind/Nethermind.Specs/ChiadoSpecProvider.cs index 006775826f2..2d7bc1291f8 100644 --- a/src/Nethermind/Nethermind.Specs/ChiadoSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/ChiadoSpecProvider.cs @@ -21,7 +21,7 @@ public class ChiadoSpecProvider : ISpecProvider private ChiadoSpecProvider() { } - IReleaseSpec ISpecProvider.GetSpecInternal(ForkActivation forkActivation) => forkActivation.BlockNumber switch + public IReleaseSpec GetSpec(ForkActivation forkActivation) => forkActivation.BlockNumber switch { _ => forkActivation.Timestamp switch { @@ -44,7 +44,9 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public ForkActivation? MergeBlockNumber { get; private set; } public ulong TimestampFork => ShanghaiTimestamp; - public UInt256? TerminalTotalDifficulty { get; private set; } = UInt256.Parse("231707791542740786049188744689299064356246512"); + + // 231707791542740786049188744689299064356246512 + public UInt256? TerminalTotalDifficulty { get; private set; } = new UInt256(18446744073375486960ul, 18446744073709551615ul, 680927ul); public IReleaseSpec GenesisSpec => London.Instance; public long? DaoBlockNumber => null; public ulong? BeaconChainGenesisTimestamp => BeaconChainGenesisTimestampConst; diff --git a/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs b/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs index 3850d3dd360..a9408a3d61c 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/00_Olympic.cs @@ -19,7 +19,7 @@ protected Olympic() MaxCodeSize = long.MaxValue; MinGasLimit = 5000; GasLimitBoundDivisor = 0x0400; - BlockReward = UInt256.Parse("5000000000000000000"); + BlockReward = new UInt256(5000000000000000000ul); DifficultyBoundDivisor = 0x0800; IsEip3607Enabled = true; MaximumUncleCount = 2; diff --git a/src/Nethermind/Nethermind.Specs/Forks/06_Byzantium.cs b/src/Nethermind/Nethermind.Specs/Forks/06_Byzantium.cs index a41a41dbd86..f37711dfd26 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/06_Byzantium.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/06_Byzantium.cs @@ -14,7 +14,7 @@ public class Byzantium : SpuriousDragon protected Byzantium() { Name = "Byzantium"; - BlockReward = UInt256.Parse("3000000000000000000"); + BlockReward = new UInt256(3000000000000000000ul); DifficultyBombDelay = 3000000L; IsEip100Enabled = true; IsEip140Enabled = true; diff --git a/src/Nethermind/Nethermind.Specs/Forks/07_Constantinople.cs b/src/Nethermind/Nethermind.Specs/Forks/07_Constantinople.cs index b922cf5bb33..c6804bf92a7 100644 --- a/src/Nethermind/Nethermind.Specs/Forks/07_Constantinople.cs +++ b/src/Nethermind/Nethermind.Specs/Forks/07_Constantinople.cs @@ -14,7 +14,7 @@ public class Constantinople : Byzantium protected Constantinople() { Name = "Constantinople"; - BlockReward = UInt256.Parse("2000000000000000000"); + BlockReward = new UInt256(2000000000000000000ul); DifficultyBombDelay = 5000000L; IsEip145Enabled = true; IsEip1014Enabled = true; diff --git a/src/Nethermind/Nethermind.Specs/FrontierSpecProvider.cs b/src/Nethermind/Nethermind.Specs/FrontierSpecProvider.cs index 975b10dab09..f172aba8b0e 100644 --- a/src/Nethermind/Nethermind.Specs/FrontierSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/FrontierSpecProvider.cs @@ -25,7 +25,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public UInt256? TerminalTotalDifficulty { get; private set; } public IReleaseSpec GenesisSpec => Frontier.Instance; - IReleaseSpec ISpecProvider.GetSpecInternal(ForkActivation forkActivation) => Frontier.Instance; + public IReleaseSpec GetSpec(ForkActivation forkActivation) => Frontier.Instance; public long? DaoBlockNumber { get; } = null; public ulong? BeaconChainGenesisTimestamp => null; diff --git a/src/Nethermind/Nethermind.Specs/GnosisSpecProvider.cs b/src/Nethermind/Nethermind.Specs/GnosisSpecProvider.cs index 479c7803097..821df7a8d0f 100644 --- a/src/Nethermind/Nethermind.Specs/GnosisSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/GnosisSpecProvider.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using Nethermind.Core; @@ -11,8 +11,8 @@ namespace Nethermind.Specs; public class GnosisSpecProvider : ISpecProvider { - public const long ConstantinopoleBlockNumber = 1_604_400; - public const long ConstantinopoleFixBlockNumber = 2_508_800; + public const long ConstantinopleBlockNumber = 1_604_400; + public const long ConstantinopleFixBlockNumber = 2_508_800; public const long IstanbulBlockNumber = 7_298_030; public const long BerlinBlockNumber = 16_101_500; public const long LondonBlockNumber = 19_040_000; @@ -20,16 +20,17 @@ public class GnosisSpecProvider : ISpecProvider public const ulong ShanghaiTimestamp = 0x64c8edbc; public const ulong CancunTimestamp = 0x65ef4dbc; public const ulong PragueTimestamp = 0x68122dbc; + public const ulong BalancerTimestamp = 0x69496dbc; // does not alter specs public static readonly Address FeeCollector = new("0x6BBe78ee9e474842Dbd4AB4987b3CeFE88426A92"); private GnosisSpecProvider() { } - IReleaseSpec ISpecProvider.GetSpecInternal(ForkActivation forkActivation) + public IReleaseSpec GetSpec(ForkActivation forkActivation) { return forkActivation.BlockNumber switch { - < ConstantinopoleBlockNumber => GenesisSpec, - < ConstantinopoleFixBlockNumber => Constantinople.Instance, + < ConstantinopleBlockNumber => GenesisSpec, + < ConstantinopleFixBlockNumber => Constantinople.Instance, < IstanbulBlockNumber => ConstantinopleFix.Instance, < BerlinBlockNumber => Istanbul.Instance, < LondonBlockNumber => Berlin.Instance, @@ -54,7 +55,8 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public ForkActivation? MergeBlockNumber { get; private set; } public ulong TimestampFork => ShanghaiTimestamp; - public UInt256? TerminalTotalDifficulty { get; private set; } = UInt256.Parse("8626000000000000000000058750000000000000000000"); + // 8626000000000000000000058750000000000000000000 + public UInt256? TerminalTotalDifficulty { get; private set; } = new UInt256(15847367919172845568ul, 12460455203863319017ul, 25349535ul); public IReleaseSpec GenesisSpec => Byzantium.Instance; public long? DaoBlockNumber => null; public ulong? BeaconChainGenesisTimestamp => BeaconChainGenesisTimestampConst; diff --git a/src/Nethermind/Nethermind.Specs/HoodiSpecProvider.cs b/src/Nethermind/Nethermind.Specs/HoodiSpecProvider.cs index e9e328fe113..bb02ff2e72f 100644 --- a/src/Nethermind/Nethermind.Specs/HoodiSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/HoodiSpecProvider.cs @@ -26,7 +26,7 @@ public class HoodiSpecProvider : ISpecProvider private HoodiSpecProvider() { } - IReleaseSpec ISpecProvider.GetSpecInternal(ForkActivation forkActivation) + public IReleaseSpec GetSpec(ForkActivation forkActivation) { return forkActivation.Timestamp switch { diff --git a/src/Nethermind/Nethermind.Specs/MainnetSpecProvider.cs b/src/Nethermind/Nethermind.Specs/MainnetSpecProvider.cs index 1c3425fe3ba..948e8ebd869 100644 --- a/src/Nethermind/Nethermind.Specs/MainnetSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/MainnetSpecProvider.cs @@ -31,7 +31,7 @@ public class MainnetSpecProvider : ISpecProvider public const ulong BPO1BlockTimestamp = 0x69383057; public const ulong BPO2BlockTimestamp = 0x695db057; - IReleaseSpec ISpecProvider.GetSpecInternal(ForkActivation forkActivation) => + public IReleaseSpec GetSpec(ForkActivation forkActivation) => forkActivation switch { { BlockNumber: < HomesteadBlockNumber } => Frontier.Instance, @@ -65,13 +65,14 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD TerminalTotalDifficulty = terminalTotalDifficulty; } - public ulong NetworkId { get; } = Core.BlockchainIds.Mainnet; + public ulong NetworkId => Core.BlockchainIds.Mainnet; public ulong ChainId => NetworkId; public long? DaoBlockNumber => DaoBlockNumberConst; public ulong? BeaconChainGenesisTimestamp => BeaconChainGenesisTimestampConst; public ForkActivation? MergeBlockNumber { get; private set; } = null; - public ulong TimestampFork { get; } = ShanghaiBlockTimestamp; - public UInt256? TerminalTotalDifficulty { get; private set; } = UInt256.Parse("58750000000000000000000"); + public ulong TimestampFork => ShanghaiBlockTimestamp; + // 58750000000000000000000 + public UInt256? TerminalTotalDifficulty { get; private set; } = new UInt256(15566869308787654656ul, 3184ul); public IReleaseSpec GenesisSpec => Frontier.Instance; public static ForkActivation ShanghaiActivation { get; } = (ParisBlockNumber + 1, ShanghaiBlockTimestamp); public static ForkActivation CancunActivation { get; } = (ParisBlockNumber + 2, CancunBlockTimestamp); diff --git a/src/Nethermind/Nethermind.Specs/MordenSpecProvider.cs b/src/Nethermind/Nethermind.Specs/MordenSpecProvider.cs index a75fe3fe8d1..aa572aad154 100644 --- a/src/Nethermind/Nethermind.Specs/MordenSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/MordenSpecProvider.cs @@ -25,7 +25,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public UInt256? TerminalTotalDifficulty { get; private set; } public IReleaseSpec GenesisSpec => Frontier.Instance; - IReleaseSpec ISpecProvider.GetSpecInternal(ForkActivation forkActivation) => + public IReleaseSpec GetSpec(ForkActivation forkActivation) => forkActivation.BlockNumber switch { < 494000 => Frontier.Instance, diff --git a/src/Nethermind/Nethermind.Specs/OlympicSpecProvider.cs b/src/Nethermind/Nethermind.Specs/OlympicSpecProvider.cs index 89a6c258ead..b3556fa01aa 100644 --- a/src/Nethermind/Nethermind.Specs/OlympicSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/OlympicSpecProvider.cs @@ -24,7 +24,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public UInt256? TerminalTotalDifficulty { get; private set; } public IReleaseSpec GenesisSpec => Olympic.Instance; - IReleaseSpec ISpecProvider.GetSpecInternal(ForkActivation forkActivation) => Olympic.Instance; + public IReleaseSpec GetSpec(ForkActivation forkActivation) => Olympic.Instance; public long? DaoBlockNumber => 0L; public ulong? BeaconChainGenesisTimestamp => null; diff --git a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs index 8dc9aa568fd..3161e3ee504 100644 --- a/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Specs/ReleaseSpec.cs @@ -4,204 +4,166 @@ using System; using System.Collections.Frozen; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Nethermind.Core; using Nethermind.Core.Precompiles; using Nethermind.Core.Specs; using Nethermind.Int256; -namespace Nethermind.Specs +namespace Nethermind.Specs; + +public class ReleaseSpec : IReleaseSpec { - public class ReleaseSpec : IReleaseSpec + public string Name { get; set; } = "Custom"; + public long MaximumExtraDataSize { get; set; } + public long MaxCodeSize { get; set; } + public long MinGasLimit { get; set; } + public long MinHistoryRetentionEpochs { get; set; } + public long GasLimitBoundDivisor { get; set; } + public UInt256 BlockReward { get; set; } + public long DifficultyBombDelay { get; set; } + public long DifficultyBoundDivisor { get; set; } + public long? FixedDifficulty { get; set; } + public int MaximumUncleCount { get; set; } + public bool IsTimeAdjustmentPostOlympic { get; set; } + public bool IsEip2Enabled { get; set; } + public bool IsEip7Enabled { get; set; } + public bool IsEip100Enabled { get; set; } + public bool IsEip140Enabled { get; set; } + public bool IsEip150Enabled { get; set; } + public bool IsEip155Enabled { get; set; } + public bool IsEip158Enabled { get; set; } + public bool IsEip160Enabled { get; set; } + public bool IsEip170Enabled { get; set; } + public bool IsEip196Enabled { get; set; } + public bool IsEip197Enabled { get; set; } + public bool IsEip198Enabled { get; set; } + public bool IsEip211Enabled { get; set; } + public bool IsEip214Enabled { get; set; } + public bool IsEip649Enabled { get; set; } + public bool IsEip658Enabled { get; set; } + public bool IsEip145Enabled { get; set; } + public bool IsEip1014Enabled { get; set; } + public bool IsEip1052Enabled { get; set; } + public bool IsEip1283Enabled { get; set; } + public bool IsEip1234Enabled { get; set; } + public bool IsEip1344Enabled { get; set; } + public bool IsEip2028Enabled { get; set; } + public bool IsEip152Enabled { get; set; } + public bool IsEip1108Enabled { get; set; } + public bool IsEip1884Enabled { get; set; } + public bool IsEip2200Enabled { get; set; } + public bool IsEip2537Enabled { get; set; } + public bool IsEip2565Enabled { get; set; } + public bool IsEip2929Enabled { get; set; } + public bool IsEip2930Enabled { get; set; } + public bool IsEip1559Enabled { get => field || IsEip4844Enabled; set; } + public bool IsEip158IgnoredAccount(Address address) => false; + public bool IsEip3198Enabled { get; set; } + public bool IsEip3529Enabled { get; set; } + public bool IsEip3607Enabled { get; set; } + public bool IsEip3541Enabled { get; set; } + public bool ValidateChainId { get; set; } + public bool ValidateReceipts { get; set; } + public long Eip1559TransitionBlock { get; set; } + public ulong WithdrawalTimestamp { get; set; } + public ulong Eip4844TransitionTimestamp { get; set; } + public Address? FeeCollector { get; set; } + public UInt256? Eip1559BaseFeeMinValue { get; set; } + public UInt256 ForkBaseFee { get; set; } = Eip1559Constants.DefaultForkBaseFee; + public UInt256 BaseFeeMaxChangeDenominator { get; set; } = Eip1559Constants.DefaultBaseFeeMaxChangeDenominator; + public long ElasticityMultiplier { get; set; } = Eip1559Constants.DefaultElasticityMultiplier; + public IBaseFeeCalculator BaseFeeCalculator { get; set; } = new DefaultBaseFeeCalculator(); + public bool IsEip1153Enabled { get; set; } + public bool IsEip3651Enabled { get; set; } + public bool IsEip3855Enabled { get; set; } + public bool IsEip3860Enabled { get; set; } + public bool IsEip4895Enabled { get; set; } + public bool IsEip4844Enabled { get; set; } + public bool IsEip7951Enabled { get; set; } + public bool IsRip7212Enabled { get; set; } + public bool IsOpGraniteEnabled { get; set; } + public bool IsOpHoloceneEnabled { get; set; } + public bool IsOpIsthmusEnabled { get; set; } + public bool IsOpJovianEnabled { get; set; } + public bool IsEip7623Enabled { get; set; } + public bool IsEip7883Enabled { get; set; } + public bool IsEip5656Enabled { get; set; } + public bool IsEip6780Enabled { get; set; } + public bool IsEip4788Enabled { get; set; } + public bool IsEip7702Enabled { get; set; } + public bool IsEip7823Enabled { get; set; } + public bool IsEip4844FeeCollectorEnabled { get; set; } + public bool IsEip7002Enabled { get; set; } + public bool IsEip7251Enabled { get; set; } + public bool IsEip7825Enabled { get; set; } + public bool IsEip7918Enabled { get; set; } + public bool IsEip7934Enabled { get; set; } + public int Eip7934MaxRlpBlockSize { get; set; } + public bool IsEip7907Enabled { get; set; } + public ulong TargetBlobCount { get; set; } + public ulong MaxBlobCount { get; set; } + public ulong MaxBlobsPerTx => IsEip7594Enabled ? Math.Min(Eip7594Constants.MaxBlobsPerTx, MaxBlobCount) : MaxBlobCount; + public UInt256 BlobBaseFeeUpdateFraction { get; set; } + [MemberNotNullWhen(true, nameof(IsEip7251Enabled))] + public Address? Eip7251ContractAddress { get => IsEip7251Enabled ? field : null; set; } + [MemberNotNullWhen(true, nameof(Eip7002ContractAddress))] + public Address? Eip7002ContractAddress { get => IsEip7002Enabled ? field : null; set; } + [MemberNotNullWhen(true, nameof(IsEip4788Enabled))] + public Address? Eip4788ContractAddress { get => IsEip4788Enabled ? field : null; set; } + public bool IsEofEnabled { get; set; } + public bool IsEip6110Enabled { get; set; } + [MemberNotNullWhen(true, nameof(IsEip6110Enabled))] + public Address? DepositContractAddress { get => IsEip6110Enabled ? field : null; set; } + public bool IsEip2935Enabled { get; set; } + public bool IsEip7709Enabled { get; set; } + [MemberNotNullWhen(true, nameof(Eip2935ContractAddress))] + public Address? Eip2935ContractAddress { get => IsEip2935Enabled ? field : null; set; } + public bool IsEip7594Enabled { get; set; } + Array? IReleaseSpec.EvmInstructionsNoTrace { get; set; } + Array? IReleaseSpec.EvmInstructionsTraced { get; set; } + public bool IsEip7939Enabled { get; set; } + public bool IsRip7728Enabled { get; set; } + private FrozenSet? _precompiles; + FrozenSet IReleaseSpec.Precompiles => _precompiles ??= BuildPrecompilesCache(); + public long Eip2935RingBufferSize { get; set; } = Eip2935Constants.RingBufferSize; + public virtual FrozenSet BuildPrecompilesCache() { - public string Name { get; set; } = "Custom"; - public long MaximumExtraDataSize { get; set; } - public long MaxCodeSize { get; set; } - public long MinGasLimit { get; set; } - public long MinHistoryRetentionEpochs { get; set; } - public long GasLimitBoundDivisor { get; set; } - public UInt256 BlockReward { get; set; } - public long DifficultyBombDelay { get; set; } - public long DifficultyBoundDivisor { get; set; } - public long? FixedDifficulty { get; set; } - public int MaximumUncleCount { get; set; } - public bool IsTimeAdjustmentPostOlympic { get; set; } - public bool IsEip2Enabled { get; set; } - public bool IsEip7Enabled { get; set; } - public bool IsEip100Enabled { get; set; } - public bool IsEip140Enabled { get; set; } - public bool IsEip150Enabled { get; set; } - public bool IsEip155Enabled { get; set; } - public bool IsEip158Enabled { get; set; } - public bool IsEip160Enabled { get; set; } - public bool IsEip170Enabled { get; set; } - public bool IsEip196Enabled { get; set; } - public bool IsEip197Enabled { get; set; } - public bool IsEip198Enabled { get; set; } - public bool IsEip211Enabled { get; set; } - public bool IsEip214Enabled { get; set; } - public bool IsEip649Enabled { get; set; } - public bool IsEip658Enabled { get; set; } - public bool IsEip145Enabled { get; set; } - public bool IsEip1014Enabled { get; set; } - public bool IsEip1052Enabled { get; set; } - public bool IsEip1283Enabled { get; set; } - public bool IsEip1234Enabled { get; set; } - public bool IsEip1344Enabled { get; set; } - public bool IsEip2028Enabled { get; set; } - public bool IsEip152Enabled { get; set; } - public bool IsEip1108Enabled { get; set; } - public bool IsEip1884Enabled { get; set; } - public bool IsEip2200Enabled { get; set; } - public bool IsEip2537Enabled { get; set; } - public bool IsEip2565Enabled { get; set; } - public bool IsEip2929Enabled { get; set; } - public bool IsEip2930Enabled { get; set; } - - // used only in testing - public ReleaseSpec Clone() => (ReleaseSpec)MemberwiseClone(); - - public bool IsEip1559Enabled - { - get => _isEip1559Enabled || IsEip4844Enabled; - set => _isEip1559Enabled = value; - } - - public bool IsEip3198Enabled { get; set; } - public bool IsEip3529Enabled { get; set; } - public bool IsEip3607Enabled { get; set; } - public bool IsEip3541Enabled { get; set; } - public bool ValidateChainId { get; set; } - public bool ValidateReceipts { get; set; } - public long Eip1559TransitionBlock { get; set; } - public ulong WithdrawalTimestamp { get; set; } - public ulong Eip4844TransitionTimestamp { get; set; } - public Address FeeCollector { get; set; } - public UInt256? Eip1559BaseFeeMinValue { get; set; } - public UInt256 ForkBaseFee { get; set; } = Eip1559Constants.DefaultForkBaseFee; - public UInt256 BaseFeeMaxChangeDenominator { get; set; } = Eip1559Constants.DefaultBaseFeeMaxChangeDenominator; - public long ElasticityMultiplier { get; set; } = Eip1559Constants.DefaultElasticityMultiplier; - public IBaseFeeCalculator BaseFeeCalculator { get; set; } = new DefaultBaseFeeCalculator(); - public bool IsEip1153Enabled { get; set; } - public bool IsEip3651Enabled { get; set; } - public bool IsEip3855Enabled { get; set; } - public bool IsEip3860Enabled { get; set; } - public bool IsEip4895Enabled { get; set; } - public bool IsEip4844Enabled { get; set; } - public bool IsEip7951Enabled { get; set; } - public bool IsRip7212Enabled { get; set; } - public bool IsOpGraniteEnabled { get; set; } - public bool IsOpHoloceneEnabled { get; set; } - public bool IsOpIsthmusEnabled { get; set; } - public bool IsOpJovianEnabled { get; set; } - public bool IsEip7623Enabled { get; set; } - public bool IsEip7883Enabled { get; set; } - public bool IsEip5656Enabled { get; set; } - public bool IsEip6780Enabled { get; set; } - public bool IsEip4788Enabled { get; set; } - public bool IsEip7702Enabled { get; set; } - public bool IsEip7823Enabled { get; set; } - public bool IsEip4844FeeCollectorEnabled { get; set; } - public bool IsEip7002Enabled { get; set; } - public bool IsEip7251Enabled { get; set; } - public bool IsEip7825Enabled { get; set; } - public bool IsEip7918Enabled { get; set; } - public bool IsEip7934Enabled { get; set; } - public int Eip7934MaxRlpBlockSize { get; set; } - public bool IsEip7907Enabled { get; set; } - - public ulong TargetBlobCount { get; set; } - public ulong MaxBlobCount { get; set; } - public ulong MaxBlobsPerTx => IsEip7594Enabled ? Math.Min(Eip7594Constants.MaxBlobsPerTx, MaxBlobCount) : MaxBlobCount; - public UInt256 BlobBaseFeeUpdateFraction { get; set; } + HashSet cache = new(); + cache.Add(PrecompiledAddresses.EcRecover); + cache.Add(PrecompiledAddresses.Sha256); + cache.Add(PrecompiledAddresses.Ripemd160); + cache.Add(PrecompiledAddresses.Identity); - private Address _eip7251ContractAddress; - public Address Eip7251ContractAddress + if (IsEip198Enabled) cache.Add(PrecompiledAddresses.ModExp); + if (IsEip196Enabled && IsEip197Enabled) { - get => IsEip7251Enabled ? _eip7251ContractAddress : null; - set => _eip7251ContractAddress = value; + cache.Add(PrecompiledAddresses.Bn128Add); + cache.Add(PrecompiledAddresses.Bn128Mul); + cache.Add(PrecompiledAddresses.Bn128Pairing); } - private Address _eip7002ContractAddress; - public Address Eip7002ContractAddress - { - get => IsEip7002Enabled ? _eip7002ContractAddress : null; - set => _eip7002ContractAddress = value; - } - - private Address _eip4788ContractAddress; - public Address Eip4788ContractAddress - { - get => IsEip4788Enabled ? _eip4788ContractAddress : null; - set => _eip4788ContractAddress = value; - } - - public bool IsEofEnabled { get; set; } - - public bool IsEip6110Enabled { get; set; } - private Address _depositContractAddress; - public Address DepositContractAddress + if (IsEip152Enabled) cache.Add(PrecompiledAddresses.Blake2F); + if (IsEip4844Enabled) cache.Add(PrecompiledAddresses.PointEvaluation); + if (IsEip2537Enabled) { - get => IsEip6110Enabled ? _depositContractAddress : null; - set => _depositContractAddress = value; + cache.Add(PrecompiledAddresses.Bls12G1Add); + cache.Add(PrecompiledAddresses.Bls12G1Msm); + cache.Add(PrecompiledAddresses.Bls12G2Add); + cache.Add(PrecompiledAddresses.Bls12G2Msm); + cache.Add(PrecompiledAddresses.Bls12PairingCheck); + cache.Add(PrecompiledAddresses.Bls12MapFpToG1); + cache.Add(PrecompiledAddresses.Bls12MapFp2ToG2); } - public bool IsEip2935Enabled { get; set; } - public bool IsEip7709Enabled { get; set; } - private Address _eip2935ContractAddress; - private bool _isEip1559Enabled; + if (IsRip7212Enabled || IsEip7951Enabled) cache.Add(PrecompiledAddresses.P256Verify); + if (IsRip7728Enabled) cache.Add(PrecompiledAddresses.L1Sload); - public Address Eip2935ContractAddress - { - get => IsEip2935Enabled ? _eip2935ContractAddress : null; - set => _eip2935ContractAddress = value; - } - - public bool IsEip7594Enabled { get; set; } - - Array? IReleaseSpec.EvmInstructionsNoTrace { get; set; } - - Array? IReleaseSpec.EvmInstructionsTraced { get; set; } - public bool IsEip7939Enabled { get; set; } - public bool IsRip7728Enabled { get; set; } - public bool IsEip7928Enabled { get; set; } - - private FrozenSet? _precompiles; - FrozenSet IReleaseSpec.Precompiles => _precompiles ??= BuildPrecompilesCache(); - public long Eip2935RingBufferSize { get; set; } = Eip2935Constants.RingBufferSize; - - public virtual FrozenSet BuildPrecompilesCache() - { - HashSet cache = new(); - - cache.Add(PrecompiledAddresses.EcRecover); - cache.Add(PrecompiledAddresses.Sha256); - cache.Add(PrecompiledAddresses.Ripemd160); - cache.Add(PrecompiledAddresses.Identity); - - if (IsEip198Enabled) cache.Add(PrecompiledAddresses.ModExp); - if (IsEip196Enabled && IsEip197Enabled) - { - cache.Add(PrecompiledAddresses.Bn128Add); - cache.Add(PrecompiledAddresses.Bn128Mul); - cache.Add(PrecompiledAddresses.Bn128Pairing); - } - if (IsEip152Enabled) cache.Add(PrecompiledAddresses.Blake2F); - if (IsEip4844Enabled) cache.Add(PrecompiledAddresses.PointEvaluation); - if (IsEip2537Enabled) - { - cache.Add(PrecompiledAddresses.Bls12G1Add); - cache.Add(PrecompiledAddresses.Bls12G1Mul); - cache.Add(PrecompiledAddresses.Bls12G1MultiExp); - cache.Add(PrecompiledAddresses.Bls12G2Add); - cache.Add(PrecompiledAddresses.Bls12G2Mul); - cache.Add(PrecompiledAddresses.Bls12G2MultiExp); - cache.Add(PrecompiledAddresses.Bls12Pairing); - } - if (IsRip7212Enabled || IsEip7951Enabled) cache.Add(PrecompiledAddresses.P256Verify); - if (IsRip7728Enabled) cache.Add(PrecompiledAddresses.L1Sload); - - return cache.ToFrozenSet(); - } + return cache.ToFrozenSet(); } + public bool IsEip7928Enabled { get; set; } + + // used only in testing + public ReleaseSpec Clone() => (ReleaseSpec)MemberwiseClone(); } diff --git a/src/Nethermind/Nethermind.Specs/SepoliaSpecProvider.cs b/src/Nethermind/Nethermind.Specs/SepoliaSpecProvider.cs index 3eb52bde3e0..c84e0fe7c79 100644 --- a/src/Nethermind/Nethermind.Specs/SepoliaSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/SepoliaSpecProvider.cs @@ -26,7 +26,7 @@ public class SepoliaSpecProvider : ISpecProvider private SepoliaSpecProvider() { } - IReleaseSpec ISpecProvider.GetSpecInternal(ForkActivation forkActivation) => + public IReleaseSpec GetSpec(ForkActivation forkActivation) => forkActivation switch { { Timestamp: null } or { Timestamp: < ShanghaiTimestamp } => London.Instance, diff --git a/src/Nethermind/Nethermind.Specs/SingleReleaseSpecProvider.cs b/src/Nethermind/Nethermind.Specs/SingleReleaseSpecProvider.cs index ded0e575e20..75fb5a087fd 100644 --- a/src/Nethermind/Nethermind.Specs/SingleReleaseSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/SingleReleaseSpecProvider.cs @@ -42,7 +42,7 @@ public SingleReleaseSpecProvider(IReleaseSpec releaseSpec, ulong networkId, ulon public IReleaseSpec GenesisSpec => _releaseSpec; - IReleaseSpec ISpecProvider.GetSpecInternal(ForkActivation forkActivation) => _releaseSpec; + public IReleaseSpec GetSpec(ForkActivation forkActivation) => _releaseSpec; public long? DaoBlockNumber { get; } public ulong? BeaconChainGenesisTimestamp { get; } diff --git a/src/Nethermind/Nethermind.Specs/TestSpecProvider.cs b/src/Nethermind/Nethermind.Specs/TestSpecProvider.cs index 054be202f35..2e8e199c913 100644 --- a/src/Nethermind/Nethermind.Specs/TestSpecProvider.cs +++ b/src/Nethermind/Nethermind.Specs/TestSpecProvider.cs @@ -30,7 +30,7 @@ public void UpdateMergeTransitionInfo(long? blockNumber, UInt256? terminalTotalD public IReleaseSpec GenesisSpec { get; set; } - IReleaseSpec ISpecProvider.GetSpecInternal(ForkActivation forkActivation) => forkActivation.BlockNumber == 0 || forkActivation.BlockNumber < ForkOnBlockNumber ? GenesisSpec : NextForkSpec; + public IReleaseSpec GetSpec(ForkActivation forkActivation) => forkActivation.BlockNumber == 0 || forkActivation.BlockNumber < ForkOnBlockNumber ? GenesisSpec : NextForkSpec; public IReleaseSpec NextForkSpec { get; set; } public long? ForkOnBlockNumber { get; set; } diff --git a/src/Nethermind/Nethermind.State.Test/PatriciaTreeBulkSetterTests.cs b/src/Nethermind/Nethermind.State.Test/PatriciaTreeBulkSetterTests.cs index fa1940a9b1b..e11940237cb 100644 --- a/src/Nethermind/Nethermind.State.Test/PatriciaTreeBulkSetterTests.cs +++ b/src/Nethermind/Nethermind.State.Test/PatriciaTreeBulkSetterTests.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using FluentAssertions; +using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Test; @@ -90,6 +91,12 @@ public static IEnumerable NewBranchesGen() (new Hash256("cccccccc00000000000000000000000000000000000000000000000000000000"), MakeRandomValue(rng)), (new Hash256("cccc000000000000000000000000000000000000000000000000000000000000"), MakeRandomValue(rng)), }).SetName("deep value"); + + yield return new TestCaseData(new List<(Hash256 key, byte[] value)>() + { + (new Hash256("3333333333333333333333333333333333333333333333333333333333333333"), MakeRandomValue(rng)), + (new Hash256("3333333332222222222222222222222222222222222222222222222222222222"), MakeRandomValue(rng)), + }).SetName("matching long extension"); } public static IEnumerable PreExistingDataGen() @@ -109,6 +116,13 @@ public static IEnumerable PreExistingDataGen() (new Hash256("3322222222222222222222222222222222222222222222222222222222222222"), MakeRandomValue(rng)), }).SetName("one extension"); + yield return new TestCaseData(new List<(Hash256 key, byte[] value)>() + { + (new Hash256("3333333332222222222222222222222222222222222222222222222222222222"), MakeRandomValue(rng)), + (new Hash256("3333333333333333333333333333333333333333333333333333333333333333"), MakeRandomValue(rng)), + (new Hash256("3333333344444444444444444444444444444444444444444444444444444444"), MakeRandomValue(rng)), + }).SetName("long extension with branch child"); + yield return new TestCaseData(GenRandomOfLength(1000)).SetName("random 1000"); } @@ -122,6 +136,10 @@ public static IEnumerable BulkSetTestGen() { yield return new TestCaseData(existingData.Arguments[0], testCaseData.Arguments[0]).SetName(existingData.TestName + " and " + testCaseData.TestName); } + + List<(Hash256 key, byte[] value)> originalSet = (List<(Hash256 key, byte[] value)>)existingData.Arguments[0]; + List<(Hash256 key, byte[] value)> removal = originalSet.Select((kv) => (kv.key, (byte[])null)).ToList(); + yield return new TestCaseData(existingData.Arguments[0], removal).SetName(existingData.TestName + " and remove self completely "); } yield return new TestCaseData( @@ -255,7 +273,7 @@ public void BulkSet(List<(Hash256 key, byte[] value)> existingItems, List<(Hash2 long newWriteCount = 0; { TestMemDb db = new TestMemDb(); - IScopedTrieStore trieStore = new RawScopedTrieStore(db); + IScopedTrieStore trieStore = new StrictRawScopedTrieStore(new RawScopedTrieStore(db)); PatriciaTree pTree = new PatriciaTree(trieStore, LimboLogs.Instance); pTree.RootHash = Keccak.EmptyTreeHash; @@ -306,7 +324,7 @@ public void BulkSetRootHashUpdated(List<(Hash256 key, byte[] value)> existingIte (Hash256 root, TimeSpan baselineTime, long baselineWriteCount, string originalDump) = CalculateBaseline(existingItems, items, recordDump); TestMemDb db = new TestMemDb(); - IScopedTrieStore trieStore = new RawScopedTrieStore(db); + IScopedTrieStore trieStore = new StrictRawScopedTrieStore(new RawScopedTrieStore(db)); PatriciaTree pTree = new PatriciaTree(trieStore, LimboLogs.Instance); pTree.RootHash = Keccak.EmptyTreeHash; @@ -338,7 +356,7 @@ public void BulkSetPreSorted(List<(Hash256 key, byte[] value)> existingItems, Li long preSortedWriteCount; { TestMemDb db = new TestMemDb(); - IScopedTrieStore trieStore = new RawScopedTrieStore(db); + IScopedTrieStore trieStore = new StrictRawScopedTrieStore(new RawScopedTrieStore(db)); PatriciaTree pTree = new PatriciaTree(trieStore, LimboLogs.Instance); pTree.RootHash = Keccak.EmptyTreeHash; @@ -396,7 +414,7 @@ public void BulkSetOneByOne(List<(Hash256 key, byte[] value)> existingItems, Lis { // Just the bulk set one stack TestMemDb db = new TestMemDb(); - IScopedTrieStore trieStore = new RawScopedTrieStore(db); + IScopedTrieStore trieStore = new StrictRawScopedTrieStore(new RawScopedTrieStore(db)); PatriciaTree pTree = new PatriciaTree(trieStore, LimboLogs.Instance); pTree.RootHash = Keccak.EmptyTreeHash; @@ -448,7 +466,7 @@ private static (Hash256, TimeSpan, long, string originalDump) CalculateBaseline( long baselineWriteCount = 0; { TestMemDb db = new TestMemDb(); - IScopedTrieStore trieStore = new RawScopedTrieStore(db); + IScopedTrieStore trieStore = new StrictRawScopedTrieStore(new RawScopedTrieStore(db)); PatriciaTree pTree = new PatriciaTree(trieStore, LimboLogs.Instance); pTree.RootHash = Keccak.EmptyTreeHash; @@ -484,7 +502,7 @@ private static (Hash256, TimeSpan, long, string originalDump) CalculateBaseline( [Test] public void BulkSet_ShouldThrowOnNonUniqueEntries() { - IScopedTrieStore trieStore = new RawScopedTrieStore(new TestMemDb()); + IScopedTrieStore trieStore = new StrictRawScopedTrieStore(new RawScopedTrieStore(new TestMemDb())); PatriciaTree pTree = new PatriciaTree(trieStore, LimboLogs.Instance); pTree.RootHash = Keccak.EmptyTreeHash; @@ -676,17 +694,17 @@ public void TestBucketSort(int nibIndex, List paths, List buffer = new ArrayPoolList(paths.Count, paths.Count); int resultMask = PatriciaTree.BucketSort16Small(items.AsSpan(), buffer.AsSpan(), nibIndex, result); - buffer.Select((it) => it.Path).ToList().Should().BeEquivalentTo(expectedPaths); + buffer.Select((it) => it.Path).Should().BeEquivalentTo(expectedPaths); result.ToArray().Should().BeEquivalentTo(expectedResult); resultMask.Should().Be(expectedMask); resultMask = PatriciaTree.BucketSort16Large(items.AsSpan(), buffer.AsSpan(), nibIndex, result); - buffer.Select((it) => it.Path).ToList().Should().BeEquivalentTo(expectedPaths); + buffer.Select((it) => it.Path).Should().BeEquivalentTo(expectedPaths); result.ToArray().Should().BeEquivalentTo(expectedResult); resultMask.Should().Be(expectedMask); resultMask = PatriciaTree.BucketSort16(items.AsSpan(), buffer.AsSpan(), nibIndex, result); - buffer.Select((it) => it.Path).ToList().Should().BeEquivalentTo(expectedPaths); + buffer.Select((it) => it.Path).Should().BeEquivalentTo(expectedPaths); result.ToArray().Should().BeEquivalentTo(expectedResult); resultMask.Should().Be(expectedMask); } @@ -715,4 +733,30 @@ public void HexarySearch(int nibIndex, List paths, List baseTrieStore.LoadRlp(in path, hash, flags); + + public byte[] TryLoadRlp(in TreePath path, Hash256 hash, ReadFlags flags = ReadFlags.None) => baseTrieStore.TryLoadRlp(in path, hash, flags); + + public ITrieNodeResolver GetStorageTrieNodeResolver(Hash256 address) => baseTrieStore.GetStorageTrieNodeResolver(address); + + public INodeStorage.KeyScheme Scheme => baseTrieStore.Scheme; + + public ICommitter BeginCommit(TrieNode root, WriteFlags writeFlags = WriteFlags.None) => baseTrieStore.BeginCommit(root, writeFlags); + + public bool IsPersisted(in TreePath path, in ValueHash256 keccak) => baseTrieStore.IsPersisted(in path, in keccak); + } } diff --git a/src/Nethermind/Nethermind.State.Test/StateReaderTests.cs b/src/Nethermind/Nethermind.State.Test/StateReaderTests.cs index 0eac67506d3..a9f3e8acfd2 100644 --- a/src/Nethermind/Nethermind.State.Test/StateReaderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StateReaderTests.cs @@ -249,7 +249,7 @@ public void Can_collect_stats() stateRoot = provider.StateRoot; } - var stats = stateReader.CollectStats(stateRoot, new MemDb(), Logger); + var stats = stateReader.CollectStats(Build.A.BlockHeader.WithStateRoot(stateRoot).WithNumber(0).TestObject, new MemDb(), Logger); stats.AccountCount.Should().Be(1); } @@ -382,7 +382,7 @@ public void Can_accepts_visitors() } TrieStatsCollector visitor = new(new MemDb(), LimboLogs.Instance); - reader.RunTreeVisitor(visitor, stateRoot); + reader.RunTreeVisitor(visitor, Build.A.BlockHeader.WithStateRoot(stateRoot).WithNumber(0).TestObject); } [Test] @@ -399,7 +399,7 @@ public void Can_dump_state() stateRoot = provider.StateRoot; } - string state = reader.DumpState(stateRoot); + string state = reader.DumpState(Build.A.BlockHeader.WithStateRoot(stateRoot).WithNumber(0).TestObject); state.Should().NotBeEmpty(); } } diff --git a/src/Nethermind/Nethermind.State.Test/StateTreeTests.cs b/src/Nethermind/Nethermind.State.Test/StateTreeTests.cs index e5a15566f4b..a0e3055f785 100644 --- a/src/Nethermind/Nethermind.State.Test/StateTreeTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StateTreeTests.cs @@ -415,7 +415,7 @@ public void No_writes_without_commit() } [Test] - public void Can_ask_about_root_hash_without_commiting() + public void Can_ask_about_root_hash_without_committing() { MemDb db = new(); StateTree tree = new(new RawScopedTrieStore(db), LimboLogs.Instance); diff --git a/src/Nethermind/Nethermind.State.Test/StatsCollectorTests.cs b/src/Nethermind/Nethermind.State.Test/StatsCollectorTests.cs index 713304b13a8..c8fd2b1b866 100644 --- a/src/Nethermind/Nethermind.State.Test/StatsCollectorTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StatsCollectorTests.cs @@ -28,9 +28,9 @@ public void Can_collect_stats([Values(false, true)] bool parallel) MemDb stateDb = new MemDb(); NodeStorage nodeStorage = new NodeStorage(stateDb); TestRawTrieStore trieStore = new(nodeStorage); - WorldState stateProvider = new(trieStore, codeDb, LimboLogs.Instance); + WorldState stateProvider = new(new TrieStoreScopeProvider(trieStore, codeDb, LimboLogs.Instance), LimboLogs.Instance); StateReader stateReader = new StateReader(trieStore, codeDb, LimboLogs.Instance); - Hash256 stateRoot; + BlockHeader baseBlock; using (var _ = stateProvider.BeginScope(IWorldState.PreGenesis)) { @@ -50,7 +50,7 @@ public void Can_collect_stats([Values(false, true)] bool parallel) stateProvider.CommitTree(0); stateProvider.CommitTree(1); - stateRoot = stateProvider.StateRoot; + baseBlock = Build.A.BlockHeader.WithNumber(1).WithStateRoot(stateProvider.StateRoot).TestObject; } codeDb.Delete(Keccak.Compute(new byte[] { 1, 2, 3, 4 })); // missing code @@ -67,7 +67,7 @@ public void Can_collect_stats([Values(false, true)] bool parallel) MaxDegreeOfParallelism = parallel ? 0 : 1 }; - stateReader.RunTreeVisitor(statsCollector, stateRoot, visitingOptions); + stateReader.RunTreeVisitor(statsCollector, baseBlock, visitingOptions); var stats = statsCollector.Stats; stats.CodeCount.Should().Be(1); diff --git a/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs b/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs index 57b3e78e394..338fd4b038c 100644 --- a/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs +++ b/src/Nethermind/Nethermind.State.Test/StorageProviderTests.cs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; +using System.Collections.Concurrent; using FluentAssertions; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -441,6 +442,161 @@ public void Selfdestruct_clears_cache() provider.Get(nonAccessedStorageCell).ToArray().Should().BeEquivalentTo(StorageTree.ZeroBytes); } + [Test] + public void Selfdestruct_works_across_blocks() + { + Context ctx = new(setInitialState: false, trackWrittenData: true); + WorldState provider = BuildStorageProvider(ctx); + + BlockHeader baseBlock = null; + using (provider.BeginScope(baseBlock)) + { + provider.CreateAccountIfNotExists(TestItem.AddressA, 100); + provider.Set(new StorageCell(TestItem.AddressA, 100), [1]); + provider.Set(new StorageCell(TestItem.AddressA, 200), [2]); + + provider.Commit(Frontier.Instance); + provider.CommitTree(0); + + baseBlock = Build.A.BlockHeader.WithStateRoot(provider.StateRoot).TestObject; + } + + Hash256 originalStateRoot = baseBlock.StateRoot; + + ctx.WrittenData.Clear(); + + using (provider.BeginScope(baseBlock)) + { + provider.CreateAccountIfNotExists(TestItem.AddressA, 100); + provider.ClearStorage(TestItem.AddressA); + provider.Set(new StorageCell(TestItem.AddressA, 101), [10]); + provider.Set(new StorageCell(TestItem.AddressA, 200), [2]); + + provider.Commit(Frontier.Instance); + provider.CommitTree(0); + + baseBlock = Build.A.BlockHeader.WithParent(baseBlock).WithStateRoot(provider.StateRoot).TestObject; + } + + baseBlock.StateRoot.Should().NotBe(originalStateRoot); + + ctx.WrittenData.SelfDestructed[TestItem.AddressA].Should().BeTrue(); + ctx.WrittenData.Clear(); + + using (provider.BeginScope(baseBlock)) + { + provider.CreateAccountIfNotExists(TestItem.AddressA, 100); + provider.ClearStorage(TestItem.AddressA); + provider.Set(new StorageCell(TestItem.AddressA, 100), [1]); + provider.Set(new StorageCell(TestItem.AddressA, 200), [2]); + + provider.Commit(Frontier.Instance); + provider.CommitTree(0); + + baseBlock = Build.A.BlockHeader.WithParent(baseBlock).WithStateRoot(provider.StateRoot).TestObject; + } + + baseBlock.StateRoot.Should().Be(originalStateRoot); + + ctx.WrittenData.SelfDestructed[TestItem.AddressA].Should().BeTrue(); + } + + [Test] + public void Selfdestruct_works_even_when_its_the_only_call() + { + Context ctx = new(setInitialState: false, trackWrittenData: true); + WorldState provider = BuildStorageProvider(ctx); + + BlockHeader baseBlock = null; + using (provider.BeginScope(baseBlock)) + { + provider.CreateAccountIfNotExists(TestItem.AddressA, 100); + provider.Set(new StorageCell(TestItem.AddressA, 100), [1]); + provider.Set(new StorageCell(TestItem.AddressA, 200), [2]); + + provider.Commit(Frontier.Instance); + provider.CommitTree(0); + + baseBlock = Build.A.BlockHeader.WithStateRoot(provider.StateRoot).TestObject; + } + + ctx.WrittenData.Clear(); + + using (provider.BeginScope(baseBlock)) + { + provider.CreateAccountIfNotExists(TestItem.AddressA, 100); + provider.ClearStorage(TestItem.AddressA); + provider.DeleteAccount(TestItem.AddressA); + + provider.Commit(Frontier.Instance); + provider.CommitTree(0); + + baseBlock = Build.A.BlockHeader.WithParent(baseBlock).WithStateRoot(provider.StateRoot).TestObject; + } + + ctx.WrittenData.SelfDestructed[TestItem.AddressA].Should().BeTrue(); + ctx.WrittenData.Clear(); + + using (provider.BeginScope(baseBlock)) + { + provider.CreateAccountIfNotExists(TestItem.AddressA, 100); + provider.Get(new StorageCell(TestItem.AddressA, 100)).ToArray().Should().BeEquivalentTo(StorageTree.ZeroBytes); + + provider.Commit(Frontier.Instance); + provider.CommitTree(0); + } + } + + [Test] + public void Selfdestruct_in_the_same_transaction() + { + Context ctx = new(setInitialState: false); + WorldState provider = BuildStorageProvider(ctx); + + BlockHeader baseBlock = null; + using (provider.BeginScope(baseBlock)) + { + provider.CreateAccountIfNotExists(TestItem.AddressA, 100); + provider.Set(new StorageCell(TestItem.AddressA, 100), [1]); + provider.Set(new StorageCell(TestItem.AddressA, 200), [2]); + provider.ClearStorage(TestItem.AddressA); + provider.DeleteAccount(TestItem.AddressA); + + provider.Commit(Frontier.Instance); + provider.CommitTree(0); + + baseBlock = Build.A.BlockHeader.WithStateRoot(provider.StateRoot).TestObject; + } + + baseBlock.StateRoot.Should().Be(Keccak.EmptyTreeHash); + } + + [Test] + public void Selfdestruct_before_commit_will_mark_contract_as_empty() + { + Context ctx = new(setInitialState: false); + IWorldState provider = BuildStorageProvider(ctx); + + BlockHeader baseBlock = null; + using (provider.BeginScope(baseBlock)) + { + provider.CreateAccountIfNotExists(TestItem.AddressA, 100); + provider.Set(new StorageCell(TestItem.AddressA, 100), [1]); + provider.Set(new StorageCell(TestItem.AddressA, 200), [2]); + provider.Commit(Frontier.Instance); + provider.CommitTree(0); + + baseBlock = Build.A.BlockHeader.WithStateRoot(provider.StateRoot).TestObject; + } + + using (provider.BeginScope(baseBlock)) + { + provider.ClearStorage(TestItem.AddressA); + provider.DeleteAccount(TestItem.AddressA); + Assert.That(provider.IsStorageEmpty(TestItem.AddressA), Is.True); + } + } + [Test] public void Selfdestruct_persist_between_commit() { @@ -460,7 +616,8 @@ public void Selfdestruct_persist_between_commit() [TestCase(1000)] public void Set_empty_value_for_storage_cell_without_read_clears_data(int numItems) { - IWorldState worldState = new WorldState(TestTrieStoreFactory.Build(new MemDb(), LimboLogs.Instance), Substitute.For(), LogManager); + IWorldState worldState = new WorldState( + new TrieStoreScopeProvider(TestTrieStoreFactory.Build(new MemDb(), LimboLogs.Instance), new MemDb(), LimboLogs.Instance), LogManager); using var disposable = worldState.BeginScope(IWorldState.PreGenesis); worldState.CreateAccount(TestItem.AddressA, 1); @@ -494,7 +651,8 @@ public void Set_empty_value_for_storage_cell_without_read_clears_data(int numIte [Test] public void Set_empty_value_for_storage_cell_with_read_clears_data() { - IWorldState worldState = new WorldState(TestTrieStoreFactory.Build(new MemDb(), LimboLogs.Instance), Substitute.For(), LogManager); + IWorldState worldState = new WorldState( + new TrieStoreScopeProvider(TestTrieStoreFactory.Build(new MemDb(), LimboLogs.Instance), new MemDb(), LimboLogs.Instance), LogManager); using var disposable = worldState.BeginScope(IWorldState.PreGenesis); worldState.CreateAccount(TestItem.AddressA, 1); @@ -525,13 +683,33 @@ public void Set_empty_value_for_storage_cell_with_read_clears_data() private class Context { public WorldState StateProvider { get; } + internal WrittenData WrittenData = null; public readonly Address Address1 = new(Keccak.Compute("1")); public readonly Address Address2 = new(Keccak.Compute("2")); - public Context(PreBlockCaches preBlockCaches = null, bool setInitialState = true) + public Context(PreBlockCaches preBlockCaches = null, bool setInitialState = true, bool trackWrittenData = false) { - StateProvider = new WorldState(TestTrieStoreFactory.Build(new MemDb(), LimboLogs.Instance), Substitute.For(), LogManager, preBlockCaches); + IWorldStateScopeProvider scopeProvider = new TrieStoreScopeProvider( + TestTrieStoreFactory.Build(new MemDb(), LimboLogs.Instance), + new MemDb(), LimboLogs.Instance); + + if (preBlockCaches is not null) + { + scopeProvider = new PrewarmerScopeProvider(scopeProvider, preBlockCaches, populatePreBlockCache: true); + } + + if (trackWrittenData) + { + WrittenData = new WrittenData( + new ConcurrentDictionary(), + new ConcurrentDictionary(), + new ConcurrentDictionary() + ); + scopeProvider = new WritesInterceptor(scopeProvider, WrittenData); + } + + StateProvider = new WorldState(scopeProvider, LogManager); if (setInitialState) { StateProvider.BeginScope(IWorldState.PreGenesis); @@ -541,4 +719,126 @@ public Context(PreBlockCaches preBlockCaches = null, bool setInitialState = true } } } + + internal record WrittenData( + ConcurrentDictionary Accounts, + ConcurrentDictionary Slots, + ConcurrentDictionary SelfDestructed) + { + public void Clear() + { + Accounts.Clear(); + Slots.Clear(); + SelfDestructed.Clear(); + } + } + + private class WritesInterceptor(IWorldStateScopeProvider scopeProvider, WrittenData writtenData) : IWorldStateScopeProvider + { + + public bool HasRoot(BlockHeader baseBlock) + { + return scopeProvider.HasRoot(baseBlock); + } + + public IWorldStateScopeProvider.IScope BeginScope(BlockHeader baseBlock) + { + return new ScopeDecorator(scopeProvider.BeginScope(baseBlock), writtenData); + } + + private class ScopeDecorator(IWorldStateScopeProvider.IScope baseScope, WrittenData writtenData) : IWorldStateScopeProvider.IScope + { + public void Dispose() + { + baseScope.Dispose(); + } + + public Hash256 RootHash => baseScope.RootHash; + + public void UpdateRootHash() + { + baseScope.UpdateRootHash(); + } + + public Account Get(Address address) + { + return baseScope.Get(address); + } + + public void HintGet(Address address, Account account) + { + baseScope.HintGet(address, account); + } + + public IWorldStateScopeProvider.ICodeDb CodeDb => baseScope.CodeDb; + + public IWorldStateScopeProvider.IStorageTree CreateStorageTree(Address address) + { + return baseScope.CreateStorageTree(address); + } + + public IWorldStateScopeProvider.IWorldStateWriteBatch StartWriteBatch(int estimatedAccountNum) + { + return new WriteBatchDecorator(baseScope.StartWriteBatch(estimatedAccountNum), writtenData); + } + + public void Commit(long blockNumber) + { + baseScope.Commit(blockNumber); + } + } + + private class WriteBatchDecorator( + IWorldStateScopeProvider.IWorldStateWriteBatch writeBatch, + WrittenData writtenData + ) + : IWorldStateScopeProvider.IWorldStateWriteBatch + { + public void Dispose() + { + writeBatch.Dispose(); + } + + public event EventHandler OnAccountUpdated + { + add => writeBatch.OnAccountUpdated += value; + remove => writeBatch.OnAccountUpdated -= value; + } + + public void Set(Address key, Account account) + { + writeBatch.Set(key, account); + } + + public IWorldStateScopeProvider.IStorageWriteBatch CreateStorageWriteBatch(Address key, int estimatedEntries) + { + return new StorageWriteBatchDecorator(writeBatch.CreateStorageWriteBatch(key, estimatedEntries), key, writtenData); + + } + } + + private class StorageWriteBatchDecorator( + IWorldStateScopeProvider.IStorageWriteBatch baseStorageBatch, + Address address, + WrittenData writtenData + ) : IWorldStateScopeProvider.IStorageWriteBatch + { + public void Dispose() + { + baseStorageBatch?.Dispose(); + } + + public void Set(in UInt256 index, byte[] value) + { + baseStorageBatch.Set(in index, value); + writtenData.Slots[new StorageCell(address, index)] = value; + } + + public void Clear() + { + baseStorageBatch.Clear(); + writtenData.SelfDestructed[address] = true; + } + } + } } diff --git a/src/Nethermind/Nethermind.State.Test/TrieStoreScopeProviderTests.cs b/src/Nethermind/Nethermind.State.Test/TrieStoreScopeProviderTests.cs new file mode 100644 index 00000000000..d3742eec32b --- /dev/null +++ b/src/Nethermind/Nethermind.State.Test/TrieStoreScopeProviderTests.cs @@ -0,0 +1,99 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test; +using Nethermind.Core.Test.Builders; +using Nethermind.Db; +using Nethermind.Evm.State; +using Nethermind.Logging; +using Nethermind.State; +using NUnit.Framework; + +namespace Nethermind.Store.Test; + +public class TrieStoreScopeProviderTests +{ + [Test] + public void Test_CanSaveToState() + { + TestMemDb kv = new TestMemDb(); + IWorldStateScopeProvider scopeProvider = new TrieStoreScopeProvider(new TestRawTrieStore(kv), new MemDb(), LimboLogs.Instance); + + Hash256 stateRoot; + using (var scope = scopeProvider.BeginScope(null)) + { + scope.Get(TestItem.AddressA).Should().Be(null); + using (var writeBatch = scope.StartWriteBatch(1)) + { + writeBatch.Set(TestItem.AddressA, new Account(100, 100)); + } + + scope.Commit(1); + stateRoot = scope.RootHash; + } + + stateRoot.Should().NotBe(Keccak.EmptyTreeHash); + kv.WritesCount.Should().Be(1); + + using (var scope = scopeProvider.BeginScope(Build.A.BlockHeader.WithStateRoot(stateRoot).WithNumber(1).TestObject)) + { + scope.Get(TestItem.AddressA).Balance.Should().Be(100); + } + } + + [Test] + public void Test_CanSaveToStorage() + { + TestMemDb kv = new TestMemDb(); + IWorldStateScopeProvider scopeProvider = new TrieStoreScopeProvider(new TestRawTrieStore(kv), new MemDb(), LimboLogs.Instance); + + Hash256 stateRoot; + using (var scope = scopeProvider.BeginScope(null)) + { + scope.Get(TestItem.AddressA).Should().Be(null); + + using (var writeBatch = scope.StartWriteBatch(1)) + { + writeBatch.Set(TestItem.AddressA, new Account(100, 100)); + + using (var storageSet = writeBatch.CreateStorageWriteBatch(TestItem.AddressA, 1)) + { + storageSet.Set(1, [1, 2, 3]); + } + } + + scope.Commit(1); + stateRoot = scope.RootHash; + } + + stateRoot.Should().NotBe(Keccak.EmptyTreeHash); + kv.WritesCount.Should().Be(2); + + using (var scope = scopeProvider.BeginScope(Build.A.BlockHeader.WithStateRoot(stateRoot).WithNumber(1).TestObject)) + { + var storage = scope.CreateStorageTree(TestItem.AddressA); + storage.Get(1).Should().BeEquivalentTo([1, 2, 3]); + } + } + + [Test] + public void Test_CanSaveToCode() + { + TestMemDb kv = new TestMemDb(); + TestMemDb codeKv = new TestMemDb(); + IWorldStateScopeProvider scopeProvider = new TrieStoreScopeProvider(new TestRawTrieStore(kv), codeKv, LimboLogs.Instance); + + using (var scope = scopeProvider.BeginScope(null)) + { + using (var writer = scope.CodeDb.BeginCodeWrite()) + { + writer.Set(TestItem.KeccakA, [1, 2, 3]); + } + } + + codeKv.WritesCount.Should().Be(1); + } +} diff --git a/src/Nethermind/Nethermind.State.Test/WorldStateManagerTests.cs b/src/Nethermind/Nethermind.State.Test/WorldStateManagerTests.cs index 4295bba26d4..c85913a1dba 100644 --- a/src/Nethermind/Nethermind.State.Test/WorldStateManagerTests.cs +++ b/src/Nethermind/Nethermind.State.Test/WorldStateManagerTests.cs @@ -7,6 +7,7 @@ using Nethermind.Blockchain; using Nethermind.Blockchain.Synchronization; using Nethermind.Config; +using Nethermind.Consensus.Processing; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Test.Builders; @@ -28,7 +29,7 @@ public class WorldStateManagerTests [Test] public void ShouldProxyGlobalWorldState() { - IWorldState worldState = Substitute.For(); + IWorldStateScopeProvider worldState = Substitute.For(); IPruningTrieStore trieStore = Substitute.For(); IDbProvider dbProvider = TestMemDbProvider.Init(); WorldStateManager worldStateManager = new WorldStateManager(worldState, trieStore, dbProvider, LimboLogs.Instance); @@ -39,7 +40,7 @@ public void ShouldProxyGlobalWorldState() [Test] public void ShouldProxyReorgBoundaryEvent() { - IWorldState worldState = Substitute.For(); + IWorldStateScopeProvider worldState = Substitute.For(); IPruningTrieStore trieStore = Substitute.For(); IDbProvider dbProvider = TestMemDbProvider.Init(); WorldStateManager worldStateManager = new WorldStateManager(worldState, trieStore, dbProvider, LimboLogs.Instance); @@ -55,7 +56,7 @@ public void ShouldProxyReorgBoundaryEvent() [TestCase(INodeStorage.KeyScheme.HalfPath, false)] public void ShouldNotSupportHashLookupOnHalfpath(INodeStorage.KeyScheme keyScheme, bool hashSupported) { - IWorldState worldState = Substitute.For(); + IWorldStateScopeProvider worldState = Substitute.For(); IPruningTrieStore trieStore = Substitute.For(); IReadOnlyTrieStore readOnlyTrieStore = Substitute.For(); trieStore.AsReadOnly().Returns(readOnlyTrieStore); @@ -93,7 +94,7 @@ public void ShouldAnnounceReorgOnDispose() .AddSingleton(blockTree) .Build(); - IWorldState worldState = ctx.Resolve().GlobalWorldState; + IWorldState worldState = ctx.Resolve().WorldState; Hash256 stateRoot; diff --git a/src/Nethermind/Nethermind.State/BlockingVerifyTrie.cs b/src/Nethermind/Nethermind.State/BlockingVerifyTrie.cs index e7921cd9513..91f0ab31581 100644 --- a/src/Nethermind/Nethermind.State/BlockingVerifyTrie.cs +++ b/src/Nethermind/Nethermind.State/BlockingVerifyTrie.cs @@ -47,8 +47,7 @@ public bool VerifyTrie(BlockHeader stateAtBlock, CancellationToken cancellationT // This is to block processing as with halfpath old nodes will be removed using IDisposable _ = _trieStore.BeginScope(stateAtBlock); - Hash256 rootNode = stateAtBlock.StateRoot; - TrieStats stats = _stateReader.CollectStats(rootNode, _codeDb, _logManager, cancellationToken); + TrieStats stats = _stateReader.CollectStats(stateAtBlock, _codeDb, _logManager, cancellationToken); if (stats.MissingNodes > 0) { if (_logger.IsError) _logger.Error($"Missing node found!"); diff --git a/src/Nethermind/Nethermind.State/Healing/HealingStorageTreeFactory.cs b/src/Nethermind/Nethermind.State/Healing/HealingStorageTreeFactory.cs deleted file mode 100644 index c1f1f59d0cd..00000000000 --- a/src/Nethermind/Nethermind.State/Healing/HealingStorageTreeFactory.cs +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Core; -using Nethermind.Core.Crypto; -using Nethermind.Logging; -using Nethermind.Trie.Pruning; - -namespace Nethermind.State.Healing; - -public class HealingStorageTreeFactory(INodeStorage nodeStorage, Lazy recovery) : IStorageTreeFactory -{ - public StorageTree Create(Address address, IScopedTrieStore trieStore, Hash256 storageRoot, Hash256 stateRoot, ILogManager? logManager) => - new HealingStorageTree(trieStore, nodeStorage, storageRoot, logManager, address, stateRoot, recovery); -} diff --git a/src/Nethermind/Nethermind.State/Healing/HealingWorldState.cs b/src/Nethermind/Nethermind.State/Healing/HealingWorldState.cs deleted file mode 100644 index b6170591623..00000000000 --- a/src/Nethermind/Nethermind.State/Healing/HealingWorldState.cs +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited -// SPDX-License-Identifier: LGPL-3.0-only - -using System; -using Nethermind.Core; -using Nethermind.Logging; -using Nethermind.Trie.Pruning; -using Nethermind.Evm.State; - -namespace Nethermind.State.Healing; - -public class HealingWorldState( - ITrieStore trieStore, - INodeStorage nodeStorage, - IKeyValueStoreWithBatching? codeDb, - Lazy pathRecovery, - ILogManager? logManager, - PreBlockCaches? preBlockCaches = null, - bool populatePreBlockCache = true) - : WorldState(trieStore, codeDb, logManager, new HealingStateTree(trieStore, nodeStorage, pathRecovery, logManager), new HealingStorageTreeFactory(nodeStorage, pathRecovery), preBlockCaches, populatePreBlockCache) -{ -} diff --git a/src/Nethermind/Nethermind.State/Healing/HealingWorldStateScopeProvider.cs b/src/Nethermind/Nethermind.State/Healing/HealingWorldStateScopeProvider.cs new file mode 100644 index 00000000000..920d1066d96 --- /dev/null +++ b/src/Nethermind/Nethermind.State/Healing/HealingWorldStateScopeProvider.cs @@ -0,0 +1,27 @@ +// SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Evm.State; +using Nethermind.Logging; +using Nethermind.Trie.Pruning; + +namespace Nethermind.State.Healing; + +public class HealingWorldStateScopeProvider(ITrieStore trieStore, IKeyValueStoreWithBatching codeDb, INodeStorage nodeStorage, Lazy recovery, ILogManager logManager) : TrieStoreScopeProvider(trieStore, codeDb, logManager) +{ + private readonly ILogManager? _logManager = logManager; + private readonly ITrieStore _trieStore = trieStore; + + protected override StateTree CreateStateTree() + { + return new HealingStateTree(_trieStore, nodeStorage, recovery, _logManager); + } + + protected override StorageTree CreateStorageTree(Address address, Hash256 storageRoot) + { + return new HealingStorageTree(_trieStore.GetTrieStore(address), nodeStorage, storageRoot, _logManager, address, _backingStateTree.RootHash, recovery); + } +} diff --git a/src/Nethermind/Nethermind.State/IStateReader.cs b/src/Nethermind/Nethermind.State/IStateReader.cs index a2d44d20eb6..f52f5f9b504 100644 --- a/src/Nethermind/Nethermind.State/IStateReader.cs +++ b/src/Nethermind/Nethermind.State/IStateReader.cs @@ -15,7 +15,7 @@ public interface IStateReader ReadOnlySpan GetStorage(BlockHeader? baseBlock, Address address, in UInt256 index); byte[]? GetCode(Hash256 codeHash); byte[]? GetCode(in ValueHash256 codeHash); - void RunTreeVisitor(ITreeVisitor treeVisitor, Hash256 stateRoot, VisitingOptions? visitingOptions = null) where TCtx : struct, INodeContext; + void RunTreeVisitor(ITreeVisitor treeVisitor, BlockHeader? baseBlock, VisitingOptions? visitingOptions = null) where TCtx : struct, INodeContext; bool HasStateForBlock(BlockHeader? baseBlock); } } diff --git a/src/Nethermind/Nethermind.State/IWorldStateManager.cs b/src/Nethermind/Nethermind.State/IWorldStateManager.cs index a4ad6c9dacf..6c56f8ca6dd 100644 --- a/src/Nethermind/Nethermind.State/IWorldStateManager.cs +++ b/src/Nethermind/Nethermind.State/IWorldStateManager.cs @@ -5,7 +5,6 @@ using System.Threading; using Nethermind.Core; using Nethermind.Evm.State; -using Nethermind.State.Healing; using Nethermind.State.SnapServer; using Nethermind.Trie.Pruning; @@ -13,7 +12,7 @@ namespace Nethermind.State; public interface IWorldStateManager { - IWorldState GlobalWorldState { get; } + IWorldStateScopeProvider GlobalWorldState { get; } IStateReader GlobalStateReader { get; } ISnapServer? SnapServer { get; } IReadOnlyKeyValueStore? HashServer { get; } @@ -22,14 +21,7 @@ public interface IWorldStateManager /// Used by read only tasks that need to execute blocks. /// /// - IWorldState CreateResettableWorldState(); - - /// - /// Create a read only world state to warm up another world state - /// - /// Specify a world state to warm up by the returned world state. - /// - IWorldState CreateWorldStateForWarmingUp(IWorldState forWarmup); + IWorldStateScopeProvider CreateResettableWorldState(); event EventHandler? ReorgBoundaryReached; @@ -52,7 +44,7 @@ public interface IWorldStateManager public interface IOverridableWorldScope { - IDisposable BeginScope(BlockHeader? header); - IWorldState WorldState { get; } + IWorldStateScopeProvider WorldState { get; } IStateReader GlobalStateReader { get; } + void ResetOverrides(); } diff --git a/src/Nethermind/Nethermind.State/OverridableEnv/DisposableScopeOverridableEnv.cs b/src/Nethermind/Nethermind.State/OverridableEnv/DisposableScopeOverridableEnv.cs index e46021c9b95..cee75b483f6 100644 --- a/src/Nethermind/Nethermind.State/OverridableEnv/DisposableScopeOverridableEnv.cs +++ b/src/Nethermind/Nethermind.State/OverridableEnv/DisposableScopeOverridableEnv.cs @@ -11,7 +11,7 @@ namespace Nethermind.State.OverridableEnv; /// /// A utility that provide `IOverridableEnv` -/// Dont forget do dispose it! +/// Don't forget to dispose it! /// /// /// diff --git a/src/Nethermind/Nethermind.State/OverridableEnv/OverridableEnvFactory.cs b/src/Nethermind/Nethermind.State/OverridableEnv/OverridableEnvFactory.cs index 9cd841602bf..18209cddf5b 100644 --- a/src/Nethermind/Nethermind.State/OverridableEnv/OverridableEnvFactory.cs +++ b/src/Nethermind/Nethermind.State/OverridableEnv/OverridableEnvFactory.cs @@ -9,6 +9,7 @@ using Nethermind.Core.Specs; using Nethermind.Evm; using Nethermind.Evm.State; +using Nethermind.Logging; namespace Nethermind.State.OverridableEnv; @@ -18,7 +19,7 @@ public IOverridableEnv Create() { IOverridableWorldScope overridableScope = worldStateManager.CreateOverridableWorldScope(); ILifetimeScope childLifetimeScope = parentLifetimeScope.BeginLifetimeScope((builder) => builder - .AddSingleton(overridableScope.WorldState) + .AddSingleton(overridableScope.WorldState) .AddDecorator() .AddScoped((codeInfoRepo) => (codeInfoRepo as OverridableCodeInfoRepository)!)); @@ -33,19 +34,20 @@ ISpecProvider specProvider { private IDisposable? _worldScopeCloser; private readonly IOverridableCodeInfoRepository _codeInfoRepository = childLifetimeScope.Resolve(); + private readonly IWorldState _worldState = childLifetimeScope.Resolve(); public IDisposable BuildAndOverride(BlockHeader header, Dictionary? stateOverride) { if (_worldScopeCloser is not null) throw new InvalidOperationException("Previous overridable world scope was not closed"); Reset(); - _worldScopeCloser = overridableScope.BeginScope(header); + _worldScopeCloser = _worldState.BeginScope(header); IDisposable scope = new Scope(this); if (stateOverride is not null) { - overridableScope.WorldState.ApplyStateOverrides(_codeInfoRepository, stateOverride, specProvider.GetSpec(header), header.Number); - header.StateRoot = overridableScope.WorldState.StateRoot; + _worldState.ApplyStateOverrides(_codeInfoRepository, stateOverride, specProvider.GetSpec(header), header.Number); + header.StateRoot = _worldState.StateRoot; } return scope; @@ -65,11 +67,12 @@ private void Reset() _worldScopeCloser?.Dispose(); _worldScopeCloser = null; + overridableScope.ResetOverrides(); } protected override void Load(ContainerBuilder builder) => builder - .AddScoped(overridableScope.WorldState) + .AddScoped(_worldState) .AddScoped(overridableScope.GlobalStateReader) .AddScoped(this) .AddScoped(_codeInfoRepository) diff --git a/src/Nethermind/Nethermind.State/OverridableWorldStateManager.cs b/src/Nethermind/Nethermind.State/OverridableWorldStateManager.cs index b64ff4a156a..46412024a93 100644 --- a/src/Nethermind/Nethermind.State/OverridableWorldStateManager.cs +++ b/src/Nethermind/Nethermind.State/OverridableWorldStateManager.cs @@ -1,8 +1,6 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using System; -using Nethermind.Core; using Nethermind.Db; using Nethermind.Evm.State; using Nethermind.Logging; @@ -21,19 +19,10 @@ public OverridableWorldStateManager(IDbProvider dbProvider, IReadOnlyTrieStore t _dbProvider = readOnlyDbProvider; OverlayTrieStore overlayTrieStore = new(readOnlyDbProvider.StateDb, trieStore); _reader = new(overlayTrieStore, readOnlyDbProvider.CodeDb, logManager); - WorldState = new WorldState(overlayTrieStore, readOnlyDbProvider.CodeDb, logManager, null, true); + WorldState = new TrieStoreScopeProvider(overlayTrieStore, readOnlyDbProvider.CodeDb, logManager); } - public IWorldState WorldState { get; } - public IDisposable BeginScope(BlockHeader? header) - { - IDisposable closer = WorldState.BeginScope(header); - return new Reactive.AnonymousDisposable(() => - { - closer.Dispose(); - ResetOverrides(); - }); - } + public IWorldStateScopeProvider WorldState { get; } public IStateReader GlobalStateReader => _reader; public void ResetOverrides() diff --git a/src/Nethermind/Nethermind.State/PartialStorageProviderBase.cs b/src/Nethermind/Nethermind.State/PartialStorageProviderBase.cs index 57c3253791e..0c8d1a3ad47 100644 --- a/src/Nethermind/Nethermind.State/PartialStorageProviderBase.cs +++ b/src/Nethermind/Nethermind.State/PartialStorageProviderBase.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System; @@ -7,6 +7,7 @@ using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Resettables; +using Nethermind.Core.Extensions; using Nethermind.Evm.Tracing.State; using Nethermind.Logging; @@ -140,42 +141,11 @@ public void Restore(int snapshot) } - /// - /// Commit persistent storage - /// - public void Commit(bool commitRoots = true) - { - Commit(NullStateTracer.Instance, commitRoots); - } - - protected struct ChangeTrace - { - public static readonly ChangeTrace _zeroBytes = new(StorageTree.ZeroBytes, StorageTree.ZeroBytes); - public static ref readonly ChangeTrace ZeroBytes => ref _zeroBytes; - - public ChangeTrace(byte[]? before, byte[]? after) - { - After = after ?? StorageTree.ZeroBytes; - Before = before ?? StorageTree.ZeroBytes; - } - - public ChangeTrace(byte[]? after) - { - After = after ?? StorageTree.ZeroBytes; - Before = StorageTree.ZeroBytes; - IsInitialValue = true; - } - - public byte[] Before; - public byte[] After; - public bool IsInitialValue; - } - /// /// Commit persistent storage /// /// State tracer - public void Commit(IStorageTracer tracer, bool commitRoots = true) + public void Commit(IStorageTracer tracer) { if (_changes.Count == 0) { @@ -185,16 +155,6 @@ public void Commit(IStorageTracer tracer, bool commitRoots = true) { CommitCore(tracer); } - - if (commitRoots) - { - CommitStorageRoots(); - } - } - - protected virtual void CommitStorageRoots() - { - // Commit storage roots } /// @@ -202,22 +162,19 @@ protected virtual void CommitStorageRoots() /// Used for storage-specific logic /// /// Storage tracer - protected virtual void CommitCore(IStorageTracer tracer) - { - _changes.Clear(); - _intraBlockCache.Clear(); - _transactionChangesSnapshots.Clear(); - } + protected virtual void CommitCore(IStorageTracer tracer) => Reset(); /// /// Reset the storage state /// - public virtual void Reset(bool resetBlockChanges = true) + public virtual void Reset(bool resetBlockChanges = true) => Reset(); + + private void Reset() { if (_logger.IsTrace) _logger.Trace("Resetting storage"); _changes.Clear(); - _intraBlockCache.Clear(); + _intraBlockCache.ResetAndClear(); _transactionChangesSnapshots.Clear(); } @@ -270,7 +227,7 @@ protected StackList SetupRegistry(in StorageCell cell) ref StackList? value = ref CollectionsMarshal.GetValueRefOrAddDefault(_intraBlockCache, cell, out bool exists); if (!exists) { - value = new StackList(); + value = StackList.Rent(); } return value; @@ -313,7 +270,6 @@ protected enum ChangeType Null = 0, JustCache, Update, - Destroy, } } } diff --git a/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs b/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs index 6e01ca39f2f..1891b69b12e 100644 --- a/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs +++ b/src/Nethermind/Nethermind.State/PersistentStorageProvider.cs @@ -1,25 +1,27 @@ -// SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Intrinsics; +using System.Threading; using System.Threading.Tasks; using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; +using Nethermind.Core.Resettables; using Nethermind.Core.Threading; +using Nethermind.Evm.State; using Nethermind.Evm.Tracing.State; -using Nethermind.Int256; using Nethermind.Logging; -using Nethermind.Serialization.Rlp; +using Nethermind.Int256; using Nethermind.Trie; -using Nethermind.Trie.Pruning; namespace Nethermind.State; @@ -31,10 +33,8 @@ namespace Nethermind.State; /// internal sealed class PersistentStorageProvider : PartialStorageProviderBase { - private readonly ITrieStore _trieStore; + private IWorldStateScopeProvider.IScope _currentScope; private readonly StateProvider _stateProvider; - private readonly ILogManager? _logManager; - internal readonly IStorageTreeFactory _storageTreeFactory; private readonly Dictionary _storages = new(4_096); private readonly Dictionary _toUpdateRoots = new(); @@ -44,30 +44,18 @@ internal sealed class PersistentStorageProvider : PartialStorageProviderBase private readonly Dictionary _originalValues = new(); private readonly HashSet _committedThisRound = new(); - private readonly ConcurrentDictionary? _preBlockCache; /// /// Manages persistent storage allowing for snapshotting and restoring /// Persists data to ITrieStore /// - public PersistentStorageProvider(ITrieStore trieStore, + public PersistentStorageProvider( StateProvider stateProvider, - ILogManager logManager, - IStorageTreeFactory? storageTreeFactory, - ConcurrentDictionary? preBlockCache, - bool populatePreBlockCache) : base(logManager) + ILogManager logManager) : base(logManager) { - _trieStore = trieStore ?? throw new ArgumentNullException(nameof(trieStore)); - _stateProvider = stateProvider ?? throw new ArgumentNullException(nameof(stateProvider)); - _logManager = logManager ?? throw new ArgumentNullException(nameof(logManager)); - _storageTreeFactory = storageTreeFactory ?? new StorageTreeFactory(); - _preBlockCache = preBlockCache; - _populatePreBlockCache = populatePreBlockCache; + _stateProvider = stateProvider; } - public Hash256 StateRoot { get; set; } = null!; - private readonly bool _populatePreBlockCache; - /// /// Reset the storage state /// @@ -78,11 +66,16 @@ public override void Reset(bool resetBlockChanges = true) _committedThisRound.Clear(); if (resetBlockChanges) { - _storages.Clear(); + _storages.ResetAndClear(); _toUpdateRoots.Clear(); } } + public void SetBackendScope(IWorldStateScopeProvider.IScope scope) + { + _currentScope = scope; + } + /// /// Get the current value at the specified location /// @@ -117,6 +110,14 @@ public byte[] GetOriginal(in StorageCell storageCell) return value; } + public Hash256 GetStorageRoot(Address address) + { + return GetOrCreateStorage(address).StorageRoot; + } + + public bool IsStorageEmpty(Address address) => + GetOrCreateStorage(address).IsEmpty; + private HashSet? _tempToUpdateRoots; /// /// Called by Commit @@ -140,10 +141,10 @@ protected override void CommitCore(IStorageTracer tracer) HashSet toUpdateRoots = (_tempToUpdateRoots ??= new()); bool isTracing = tracer.IsTracingStorage; - Dictionary? trace = null; + Dictionary? trace = null; if (isTracing) { - trace = new Dictionary(); + trace = []; } for (int i = 0; i <= currentPosition; i++) @@ -158,7 +159,7 @@ protected override void CommitCore(IStorageTracer tracer) { if (isTracing && change.ChangeType == ChangeType.JustCache) { - trace![change.StorageCell] = new ChangeTrace(change.Value, trace[change.StorageCell].After); + trace![change.StorageCell] = new StorageChangeTrace(change.Value, trace[change.StorageCell].After); } continue; @@ -170,12 +171,6 @@ protected override void CommitCore(IStorageTracer tracer) } _committedThisRound.Add(change.StorageCell); - - if (change.ChangeType == ChangeType.Destroy) - { - continue; - } - int forAssertion = _intraBlockCache[change.StorageCell].Pop(); if (forAssertion != currentPosition - i) { @@ -204,7 +199,7 @@ protected override void CommitCore(IStorageTracer tracer) if (isTracing) { - trace![change.StorageCell] = new ChangeTrace(change.Value); + trace![change.StorageCell] = new StorageChangeTrace(change.Value); } } } @@ -241,7 +236,7 @@ protected override void CommitCore(IStorageTracer tracer) } } - protected override void CommitStorageRoots() + internal void FlushToTree(IWorldStateScopeProvider.IWorldStateWriteBatch writeBatch) { if (_toUpdateRoots.Count == 0) { @@ -271,19 +266,26 @@ void UpdateRootHashesSingleThread() } PerContractState contractState = kvp.Value; - (int writes, int skipped) = contractState.ProcessStorageChanges(); + (int writes, int skipped) = contractState.ProcessStorageChanges(writeBatch.CreateStorageWriteBatch(kvp.Key, kvp.Value.EstimatedChanges)); ReportMetrics(writes, skipped); - if (writes > 0) - { - _stateProvider.UpdateStorageRoot(address: kvp.Key, contractState.RootHash); - } } } void UpdateRootHashesMultiThread() { // We can recalculate the roots in parallel as they are all independent tries - using var storages = _storages.ToPooledList(); + using ArrayPoolList<(AddressAsKey Key, PerContractState ContractState, IWorldStateScopeProvider.IStorageWriteBatch WriteBatch)> storages = _storages + // Only consider contracts that actually have pending changes + .Where(kv => _toUpdateRoots.TryGetValue(kv.Key, out bool hasChanges) && hasChanges) + // Schedule larger changes first to help balance the work + .OrderByDescending(kv => kv.Value.EstimatedChanges) + .Select((kv) => ( + kv.Key, + kv.Value, + writeBatch.CreateStorageWriteBatch(kv.Key, kv.Value.EstimatedChanges) + )) + .ToPooledList(_storages.Count); + ParallelUnbalancedWork.For( 0, storages.Count, @@ -292,13 +294,7 @@ void UpdateRootHashesMultiThread() static (i, state) => { ref var kvp = ref state.storages.GetRef(i); - if (!state.toUpdateRoots.TryGetValue(kvp.Key, out bool hasChanges) || !hasChanges) - { - // Wasn't updated don't recalculate - return state; - } - - (int writes, int skipped) = kvp.Value.ProcessStorageChanges(); + (int writes, int skipped) = kvp.ContractState.ProcessStorageChanges(kvp.WriteBatch); if (writes == 0) { // Mark as no changes; we set as false rather than removing so @@ -315,19 +311,6 @@ void UpdateRootHashesMultiThread() return state; }, (state) => ReportMetrics(state.writes, state.skips)); - - // Update the storage roots in the main thread not in parallel, - // as can't update the StateTrie in parallel. - foreach (ref var kvp in storages.AsSpan()) - { - if (!_toUpdateRoots.TryGetValue(kvp.Key, out bool hasChanges) || !hasChanges) - { - continue; - } - - // Update the storage root for the Account - _stateProvider.UpdateStorageRoot(address: kvp.Key, kvp.Value.RootHash); - } } static void ReportMetrics(int writes, int skipped) @@ -343,43 +326,15 @@ static void ReportMetrics(int writes, int skipped) } } - /// - /// Commit persistent storage trees - /// - /// Current block number - public void CommitTrees(IBlockCommitter blockCommitter) + public void ClearStorageMap() { - // Note: These all run in about 0.4ms. So the little overhead like attempting to sort the tasks - // may make it worse. Always check on mainnet. - - using ArrayPoolListRef commitTask = new(_storages.Count); - foreach (KeyValuePair storage in _storages) - { - storage.Value.EnsureStorageTree(); // Cannot be called concurrently - if (blockCommitter.TryRequestConcurrencyQuota()) - { - commitTask.Add(Task.Factory.StartNew((ctx) => - { - PerContractState st = (PerContractState)ctx; - st.Commit(); - blockCommitter.ReturnConcurrencyQuota(); - }, storage.Value)); - } - else - { - storage.Value.Commit(); - } - } - - Task.WaitAll(commitTask.AsSpan()); - _storages.Clear(); } private PerContractState GetOrCreateStorage(Address address) { ref PerContractState? value = ref CollectionsMarshal.GetValueRefOrAddDefault(_storages, address, out bool exists); - if (!exists) value = new PerContractState(address, this); + if (!exists) value = PerContractState.Rent(address, this); return value; } @@ -387,10 +342,6 @@ public void WarmUp(in StorageCell storageCell, bool isEmpty) { if (isEmpty) { - if (_preBlockCache is not null) - { - _preBlockCache[storageCell] = []; - } } else { @@ -411,9 +362,9 @@ private void PushToRegistryOnly(in StorageCell cell, byte[] value) _changes.Add(new Change(in cell, value, ChangeType.JustCache)); } - private static void ReportChanges(IStorageTracer tracer, Dictionary trace) + private static void ReportChanges(IStorageTracer tracer, Dictionary trace) { - foreach ((StorageCell address, ChangeTrace change) in trace) + foreach ((StorageCell address, StorageChangeTrace change) in trace) { byte[] before = change.Before; byte[] after = change.After; @@ -433,57 +384,54 @@ public override void ClearStorage(Address address) { base.ClearStorage(address); - // here it is important to make sure that we will not reuse the same tree when the contract is revived - // by means of CREATE 2 - notice that the cached trie may carry information about items that were not - // touched in this block, hence were not zeroed above - // TODO: how does it work with pruning? - _toUpdateRoots.Remove(address); + _toUpdateRoots.TryAdd(address, true); PerContractState state = GetOrCreateStorage(address); state.Clear(); } - private class StorageTreeFactory : IStorageTreeFactory - { - public StorageTree Create(Address address, IScopedTrieStore trieStore, Hash256 storageRoot, Hash256 stateRoot, ILogManager? logManager) - => new(trieStore, storageRoot, logManager); - } - private sealed class DefaultableDictionary() { private bool _missingAreDefault; - private readonly Dictionary _dictionary = new(Comparer.Instance); - public int EstimatedSize => _dictionary.Count; + private readonly Dictionary _dictionary = new(Comparer.Instance); + public int EstimatedSize => _dictionary.Count + (_missingAreDefault ? 1 : 0); + public bool HasClear => _missingAreDefault; + public int Capacity => _dictionary.Capacity; + public void Reset() + { + _missingAreDefault = false; + _dictionary.Clear(); + } public void ClearAndSetMissingAsDefault() { _missingAreDefault = true; _dictionary.Clear(); } - public ref ChangeTrace GetValueRefOrAddDefault(UInt256 storageCellIndex, out bool exists) + public ref StorageChangeTrace GetValueRefOrAddDefault(UInt256 storageCellIndex, out bool exists) { - ref ChangeTrace value = ref CollectionsMarshal.GetValueRefOrAddDefault(_dictionary, storageCellIndex, out exists); + ref StorageChangeTrace value = ref CollectionsMarshal.GetValueRefOrAddDefault(_dictionary, storageCellIndex, out exists); if (!exists && _missingAreDefault) { // Where we know the rest of the tree is empty // we can say the value was found but is default // rather than having to check the database - value = ChangeTrace.ZeroBytes; + value = StorageChangeTrace.ZeroBytes; exists = true; } return ref value; } - public ref ChangeTrace GetValueRefOrNullRef(UInt256 storageCellIndex) + public ref StorageChangeTrace GetValueRefOrNullRef(UInt256 storageCellIndex) => ref CollectionsMarshal.GetValueRefOrNullRef(_dictionary, storageCellIndex); - public ChangeTrace this[UInt256 key] + public StorageChangeTrace this[UInt256 key] { set => _dictionary[key] = value; } - public Dictionary.Enumerator GetEnumerator() => _dictionary.GetEnumerator(); + public Dictionary.Enumerator GetEnumerator() => _dictionary.GetEnumerator(); private sealed class Comparer : IEqualityComparer { @@ -497,89 +445,110 @@ public bool Equals(UInt256 x, UInt256 y) public int GetHashCode([DisallowNull] UInt256 obj) => MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(in obj, 1)).FastHash(); } + + public void UnmarkClear() + { + _missingAreDefault = false; + } } - private sealed class PerContractState + private sealed class PerContractState : IReturnable { - private StorageTree? StorageTree; - private DefaultableDictionary BlockChange = new DefaultableDictionary(); + private static readonly Func _loadFromTreeStorageFunc = LoadFromTreeStorage; + private IWorldStateScopeProvider.IStorageTree? _backend; + + private readonly DefaultableDictionary BlockChange = new(); private bool _wasWritten = false; - private readonly Func _loadFromTreeStorageFunc; - private readonly Address _address; - private readonly PersistentStorageProvider _provider; + private PersistentStorageProvider _provider; + private Address _address; - public PerContractState(Address address, - PersistentStorageProvider provider) + private PerContractState(Address address, PersistentStorageProvider provider) => Initialize(address, provider); + + private void Initialize(Address address, PersistentStorageProvider provider) { _address = address; _provider = provider; - _loadFromTreeStorageFunc = LoadFromTreeStorage; } - public void EnsureStorageTree() - { - if (StorageTree is not null) return; - - // Note: GetStorageRoot is not concurrent safe! And so do this whole method! - Account? acc = _provider._stateProvider.GetAccount(_address); - Hash256 storageRoot = acc?.StorageRoot ?? Keccak.EmptyTreeHash; - bool isEmpty = storageRoot == Keccak.EmptyTreeHash; // We know all lookups will be empty against this tree - StorageTree = _provider._storageTreeFactory.Create(_address, - _provider._trieStore.GetTrieStore(_address), - storageRoot, - _provider.StateRoot, - _provider._logManager); + public int EstimatedChanges => BlockChange.EstimatedSize; - if (isEmpty && !_wasWritten) + public Hash256 StorageRoot + { + get { - // Slight optimization that skips the tree - BlockChange.ClearAndSetMissingAsDefault(); + EnsureStorageTree(); + return _backend.RootHash; } } - public Hash256 RootHash + public bool IsEmpty { get { + // _backend.RootHash is not reflected until after commit, but this need to be reflected before commit + // for SelfDestruct, since the deletion is not part of changelog, it need to be handled here. + if (BlockChange.HasClear) return true; + EnsureStorageTree(); - return StorageTree.RootHash; + return _backend.RootHash == Keccak.EmptyTreeHash; } } - public void Commit() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void EnsureStorageTree() { - EnsureStorageTree(); - StorageTree.Commit(); + if (_backend is not null) return; + CreateStorageTree(); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private void CreateStorageTree() + { + _backend = _provider._currentScope.CreateStorageTree(_address); + + bool isEmpty = _backend.RootHash == Keccak.EmptyTreeHash; + if (isEmpty && !_wasWritten) + { + // Slight optimization that skips the tree + BlockChange.ClearAndSetMissingAsDefault(); + } } public void Clear() { - StorageTree = new StorageTree(_provider._trieStore.GetTrieStore(_address), Keccak.EmptyTreeHash, _provider._logManager); + EnsureStorageTree(); BlockChange.ClearAndSetMissingAsDefault(); } + public void Return() + { + _address = null; + _provider = null; + _backend = null; + _wasWritten = false; + Pool.Return(this); + } + public void SaveChange(StorageCell storageCell, byte[] value) { _wasWritten = true; - ref ChangeTrace valueChanges = ref BlockChange.GetValueRefOrAddDefault(storageCell.Index, out bool exists); + ref StorageChangeTrace valueChanges = ref BlockChange.GetValueRefOrAddDefault(storageCell.Index, out bool exists); if (!exists) { - valueChanges = new ChangeTrace(value); + valueChanges = new StorageChangeTrace(value); } else { - valueChanges.After = value; + valueChanges = new StorageChangeTrace(valueChanges.Before, value); } } public ReadOnlySpan LoadFromTree(in StorageCell storageCell) { - ref ChangeTrace valueChange = ref BlockChange.GetValueRefOrAddDefault(storageCell.Index, out bool exists); + ref StorageChangeTrace valueChange = ref BlockChange.GetValueRefOrAddDefault(storageCell.Index, out bool exists); if (!exists) { - byte[] value = !_provider._populatePreBlockCache ? - LoadFromTreeReadPreWarmCache(in storageCell) : - LoadFromTreePopulatePrewarmCache(in storageCell); + byte[] value = LoadFromTreeStorage(storageCell); valueChange = new(value, value); } @@ -592,105 +561,119 @@ public ReadOnlySpan LoadFromTree(in StorageCell storageCell) return valueChange.After; } - private byte[] LoadFromTreeReadPreWarmCache(in StorageCell storageCell) - { - if (_provider._preBlockCache?.TryGetValue(storageCell, out byte[] value) ?? false) - { - Db.Metrics.IncrementStorageTreeCache(); - } - else - { - value = LoadFromTreeStorage(storageCell); - } - return value; - } - - private byte[] LoadFromTreePopulatePrewarmCache(in StorageCell storageCell) - { - long priorReads = Db.Metrics.ThreadLocalStorageTreeReads; - - byte[] value = _provider._preBlockCache is not null - ? _provider._preBlockCache.GetOrAdd(storageCell, _loadFromTreeStorageFunc) - : LoadFromTreeStorage(storageCell); - - if (Db.Metrics.ThreadLocalStorageTreeReads == priorReads) - { - // Read from Concurrent Cache - Db.Metrics.IncrementStorageTreeCache(); - } - return value; - } - private byte[] LoadFromTreeStorage(StorageCell storageCell) { Db.Metrics.IncrementStorageTreeReads(); EnsureStorageTree(); return !storageCell.IsHash - ? StorageTree.Get(storageCell.Index) - : StorageTree.GetArray(storageCell.Hash.Bytes); + ? _backend.Get(storageCell.Index) + : _backend.Get(storageCell.Hash); } - public (int writes, int skipped) ProcessStorageChanges() + private static byte[] LoadFromTreeStorage(StorageCell storageCell, PerContractState @this) + => @this.LoadFromTreeStorage(storageCell); + + public (int writes, int skipped) ProcessStorageChanges(IWorldStateScopeProvider.IStorageWriteBatch storageWriteBatch) { EnsureStorageTree(); + using IWorldStateScopeProvider.IStorageWriteBatch _ = storageWriteBatch; + int writes = 0; int skipped = 0; - if (BlockChange.EstimatedSize < PatriciaTree.MinEntriesToParallelizeThreshold) + + if (BlockChange.HasClear) { - foreach (var kvp in BlockChange) - { - byte[] after = kvp.Value.After; - if (!Bytes.AreEqual(kvp.Value.Before, after) || kvp.Value.IsInitialValue) - { - BlockChange[kvp.Key] = new(after, after); - StorageTree.Set(kvp.Key, after); - writes++; - } - else - { - skipped++; - } - } + storageWriteBatch.Clear(); + BlockChange.UnmarkClear(); // Note: Until the storage write batch is disposed, this BlockCache will pass read through the uncleared storage tree } - else + + foreach (var kvp in BlockChange) { - using ArrayPoolListRef bulkWrite = new(BlockChange.EstimatedSize); + byte[] after = kvp.Value.After; + if (!Bytes.AreEqual(kvp.Value.Before, after) || kvp.Value.IsInitialValue) + { + BlockChange[kvp.Key] = new(after, after); + storageWriteBatch.Set(kvp.Key, after); - Span keyBuf = stackalloc byte[32]; - foreach (KeyValuePair kvp in BlockChange) + writes++; + } + else { - byte[] after = kvp.Value.After; - if (!Bytes.AreEqual(kvp.Value.Before, after) || kvp.Value.IsInitialValue) - { - BlockChange[kvp.Key] = new(after, after); + skipped++; + } + } - StorageTree.ComputeKeyWithLookup(kvp.Key, keyBuf); - bulkWrite.Add(StorageTree.CreateBulkSetEntry(new ValueHash256(keyBuf), after)); + return (writes, skipped); + } - writes++; - } - else - { - skipped++; - } + public void RemoveStorageTree() + { + _backend = null; + } + + internal static PerContractState Rent(Address address, PersistentStorageProvider persistentStorageProvider) + => Pool.Rent(address, persistentStorageProvider); + + private static class Pool + { + private static readonly ConcurrentQueue _pool = []; + private static int _poolCount; + + public static PerContractState Rent(Address address, PersistentStorageProvider provider) + { + if (Volatile.Read(ref _poolCount) > 0 && _pool.TryDequeue(out PerContractState item)) + { + Interlocked.Decrement(ref _poolCount); + item.Initialize(address, provider); + return item; } - StorageTree.BulkSet(bulkWrite); + return new PerContractState(address, provider); } - if (writes > 0) + public static void Return(PerContractState item) { - StorageTree.UpdateRootHash(canBeParallel: writes > 64); + const int MaxItemSize = 512; + const int MaxPooledCount = 2048; + + if (item.BlockChange.Capacity > MaxItemSize) + return; + + // shared pool fallback + if (Interlocked.Increment(ref _poolCount) > MaxPooledCount) + { + Interlocked.Decrement(ref _poolCount); + return; + } + + item.BlockChange.Reset(); + _pool.Enqueue(item); } + } + } - return (writes, skipped); + private readonly struct StorageChangeTrace + { + public static readonly StorageChangeTrace _zeroBytes = new(StorageTree.ZeroBytes, StorageTree.ZeroBytes); + public static ref readonly StorageChangeTrace ZeroBytes => ref _zeroBytes; + + public StorageChangeTrace(byte[]? before, byte[]? after) + { + After = after ?? StorageTree.ZeroBytes; + Before = before ?? StorageTree.ZeroBytes; } - public void RemoveStorageTree() + public StorageChangeTrace(byte[]? after) { - StorageTree = null; + After = after ?? StorageTree.ZeroBytes; + Before = StorageTree.ZeroBytes; + IsInitialValue = true; } + + public readonly byte[] Before; + public readonly byte[] After; + public readonly bool IsInitialValue; } } diff --git a/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs b/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs new file mode 100644 index 00000000000..d88567071d1 --- /dev/null +++ b/src/Nethermind/Nethermind.State/PrewarmerScopeProvider.cs @@ -0,0 +1,154 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Concurrent; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Db; +using Nethermind.Evm.State; +using Nethermind.Int256; + +namespace Nethermind.State; + +public class PrewarmerScopeProvider( + IWorldStateScopeProvider baseProvider, + PreBlockCaches preBlockCaches, + bool populatePreBlockCache = true +) : IWorldStateScopeProvider, IPreBlockCaches +{ + public bool HasRoot(BlockHeader? baseBlock) => baseProvider.HasRoot(baseBlock); + + public IWorldStateScopeProvider.IScope BeginScope(BlockHeader? baseBlock) => new ScopeWrapper(baseProvider.BeginScope(baseBlock), preBlockCaches, populatePreBlockCache); + + public PreBlockCaches? Caches => preBlockCaches; + public bool IsWarmWorldState => !populatePreBlockCache; + + private sealed class ScopeWrapper( + IWorldStateScopeProvider.IScope baseScope, + PreBlockCaches preBlockCaches, + bool populatePreBlockCache) + : IWorldStateScopeProvider.IScope + { + ConcurrentDictionary preBlockCache = preBlockCaches.StateCache; + + public void Dispose() => baseScope.Dispose(); + + public IWorldStateScopeProvider.ICodeDb CodeDb => baseScope.CodeDb; + + public IWorldStateScopeProvider.IStorageTree CreateStorageTree(Address address) + { + return new StorageTreeWrapper( + baseScope.CreateStorageTree(address), + preBlockCaches.StorageCache, + address, + populatePreBlockCache); + } + + public IWorldStateScopeProvider.IWorldStateWriteBatch StartWriteBatch(int estimatedAccountNum) + { + return baseScope.StartWriteBatch(estimatedAccountNum); + } + + public void Commit(long blockNumber) => baseScope.Commit(blockNumber); + + public Hash256 RootHash => baseScope.RootHash; + + public void UpdateRootHash() + { + baseScope.UpdateRootHash(); + } + + public Account? Get(Address address) + { + AddressAsKey addressAsKey = address; + if (populatePreBlockCache) + { + long priorReads = Metrics.ThreadLocalStateTreeReads; + Account? account = preBlockCache.GetOrAdd(address, GetFromBaseTree); + + if (Metrics.ThreadLocalStateTreeReads == priorReads) + { + Metrics.IncrementStateTreeCacheHits(); + } + return account; + } + else + { + if (preBlockCache?.TryGetValue(addressAsKey, out Account? account) ?? false) + { + baseScope.HintGet(address, account); + Metrics.IncrementStateTreeCacheHits(); + } + else + { + account = GetFromBaseTree(addressAsKey); + } + return account; + } + } + + public void HintGet(Address address, Account? account) => baseScope.HintGet(address, account); + + private Account? GetFromBaseTree(AddressAsKey address) + { + return baseScope.Get(address); + } + } + + private sealed class StorageTreeWrapper( + IWorldStateScopeProvider.IStorageTree baseStorageTree, + ConcurrentDictionary preBlockCache, + Address address, + bool populatePreBlockCache + ) : IWorldStateScopeProvider.IStorageTree + { + public Hash256 RootHash => baseStorageTree.RootHash; + + public byte[] Get(in UInt256 index) + { + StorageCell storageCell = new StorageCell(address, in index); // TODO: Make the dictionary use UInt256 directly + if (populatePreBlockCache) + { + long priorReads = Db.Metrics.ThreadLocalStorageTreeReads; + + byte[] value = preBlockCache.GetOrAdd(storageCell, LoadFromTreeStorage); + + if (Db.Metrics.ThreadLocalStorageTreeReads == priorReads) + { + // Read from Concurrent Cache + Db.Metrics.IncrementStorageTreeCache(); + } + return value; + } + else + { + if (preBlockCache?.TryGetValue(storageCell, out byte[] value) ?? false) + { + baseStorageTree.HintGet(index, value); + Db.Metrics.IncrementStorageTreeCache(); + } + else + { + value = LoadFromTreeStorage(storageCell); + } + return value; + } + } + + public void HintGet(in UInt256 index, byte[]? value) => baseStorageTree.HintGet(in index, value); + + private byte[] LoadFromTreeStorage(StorageCell storageCell) + { + Db.Metrics.IncrementStorageTreeReads(); + + return !storageCell.IsHash + ? baseStorageTree.Get(storageCell.Index) + : baseStorageTree.Get(storageCell.Hash); + } + + public byte[] Get(in ValueHash256 hash) => + // Not a critical path. so we just forward for simplicity + baseStorageTree.Get(in hash); + } +} diff --git a/src/Nethermind/Nethermind.State/Proofs/AccountProofCollector.cs b/src/Nethermind/Nethermind.State/Proofs/AccountProofCollector.cs index fbe3410dbe7..c1f57a417e5 100644 --- a/src/Nethermind/Nethermind.State/Proofs/AccountProofCollector.cs +++ b/src/Nethermind/Nethermind.State/Proofs/AccountProofCollector.cs @@ -112,13 +112,13 @@ public AccountProofCollector(ReadOnlySpan hashedAddress, params byte[][]? { } - public AccountProofCollector(Address address, params byte[][] storageKeys) + public AccountProofCollector(Address? address, params byte[][] storageKeys) : this(Keccak.Compute(address?.Bytes ?? Address.Zero.Bytes).Bytes, storageKeys) { _accountProof.Address = _address = address ?? throw new ArgumentNullException(nameof(address)); } - public AccountProofCollector(Address address, UInt256[] storageKeys) + public AccountProofCollector(Address? address, IEnumerable storageKeys) : this(address, storageKeys.Select(ToKey).ToArray()) { } diff --git a/src/Nethermind/Nethermind.State/SnapServer/SnapServer.cs b/src/Nethermind/Nethermind.State/SnapServer/SnapServer.cs index ecb459db54e..bc3dd8b21dc 100644 --- a/src/Nethermind/Nethermind.State/SnapServer/SnapServer.cs +++ b/src/Nethermind/Nethermind.State/SnapServer/SnapServer.cs @@ -226,7 +226,7 @@ public IOwnedReadOnlyList GetByteCodes(IReadOnlyList reque break; } - if (responseSize > 1 && (Math.Min(byteLimit, HardResponseByteLimit) - responseSize) < 10000) + if (responseSize > 1 && (byteLimit - responseSize) < 10000) { break; } diff --git a/src/Nethermind/Nethermind.State/StateProvider.cs b/src/Nethermind/Nethermind.State/StateProvider.cs index 861f6231d6f..660ca0d7b4b 100644 --- a/src/Nethermind/Nethermind.State/StateProvider.cs +++ b/src/Nethermind/Nethermind.State/StateProvider.cs @@ -18,12 +18,10 @@ using Nethermind.Core.Extensions; using Nethermind.Core.Resettables; using Nethermind.Core.Specs; +using Nethermind.Evm.State; using Nethermind.Evm.Tracing.State; using Nethermind.Int256; using Nethermind.Logging; -using Nethermind.Serialization.Rlp; -using Nethermind.Trie; -using Nethermind.Trie.Pruning; using Metrics = Nethermind.Db.Metrics; using static Nethermind.State.StateProvider; @@ -33,9 +31,9 @@ internal class StateProvider : IJournal { private static readonly UInt256 _zero = UInt256.Zero; - private readonly Dictionary> _intraTxCache = []; - private readonly HashSet _committedThisRound = []; - private readonly HashSet _nullAccountReads = []; + private readonly Dictionary> _intraTxCache = new(); + private readonly HashSet _committedThisRound = new(); + private readonly HashSet _nullAccountReads = new(); // Only guarding against hot duplicates so filter doesn't need to be too big // Note: // False negatives are fine as they will just result in a overwrite set @@ -43,46 +41,22 @@ internal class StateProvider : IJournal private readonly ClockKeyCacheNonConcurrent _persistedCodeInsertFilter = new(1_024); private readonly ClockKeyCacheNonConcurrent _blockCodeInsertFilter = new(256); private readonly Dictionary _blockChanges = new(4_096); - private readonly ConcurrentDictionary? _preBlockCache; private readonly List _keptInCache = []; private readonly ILogger _logger; - private readonly IKeyValueStoreWithBatching _codeDb; private Dictionary _codeBatch; private Dictionary.AlternateLookup _codeBatchAlternate; private readonly List _changes = new(Resettable.StartCapacity); - internal readonly StateTree _tree; - private readonly Func _getStateFromTrie; + internal IWorldStateScopeProvider.IScope? _tree; - private readonly bool _populatePreBlockCache; private bool _needsStateRootUpdate; + private IWorldStateScopeProvider.ICodeDb? _codeDb; - public StateProvider(IScopedTrieStore? trieStore, - IKeyValueStoreWithBatching codeDb, - ILogManager logManager, - StateTree? stateTree = null, - ConcurrentDictionary? preBlockCache = null, - bool populatePreBlockCache = true) + public StateProvider( + ILogManager logManager) { - _preBlockCache = preBlockCache; - _populatePreBlockCache = populatePreBlockCache; _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); - _codeDb = codeDb ?? throw new ArgumentNullException(nameof(codeDb)); - _tree = stateTree ?? new StateTree(trieStore, logManager); - _getStateFromTrie = address => - { - Metrics.IncrementStateTreeReads(); - return _tree.Get(address); - }; - } - - public void Accept(ITreeVisitor visitor, Hash256? stateRoot, VisitingOptions? visitingOptions = null) where TCtx : struct, INodeContext - { - ArgumentNullException.ThrowIfNull(visitor); - ArgumentNullException.ThrowIfNull(stateRoot); - - _tree.Accept(visitor, stateRoot, visitingOptions); } public void RecalculateStateRoot() @@ -101,7 +75,14 @@ public Hash256 StateRoot [DoesNotReturn, StackTraceHidden] static void ThrowStateRootNeedsToBeUpdated() => throw new InvalidOperationException("State root needs to be updated"); } - set => _tree.RootHash = value; + } + + public int ChangedAccountCount => _blockChanges.Count; + + public void SetScope(IWorldStateScopeProvider.IScope? scope) + { + _tree = scope; + _codeDb = scope?.CodeDb; } public bool IsContract(Address address) @@ -111,7 +92,7 @@ public bool IsContract(Address address) } public bool AccountExists(Address address) => - _intraTxCache.TryGetValue(address, out Stack value) + _intraTxCache.TryGetValue(address, out StackList value) ? _changes[value.Peek()]!.ChangeType != ChangeType.Delete : GetAndAddToCache(address) is not null; @@ -129,16 +110,6 @@ public UInt256 GetNonce(Address address) return account?.Nonce ?? UInt256.Zero; } - public Hash256 GetStorageRoot(Address address) - { - Account? account = GetThroughCache(address); - return account is not null ? account.StorageRoot : ThrowIfNull(address); - - [DoesNotReturn, StackTraceHidden] - static Hash256 ThrowIfNull(Address address) - => throw new InvalidOperationException($"Account {address} is null when accessing storage root"); - } - public ref readonly UInt256 GetBalance(Address address) { Account? account = GetThroughCache(address); @@ -160,8 +131,8 @@ public bool InsertCode(Address address, in ValueHash256 codeHash, ReadOnlyMemory _codeBatchAlternate = _codeBatch.GetAlternateLookup(); } if (MemoryMarshal.TryGetArray(code, out ArraySegment codeArray) - && codeArray.Offset == 0 - && codeArray.Count == code.Length) + && codeArray.Offset == 0 + && codeArray.Count == code.Length) { _codeBatchAlternate[codeHash] = codeArray.Array; } @@ -290,32 +261,6 @@ public void AddToBalance(Address address, in UInt256 balanceChange, IReleaseSpec SetNewBalance(address, balanceChange, releaseSpec, false, out oldBalance); } - /// - /// This is a coupling point between storage provider and state provider. - /// This is pointing at the architectural change likely required where Storage and State Provider are represented by a single world state class. - /// - /// - /// - public void UpdateStorageRoot(Address address, Hash256 storageRoot) - { - _needsStateRootUpdate = true; - Account account = GetThroughCache(address) ?? ThrowNullAccount(address); - if (account.StorageRoot != storageRoot) - { - if (_logger.IsTrace) Trace(address, storageRoot, account); - Account changedAccount = account.WithChangedStorageRoot(storageRoot); - PushUpdate(address, changedAccount); - } - - [MethodImpl(MethodImplOptions.NoInlining)] - void Trace(Address address, Hash256 storageRoot, Account account) - => _logger.Trace($"Update {address} S {account.StorageRoot} -> {storageRoot}"); - - [DoesNotReturn, StackTraceHidden] - static Account ThrowNullAccount(Address address) - => throw new InvalidOperationException($"Account {address} is null when updating storage hash"); - } - public void IncrementNonce(Address address, UInt256 delta) => IncrementNonce(address, delta, out _); @@ -371,7 +316,7 @@ private byte[] GetCodeCore(in ValueHash256 codeHash) if (_codeBatch is null || !_codeBatchAlternate.TryGetValue(codeHash, out byte[]? code)) { - code = _codeDb[codeHash.Bytes]; + code = _codeDb.GetCode(codeHash); } return code ?? ThrowMissingCode(in codeHash); @@ -437,7 +382,7 @@ public void Restore(int snapshot) { int nextPosition = lastIndex - i; ref readonly Change change = ref changes[nextPosition]; - Stack stack = _intraTxCache[change!.Address]; + StackList stack = _intraTxCache[change!.Address]; int actualPosition = stack.Pop(); if (actualPosition != nextPosition) ThrowUnexpectedPosition(lastIndex, i, actualPosition); @@ -452,7 +397,10 @@ public void Restore(int snapshot) else { // Remove address entry entirely if no more changes - _intraTxCache.Remove(change.Address); + if (_intraTxCache.Remove(change.Address, out StackList? removed)) + { + removed.Return(); + } } } } @@ -498,7 +446,7 @@ void Trace(Address address, in UInt256 balance, in UInt256 nonce) public void CreateEmptyAccountIfDeletedOrNew(Address address) { - if (_intraTxCache.TryGetValue(address, out Stack value)) + if (_intraTxCache.TryGetValue(address, out StackList value)) { //we only want to persist empty accounts if they were deleted or created as empty //we don't want to do it for account empty due to a change (e.g. changed balance to zero) @@ -552,17 +500,13 @@ public void Commit(IReleaseSpec releaseSpec, IWorldStateTracer stateTracer, bool { Task codeFlushTask = !commitRoots || _codeBatch is null || _codeBatch.Count == 0 ? Task.CompletedTask - : CommitCodeAsync(); + : CommitCodeAsync(_codeDb); bool isTracing = _logger.IsTrace; int stepsBack = _changes.Count - 1; if (stepsBack < 0) { if (isTracing) TraceNoChanges(); - if (commitRoots) - { - FlushToTree(); - } codeFlushTask.GetAwaiter().GetResult(); return; @@ -602,7 +546,7 @@ public void Commit(IReleaseSpec releaseSpec, IWorldStateTracer stateTracer, bool continue; } - Stack stack = _intraTxCache[change.Address]; + StackList stack = _intraTxCache[change.Address]; int forAssertion = stack.Pop(); if (forAssertion != stepsBack - i) { @@ -685,16 +629,11 @@ public void Commit(IReleaseSpec releaseSpec, IWorldStateTracer stateTracer, bool _changes.Clear(); _committedThisRound.Clear(); _nullAccountReads.Clear(); - _intraTxCache.Clear(); - - if (commitRoots) - { - FlushToTree(); - } + _intraTxCache.ResetAndClear(); codeFlushTask.GetAwaiter().GetResult(); - Task CommitCodeAsync() + Task CommitCodeAsync(IWorldStateScopeProvider.ICodeDb codeDb) { Dictionary dict = Interlocked.Exchange(ref _codeBatch, null); if (dict is null) return Task.CompletedTask; @@ -702,19 +641,19 @@ Task CommitCodeAsync() return Task.Run(() => { - using (var batch = _codeDb.StartWriteBatch()) + using (var batch = codeDb.BeginCodeWrite()) { // Insert ordered for improved performance foreach (var kvp in dict.OrderBy(static kvp => kvp.Key)) { - batch.PutSpan(kvp.Key.Value.Bytes, kvp.Value); + batch.Set(kvp.Key.Value, kvp.Value); } } // Mark all inserted codes as persisted - foreach (var kvp in dict) + foreach (Hash256AsKey kvp in dict.Keys) { - _persistedCodeInsertFilter.Set(kvp.Key.Value.ValueHash256); + _persistedCodeInsertFilter.Set(kvp.Value.ValueHash256); } // Reuse Dictionary if not already re-initialized @@ -759,25 +698,18 @@ static void ThrowUnexpectedPosition(int currentPosition, int i, int forAssertion => throw new InvalidOperationException($"Expected checked value {forAssertion} to be equal to {currentPosition} - {i}"); } - private void FlushToTree() + internal void FlushToTree(IWorldStateScopeProvider.IWorldStateWriteBatch writeBatch) { int writes = 0; int skipped = 0; - using ArrayPoolListRef bulkWrite = new(_blockChanges.Count); foreach (AddressAsKey key in _blockChanges.Keys) { ref ChangeTrace change = ref CollectionsMarshal.GetValueRefOrNullRef(_blockChanges, key); if (change.Before != change.After) { change.Before = change.After; - - KeccakCache.ComputeTo(key.Value.Bytes, out ValueHash256 keccak); - - var account = change.After; - Rlp accountRlp = account is null ? null : account.IsTotallyEmpty ? StateTree.EmptyAccountRlp : Rlp.Encode(account); - - bulkWrite.Add(new PatriciaTree.BulkSetEntry(keccak, accountRlp?.Bytes)); + writeBatch.Set(key, change.After); writes++; } else @@ -786,8 +718,6 @@ private void FlushToTree() } } - _tree.BulkSet(bulkWrite); - if (writes > 0) Metrics.IncrementStateTreeWrites(writes); if (skipped > 0) @@ -803,9 +733,8 @@ public bool WarmUp(Address address) ref ChangeTrace accountChanges = ref CollectionsMarshal.GetValueRefOrAddDefault(_blockChanges, addressAsKey, out bool exists); if (!exists) { - Account? account = !_populatePreBlockCache ? - GetStateReadPreWarmCache(addressAsKey) : - GetStatePopulatePrewarmCache(addressAsKey); + Metrics.IncrementStateTreeReads(); + Account? account = _tree.Get(address); accountChanges = new(account, account); } @@ -816,34 +745,7 @@ public bool WarmUp(Address address) return accountChanges.After; } - private Account? GetStatePopulatePrewarmCache(AddressAsKey addressAsKey) - { - long priorReads = Metrics.ThreadLocalStateTreeReads; - Account? account = _preBlockCache is not null - ? _preBlockCache.GetOrAdd(addressAsKey, _getStateFromTrie) - : _getStateFromTrie(addressAsKey); - - if (Metrics.ThreadLocalStateTreeReads == priorReads) - { - Metrics.IncrementStateTreeCacheHits(); - } - return account; - } - - private Account? GetStateReadPreWarmCache(AddressAsKey addressAsKey) - { - if (_preBlockCache?.TryGetValue(addressAsKey, out Account? account) ?? false) - { - Metrics.IncrementStateTreeCacheHits(); - } - else - { - account = _getStateFromTrie(addressAsKey); - } - return account; - } - - private void SetState(Address address, Account? account) + internal void SetState(Address address, Account? account) { ref ChangeTrace accountChanges = ref CollectionsMarshal.GetValueRefOrAddDefault(_blockChanges, address, out _); accountChanges.After = account; @@ -870,7 +772,7 @@ private void SetState(Address address, Account? account) private Account? GetThroughCache(Address address) { - if (_intraTxCache.TryGetValue(address, out Stack value)) + if (_intraTxCache.TryGetValue(address, out StackList value)) { return _changes[value.Peek()].Account; } @@ -896,7 +798,7 @@ private void PushDelete(Address address) private void Push(Address address, Account? touchedAccount, ChangeType changeType) { - Stack stack = SetupCache(address); + StackList stack = SetupCache(address); if (changeType == ChangeType.Touch && _changes[stack.Peek()]!.ChangeType == ChangeType.Touch) { @@ -909,23 +811,23 @@ private void Push(Address address, Account? touchedAccount, ChangeType changeTyp private void PushNew(Address address, Account account) { - Stack stack = SetupCache(address); + StackList stack = SetupCache(address); stack.Push(_changes.Count); _changes.Add(new Change(address, account, ChangeType.New)); } - private void PushRecreateEmpty(Address address, Account account, Stack stack) + private void PushRecreateEmpty(Address address, Account account, StackList stack) { stack.Push(_changes.Count); _changes.Add(new Change(address, account, ChangeType.RecreateEmpty)); } - private Stack SetupCache(Address address) + private StackList SetupCache(Address address) { - ref Stack? value = ref CollectionsMarshal.GetValueRefOrAddDefault(_intraTxCache, address, out bool exists); + ref StackList? value = ref CollectionsMarshal.GetValueRefOrAddDefault(_intraTxCache, address, out bool exists); if (!exists) { - value = new Stack(); + value = StackList.Rent(); } return value; @@ -958,7 +860,7 @@ public void Reset(bool resetBlockChanges = true) _blockChanges.Clear(); _codeBatch?.Clear(); } - _intraTxCache.Clear(); + _intraTxCache.ResetAndClear(); _committedThisRound.Clear(); _nullAccountReads.Clear(); _changes.Clear(); @@ -968,14 +870,12 @@ public void Reset(bool resetBlockChanges = true) void Trace() => _logger.Trace("Clearing state provider caches"); } - public void CommitTree() + public void UpdateStateRootIfNeeded() { if (_needsStateRootUpdate) { RecalculateStateRoot(); } - - _tree.Commit(); } // used in EthereumTests diff --git a/src/Nethermind/Nethermind.State/StateReader.cs b/src/Nethermind/Nethermind.State/StateReader.cs index 1eac7ece078..f62975701d6 100644 --- a/src/Nethermind/Nethermind.State/StateReader.cs +++ b/src/Nethermind/Nethermind.State/StateReader.cs @@ -41,9 +41,9 @@ public ReadOnlySpan GetStorage(BlockHeader? baseBlock, Address address, in public byte[]? GetCode(Hash256 codeHash) => codeHash == Keccak.OfAnEmptyString ? [] : _codeDb[codeHash.Bytes]; - public void RunTreeVisitor(ITreeVisitor treeVisitor, Hash256 stateRoot, VisitingOptions? visitingOptions = null) where TCtx : struct, INodeContext + public void RunTreeVisitor(ITreeVisitor treeVisitor, BlockHeader? header, VisitingOptions? visitingOptions = null) where TCtx : struct, INodeContext { - _state.Accept(treeVisitor, stateRoot, visitingOptions); + _state.Accept(treeVisitor, header?.StateRoot ?? Keccak.EmptyTreeHash, visitingOptions); } public bool HasStateForBlock(BlockHeader? baseBlock) => trieStore.HasRoot(baseBlock?.StateRoot ?? Keccak.EmptyTreeHash); diff --git a/src/Nethermind/Nethermind.State/StateReaderExtensions.cs b/src/Nethermind/Nethermind.State/StateReaderExtensions.cs index 933ec39162b..096f05709be 100644 --- a/src/Nethermind/Nethermind.State/StateReaderExtensions.cs +++ b/src/Nethermind/Nethermind.State/StateReaderExtensions.cs @@ -44,21 +44,22 @@ public static ValueHash256 GetCodeHash(this IStateReader stateReader, BlockHeade return account.CodeHash; } - public static TrieStats CollectStats(this IStateReader stateProvider, Hash256 root, IKeyValueStore codeStorage, ILogManager logManager, CancellationToken cancellationToken = default) + public static TrieStats CollectStats(this IStateReader stateProvider, BlockHeader? baseBlock, IKeyValueStore codeStorage, ILogManager logManager, CancellationToken cancellationToken = default) { TrieStatsCollector collector = new(codeStorage, logManager, cancellationToken); - stateProvider.RunTreeVisitor(collector, root, new VisitingOptions + stateProvider.RunTreeVisitor(collector, baseBlock, new VisitingOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, FullScanMemoryBudget = 16.GiB(), // Gonna guess that if you are running this, you have a decent setup. }); + collector.Finish(); return collector.Stats; } - public static string DumpState(this IStateReader stateReader, Hash256 root) + public static string DumpState(this IStateReader stateReader, BlockHeader? baseBlock) { TreeDumper dumper = new(); - stateReader.RunTreeVisitor(dumper, root); + stateReader.RunTreeVisitor(dumper, baseBlock); return dumper.ToString(); } } diff --git a/src/Nethermind/Nethermind.State/StateTree.cs b/src/Nethermind/Nethermind.State/StateTree.cs index ff0f9e0a360..cc3a384659b 100644 --- a/src/Nethermind/Nethermind.State/StateTree.cs +++ b/src/Nethermind/Nethermind.State/StateTree.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using Nethermind.Core; using Nethermind.Core.Buffers; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; using Nethermind.Db; using Nethermind.Logging; @@ -73,6 +74,32 @@ public void Set(Address address, Account? account) Set(keccak.BytesAsSpan, account is null ? null : account.IsTotallyEmpty ? EmptyAccountRlp : Rlp.Encode(account)); } + public StateTreeBulkSetter BeginSet(int estimatedEntries) + { + return new StateTreeBulkSetter(estimatedEntries, this); + } + + public class StateTreeBulkSetter(int estimatedEntries, StateTree tree) : IDisposable + { + ArrayPoolList _bulkWrite = new(estimatedEntries); + + public void Set(Address key, Account account) + { + KeccakCache.ComputeTo(key.Bytes, out ValueHash256 keccak); + + Rlp accountRlp = account is null ? null : account.IsTotallyEmpty ? StateTree.EmptyAccountRlp : Rlp.Encode(account); + + _bulkWrite.Add(new BulkSetEntry(keccak, accountRlp?.Bytes)); + } + + public void Dispose() + { + using ArrayPoolListRef asRef = new ArrayPoolListRef(_bulkWrite.AsSpan()); + tree.BulkSet(asRef); + _bulkWrite.Dispose(); + } + } + [DebuggerStepThrough] public Rlp? Set(Hash256 keccak, Account? account) { @@ -89,5 +116,20 @@ public void Set(Address address, Account? account) Set(keccak.Bytes, rlp); return rlp; } + + public Account? Get(Address address) + { + return Get(address, null); + } + + public void UpdateRootHash() + { + UpdateRootHash(true); + } + + public void Commit() + { + Commit(false, WriteFlags.None); + } } } diff --git a/src/Nethermind/Nethermind.State/StorageTree.cs b/src/Nethermind/Nethermind.State/StorageTree.cs index f1f71924f1d..a756a8a5b95 100644 --- a/src/Nethermind/Nethermind.State/StorageTree.cs +++ b/src/Nethermind/Nethermind.State/StorageTree.cs @@ -2,38 +2,40 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Frozen; -using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; +using Nethermind.Evm.State; using Nethermind.Logging; using Nethermind.Int256; using Nethermind.Serialization.Rlp; using Nethermind.Trie; using Nethermind.Trie.Pruning; +using System.Runtime.InteropServices; namespace Nethermind.State { - public class StorageTree : PatriciaTree + public class StorageTree : PatriciaTree, IWorldStateScopeProvider.IStorageTree { - private const int LookupSize = 1024; - private static readonly FrozenDictionary Lookup = CreateLookup(); + private static readonly ValueHash256[] Lookup = CreateLookup(); public static readonly byte[] ZeroBytes = [0]; - private static FrozenDictionary CreateLookup() + private static ValueHash256[] CreateLookup() { + const int LookupSize = 1024; + Span buffer = stackalloc byte[32]; - Dictionary lookup = new Dictionary(LookupSize); - for (int i = 0; i < LookupSize; i++) + ValueHash256[] lookup = new ValueHash256[LookupSize]; + + for (int i = 0; i < lookup.Length; i++) { - UInt256 index = (UInt256)i; + UInt256 index = new UInt256((uint)i); index.ToBigEndian(buffer); - lookup[index] = Keccak.Compute(buffer).BytesToArray(); + lookup[i] = ValueKeccak.Compute(buffer); } - return lookup.ToFrozenDictionary(); + return lookup; } public StorageTree(IScopedTrieStore? trieStore, ILogManager? logManager) @@ -49,27 +51,30 @@ public StorageTree(IScopedTrieStore? trieStore, Hash256 rootHash, ILogManager? l [SkipLocalsInit] [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void ComputeKey(in UInt256 index, Span key) + private static void ComputeKey(in UInt256 index, out ValueHash256 key) { - index.ToBigEndian(key); - - // We can't direct ComputeTo the key as its also the input, so need a separate variable - KeccakCache.ComputeTo(key, out ValueHash256 keyHash); - // Which we can then directly assign to fast update the key - Unsafe.As(ref MemoryMarshal.GetReference(key)) = keyHash; + // Cannot use key as both in and out to KeccakCache.ComputeTo, + // so create another 32-byte buffer + Unsafe.SkipInit(out ValueHash256 buffer); + index.ToBigEndian(buffer.BytesAsSpan); + KeccakCache.ComputeTo(buffer.Bytes, out key); } - public static void ComputeKeyWithLookup(in UInt256 index, Span key) + [SkipLocalsInit] + public static void ComputeKeyWithLookup(in UInt256 index, ref ValueHash256 key) { - if (index < LookupSize) + ValueHash256[] lookup = Lookup; + ulong u0 = index.u0; + if (index.IsUint64 && u0 < (uint)lookup.Length) { - Lookup[index].CopyTo(key); + key = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(lookup), (nuint)u0); + return; } - ComputeKey(index, key); + ComputeKey(index, out key); } - public static BulkSetEntry CreateBulkSetEntry(ValueHash256 key, byte[]? value) + public static BulkSetEntry CreateBulkSetEntry(in ValueHash256 key, byte[]? value) { byte[] encodedValue; if (value.IsZero()) @@ -89,15 +94,19 @@ public static BulkSetEntry CreateBulkSetEntry(ValueHash256 key, byte[]? value) } } - return new BulkSetEntry(key, encodedValue); + return new BulkSetEntry(in key, encodedValue); } [SkipLocalsInit] public byte[] Get(in UInt256 index, Hash256? storageRoot = null) { - if (index < LookupSize) + ValueHash256[] lookup = Lookup; + ulong u0 = index.u0; + if (index.IsUint64 && u0 < (uint)lookup.Length) { - return GetArray(Lookup[index], storageRoot); + return GetArray( + in Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(lookup), (nuint)u0), + storageRoot); } return GetWithKeyGenerate(in index, storageRoot); @@ -105,14 +114,14 @@ public byte[] Get(in UInt256 index, Hash256? storageRoot = null) [SkipLocalsInit] byte[] GetWithKeyGenerate(in UInt256 index, Hash256 storageRoot) { - Span key = stackalloc byte[32]; - ComputeKey(index, key); - return GetArray(key, storageRoot); + ComputeKey(index, out ValueHash256 key); + return GetArray(in key, storageRoot); } } - public byte[] GetArray(ReadOnlySpan rawKey, Hash256? rootHash = null) + public byte[] GetArray(in ValueHash256 key, Hash256? rootHash = null) { + ReadOnlySpan rawKey = key.Bytes; ReadOnlySpan value = Get(rawKey, rootHash); if (value.IsEmpty) @@ -124,12 +133,40 @@ public byte[] GetArray(ReadOnlySpan rawKey, Hash256? rootHash = null) return rlp.DecodeByteArray(); } + public void Commit() + { + Commit(false, WriteFlags.None); + } + + public void Clear() + { + RootHash = EmptyTreeHash; + } + + public bool WasEmptyTree => RootHash == EmptyTreeHash; + + public byte[] Get(in UInt256 index) + { + return Get(index, null); + } + + public void HintGet(in UInt256 index, byte[]? value) + { + } + + public byte[] Get(in ValueHash256 hash) + { + return GetArray(in hash, null); + } + [SkipLocalsInit] public void Set(in UInt256 index, byte[] value) { - if (index < LookupSize) + ValueHash256[] lookup = Lookup; + ulong u0 = index.u0; + if (index.IsUint64 && u0 < (uint)lookup.Length) { - SetInternal(Lookup[index], value); + SetInternal(in Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(lookup), (nuint)u0), value); } else { @@ -139,19 +176,19 @@ public void Set(in UInt256 index, byte[] value) [SkipLocalsInit] void SetWithKeyGenerate(in UInt256 index, byte[] value) { - Span key = stackalloc byte[32]; - ComputeKey(index, key); - SetInternal(key, value); + ComputeKey(index, out ValueHash256 key); + SetInternal(in key, value); } } public void Set(in ValueHash256 key, byte[] value, bool rlpEncode = true) { - SetInternal(key.Bytes, value, rlpEncode); + SetInternal(in key, value, rlpEncode); } - private void SetInternal(ReadOnlySpan rawKey, byte[] value, bool rlpEncode = true) + private void SetInternal(in ValueHash256 hash, byte[] value, bool rlpEncode = true) { + ReadOnlySpan rawKey = hash.Bytes; if (value.IsZero()) { Set(rawKey, []); diff --git a/src/Nethermind/Nethermind.State/TrieStoreScopeProvider.cs b/src/Nethermind/Nethermind.State/TrieStoreScopeProvider.cs new file mode 100644 index 00000000000..30ee38f58a7 --- /dev/null +++ b/src/Nethermind/Nethermind.State/TrieStoreScopeProvider.cs @@ -0,0 +1,320 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Nethermind.Core; +using Nethermind.Core.Collections; +using Nethermind.Core.Crypto; +using Nethermind.Evm.State; +using Nethermind.Int256; +using Nethermind.Logging; +using Nethermind.Trie; +using Nethermind.Trie.Pruning; +using NonBlocking; + +namespace Nethermind.State; + +public class TrieStoreScopeProvider : IWorldStateScopeProvider +{ + private readonly ITrieStore _trieStore; + private readonly ILogManager _logManager; + protected StateTree _backingStateTree; + private readonly KeyValueWithBatchingBackedCodeDb _codeDb; + + public TrieStoreScopeProvider(ITrieStore trieStore, IKeyValueStoreWithBatching codeDb, ILogManager logManager) + { + _trieStore = trieStore; + _logManager = logManager; + _codeDb = new KeyValueWithBatchingBackedCodeDb(codeDb); + + _backingStateTree = CreateStateTree(); + } + + protected virtual StateTree CreateStateTree() + { + return new StateTree(_trieStore.GetTrieStore(null), _logManager); + } + + public bool HasRoot(BlockHeader? baseBlock) + { + return _trieStore.HasRoot(baseBlock?.StateRoot ?? Keccak.EmptyTreeHash); + } + + public IWorldStateScopeProvider.IScope BeginScope(BlockHeader? baseBlock) + { + var trieStoreCloser = _trieStore.BeginScope(baseBlock); + _backingStateTree.RootHash = baseBlock?.StateRoot ?? Keccak.EmptyTreeHash; + + return new TrieStoreWorldStateBackendScope(_backingStateTree, this, _codeDb, trieStoreCloser, _logManager); + } + + protected virtual StorageTree CreateStorageTree(Address address, Hash256 storageRoot) + { + return new StorageTree(_trieStore.GetTrieStore(address), storageRoot, _logManager); + } + + private class TrieStoreWorldStateBackendScope : IWorldStateScopeProvider.IScope + { + public void Dispose() + { + _trieStoreCloser.Dispose(); + _backingStateTree.RootHash = Keccak.EmptyTreeHash; + _storages.Clear(); + } + + public Hash256 RootHash => _backingStateTree.RootHash; + public void UpdateRootHash() => _backingStateTree.UpdateRootHash(); + + public Account? Get(Address address) + { + ref Account? account = ref CollectionsMarshal.GetValueRefOrAddDefault(_loadedAccounts, address, out bool exists); + if (!exists) + { + account = _backingStateTree.Get(address); + } + + return account; + } + + public void HintGet(Address address, Account? account) + { + _loadedAccounts.TryAdd(address, account); + } + + public IWorldStateScopeProvider.ICodeDb CodeDb => _codeDb1; + + internal StateTree _backingStateTree; + private readonly Dictionary _storages = new(); + private readonly Dictionary _loadedAccounts = new(); + private readonly TrieStoreScopeProvider _scopeProvider; + private readonly IWorldStateScopeProvider.ICodeDb _codeDb1; + private readonly IDisposable _trieStoreCloser; + private readonly ILogManager _logManager; + + public TrieStoreWorldStateBackendScope(StateTree backingStateTree, TrieStoreScopeProvider scopeProvider, IWorldStateScopeProvider.ICodeDb codeDb, IDisposable trieStoreCloser, ILogManager logManager) + { + _backingStateTree = backingStateTree; + _logManager = logManager; + _scopeProvider = scopeProvider; + _codeDb1 = codeDb; + _trieStoreCloser = trieStoreCloser; + } + + public IWorldStateScopeProvider.IWorldStateWriteBatch StartWriteBatch(int estimatedAccountNumber) + { + return new WorldStateWriteBatch(this, estimatedAccountNumber, _logManager.GetClassLogger()); + } + + public void Commit(long blockNumber) + { + using var blockCommitter = _scopeProvider._trieStore.BeginBlockCommit(blockNumber); + + // Note: These all runs in about 0.4ms. So the little overhead like attempting to sort the tasks + // may make it worst. Always check on mainnet. + using ArrayPoolList commitTask = new ArrayPoolList(_storages.Count); + foreach (KeyValuePair storage in _storages) + { + if (blockCommitter.TryRequestConcurrencyQuota()) + { + commitTask.Add(Task.Factory.StartNew((ctx) => + { + StorageTree st = (StorageTree)ctx; + st.Commit(); + blockCommitter.ReturnConcurrencyQuota(); + }, storage.Value)); + } + else + { + storage.Value.Commit(); + } + } + + Task.WaitAll(commitTask.AsSpan()); + _backingStateTree.Commit(); + _storages.Clear(); + } + + internal StorageTree LookupStorageTree(Address address) + { + if (_storages.TryGetValue(address, out var storageTree)) + { + return storageTree; + } + + storageTree = _scopeProvider.CreateStorageTree(address, Get(address)?.StorageRoot ?? Keccak.EmptyTreeHash); + _storages[address] = storageTree; + return storageTree; + } + + public void ClearLoadedAccounts() + { + _loadedAccounts.Clear(); + } + + public IWorldStateScopeProvider.IStorageTree CreateStorageTree(Address address) + { + return LookupStorageTree(address); + } + } + + private class WorldStateWriteBatch( + TrieStoreWorldStateBackendScope scope, + int estimatedAccountCount, + ILogger logger) : IWorldStateScopeProvider.IWorldStateWriteBatch + { + private readonly Dictionary _dirtyAccounts = new(estimatedAccountCount); + private readonly ConcurrentQueue<(AddressAsKey, Hash256)> _dirtyStorageTree = new(); + + public event EventHandler? OnAccountUpdated; + + public void Set(Address key, Account? account) + { + _dirtyAccounts[key] = account; + } + + public IWorldStateScopeProvider.IStorageWriteBatch CreateStorageWriteBatch(Address address, int estimatedEntries) + { + return new StorageTreeBulkWriteBatch(estimatedEntries, scope.LookupStorageTree(address), this, address); + } + + public void MarkDirty(AddressAsKey address, Hash256 storageTreeRootHash) + { + _dirtyStorageTree.Enqueue((address, storageTreeRootHash)); + } + + public void Dispose() + { + while (_dirtyStorageTree.TryDequeue(out (AddressAsKey, Hash256) entry)) + { + (AddressAsKey key, Hash256 storageRoot) = entry; + if (!_dirtyAccounts.TryGetValue(key, out var account)) account = scope.Get(key); + if (account == null && storageRoot == Keccak.EmptyTreeHash) continue; + account ??= ThrowNullAccount(key); + account = account!.WithChangedStorageRoot(storageRoot); + _dirtyAccounts[key] = account; + OnAccountUpdated?.Invoke(key, new IWorldStateScopeProvider.AccountUpdated(key, account)); + if (logger.IsTrace) Trace(key, storageRoot, account); + } + + using (var stateSetter = scope._backingStateTree.BeginSet(_dirtyAccounts.Count)) + { + foreach (var kv in _dirtyAccounts) + { + stateSetter.Set(kv.Key, kv.Value); + } + } + + scope.ClearLoadedAccounts(); + + + [MethodImpl(MethodImplOptions.NoInlining)] + void Trace(Address address, Hash256 storageRoot, Account? account) + => logger.Trace($"Update {address} S {account?.StorageRoot} -> {storageRoot}"); + + [DoesNotReturn, StackTraceHidden] + static Account ThrowNullAccount(Address address) + => throw new InvalidOperationException($"Account {address} is null when updating storage hash"); + } + } + + private class StorageTreeBulkWriteBatch(int estimatedEntries, StorageTree storageTree, WorldStateWriteBatch worldStateWriteBatch, AddressAsKey address) : IWorldStateScopeProvider.IStorageWriteBatch + { + // Slight optimization on small contract as the index hash can be precalculated in some case. + private const int MIN_ENTRIES_TO_BATCH = 16; + + private bool _hasSelfDestruct; + private bool _wasSetCalled = false; + + private ArrayPoolList? _bulkWrite = + estimatedEntries > MIN_ENTRIES_TO_BATCH + ? new(estimatedEntries) + : null; + + private ValueHash256 _keyBuff = new ValueHash256(); + + public void Set(in UInt256 index, byte[] value) + { + _wasSetCalled = true; + if (_bulkWrite is null) + { + storageTree.Set(index, value); + } + else + { + StorageTree.ComputeKeyWithLookup(index, ref _keyBuff); + _bulkWrite.Add(StorageTree.CreateBulkSetEntry(_keyBuff, value)); + } + } + + public void Clear() + { + if (_bulkWrite is null) + { + storageTree.RootHash = Keccak.EmptyTreeHash; + } + else + { + if (_wasSetCalled) throw new InvalidOperationException("Must call clear first in a storage write batch"); + _hasSelfDestruct = true; + } + } + + public void Dispose() + { + bool hasSet = _wasSetCalled || _hasSelfDestruct; + if (_bulkWrite is not null) + { + if (_hasSelfDestruct) + { + storageTree.RootHash = Keccak.EmptyTreeHash; + } + + using ArrayPoolListRef asRef = + new ArrayPoolListRef(_bulkWrite.AsSpan()); + storageTree.BulkSet(asRef); + + _bulkWrite?.Dispose(); + } + + if (hasSet) + { + storageTree.UpdateRootHash(_bulkWrite?.Count > 64); + worldStateWriteBatch.MarkDirty(address, storageTree.RootHash); + } + } + } + + private class KeyValueWithBatchingBackedCodeDb(IKeyValueStoreWithBatching codeDb) : IWorldStateScopeProvider.ICodeDb + { + public byte[]? GetCode(in ValueHash256 codeHash) + { + return codeDb[codeHash.Bytes]?.ToArray(); + } + + public IWorldStateScopeProvider.ICodeSetter BeginCodeWrite() + { + return new CodeSetter(codeDb.StartWriteBatch()); + } + + private class CodeSetter(IWriteBatch writeBatch) : IWorldStateScopeProvider.ICodeSetter + { + public void Set(in ValueHash256 codeHash, ReadOnlySpan code) + { + writeBatch.PutSpan(codeHash.Bytes, code); + } + + public void Dispose() + { + writeBatch.Dispose(); + } + } + } +} diff --git a/src/Nethermind/Nethermind.State/WorldState.cs b/src/Nethermind/Nethermind.State/WorldState.cs index 79e28ba908b..a5cb906e1e5 100644 --- a/src/Nethermind/Nethermind.State/WorldState.cs +++ b/src/Nethermind/Nethermind.State/WorldState.cs @@ -15,7 +15,6 @@ using Nethermind.Evm.Tracing.State; using Nethermind.Int256; using Nethermind.Logging; -using Nethermind.Trie.Pruning; [assembly: InternalsVisibleTo("Ethereum.Test.Base")] [assembly: InternalsVisibleTo("Ethereum.Blockchain.Test")] @@ -27,16 +26,14 @@ namespace Nethermind.State { - public class WorldState : IWorldState, IPreBlockCaches + public class WorldState : IWorldState { internal readonly StateProvider _stateProvider; internal readonly PersistentStorageProvider _persistentStorageProvider; private readonly TransientStorageProvider _transientStorageProvider; - private readonly ITrieStore _trieStore; - private bool _isInScope = false; + private IWorldStateScopeProvider.IScope? _currentScope; + private bool _isInScope; private readonly ILogger _logger; - private PreBlockCaches? PreBlockCaches { get; } - public bool IsWarmWorldState { get; } public Hash256 StateRoot { @@ -45,52 +42,31 @@ public Hash256 StateRoot GuardInScope(); return _stateProvider.StateRoot; } - private set - { - _stateProvider.StateRoot = value; - _persistentStorageProvider.StateRoot = value; - } - } - - public WorldState(ITrieStore trieStore, IKeyValueStoreWithBatching? codeDb, ILogManager? logManager) - : this(trieStore, codeDb, logManager, null, null) - { } - internal WorldState( - ITrieStore trieStore, - IKeyValueStoreWithBatching? codeDb, - ILogManager? logManager, - StateTree? stateTree = null, - IStorageTreeFactory? storageTreeFactory = null, - PreBlockCaches? preBlockCaches = null, - bool populatePreBlockCache = true) + public WorldState( + IWorldStateScopeProvider scopeProvider, + ILogManager? logManager) { - PreBlockCaches = preBlockCaches; - IsWarmWorldState = !populatePreBlockCache; - _trieStore = trieStore; - _stateProvider = new StateProvider(trieStore.GetTrieStore(null), codeDb, logManager, stateTree, PreBlockCaches?.StateCache, populatePreBlockCache); - _persistentStorageProvider = new PersistentStorageProvider(trieStore, _stateProvider, logManager, storageTreeFactory, PreBlockCaches?.StorageCache, populatePreBlockCache); + ScopeProvider = scopeProvider; + _stateProvider = new StateProvider(logManager); + _persistentStorageProvider = new PersistentStorageProvider(_stateProvider, logManager); _transientStorageProvider = new TransientStorageProvider(logManager); _logger = logManager.GetClassLogger(); } - public WorldState(ITrieStore trieStore, IKeyValueStoreWithBatching? codeDb, ILogManager? logManager, PreBlockCaches? preBlockCaches, bool populatePreBlockCache = true) - : this(trieStore, codeDb, logManager, null, preBlockCaches: preBlockCaches, populatePreBlockCache: populatePreBlockCache) - { - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] private void GuardInScope() { - if (!_isInScope) ThrowOutOfScope(); + if (_currentScope is null) ThrowOutOfScope(); } + [Conditional("DEBUG")] [MethodImpl(MethodImplOptions.AggressiveInlining)] private void DebugGuardInScope() { #if DEBUG - if (!_isInScope) ThrowOutOfScope(); + if (_currentScope is null) ThrowOutOfScope(); #endif } @@ -108,10 +84,34 @@ public Account GetAccount(Address address) bool IAccountStateProvider.TryGetAccount(Address address, out AccountStruct account) { - account = _stateProvider.GetAccount(address).ToStruct(); + // Note: This call is for compatibility with `IAccountStateProvider` and should not be called directly by VM. Because its slower. + account = _stateProvider.GetAccount(address) + .WithChangedStorageRoot(_persistentStorageProvider.GetStorageRoot(address)) + .ToStruct(); + return !account.IsTotallyEmpty; } + UInt256 IAccountStateProvider.GetNonce(Address address) + { + return _stateProvider.GetAccount(address).Nonce; + } + + UInt256 IAccountStateProvider.GetBalance(Address address) + { + return _stateProvider.GetAccount(address).Balance; + } + + bool IAccountStateProvider.IsStorageEmpty(Address address) + { + return _persistentStorageProvider.IsStorageEmpty(address); + } + + bool IAccountStateProvider.HasCode(Address address) + { + return _stateProvider.GetAccount(address).HasCode; + } + public bool IsContract(Address address) { DebugGuardInScope(); @@ -219,11 +219,6 @@ public void SubtractFromBalance(Address address, in UInt256 balanceChange, IRele } public void SubtractFromBalance(Address address, in UInt256 balanceChange, IReleaseSpec spec) => SubtractFromBalance(address, balanceChange, spec, out _); - public void UpdateStorageRoot(Address address, Hash256 storageRoot) - { - DebugGuardInScope(); - _stateProvider.UpdateStorageRoot(address, storageRoot); - } public void IncrementNonce(Address address, UInt256 delta) => IncrementNonce(address, delta, out _); public void IncrementNonce(Address address, UInt256 delta, out UInt256 oldNonce) @@ -240,12 +235,9 @@ public void DecrementNonce(Address address, UInt256 delta) public void CommitTree(long blockNumber) { DebugGuardInScope(); - using (IBlockCommitter committer = _trieStore.BeginBlockCommit(blockNumber)) - { - _persistentStorageProvider.CommitTrees(committer); - _stateProvider.CommitTree(); - } - _persistentStorageProvider.StateRoot = _stateProvider.StateRoot; + _stateProvider.UpdateStateRootIfNeeded(); + _currentScope.Commit(blockNumber); + _persistentStorageProvider.ClearStorageMap(); } public UInt256 GetNonce(Address address) @@ -263,20 +255,23 @@ public IDisposable BeginScope(BlockHeader? baseBlock) if (_logger.IsTrace) _logger.Trace($"Beginning WorldState scope with baseblock {baseBlock?.ToString(BlockHeader.Format.Short) ?? "null"} with stateroot {baseBlock?.StateRoot?.ToString() ?? "null"}."); - StateRoot = baseBlock?.StateRoot ?? Keccak.EmptyTreeHash; - IDisposable trieStoreCloser = _trieStore.BeginScope(baseBlock); + _currentScope = ScopeProvider.BeginScope(baseBlock); + _stateProvider.SetScope(_currentScope); + _persistentStorageProvider.SetBackendScope(_currentScope); return new Reactive.AnonymousDisposable(() => { Reset(); - StateRoot = Keccak.EmptyTreeHash; - trieStoreCloser.Dispose(); + _stateProvider.SetScope(null); + _currentScope.Dispose(); + _currentScope = null; _isInScope = false; if (_logger.IsTrace) _logger.Trace($"WorldState scope for baseblock {baseBlock?.ToString(BlockHeader.Format.Short) ?? "null"} closed"); }); } - public bool IsInScope => _isInScope; + public bool IsInScope => _currentScope is not null; + public IWorldStateScopeProvider ScopeProvider { get; } public UInt256 GetBalance(Address address) { @@ -288,7 +283,7 @@ public ValueHash256 GetStorageRoot(Address address) { DebugGuardInScope(); ArgumentNullException.ThrowIfNull(address); - return _stateProvider.GetStorageRoot(address); + return _persistentStorageProvider.GetStorageRoot(address); } public byte[] GetCode(Address address) @@ -328,23 +323,23 @@ public bool IsDeadAccount(Address address) public bool HasStateForBlock(BlockHeader? header) { - return _trieStore.HasRoot(header?.StateRoot ?? Keccak.EmptyTreeHash); - } - - public void Commit(IReleaseSpec releaseSpec, bool isGenesis = false, bool commitRoots = true) - { - DebugGuardInScope(); - _persistentStorageProvider.Commit(commitRoots); - _transientStorageProvider.Commit(commitRoots); - _stateProvider.Commit(releaseSpec, commitRoots, isGenesis); + return ScopeProvider.HasRoot(header); } public void Commit(IReleaseSpec releaseSpec, IWorldStateTracer tracer, bool isGenesis = false, bool commitRoots = true) { DebugGuardInScope(); - _persistentStorageProvider.Commit(tracer, commitRoots); - _transientStorageProvider.Commit(tracer, commitRoots); + _transientStorageProvider.Commit(tracer); + _persistentStorageProvider.Commit(tracer); _stateProvider.Commit(releaseSpec, tracer, commitRoots, isGenesis); + + if (commitRoots) + { + using IWorldStateScopeProvider.IWorldStateWriteBatch writeBatch = _currentScope.StartWriteBatch(_stateProvider.ChangedAccountCount); + writeBatch.OnAccountUpdated += (_, updatedAccount) => _stateProvider.SetState(updatedAccount.Address, updatedAccount.Account); + _persistentStorageProvider.FlushToTree(writeBatch); + _stateProvider.FlushToTree(writeBatch); + } } public Snapshot TakeSnapshot(bool newTransactionStart = false) @@ -394,7 +389,5 @@ public void ResetTransient() DebugGuardInScope(); _transientStorageProvider.Reset(); } - - PreBlockCaches? IPreBlockCaches.Caches => PreBlockCaches; } } diff --git a/src/Nethermind/Nethermind.State/WorldStateManager.cs b/src/Nethermind/Nethermind.State/WorldStateManager.cs index 6d384c06e0d..b6f5838627f 100644 --- a/src/Nethermind/Nethermind.State/WorldStateManager.cs +++ b/src/Nethermind/Nethermind.State/WorldStateManager.cs @@ -7,16 +7,14 @@ using Nethermind.Db; using Nethermind.Evm.State; using Nethermind.Logging; -using Nethermind.State.Healing; using Nethermind.State.SnapServer; -using Nethermind.Trie; using Nethermind.Trie.Pruning; namespace Nethermind.State; public class WorldStateManager : IWorldStateManager { - private readonly IWorldState _worldState; + private readonly IWorldStateScopeProvider _worldState; private readonly IPruningTrieStore _trieStore; private readonly IReadOnlyTrieStore _readOnlyTrieStore; private readonly ILogManager _logManager; @@ -26,7 +24,7 @@ public class WorldStateManager : IWorldStateManager private readonly ILastNStateRootTracker _lastNStateRootTracker; public WorldStateManager( - IWorldState worldState, + IWorldStateScopeProvider worldState, IPruningTrieStore trieStore, IDbProvider dbProvider, ILogManager logManager, @@ -46,7 +44,7 @@ public WorldStateManager( _lastNStateRootTracker = lastNStateRootTracker; } - public IWorldState GlobalWorldState => _worldState; + public IWorldStateScopeProvider GlobalWorldState => _worldState; public IReadOnlyKeyValueStore? HashServer => _trieStore.Scheme != INodeStorage.KeyScheme.Hash ? null : _trieStore.TrieNodeRlpStore; @@ -60,24 +58,9 @@ public event EventHandler? ReorgBoundaryReached public ISnapServer? SnapServer => _trieStore.Scheme == INodeStorage.KeyScheme.Hash ? null : new SnapServer.SnapServer(_readOnlyTrieStore, _readaOnlyCodeCb, GlobalStateReader, _logManager, _lastNStateRootTracker); - public IWorldState CreateResettableWorldState() + public IWorldStateScopeProvider CreateResettableWorldState() { - return new WorldState( - _readOnlyTrieStore, - _readaOnlyCodeCb, - _logManager); - } - - public IWorldState CreateWorldStateForWarmingUp(IWorldState forWarmup) - { - PreBlockCaches? preBlockCaches = (forWarmup as IPreBlockCaches)?.Caches; - return preBlockCaches is not null - ? new WorldState( - new PreCachedTrieStore(_readOnlyTrieStore, preBlockCaches.RlpCache), - _readaOnlyCodeCb, - _logManager, - preBlockCaches) - : CreateResettableWorldState(); + return new TrieStoreScopeProvider(_readOnlyTrieStore, _readaOnlyCodeCb, _logManager); } public IOverridableWorldScope CreateOverridableWorldScope() diff --git a/src/Nethermind/Nethermind.State/WorldStateMetricsDecorator.cs b/src/Nethermind/Nethermind.State/WorldStateMetricsDecorator.cs index 9a9142d8b56..8d88701f850 100644 --- a/src/Nethermind/Nethermind.State/WorldStateMetricsDecorator.cs +++ b/src/Nethermind/Nethermind.State/WorldStateMetricsDecorator.cs @@ -62,14 +62,6 @@ public override void DecrementNonce(Address address, UInt256 delta) public override void SetNonce(Address address, in UInt256 nonce) => _innerWorldState.SetNonce(address, nonce); - public override void Commit(IReleaseSpec releaseSpec, bool isGenesis = false, bool commitRoots = true) - { - long start = Stopwatch.GetTimestamp(); - _innerWorldState.Commit(releaseSpec, isGenesis, commitRoots); - if (commitRoots) - StateMerkleizationTime += Stopwatch.GetElapsedTime(start).TotalMilliseconds; - } - public override void Commit(IReleaseSpec releaseSpec, IWorldStateTracer tracer, bool isGenesis = false, bool commitRoots = true) { long start = Stopwatch.GetTimestamp(); diff --git a/src/Nethermind/Nethermind.State/WorldStateScopeOperationLogger.cs b/src/Nethermind/Nethermind.State/WorldStateScopeOperationLogger.cs new file mode 100644 index 00000000000..eda6421c878 --- /dev/null +++ b/src/Nethermind/Nethermind.State/WorldStateScopeOperationLogger.cs @@ -0,0 +1,153 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Extensions; +using Nethermind.Evm.State; +using Nethermind.Int256; +using Nethermind.Logging; + +namespace Nethermind.State; + +public class WorldStateScopeOperationLogger(IWorldStateScopeProvider baseScopeProvider, ILogManager logManager) : IWorldStateScopeProvider +{ + private ILogger _logger = logManager.GetClassLogger(); + private long _currentScopeId = 0; + + public bool HasRoot(BlockHeader? baseBlock) => + baseScopeProvider.HasRoot(baseBlock); + + public IWorldStateScopeProvider.IScope BeginScope(BlockHeader? baseBlock) + { + long scopeId = Interlocked.Increment(ref _currentScopeId); + return new ScopeWrapper(baseScopeProvider.BeginScope(baseBlock), scopeId, _logger); + } + + private class ScopeWrapper(IWorldStateScopeProvider.IScope innerScope, long scopeId, ILogger logger) : IWorldStateScopeProvider.IScope + { + public void Dispose() + { + innerScope.Dispose(); + logger.Trace($"{scopeId}: Scope disposed"); + } + + public Hash256 RootHash => innerScope.RootHash; + + public void UpdateRootHash() + { + innerScope.UpdateRootHash(); + logger.Trace($"{scopeId}: Update root hash"); + } + + public Account? Get(Address address) + { + Account? res = innerScope.Get(address); + logger.Trace($"{scopeId}: Get account {address}, got {res}"); + return res; + } + + public void HintGet(Address address, Account? account) + { + innerScope.HintGet(address, account); + } + + public IWorldStateScopeProvider.ICodeDb CodeDb => innerScope.CodeDb; + + public IWorldStateScopeProvider.IStorageTree CreateStorageTree(Address address) => + new StorageTreeWrapper(innerScope.CreateStorageTree(address), address, scopeId, logger); + + public IWorldStateScopeProvider.IWorldStateWriteBatch StartWriteBatch(int estimatedAccountNum) => + new WriteBatchWrapper(innerScope.StartWriteBatch(estimatedAccountNum), scopeId, logger); + + public void Commit(long blockNumber) => innerScope.Commit(blockNumber); + } + + private class StorageTreeWrapper(IWorldStateScopeProvider.IStorageTree storageTree, Address address, long scopeId, ILogger logger) : IWorldStateScopeProvider.IStorageTree + { + public Hash256 RootHash => storageTree.RootHash; + + public byte[] Get(in UInt256 index) + { + byte[]? bytes = storageTree.Get(in index); + logger.Trace($"{scopeId}: S:{address} Get slot {index}, got {bytes?.ToHexString()}"); + return bytes; + } + + public void HintGet(in UInt256 index, byte[]? value) => storageTree.HintGet(in index, value); + + public byte[] Get(in ValueHash256 hash) + { + byte[]? bytes = storageTree.Get(in hash); + logger.Trace($"{scopeId}: S:{address} Get slot via hash {hash}, got {bytes?.ToHexString()}"); + return bytes; + } + } + + private class WriteBatchWrapper : IWorldStateScopeProvider.IWorldStateWriteBatch + { + private readonly IWorldStateScopeProvider.IWorldStateWriteBatch _writeBatch; + private readonly long _scopeId; + private readonly ILogger _logger1; + + public WriteBatchWrapper(IWorldStateScopeProvider.IWorldStateWriteBatch writeBatch, long scopeId, ILogger logger) + { + _writeBatch = writeBatch; + _scopeId = scopeId; + _logger1 = logger; + + _writeBatch.OnAccountUpdated += (sender, updated) => + { + logger.Trace($"{scopeId}: OnAccountUpdated callback. {updated.Address} -> {updated.Account}"); + }; + } + + public void Dispose() + { + _writeBatch.Dispose(); + _logger1.Trace($"{_scopeId}: Write batch disposed"); + } + + public event EventHandler? OnAccountUpdated + { + add => _writeBatch.OnAccountUpdated += value; + remove => _writeBatch.OnAccountUpdated -= value; + } + + public void Set(Address key, Account? account) + { + _writeBatch.Set(key, account); + _logger1.Trace($"{_scopeId}: Set account {key} to {account}"); + } + + public IWorldStateScopeProvider.IStorageWriteBatch CreateStorageWriteBatch(Address key, int estimatedEntries) => + new StorageWriteBatchWrapper(_writeBatch.CreateStorageWriteBatch(key, estimatedEntries), key, _scopeId, _logger1); + } + + private class StorageWriteBatchWrapper( + IWorldStateScopeProvider.IStorageWriteBatch writeBatch, + Address address, + long scopeId, + ILogger logger) : IWorldStateScopeProvider.IStorageWriteBatch + { + public void Dispose() + { + writeBatch.Dispose(); + logger.Trace($"{scopeId}: {address}, Storage write batch disposed"); + } + + public void Set(in UInt256 index, byte[] value) + { + writeBatch.Set(in index, value); + logger.Trace($"{scopeId}: {address}, Set {index} to {value?.ToHexString()}"); + } + + public void Clear() + { + writeBatch.Clear(); + logger.Trace($"{scopeId}: {address}, Clear"); + } + } +} diff --git a/src/Nethermind/Nethermind.Synchronization.Test/BlockDownloaderTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/BlockDownloaderTests.cs index 1f6e5a39800..0cbbaae30d4 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/BlockDownloaderTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/BlockDownloaderTests.cs @@ -589,7 +589,7 @@ public async Task Can_DownloadBlockOutOfOrder(bool isMerge) { FastSync = true, StateMinDistanceFromHead = fastSyncLag, - PivotNumber = syncPivot.Number.ToString(), + PivotNumber = syncPivot.Number, PivotHash = syncPivot.Hash!.ToString(), }; diff --git a/src/Nethermind/Nethermind.Synchronization.Test/E2ESyncTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/E2ESyncTests.cs index 398d142a009..29d322ab0c3 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/E2ESyncTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/E2ESyncTests.cs @@ -286,7 +286,7 @@ private async Task SetPivot(SyncConfig syncConfig, CancellationToken cancellatio long serverHeadNumber = serverBlockTree.Head!.Number; BlockHeader pivot = serverBlockTree.FindHeader(serverHeadNumber - HeadPivotDistance)!; syncConfig.PivotHash = pivot.Hash!.ToString(); - syncConfig.PivotNumber = pivot.Number.ToString(); + syncConfig.PivotNumber = pivot.Number; syncConfig.PivotTotalDifficulty = pivot.TotalDifficulty!.Value.ToString(); } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/FastBlocks/BodiesSyncFeedTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/FastBlocks/BodiesSyncFeedTests.cs index 3ef05a221fc..e019dbc6130 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/FastBlocks/BodiesSyncFeedTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/FastBlocks/BodiesSyncFeedTests.cs @@ -66,7 +66,7 @@ public void Setup() { FastSync = true, PivotHash = _pivotBlock.Hash!.ToString(), - PivotNumber = _pivotBlock.Number.ToString(), + PivotNumber = _pivotBlock.Number, AncientBodiesBarrier = 0, DownloadBodiesInFastSync = true, }; @@ -182,23 +182,23 @@ public async Task ShouldRecoverOnInsertFailure() [TestCase(1, 99, false, null, false)] [TestCase(1, 99, true, null, false)] [TestCase(1, 99, false, 0, false)] - public void When_finished_sync_with_old_default_barrier_then_finishes_imedietely( + public void When_finished_sync_with_old_default_barrier_then_finishes_immediately( long AncientBarrierInConfig, long lowestInsertedBlockNumber, bool JustStarted, long? previousBarrierInDb, - bool shouldfinish) + bool shouldFinish) { _syncConfig.AncientBodiesBarrier = AncientBarrierInConfig; _syncConfig.AncientReceiptsBarrier = AncientBarrierInConfig; - _syncConfig.PivotNumber = (AncientBarrierInConfig + 1_000_000).ToString(); + _syncConfig.PivotNumber = AncientBarrierInConfig + 1_000_000; _syncPointers.LowestInsertedBodyNumber = JustStarted ? null : _pivotBlock.Number; if (previousBarrierInDb is not null) _metadataDb.Set(MetadataDbKeys.BodiesBarrierWhenStarted, previousBarrierInDb.Value.ToBigEndianByteArrayWithoutLeadingZeros()); _feed.InitializeFeed(); _syncPointers.LowestInsertedBodyNumber = lowestInsertedBlockNumber; - _feed.IsFinished.Should().Be(shouldfinish); + _feed.IsFinished.Should().Be(shouldFinish); } [Test] diff --git a/src/Nethermind/Nethermind.Synchronization.Test/FastBlocks/FastHeadersSyncTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/FastBlocks/FastHeadersSyncTests.cs index 61260d3ddeb..e72a272a30f 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/FastBlocks/FastHeadersSyncTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/FastBlocks/FastHeadersSyncTests.cs @@ -67,7 +67,7 @@ public async Task Can_prepare_3_requests_in_a_row() syncConfig: new TestSyncConfig { FastSync = true, - PivotNumber = "1000", + PivotNumber = 1000, PivotHash = Keccak.Zero.ToString(), PivotTotalDifficulty = "1000" }, @@ -101,7 +101,7 @@ public async Task Can_handle_forks_with_persisted_headers() syncConfig: new TestSyncConfig { FastSync = true, - PivotNumber = pivotBlock.Number.ToString(), + PivotNumber = pivotBlock.Number, PivotHash = pivotBlock.Hash!.ToString(), PivotTotalDifficulty = pivotBlock.TotalDifficulty.ToString()!, }, @@ -141,7 +141,7 @@ public async Task When_next_header_hash_update_is_delayed_do_not_drop_peer() syncConfig: new TestSyncConfig { FastSync = true, - PivotNumber = "1000", + PivotNumber = 1000, PivotHash = pivot.Hash!.Bytes.ToHexString(), PivotTotalDifficulty = pivot.TotalDifficulty.ToString()! }, @@ -200,7 +200,7 @@ public async Task Can_prepare_several_request_and_ignore_request_from_previous_s syncConfig: new TestSyncConfig { FastSync = true, - PivotNumber = "500", + PivotNumber = 500, PivotHash = pivot.Hash!.Bytes.ToHexString(), PivotTotalDifficulty = pivot.TotalDifficulty!.ToString()! }, @@ -246,7 +246,7 @@ public async Task Will_dispatch_when_only_partially_processed_dependency() syncConfig: new TestSyncConfig { FastSync = true, - PivotNumber = pivot.Number.ToString(), + PivotNumber = pivot.Number, PivotHash = pivot.Hash!.ToString(), PivotTotalDifficulty = pivot.TotalDifficulty.ToString()!, }, @@ -317,7 +317,7 @@ public async Task Can_reset_and_not_hang_when_a_batch_is_processing() syncConfig: new TestSyncConfig { FastSync = true, - PivotNumber = "500", + PivotNumber = 500, PivotHash = pivot.Hash!.Bytes.ToHexString(), PivotTotalDifficulty = pivot.TotalDifficulty!.ToString()! }, @@ -372,7 +372,7 @@ public async Task Can_keep_returning_nulls_after_all_batches_were_prepared() syncConfig: new TestSyncConfig { FastSync = true, - PivotNumber = "1000", + PivotNumber = 1000, PivotHash = Keccak.Zero.ToString(), PivotTotalDifficulty = "1000" }, @@ -403,7 +403,7 @@ public async Task Finishes_when_all_downloaded() new TestSyncConfig { FastSync = true, - PivotNumber = "1000", + PivotNumber = 1000, PivotHash = Keccak.Zero.ToString(), PivotTotalDifficulty = "1000" }, @@ -437,7 +437,7 @@ public async Task Can_resume_downloading_from_parent_of_lowest_inserted_header() new TestSyncConfig { FastSync = true, - PivotNumber = "1000", + PivotNumber = 1000, PivotHash = Keccak.Zero.ToString(), PivotTotalDifficulty = "1000" }, @@ -471,15 +471,15 @@ public async Task Can_resume_downloading_from_parent_of_lowest_inserted_header() [TestCase(190, 1, 1, true, true)] [TestCase(80, 1, 1, true, false)] [TestCase(80, 1, 1, true, true)] - //All empty reponse + // All empty response [TestCase(0, 192, 1, false, false)] - //All null reponse + // All null response [TestCase(0, 192, 1, false, true)] public async Task Can_insert_all_good_headers_from_dependent_batch_with_missing_or_null_headers(int nullIndex, int count, int increment, bool shouldReport, bool useNulls) { var peerChain = CachedBlockTreeBuilder.OfLength(1000); var pivotHeader = peerChain.FindHeader(998)!; - var syncConfig = new TestSyncConfig { FastSync = true, PivotNumber = pivotHeader.Number.ToString(), PivotHash = pivotHeader.Hash!.ToString(), PivotTotalDifficulty = pivotHeader.TotalDifficulty.ToString()! }; + var syncConfig = new TestSyncConfig { FastSync = true, PivotNumber = pivotHeader.Number, PivotHash = pivotHeader.Hash!.ToString(), PivotTotalDifficulty = pivotHeader.TotalDifficulty.ToString()! }; IBlockTree localBlockTree = Build.A.BlockTree(peerChain.FindBlock(0, BlockTreeLookupOptions.None)!, null).WithSyncConfig(syncConfig).TestObject; localBlockTree.SyncPivot = (pivotHeader.Number, pivotHeader.Hash); @@ -518,13 +518,13 @@ void FillBatch(HeadersSyncBatch batch, long start, bool applyNulls) feed.HandleResponse(dependentBatch); feed.HandleResponse(firstBatch); - using HeadersSyncBatch? thirdbatch = await feed.PrepareRequest(); - FillBatch(thirdbatch!, thirdbatch!.StartNumber, false); - feed.HandleResponse(thirdbatch); - using HeadersSyncBatch? fourthbatch = await feed.PrepareRequest(); - FillBatch(fourthbatch!, fourthbatch!.StartNumber, false); - feed.HandleResponse(fourthbatch); - using HeadersSyncBatch? fifthbatch = await feed.PrepareRequest(); + using HeadersSyncBatch? thirdBatch = await feed.PrepareRequest(); + FillBatch(thirdBatch!, thirdBatch!.StartNumber, false); + feed.HandleResponse(thirdBatch); + using HeadersSyncBatch? fourthBatch = await feed.PrepareRequest(); + FillBatch(fourthBatch!, fourthBatch!.StartNumber, false); + feed.HandleResponse(fourthBatch); + using HeadersSyncBatch? fifthBatch = await feed.PrepareRequest(); Assert.That(localBlockTree.LowestInsertedHeader!.Number, Is.LessThanOrEqualTo(targetHeaderInDependentBatch)); syncPeerPool.Received(shouldReport ? 1 : 0).ReportBreachOfProtocol(Arg.Any(), Arg.Any(), Arg.Any()); @@ -535,7 +535,7 @@ public async Task Does_not_download_persisted_header() { var peerChain = CachedBlockTreeBuilder.OfLength(1000); var pivotHeader = peerChain.FindHeader(999)!; - var syncConfig = new TestSyncConfig { FastSync = true, PivotNumber = pivotHeader.Number.ToString(), PivotHash = pivotHeader.Hash!.ToString(), PivotTotalDifficulty = pivotHeader.TotalDifficulty.ToString()! }; + var syncConfig = new TestSyncConfig { FastSync = true, PivotNumber = pivotHeader.Number, PivotHash = pivotHeader.Hash!.ToString(), PivotTotalDifficulty = pivotHeader.TotalDifficulty.ToString()! }; IBlockTree localBlockTree = Build.A.BlockTree(peerChain.FindBlock(0, BlockTreeLookupOptions.None)!, null).WithSyncConfig(syncConfig).TestObject; localBlockTree.SyncPivot = (pivotHeader.Number, pivotHeader.Hash); @@ -591,7 +591,7 @@ public async Task Limits_persisted_headers_dependency() var syncConfig = new TestSyncConfig { FastSync = true, - PivotNumber = pivotHeader.Number.ToString(), + PivotNumber = pivotHeader.Number, PivotHash = pivotHeader.Hash!.ToString(), PivotTotalDifficulty = pivotHeader.TotalDifficulty.ToString()!, FastHeadersMemoryBudget = (ulong)100.KB(), @@ -623,7 +623,7 @@ public async Task Can_use_persisted_header_without_total_difficulty() var syncConfig = new TestSyncConfig { FastSync = true, - PivotNumber = pivotHeader.Number.ToString(), + PivotNumber = pivotHeader.Number, PivotHash = pivotHeader.Hash!.ToString(), PivotTotalDifficulty = pivotHeader.TotalDifficulty.ToString()! }; @@ -666,7 +666,7 @@ public async Task Will_never_lose_batch_on_invalid_batch() new TestSyncConfig { FastSync = true, - PivotNumber = "1000", + PivotNumber = 1000, PivotHash = Keccak.Zero.ToString(), PivotTotalDifficulty = "1000" }, @@ -737,7 +737,7 @@ public void IsFinished_returns_false_when_headers_not_downloaded() FastSync = true, DownloadBodiesInFastSync = true, DownloadReceiptsInFastSync = true, - PivotNumber = "1", + PivotNumber = 1, }; blockTree.LowestInsertedHeader.Returns(Build.A.BlockHeader.WithNumber(2).WithStateRoot(TestItem.KeccakA).TestObject); @@ -764,7 +764,7 @@ public void When_lowestInsertedHeaderHasNoTD_then_fetchFromBlockTreeAgain() syncConfig: new TestSyncConfig { FastSync = true, - PivotNumber = "1000", + PivotNumber = 1000, PivotHash = TestItem.KeccakA.ToString(), PivotTotalDifficulty = "1000", }, @@ -797,7 +797,7 @@ public void When_cant_determine_pivot_total_difficulty_then_throw() syncConfig: new TestSyncConfig { FastSync = true, - PivotNumber = "1000", + PivotNumber = 1000, PivotHash = TestItem.KeccakA.ToString(), PivotTotalDifficulty = "1000", }, @@ -822,7 +822,7 @@ public async Task Should_Limit_BatchSize_ToEstimate() syncConfig: new TestSyncConfig { FastSync = true, - PivotNumber = "1000", + PivotNumber = 1000, PivotHash = TestItem.KeccakA.ToString(), PivotTotalDifficulty = "1000", }, diff --git a/src/Nethermind/Nethermind.Synchronization.Test/FastBlocks/ReceiptsSyncFeedTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/FastBlocks/ReceiptsSyncFeedTests.cs index ac96d287138..9ae8b4d0746 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/FastBlocks/ReceiptsSyncFeedTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/FastBlocks/ReceiptsSyncFeedTests.cs @@ -108,7 +108,7 @@ public void Setup() _syncConfig = new TestSyncConfig { FastSync = true, - PivotNumber = _pivotNumber.ToString(), + PivotNumber = _pivotNumber, PivotHash = Keccak.Zero.ToString() }; _blockTree.SyncPivot.Returns((_pivotNumber, Keccak.Zero)); @@ -255,12 +255,12 @@ public async Task When_configured_to_skip_receipts_then_finishes_immediately() [TestCase(1, 1024, false, null, false)] [TestCase(1, 1024, true, null, false)] [TestCase(1, 1024, false, 0, false)] - public void When_finished_sync_with_old_default_barrier_then_finishes_imedietely( + public void When_finished_sync_with_old_default_barrier_then_finishes_immediately( long AncientBarrierInConfig, long? lowestInsertedReceiptBlockNumber, bool JustStarted, long? previousBarrierInDb, - bool shouldfinish) + bool shouldFinish) { _syncPointers = Substitute.For(); _syncConfig.AncientBodiesBarrier = AncientBarrierInConfig; @@ -271,7 +271,7 @@ public void When_finished_sync_with_old_default_barrier_then_finishes_imedietely _metadataDb.Set(MetadataDbKeys.ReceiptsBarrierWhenStarted, previousBarrierInDb.Value.ToBigEndianByteArrayWithoutLeadingZeros()); LoadScenario(_256BodiesWithOneTxEach); _syncPointers.LowestInsertedReceiptBlockNumber.Returns(lowestInsertedReceiptBlockNumber); - _feed.IsFinished.Should().Be(shouldfinish); + _feed.IsFinished.Should().Be(shouldFinish); } private void LoadScenario(Scenario scenario) @@ -282,7 +282,7 @@ private void LoadScenario(Scenario scenario) private void LoadScenario(Scenario scenario, ISyncConfig syncConfig) { _syncConfig = syncConfig; - _syncConfig.PivotNumber = _pivotNumber.ToString(); + _syncConfig.PivotNumber = _pivotNumber; _syncConfig.PivotHash = scenario.Blocks.Last()?.Hash?.ToString(); _blockTree.SyncPivot.Returns((_pivotNumber, scenario.Blocks.Last()?.Hash!)); _syncPointers = Substitute.For(); @@ -412,7 +412,7 @@ public void Is_fast_block_receipts_finished_returns_false_when_receipts_not_down FastSync = true, DownloadBodiesInFastSync = true, DownloadReceiptsInFastSync = true, - PivotNumber = "1", + PivotNumber = 1, }; _blockTree.LowestInsertedHeader.Returns(Build.A.BlockHeader.WithNumber(1).WithStateRoot(TestItem.KeccakA).TestObject); @@ -433,7 +433,7 @@ public void Is_fast_block_bodies_finished_returns_true_when_bodies_not_downloade FastSync = true, DownloadBodiesInFastSync = false, DownloadReceiptsInFastSync = true, - PivotNumber = "1", + PivotNumber = 1, }; _blockTree.LowestInsertedHeader.Returns(Build.A.BlockHeader.WithNumber(1).WithStateRoot(TestItem.KeccakA).TestObject); diff --git a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTestsBase.cs b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTestsBase.cs index 23037d91fc9..4e427df4f5e 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTestsBase.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/FastSync/StateSyncFeedTestsBase.cs @@ -43,8 +43,8 @@ public abstract class StateSyncFeedTestsBase(int defaultPeerCount = 1, int defau { public const int TimeoutLength = 20000; - private static IBlockTree? _blockTree; - protected static IBlockTree BlockTree => LazyInitializer.EnsureInitialized(ref _blockTree, static () => Build.A.BlockTree().OfChainLength(100).TestObject); + // Chain length used for test block trees, use a constant to avoid shared state + private const int TestChainLength = 100; protected ILogger _logger; protected ILogManager _logManager = null!; @@ -81,7 +81,7 @@ protected IContainer PrepareDownloader(DbContext dbContext, Action { Node node = new Node(TestItem.PublicKeys[i], $"127.0.0.{i}", 30302, true) { - EthDetails = "eth66", + EthDetails = "eth68", }; SyncPeerMock mock = new SyncPeerMock(dbContext.RemoteStateDb, dbContext.RemoteCodeDb, node: node, maxRandomizedLatencyMs: defaultPeerMaxRandomLatency); mockMutator?.Invoke(mock); @@ -93,9 +93,12 @@ protected IContainer PrepareDownloader(DbContext dbContext, Action builder.RegisterBuildCallback((ctx) => { + IBlockTree blockTree = ctx.Resolve(); ISyncPeerPool peerPool = ctx.Resolve(); - foreach (ISyncPeer syncPeer in syncPeers) + foreach (SyncPeerMock syncPeer in syncPeers) { + // Set per-test block tree to avoid race conditions during parallel execution + syncPeer.SetBlockTree(blockTree); peerPool.AddPeer(syncPeer); } }); @@ -121,9 +124,10 @@ protected ContainerBuilder BuildTestContainerBuilder(DbContext dbContext, int sy .AddSingleton(dbContext.LocalNodeStorage) // Use factory function to make it lazy in case test need to replace IBlockTree + // Cache key includes type name so different inherited test classes don't share the same blocktree .AddSingleton((ctx) => CachedBlockTreeBuilder.BuildCached( - $"{nameof(StateSyncFeedTestsBase)}{dbContext.RemoteStateTree.RootHash}{BlockTree.BestSuggestedHeader!.Number}", - () => Build.A.BlockTree().WithStateRoot(dbContext.RemoteStateTree.RootHash).OfChainLength((int)BlockTree.BestSuggestedHeader!.Number))) + $"{GetType().Name}{dbContext.RemoteStateTree.RootHash}{TestChainLength}", + () => Build.A.BlockTree().WithStateRoot(dbContext.RemoteStateTree.RootHash).OfChainLength(TestChainLength))) .Add(); @@ -280,6 +284,9 @@ protected class SyncPeerMock : BaseSyncPeerMock private readonly Func, Task>>? _executorResultFunction; private readonly long _maxRandomizedLatencyMs; + // Per-test block tree to avoid race conditions during parallel test execution + private IBlockTree? _blockTree; + public SyncPeerMock( IDb stateDb, IDb codeDb, @@ -291,7 +298,7 @@ public SyncPeerMock( _codeDb = codeDb; _executorResultFunction = executorResultFunction; - Node = node ?? new Node(TestItem.PublicKeyA, "127.0.0.1", 30302, true) { EthDetails = "eth67" }; + Node = node ?? new Node(TestItem.PublicKeyA, "127.0.0.1", 30302, true) { EthDetails = "eth68" }; _maxRandomizedLatencyMs = maxRandomizedLatencyMs ?? 0; IStateReader alwaysAvailableRootTracker = Substitute.For(); @@ -345,6 +352,11 @@ public void SetFilter(Hash256[]? availableHashes) _filter = availableHashes; } + public void SetBlockTree(IBlockTree blockTree) + { + _blockTree = blockTree; + } + public override bool TryGetSatelliteProtocol(string protocol, out T protocolHandler) where T : class { if (protocol == Protocol.Snap) @@ -358,7 +370,7 @@ public override bool TryGetSatelliteProtocol(string protocol, out T protocolH public override Task GetHeadBlockHeader(Hash256? hash, CancellationToken token) { - return Task.FromResult(BlockTree.Head?.Header); + return Task.FromResult(_blockTree?.Head?.Header); } public override Task> GetByteCodes(IReadOnlyList codeHashes, CancellationToken token) diff --git a/src/Nethermind/Nethermind.Synchronization.Test/ForwardHeaderProviderTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/ForwardHeaderProviderTests.cs index 78df4b4c558..dbda18297e0 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/ForwardHeaderProviderTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/ForwardHeaderProviderTests.cs @@ -118,7 +118,7 @@ public async Task Ancestor_lookup_with_sync_pivot() { }, new ConfigProvider(new SyncConfig() { - PivotNumber = syncPivot.Number.ToString(), + PivotNumber = syncPivot.Number, PivotHash = syncPivot.Hash!.ToString(), })); diff --git a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTests.Scenario.cs b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTests.Scenario.cs index f5852c48faf..8313c70c314 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTests.Scenario.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/ParallelSync/MultiSyncModeSelectorTests.Scenario.cs @@ -171,7 +171,7 @@ private void SetDefaults() SyncProgressResolver.SyncPivot.Returns((Pivot.Number, Keccak.Zero)); SyncConfig.FastSync = false; - SyncConfig.PivotNumber = Pivot.Number.ToString(); + SyncConfig.PivotNumber = Pivot.Number; SyncConfig.PivotHash = Keccak.Zero.ToString(); SyncConfig.SynchronizationEnabled = true; SyncConfig.NetworkingEnabled = true; diff --git a/src/Nethermind/Nethermind.Synchronization.Test/ReceiptSyncFeedTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/ReceiptSyncFeedTests.cs index 60c714970dd..ff7c6bc9f6b 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/ReceiptSyncFeedTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/ReceiptSyncFeedTests.cs @@ -65,7 +65,7 @@ public void Setup() { FastSync = true, PivotHash = _pivotBlock.Hash!.ToString(), - PivotNumber = _pivotBlock.Number.ToString(), + PivotNumber = _pivotBlock.Number, AncientBodiesBarrier = 0, DownloadBodiesInFastSync = true, }; diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/StateSyncPivotTest.cs b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/StateSyncPivotTest.cs index bd6404cf9c0..815606d13b7 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/StateSyncPivotTest.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SnapSync/StateSyncPivotTest.cs @@ -34,7 +34,7 @@ int syncPivot Synchronization.FastSync.StateSyncPivot stateSyncPivot = new Synchronization.FastSync.StateSyncPivot(blockTree, new TestSyncConfig() { - PivotNumber = syncPivot.ToString(), + PivotNumber = syncPivot, FastSync = true, StateMinDistanceFromHead = minDistance, StateMaxDistanceFromHead = maxDistance, diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs index 4a8bcfde2c0..af08c2f41a6 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncProgressResolverTests.cs @@ -28,7 +28,7 @@ public void Header_block_is_0_when_no_header_was_suggested() IStateReader stateReader = Substitute.For(); SyncConfig syncConfig = new() { - PivotNumber = "1", + PivotNumber = 1, }; SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); @@ -43,7 +43,7 @@ public void Best_block_is_0_when_no_block_was_suggested() IStateReader stateReader = Substitute.For(); SyncConfig syncConfig = new() { - PivotNumber = "1", + PivotNumber = 1, }; SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); @@ -58,7 +58,7 @@ public void Best_state_is_head_when_there_are_no_suggested_blocks() IStateReader stateReader = Substitute.For(); SyncConfig syncConfig = new() { - PivotNumber = "1", + PivotNumber = 1, }; SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); @@ -76,7 +76,7 @@ public void Best_state_is_suggested_if_there_is_suggested_block_with_state() IStateReader stateReader = Substitute.For(); SyncConfig syncConfig = new() { - PivotNumber = "1", + PivotNumber = 1, }; SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); @@ -98,7 +98,7 @@ public void Best_state_is_head_if_there_is_suggested_block_without_state() IStateReader stateReader = Substitute.For(); SyncConfig syncConfig = new() { - PivotNumber = "1", + PivotNumber = 1, }; SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); @@ -120,7 +120,7 @@ public void Is_fast_block_finished_returns_true_when_no_fast_sync_is_used() SyncConfig syncConfig = new() { FastSync = false, - PivotNumber = "1", + PivotNumber = 1, }; SyncProgressResolver syncProgressResolver = CreateProgressResolver(blockTree, stateReader, false, syncConfig, LimboLogs.Instance); @@ -139,7 +139,7 @@ public void Is_fast_block_bodies_finished_returns_false_when_blocks_not_download FastSync = true, DownloadBodiesInFastSync = true, DownloadReceiptsInFastSync = true, - PivotNumber = "1", + PivotNumber = 1, }; blockTree.SyncPivot.Returns((1, Hash256.Zero)); @@ -159,7 +159,7 @@ public void Is_fast_block_receipts_finished_returns_true_when_receipts_not_downl FastSync = true, DownloadBodiesInFastSync = true, DownloadReceiptsInFastSync = false, - PivotNumber = "1", + PivotNumber = 1, }; blockTree.LowestInsertedHeader.Returns(Build.A.BlockHeader.WithNumber(1).WithStateRoot(TestItem.KeccakA).TestObject); diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SyncReportTest.cs b/src/Nethermind/Nethermind.Synchronization.Test/SyncReportTest.cs index d542b0498f0..f8a0a0eeafa 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SyncReportTest.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SyncReportTest.cs @@ -84,7 +84,7 @@ public void Ancient_bodies_and_receipts_are_reported_correctly() SyncConfig syncConfig = new() { FastSync = true, - PivotNumber = "100", + PivotNumber = 100, }; SyncReport syncReport = new(pool, Substitute.For(), syncConfig, Substitute.For(), logManager, timerFactory); @@ -127,7 +127,7 @@ public void Ancient_bodies_and_receipts_are_not_reported_until_feed_finishes_Ini SyncConfig syncConfig = new() { FastSync = true, - PivotNumber = "100", + PivotNumber = 100, }; if (setBarriers) { diff --git a/src/Nethermind/Nethermind.Synchronization.Test/SynchronizerModuleTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/SynchronizerModuleTests.cs index 3920eb07a27..d635aadccf8 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/SynchronizerModuleTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/SynchronizerModuleTests.cs @@ -8,9 +8,11 @@ using Nethermind.Core; using Nethermind.Core.Test.Builders; using Nethermind.Core.Test.Modules; +using Nethermind.Network.Config; using Nethermind.State; using Nethermind.Synchronization.FastSync; using Nethermind.Synchronization.ParallelSync; +using Nethermind.Synchronization.Peers; using NSubstitute; using NUnit.Framework; @@ -52,4 +54,21 @@ public async Task TestOnTreeSyncFinish_CallVisit() .Received(1) .VerifyTrie(Arg.Any(), Arg.Any()); } + + [Test] + public void SyncPeerPool_should_use_INetworkConfig_MaxActivePeers() + { + NetworkConfig networkConfig = new() { MaxActivePeers = 75 }; + + using IContainer container = new ContainerBuilder() + .AddModule(new TestNethermindModule(networkConfig)) + .AddModule(new SynchronizerModule(new TestSyncConfig())) + .AddSingleton(Substitute.For()) + .AddSingleton(Substitute.For()) + .Build(); + + SyncPeerPool pool = container.Resolve(); + + Assert.That(pool.PeerMaxCount, Is.EqualTo(75)); + } } diff --git a/src/Nethermind/Nethermind.Synchronization.Test/Trie/HealingTreeTests.cs b/src/Nethermind/Nethermind.Synchronization.Test/Trie/HealingTreeTests.cs index 6491496fecd..c8e19f03e07 100644 --- a/src/Nethermind/Nethermind.Synchronization.Test/Trie/HealingTreeTests.cs +++ b/src/Nethermind/Nethermind.Synchronization.Test/Trie/HealingTreeTests.cs @@ -193,7 +193,7 @@ void RandomCopyState(IContainer server, IContainer client) Random random = new Random(0); using ArrayPoolList> allValues = serverStateDb.GetAll().ToPooledList(10); - // Sort for reproducability + // Sort for reproducibility allValues.AsSpan().Sort(((k1, k2) => ((IComparer)Bytes.Comparer).Compare(k1.Key, k2.Key))); // Copy from server to client, but randomly remove some of them. diff --git a/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloader.cs b/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloader.cs index 3b2ea7ca142..3ad89149e6b 100644 --- a/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloader.cs +++ b/src/Nethermind/Nethermind.Synchronization/Blocks/BlockDownloader.cs @@ -497,7 +497,7 @@ public SyncResponseHandlingResult HandleResponse(BlocksRequest response, PeerInf if (result == SyncResponseHandlingResult.OK) { - // Request and body does not have the same size so this hueristic is wrong. + // Request and body does not have the same size so this heuristic is wrong. if (bodiesCount + receiptsCount == 0) { // Trigger sleep diff --git a/src/Nethermind/Nethermind.Synchronization/Blocks/PowForwardHeaderProvider.cs b/src/Nethermind/Nethermind.Synchronization/Blocks/PowForwardHeaderProvider.cs index b2905f365de..9db02fb7c01 100644 --- a/src/Nethermind/Nethermind.Synchronization/Blocks/PowForwardHeaderProvider.cs +++ b/src/Nethermind/Nethermind.Synchronization/Blocks/PowForwardHeaderProvider.cs @@ -73,7 +73,7 @@ private IOwnedReadOnlyList? LastResponseBatch IOwnedReadOnlyList? headers = AssembleResponseFromLastResponseBatch(); if (headers is not null) { - if (_logger.IsTrace) _logger.Trace($"PoW header info from last response from {headers[0].ToString(BlockHeader.Format.Short)} to {headers[1].ToString(BlockHeader.Format.Short)}"); + if (_logger.IsTrace) _logger.Trace($"PoW header info from last response from {headers[0].ToString(BlockHeader.Format.Short)} to {headers[^1].ToString(BlockHeader.Format.Short)}"); return headers; } @@ -160,7 +160,7 @@ private void OnNewBestPeer(PeerInfo newBestPeer) await RequestHeaders(bestPeer, cancellation, _currentNumber, headersToRequest); if (headers.Count < 2) { - // Peer dont have new header + // Peer doesn't have a new header headers.Dispose(); return null; } diff --git a/src/Nethermind/Nethermind.Synchronization/DbTuner/SyncDbOptimizer.cs b/src/Nethermind/Nethermind.Synchronization/DbTuner/SyncDbOptimizer.cs index bb5e64e6059..6f5d1625f32 100644 --- a/src/Nethermind/Nethermind.Synchronization/DbTuner/SyncDbOptimizer.cs +++ b/src/Nethermind/Nethermind.Synchronization/DbTuner/SyncDbOptimizer.cs @@ -39,7 +39,7 @@ public SyncDbTuner( // Only these three make sense as they are write heavy // Headers is used everywhere, so slowing read might slow the whole sync. - // Statesync is read heavy, Forward sync is just plain too slow to saturate IO. + // State sync is read heavy, Forward sync is just plain too slow to saturate IO. if (snapSyncFeed is not NoopSyncFeed) { snapSyncFeed.StateChanged += SnapStateChanged; diff --git a/src/Nethermind/Nethermind.Synchronization/FastBlocks/FastHeadersSyncFeed.cs b/src/Nethermind/Nethermind.Synchronization/FastBlocks/FastHeadersSyncFeed.cs index ec74a91c718..ad3fc5c5c1d 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastBlocks/FastHeadersSyncFeed.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastBlocks/FastHeadersSyncFeed.cs @@ -225,7 +225,7 @@ protected virtual void ResetPivot() private UInt256 TryGetPivotTotalDifficulty(Hash256 headerHash) { - if (_pivotNumber == LongConverter.FromString(_syncConfig.PivotNumber)) + if (_pivotNumber == _syncConfig.PivotNumber) return _syncConfig.PivotTotalDifficultyParsed; // Pivot is the same as in config // Got from header diff --git a/src/Nethermind/Nethermind.Synchronization/FastBlocks/ReceiptsSyncFeed.cs b/src/Nethermind/Nethermind.Synchronization/FastBlocks/ReceiptsSyncFeed.cs index a1e6a7e2766..c8b87ae733e 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastBlocks/ReceiptsSyncFeed.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastBlocks/ReceiptsSyncFeed.cs @@ -28,7 +28,6 @@ namespace Nethermind.Synchronization.FastBlocks { - public class ReceiptsSyncFeed : BarrierSyncFeed { protected override long? LowestInsertedNumber => _syncPointers.LowestInsertedReceiptBlockNumber; diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/PendingSyncItems.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/PendingSyncItems.cs index c2d0433d382..da1923c80e7 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastSync/PendingSyncItems.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastSync/PendingSyncItems.cs @@ -174,7 +174,7 @@ public List TakeBatch(int maxSize) } else { - // With snap sync, we want the top level nodes. Low level nodes are mostly snapsyncd. + // With snap sync, we want the top-level nodes. Low-level nodes are mostly snap-synced. length = maxSize; } diff --git a/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncItem.cs b/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncItem.cs index 26dddeabc47..97c558ddec0 100644 --- a/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncItem.cs +++ b/src/Nethermind/Nethermind.Synchronization/FastSync/StateSyncItem.cs @@ -64,8 +64,8 @@ public override bool Equals(object obj) public override int GetHashCode() { - uint hash0 = (uint)hash.GetHashCode(); - ulong hash1 = ((ulong)(uint)(address?.GetHashCode() ?? 1) << 32) | (ulong)(uint)(Path?.GetHashCode() ?? 2); + uint hash0 = (uint)Hash.GetHashCode(); + ulong hash1 = ((ulong)(uint)(Address.GetHashCode()) << 32) | (ulong)(uint)(Path?.GetHashCode() ?? 2); return (int)BitOperations.Crc32C(hash0, hash1); } diff --git a/src/Nethermind/Nethermind.Synchronization/Metrics.cs b/src/Nethermind/Nethermind.Synchronization/Metrics.cs index 6eb17051077..c383389c824 100644 --- a/src/Nethermind/Nethermind.Synchronization/Metrics.cs +++ b/src/Nethermind/Nethermind.Synchronization/Metrics.cs @@ -88,7 +88,7 @@ public static class Metrics public static IMetricObserver SyncDispatcherPrepareRequestTimeMicros = NoopMetricObserver.Instance; [ExponentialPowerHistogramMetric(LabelNames = ["sync_type"], Start = 10, Factor = 10, Count = 5)] - [Description("Sinc dispatcher time in dispatch. High value indicate slow peer or internet.")] + [Description("Sync dispatcher time in dispatch. High value indicates a slow peer or internet.")] [DetailedMetric] public static IMetricObserver SyncDispatcherDispatchTimeMicros = NoopMetricObserver.Instance; diff --git a/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs index 48c8403e798..3eb5e9e616a 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/MultiSyncModeSelector.cs @@ -846,7 +846,7 @@ long pivotNumber /// /// The best block that we want to go to. best.Peer.Block for PoW, beaconSync.ProcessDestination for PoS, - /// whith is the NewPayload/FCU block. + /// which is the NewPayload/FCU block. /// public long TargetBlock { get; } diff --git a/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncDispatcher.cs b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncDispatcher.cs index a62c8a3f699..9770595b696 100644 --- a/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncDispatcher.cs +++ b/src/Nethermind/Nethermind.Synchronization/ParallelSync/SyncDispatcher.cs @@ -334,6 +334,7 @@ public async ValueTask DisposeAsync() { if (Logger.IsWarn) Logger.Warn($"Timeout on waiting for active tasks for feed {Feed.GetType().Name} {_activeTasks.CurrentCount}"); } + _activeTasks.Dispose(); _cancellationTokenSource.Dispose(); _concurrentProcessingSemaphore.Dispose(); } diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerExtensions.cs b/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerExtensions.cs index e477c1b0b5a..7dabd1b0680 100644 --- a/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerExtensions.cs +++ b/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerExtensions.cs @@ -24,8 +24,8 @@ public static bool SupportsAllocation(this PeerInfo peerInfo, AllocationContexts Version? openEthereumVersion = peerInfo.SyncPeer.GetOpenEthereumVersion(out _); if (openEthereumVersion is not null) { - int versionComparision = openEthereumVersion.CompareTo(_openEthereumSecondRemoveGetNodeDataVersion); - return versionComparision >= 0 || openEthereumVersion < _openEthereumFirstRemoveGetNodeDataVersion; + int versionComparison = openEthereumVersion.CompareTo(_openEthereumSecondRemoveGetNodeDataVersion); + return versionComparison >= 0 || openEthereumVersion < _openEthereumFirstRemoveGetNodeDataVersion; } } diff --git a/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerPool.cs b/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerPool.cs index 25d3d60e5d3..4cdc6c68723 100644 --- a/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerPool.cs +++ b/src/Nethermind/Nethermind.Synchronization/Peers/SyncPeerPool.cs @@ -15,6 +15,7 @@ using Nethermind.Blockchain.Synchronization; using Nethermind.Consensus.Validators; using Nethermind.Core; +using Nethermind.Core.Container; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; using Nethermind.Int256; @@ -61,6 +62,7 @@ public class SyncPeerPool : ISyncPeerPool, IPeerDifficultyRefreshPool private readonly TimeSpan _timeBeforeWakingShallowSleepingPeerUp = TimeSpan.FromMilliseconds(DefaultUpgradeIntervalInMs); private Timer? _upgradeTimer; + [UseConstructorForDependencyInjection] public SyncPeerPool(IBlockTree blockTree, INodeStatsManager nodeStatsManager, IBetterPeerStrategy betterPeerStrategy, @@ -446,7 +448,6 @@ private async Task RunRefreshPeerLoop() } if (_logger.IsInfo) _logger.Info("Exiting sync peer refresh loop"); - await Task.CompletedTask; } private void StartUpgradeTimer() @@ -540,7 +541,12 @@ currentPeer.TotalDifficulty is not null && } } - worstPeer?.SyncPeer.Disconnect(DisconnectReason.DropWorstPeer, $"PEER REVIEW / {worstReason}"); + if (worstPeer is null) + { + return 0; + } + + worstPeer.SyncPeer.Disconnect(DisconnectReason.DropWorstPeer, $"PEER REVIEW / {worstReason}"); return 1; } diff --git a/src/Nethermind/Nethermind.Synchronization/Reporting/SnapshotReport.cs b/src/Nethermind/Nethermind.Synchronization/Reporting/SnapshotReport.cs index a882403ea4e..232e1a495b0 100644 --- a/src/Nethermind/Nethermind.Synchronization/Reporting/SnapshotReport.cs +++ b/src/Nethermind/Nethermind.Synchronization/Reporting/SnapshotReport.cs @@ -3,7 +3,7 @@ namespace Nethermind.Synchronization.Reporting { - public class SyncReportSymmary + public class SyncReportSummary { public string CurrentStage { get; set; } } diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/ProgressTracker.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/ProgressTracker.cs index ff56c9df8fa..20ee5685621 100644 --- a/src/Nethermind/Nethermind.Synchronization/SnapSync/ProgressTracker.cs +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/ProgressTracker.cs @@ -496,9 +496,9 @@ private void LogRequest(string reqType) int totalPathProgress = 0; foreach (KeyValuePair kv in AccountRangePartitions) { - AccountRangePartition? partiton = kv.Value; - int nextAccount = partiton.NextAccountPath.Bytes[0] * 256 + partiton.NextAccountPath.Bytes[1]; - int startAccount = partiton.AccountPathStart.Bytes[0] * 256 + partiton.AccountPathStart.Bytes[1]; + AccountRangePartition? partition = kv.Value; + int nextAccount = partition.NextAccountPath.Bytes[0] * 256 + partition.NextAccountPath.Bytes[1]; + int startAccount = partition.AccountPathStart.Bytes[0] * 256 + partition.AccountPathStart.Bytes[1]; totalPathProgress += nextAccount - startAccount; } diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs index eee17b694d8..26217d22f52 100644 --- a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProvider.cs @@ -254,13 +254,13 @@ public void RefreshAccounts(AccountsToRefreshRequest request, IOwnedReadOnlyList { int respLength = response.Count; IScopedTrieStore stateStore = _stateTrieStore; - for (int reqi = 0; reqi < request.Paths.Count; reqi++) + for (int reqIndex = 0; reqIndex < request.Paths.Count; reqIndex++) { - var requestedPath = request.Paths[reqi]; + var requestedPath = request.Paths[reqIndex]; - if (reqi < respLength) + if (reqIndex < respLength) { - byte[] nodeData = response[reqi]; + byte[] nodeData = response[reqIndex]; if (nodeData.Length == 0) { diff --git a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProviderHelper.cs b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProviderHelper.cs index cd30e4a6649..ebdd23dfd1c 100644 --- a/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProviderHelper.cs +++ b/src/Nethermind/Nethermind.Synchronization/SnapSync/SnapProviderHelper.cs @@ -97,7 +97,7 @@ public static (AddRangeResult result, bool moreChildrenToRight, List> task = null; HashList? hashList = null; GetTrieNodesRequest? getTrieNodesRequest = null; - // Use GETNODEDATA if possible. Firstly via dedicated NODEDATA protocol + // Use GetNodeData if possible, starting with the dedicated NodeData protocol if (peer.TryGetSatelliteProtocol(Protocol.NodeData, out INodeDataPeer nodeDataHandler)) { if (Logger.IsTrace) Logger.Trace($"Requested NodeData via NodeDataProtocol from peer {peer}"); hashList = HashList.Rent(batch.RequestedNodes); task = nodeDataHandler.GetNodeData(hashList, cancellationToken); } - // If NODEDATA protocol is not supported, try eth66 + // If the NodeData protocol is not supported, try eth66 else if (peer.ProtocolVersion < EthVersions.Eth67) { if (Logger.IsTrace) Logger.Trace($"Requested NodeData via EthProtocol from peer {peer}"); hashList = HashList.Rent(batch.RequestedNodes); task = peer.GetNodeData(hashList, cancellationToken); } - // GETNODEDATA is not supported so we try with SNAP protocol + // If GetNodeData is not supported, fall back to the Snap protocol else if (peer.TryGetSatelliteProtocol(Protocol.Snap, out ISnapSyncPeer snapHandler)) { if (batch.NodeDataType == NodeDataType.Code) diff --git a/src/Nethermind/Nethermind.Synchronization/Synchronizer.cs b/src/Nethermind/Nethermind.Synchronization/Synchronizer.cs index 9eb2b50f189..200cabd887e 100644 --- a/src/Nethermind/Nethermind.Synchronization/Synchronizer.cs +++ b/src/Nethermind/Nethermind.Synchronization/Synchronizer.cs @@ -312,7 +312,7 @@ protected override void Load(ContainerBuilder builder) .AddScoped>() // The direct implementation is decorated by merge plugin (not the interface) - // so its declared on its own and other use is binded. + // so it's declared on its own and other usage is bound. .AddSingleton() .Bind() diff --git a/src/Nethermind/Nethermind.Taiko.Test/L1OriginStoreTests.cs b/src/Nethermind/Nethermind.Taiko.Test/L1OriginStoreTests.cs new file mode 100644 index 00000000000..954bfacd308 --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko.Test/L1OriginStoreTests.cs @@ -0,0 +1,206 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Linq; +using FluentAssertions; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test; +using Nethermind.Db; +using Nethermind.Int256; +using Nethermind.Serialization.Rlp; +using NUnit.Framework; + +namespace Nethermind.Taiko.Test; + +public class L1OriginStoreTests +{ + private IDb _db = null!; + private L1OriginStore _store = null!; + private L1OriginDecoder _decoder = null!; + + [SetUp] + public void Setup() + { + _db = new TestMemDb(); + _decoder = new L1OriginDecoder(); + _store = new L1OriginStore(_db, _decoder); + } + + [Test] + public void Can_write_and_read_l1_origin() + { + UInt256 blockId = 123; + L1Origin origin = new(blockId, Hash256.Zero, 456, Hash256.Zero, null); + + _store.WriteL1Origin(blockId, origin); + L1Origin? retrieved = _store.ReadL1Origin(blockId); + + retrieved.Should().NotBeNull(); + retrieved!.BlockId.Should().Be(blockId); + retrieved.L1BlockHeight.Should().Be(456); + } + + [Test] + public void Returns_null_for_non_existent_l1_origin() + { + L1Origin? retrieved = _store.ReadL1Origin(999); + retrieved.Should().BeNull(); + } + + [Test] + public void Can_write_and_read_head_l1_origin() + { + UInt256 headBlockId = 789; + + _store.WriteHeadL1Origin(headBlockId); + UInt256? retrieved = _store.ReadHeadL1Origin(); + + retrieved.Should().Be((UInt256)789); + } + + [Test] + public void Returns_null_for_non_existent_head() + { + UInt256? retrieved = _store.ReadHeadL1Origin(); + retrieved.Should().BeNull(); + } + + [Test] + public void Can_write_and_read_batch_to_block_mapping() + { + UInt256 batchId = 100; + UInt256 blockId = 200; + + _store.WriteBatchToLastBlockID(batchId, blockId); + UInt256? retrieved = _store.ReadBatchToLastBlockID(batchId); + + retrieved.Should().Be((UInt256)200); + } + + [Test] + public void Returns_null_for_non_existent_batch_mapping() + { + UInt256? retrieved = _store.ReadBatchToLastBlockID(999); + retrieved.Should().BeNull(); + } + + [Test] + public void Different_batch_ids_store_separately() + { + _store.WriteBatchToLastBlockID(1, 100); + _store.WriteBatchToLastBlockID(2, 200); + + _store.ReadBatchToLastBlockID(1).Should().Be((UInt256)100); + _store.ReadBatchToLastBlockID(2).Should().Be((UInt256)200); + } + + [Test] + public void Different_block_ids_store_separately() + { + L1Origin origin1 = new(1, Hash256.Zero, 100, Hash256.Zero, null); + L1Origin origin2 = new(2, Hash256.Zero, 200, Hash256.Zero, null); + + _store.WriteL1Origin(1, origin1); + _store.WriteL1Origin(2, origin2); + + _store.ReadL1Origin(1)!.L1BlockHeight.Should().Be(100); + _store.ReadL1Origin(2)!.L1BlockHeight.Should().Be(200); + } + + [Test] + public void Can_overwrite_existing_values() + { + UInt256 blockId = 1; + L1Origin origin1 = new(blockId, Hash256.Zero, 100, Hash256.Zero, null); + L1Origin origin2 = new(blockId, Hash256.Zero, 200, Hash256.Zero, null); + + _store.WriteL1Origin(blockId, origin1); + _store.WriteL1Origin(blockId, origin2); + + _store.ReadL1Origin(blockId)!.L1BlockHeight.Should().Be(200); + } + + [Test] + public void Keys_use_correct_33_byte_format() + { + UInt256 blockId = 42; + L1Origin origin = new(blockId, Hash256.Zero, 100, Hash256.Zero, null); + + _store.WriteL1Origin(blockId, origin); + + var testDb = (TestMemDb)_db; + var allKeys = testDb.Keys.ToArray(); + allKeys.Should().HaveCount(1); + allKeys[0].Length.Should().Be(33, "Keys should be 33 bytes (1 prefix + 32 UInt256)"); + allKeys[0][0].Should().Be(0x00, "L1Origin keys should have prefix 0x00"); + } + + [Test] + public void Batch_keys_use_correct_prefix() + { + _store.WriteBatchToLastBlockID(1, 100); + + var testDb = (TestMemDb)_db; + var allKeys = testDb.Keys.ToArray(); + allKeys.Should().HaveCount(1); + allKeys[0].Length.Should().Be(33); + allKeys[0][0].Should().Be(0x01, "Batch keys should have prefix 0x01"); + } + + [Test] + public void Head_key_uses_correct_prefix() + { + _store.WriteHeadL1Origin(1); + + var testDb = (TestMemDb)_db; + var allKeys = testDb.Keys.ToArray(); + allKeys.Should().HaveCount(1); + allKeys[0].Length.Should().Be(1); + allKeys[0][0].Should().Be(0xFF, "Head key should have prefix 0xFF"); + } + + [Test] + public void Can_store_and_retrieve_signature() + { + UInt256 blockId = 123; + int[] signature = Enumerable.Range(0, 65).ToArray(); + L1Origin origin = new(blockId, Hash256.Zero, 456, Hash256.Zero, null) { Signature = signature }; + + _store.WriteL1Origin(blockId, origin); + L1Origin? retrieved = _store.ReadL1Origin(blockId); + + retrieved.Should().NotBeNull(); + retrieved!.Signature.Should().NotBeNull(); + retrieved.Signature!.Length.Should().Be(65); + retrieved.Signature.Should().BeEquivalentTo(signature); + } + + [Test] + public void Can_write_and_read_l1_origin_with_null_block_height() + { + UInt256 blockId = 456; + L1Origin origin = new(blockId, Hash256.Zero, null, Hash256.Zero, null); + + _store.WriteL1Origin(blockId, origin); + L1Origin? retrieved = _store.ReadL1Origin(blockId); + + retrieved.Should().NotBeNull(); + retrieved!.L1BlockHeight.Should().Be(0); + retrieved.IsPreconfBlock.Should().BeTrue(); + } + + [TestCase(0)] + [TestCase(L1OriginDecoder.SignatureLength - 1)] + [TestCase(L1OriginDecoder.SignatureLength + 1)] + [TestCase(L1OriginDecoder.SignatureLength * 2)] + public void Fails_for_invalid_length_signature(int signatureLength) + { + int[] signature = Enumerable.Range(0, signatureLength).ToArray(); + L1Origin origin = new(1, Hash256.Zero, 456, Hash256.Zero, null) { Signature = signature }; + + Action act = () => _decoder.Encode(origin); + act.Should().Throw().WithMessage($"*Signature*{L1OriginDecoder.SignatureLength}*"); + } +} + diff --git a/src/Nethermind/Nethermind.Taiko.Test/SurgeGasPriceOracleTests.cs b/src/Nethermind/Nethermind.Taiko.Test/SurgeGasPriceOracleTests.cs index adfa00daaa5..82b0daed91b 100644 --- a/src/Nethermind/Nethermind.Taiko.Test/SurgeGasPriceOracleTests.cs +++ b/src/Nethermind/Nethermind.Taiko.Test/SurgeGasPriceOracleTests.cs @@ -144,7 +144,7 @@ public async ValueTask GetGasPriceEstimate_WithValidL1FeeHistory_CalculatesCorre } [Test] - public async ValueTask GetGasPriceEstimate_WithZeroGasUsed_ReturnsAtleastMinGasPrice() + public async ValueTask GetGasPriceEstimate_WithZeroGasUsed_ReturnsAtLeastMinGasPrice() { Block headBlock = Build.A.Block.WithNumber(1).WithGasUsed(0).TestObject; _blockFinder.Head.Returns(headBlock); diff --git a/src/Nethermind/Nethermind.Taiko.Test/TaikoEngineApiTests.cs b/src/Nethermind/Nethermind.Taiko.Test/TaikoEngineApiTests.cs index 4765b12036b..eb9a581d29b 100644 --- a/src/Nethermind/Nethermind.Taiko.Test/TaikoEngineApiTests.cs +++ b/src/Nethermind/Nethermind.Taiko.Test/TaikoEngineApiTests.cs @@ -52,8 +52,8 @@ public async Task Test_ForkchoiceUpdatedHandler_Allows_UnknownFinalizedSafeBlock Substitute.For() ); - ResultWrapper beforeNewBlockAadded = await forkchoiceUpdatedHandler.Handle(new ForkchoiceStateV1(genesisBlock.Hash!, futureBlock.Hash!, futureBlock.Hash!), null, 2); - Assert.That(beforeNewBlockAadded.Data.PayloadStatus.Status, Is.EqualTo(PayloadStatus.Valid)); + ResultWrapper beforeNewBlockAdded = await forkchoiceUpdatedHandler.Handle(new ForkchoiceStateV1(genesisBlock.Hash!, futureBlock.Hash!, futureBlock.Hash!), null, 2); + Assert.That(beforeNewBlockAdded.Data.PayloadStatus.Status, Is.EqualTo(PayloadStatus.Valid)); AddBlock(blockTree, futureBlock); diff --git a/src/Nethermind/Nethermind.Taiko.Test/TaikoExtendedEthModuleTests.cs b/src/Nethermind/Nethermind.Taiko.Test/TaikoExtendedEthModuleTests.cs index 291276de31d..3f393b33589 100644 --- a/src/Nethermind/Nethermind.Taiko.Test/TaikoExtendedEthModuleTests.cs +++ b/src/Nethermind/Nethermind.Taiko.Test/TaikoExtendedEthModuleTests.cs @@ -3,6 +3,7 @@ using Autofac; using FluentAssertions; +using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Synchronization; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -22,6 +23,7 @@ public void TestCanResolve() { using IContainer container = new ContainerBuilder() .AddModule(new TestNethermindModule()) + .AddSingleton(Substitute.For()) .AddModule(new TaikoModule()) .Build(); @@ -36,7 +38,7 @@ public void TestSyncMode(bool snapEnabled, string result) TaikoExtendedEthModule rpc = new TaikoExtendedEthModule(new SyncConfig() { SnapSync = snapEnabled - }, Substitute.For()); + }, Substitute.For(), Substitute.For()); rpc.taiko_getSyncMode().Result.Data.Should().Be(result); } @@ -45,7 +47,7 @@ public void TestSyncMode(bool snapEnabled, string result) public void TestHeadL1Origin() { IL1OriginStore originStore = Substitute.For(); - TaikoExtendedEthModule rpc = new TaikoExtendedEthModule(new SyncConfig(), originStore); + TaikoExtendedEthModule rpc = new TaikoExtendedEthModule(new SyncConfig(), originStore, Substitute.For()); L1Origin origin = new L1Origin(0, TestItem.KeccakA, 1, Hash256.Zero, null); originStore.ReadHeadL1Origin().Returns((UInt256)1); @@ -58,7 +60,7 @@ public void TestHeadL1Origin() public void TestL1OriginById() { IL1OriginStore originStore = Substitute.For(); - TaikoExtendedEthModule rpc = new TaikoExtendedEthModule(new SyncConfig(), originStore); + TaikoExtendedEthModule rpc = new TaikoExtendedEthModule(new SyncConfig(), originStore, Substitute.For()); L1Origin origin = new L1Origin(0, TestItem.KeccakA, 1, Hash256.Zero, null); originStore.ReadL1Origin((UInt256)0).Returns(origin); @@ -70,7 +72,7 @@ public void TestL1OriginById() public void TestL1OriginById_WithBuildPayloadArgsId() { IL1OriginStore originStore = Substitute.For(); - TaikoExtendedEthModule rpc = new TaikoExtendedEthModule(new SyncConfig(), originStore); + TaikoExtendedEthModule rpc = new TaikoExtendedEthModule(new SyncConfig(), originStore, Substitute.For()); var buildPayloadArgsId = new int[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; L1Origin origin = new L1Origin(0, TestItem.KeccakA, 1, Hash256.Zero, buildPayloadArgsId); @@ -83,7 +85,7 @@ public void TestL1OriginById_WithBuildPayloadArgsId() public void TestL1OriginById_ValueHash256_EvenLengthHex() { IL1OriginStore originStore = Substitute.For(); - TaikoExtendedEthModule rpc = new TaikoExtendedEthModule(new SyncConfig(), originStore); + TaikoExtendedEthModule rpc = new TaikoExtendedEthModule(new SyncConfig(), originStore, Substitute.For()); int expectedLengthInChars = ValueHash256.Length * 2 + 2; // Create odd length hash values diff --git a/src/Nethermind/Nethermind.Taiko.Test/Tdx/TdxRpcModuleTests.cs b/src/Nethermind/Nethermind.Taiko.Test/Tdx/TdxRpcModuleTests.cs new file mode 100644 index 00000000000..f2dd7f08324 --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko.Test/Tdx/TdxRpcModuleTests.cs @@ -0,0 +1,201 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading.Tasks; +using FluentAssertions; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Find; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.JsonRpc; +using Nethermind.Logging; +using Nethermind.Taiko.Config; +using Nethermind.Taiko.Tdx; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Taiko.Test.Tdx; + +public class TdxRpcModuleTests +{ + private ISurgeConfig _config = null!; + private ITdxService _tdxService = null!; + private IBlockFinder _blockFinder = null!; + private TdxRpcModule _rpcModule = null!; + + [SetUp] + public void Setup() + { + _config = Substitute.For(); + _tdxService = Substitute.For(); + _blockFinder = Substitute.For(); + + _rpcModule = new TdxRpcModule(_config, _tdxService, _blockFinder, LimboLogs.Instance); + } + + [Test] + public async Task SignBlockHeader_returns_error_when_disabled() + { + _config.TdxEnabled.Returns(false); + + ResultWrapper result = await _rpcModule.taiko_tdxSignBlockHeader(new BlockParameter(1)); + + result.Result.ResultType.Should().Be(ResultType.Failure); + result.Result.Error.Should().Contain("not enabled"); + } + + [Test] + public async Task SignBlockHeader_returns_error_when_not_bootstrapped() + { + _config.TdxEnabled.Returns(true); + _tdxService.IsBootstrapped.Returns(false); + + ResultWrapper result = await _rpcModule.taiko_tdxSignBlockHeader(new BlockParameter(1)); + + result.Result.ResultType.Should().Be(ResultType.Failure); + result.Result.Error.Should().Contain("not bootstrapped"); + } + + [Test] + public async Task SignBlockHeader_returns_error_when_block_not_found() + { + _config.TdxEnabled.Returns(true); + _tdxService.IsBootstrapped.Returns(true); + _blockFinder.FindHeader(Arg.Any(), Arg.Any()).Returns((BlockHeader?)null); + + ResultWrapper result = await _rpcModule.taiko_tdxSignBlockHeader(new BlockParameter(1)); + + result.Result.ResultType.Should().Be(ResultType.Failure); + result.Result.Error.Should().Contain("not found"); + } + + [Test] + public async Task SignBlockHeader_returns_signature_when_successful() + { + _config.TdxEnabled.Returns(true); + _tdxService.IsBootstrapped.Returns(true); + + BlockHeader header = Build.A.BlockHeader.WithStateRoot(TestItem.KeccakA).TestObject; + _blockFinder.FindHeader(Arg.Any(), Arg.Any()).Returns(header); + + var signature = new TdxBlockHeaderSignature + { + Signature = new byte[Signature.Size], + BlockHash = header.Hash!, + StateRoot = header.StateRoot!, + Header = new BlockHeaderForRpc(header) + }; + _tdxService.SignBlockHeader(header).Returns(signature); + + ResultWrapper result = await _rpcModule.taiko_tdxSignBlockHeader(new BlockParameter(1)); + + result.Result.ResultType.Should().Be(ResultType.Success); + result.Data.Should().NotBeNull(); + result.Data!.Header.Hash.Should().Be(header.Hash!); + } + + [Test] + public async Task SignBlockHeader_returns_error_on_signing_failure() + { + _config.TdxEnabled.Returns(true); + _tdxService.IsBootstrapped.Returns(true); + + BlockHeader header = Build.A.BlockHeader.TestObject; + _blockFinder.FindHeader(Arg.Any(), Arg.Any()).Returns(header); + _tdxService.SignBlockHeader(header).Returns(_ => throw new TdxException("Signing failed")); + + ResultWrapper result = await _rpcModule.taiko_tdxSignBlockHeader(new BlockParameter(1)); + + result.Result.ResultType.Should().Be(ResultType.Failure); + result.Result.Error.Should().Contain("Signing failed"); + } + + [Test] + public async Task GetTdxGuestInfo_returns_error_when_disabled() + { + _config.TdxEnabled.Returns(false); + + ResultWrapper result = await _rpcModule.taiko_getTdxGuestInfo(); + + result.Result.ResultType.Should().Be(ResultType.Failure); + result.Result.Error.Should().Contain("not enabled"); + } + + [Test] + public async Task GetTdxGuestInfo_returns_error_when_not_bootstrapped() + { + _config.TdxEnabled.Returns(true); + _tdxService.GetGuestInfo().Returns((TdxGuestInfo?)null); + + ResultWrapper result = await _rpcModule.taiko_getTdxGuestInfo(); + + result.Result.ResultType.Should().Be(ResultType.Failure); + result.Result.Error.Should().Contain("not bootstrapped"); + } + + [Test] + public async Task GetTdxGuestInfo_returns_info_when_available() + { + _config.TdxEnabled.Returns(true); + var info = new TdxGuestInfo + { + IssuerType = "test", + PublicKey = "0x1234", + Quote = "abcd", + Nonce = "1234" + }; + _tdxService.GetGuestInfo().Returns(info); + + ResultWrapper result = await _rpcModule.taiko_getTdxGuestInfo(); + + result.Result.ResultType.Should().Be(ResultType.Success); + result.Data.Should().NotBeNull(); + result.Data!.IssuerType.Should().Be("test"); + } + + [Test] + public async Task TdxBootstrap_returns_error_when_disabled() + { + _config.TdxEnabled.Returns(false); + + ResultWrapper result = await _rpcModule.taiko_tdxBootstrap(); + + result.Result.ResultType.Should().Be(ResultType.Failure); + result.Result.Error.Should().Contain("not enabled"); + } + + [Test] + public async Task TdxBootstrap_returns_info_when_successful() + { + _config.TdxEnabled.Returns(true); + var info = new TdxGuestInfo + { + IssuerType = "azure", + PublicKey = "0xabcd", + Quote = "quote", + Nonce = "nonce" + }; + _tdxService.Bootstrap().Returns(info); + + ResultWrapper result = await _rpcModule.taiko_tdxBootstrap(); + + result.Result.ResultType.Should().Be(ResultType.Success); + result.Data.Should().NotBeNull(); + result.Data!.IssuerType.Should().Be("azure"); + } + + [Test] + public async Task TdxBootstrap_returns_error_on_exception() + { + _config.TdxEnabled.Returns(true); + _tdxService.Bootstrap().Returns(_ => throw new TdxException("Connection failed")); + + ResultWrapper result = await _rpcModule.taiko_tdxBootstrap(); + + result.Result.ResultType.Should().Be(ResultType.Failure); + result.Result.Error.Should().Contain("Connection failed"); + } + +} + diff --git a/src/Nethermind/Nethermind.Taiko.Test/Tdx/TdxServiceTests.cs b/src/Nethermind/Nethermind.Taiko.Test/Tdx/TdxServiceTests.cs new file mode 100644 index 00000000000..1eaf9d1eed9 --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko.Test/Tdx/TdxServiceTests.cs @@ -0,0 +1,171 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.IO; +using FluentAssertions; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Logging; +using Nethermind.Taiko.Tdx; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Taiko.Test.Tdx; + +public class TdxServiceTests +{ + private ISurgeTdxConfig _config = null!; + private ITdxsClient _client = null!; + private TdxService _service = null!; + private string _tempDir = null!; + + [SetUp] + public void Setup() + { + _tempDir = Path.Combine(Path.GetTempPath(), $"tdx_test_{Guid.NewGuid():N}"); + Directory.CreateDirectory(_tempDir); + + _config = Substitute.For(); + _config.SocketPath.Returns("/tmp/tdxs.sock"); + _config.ConfigPath.Returns(_tempDir); + + _client = Substitute.For(); + _client.GetMetadata().Returns(new TdxMetadata { IssuerType = "test", Metadata = null }); + _client.Issue(Arg.Any(), Arg.Any()).Returns(new byte[100]); + + _service = new TdxService(_config, _client, LimboLogs.Instance); + } + + [TearDown] + public void TearDown() + { + if (Directory.Exists(_tempDir)) + Directory.Delete(_tempDir, true); + } + + [Test] + public void IsBootstrapped_returns_false_before_bootstrap() + { + _service.IsBootstrapped.Should().BeFalse(); + } + + [Test] + public void Bootstrap_generates_keys_and_quote() + { + TdxGuestInfo info = _service.Bootstrap(); + + info.Should().NotBeNull(); + info.IssuerType.Should().Be("test"); + info.PublicKey.Should().NotBeNullOrEmpty(); + info.Quote.Should().NotBeNullOrEmpty(); + _service.IsBootstrapped.Should().BeTrue(); + } + + [Test] + public void Bootstrap_returns_same_info_when_called_twice() + { + TdxGuestInfo info1 = _service.Bootstrap(); + TdxGuestInfo info2 = _service.Bootstrap(); + + info1.PublicKey.Should().Be(info2.PublicKey); + info1.Quote.Should().Be(info2.Quote); + } + + [Test] + public void GetGuestInfo_returns_null_before_bootstrap() + { + _service.GetGuestInfo().Should().BeNull(); + } + + [Test] + public void GetGuestInfo_returns_info_after_bootstrap() + { + _service.Bootstrap(); + TdxGuestInfo? info = _service.GetGuestInfo(); + + info.Should().NotBeNull(); + info!.PublicKey.Should().NotBeNullOrEmpty(); + } + + [Test] + public void SignBlockHeader_throws_when_not_bootstrapped() + { + Block block = Build.A.Block.WithStateRoot(TestItem.KeccakA).TestObject; + + Action act = () => _service.SignBlockHeader(block.Header); + + act.Should().Throw().WithMessage("*not bootstrapped*"); + } + + [Test] + public void SignBlockHeader_generates_valid_signature() + { + _service.Bootstrap(); + Block block = Build.A.Block + .WithNumber(100) + .WithStateRoot(TestItem.KeccakA) + .WithParent(Build.A.BlockHeader.TestObject) + .TestObject; + + TdxBlockHeaderSignature signature = _service.SignBlockHeader(block.Header); + + signature.Should().NotBeNull(); + signature.Signature.Should().HaveCount(Signature.Size); + signature.BlockHash.Should().Be(block.Hash!); + signature.StateRoot.Should().Be(TestItem.KeccakA); + signature.Header.Hash.Should().Be(block.Hash!); + } + + [Test] + public void Bootstrap_persists_and_reloads_data() + { + TdxGuestInfo info1 = _service.Bootstrap(); + + var newService = new TdxService(_config, _client, LimboLogs.Instance); + + newService.IsBootstrapped.Should().BeTrue(); + TdxGuestInfo? info2 = newService.GetGuestInfo(); + info2.Should().NotBeNull(); + info2!.PublicKey.Should().Be(info1.PublicKey); + } + + [Test] + public void Signature_v_value_is_27_or_28() + { + _service.Bootstrap(); + Block block = Build.A.Block.WithNumber(1).WithStateRoot(TestItem.KeccakA).TestObject; + + TdxBlockHeaderSignature signature = _service.SignBlockHeader(block.Header); + + byte v = signature.Signature[Signature.Size - 1]; + v.Should().BeOneOf((byte)27, (byte)28); + } + + [Test] + public void New_service_bootstrap_loads_existing_data() + { + TdxGuestInfo info1 = _service.Bootstrap(); + + var newService = new TdxService(_config, _client, LimboLogs.Instance); + TdxGuestInfo info2 = newService.Bootstrap(); + + info2.PublicKey.Should().Be(info1.PublicKey); + info2.Quote.Should().Be(info1.Quote); + } + + [Test] + public void New_service_sign_uses_persisted_keys() + { + _service.Bootstrap(); + Block block = Build.A.Block.WithNumber(1).WithStateRoot(TestItem.KeccakA).TestObject; + TdxBlockHeaderSignature signature1 = _service.SignBlockHeader(block.Header); + + var newService = new TdxService(_config, _client, LimboLogs.Instance); + newService.Bootstrap(); + TdxBlockHeaderSignature signature2 = newService.SignBlockHeader(block.Header); + + signature2.Signature.Should().BeEquivalentTo(signature1.Signature); + } +} diff --git a/src/Nethermind/Nethermind.Taiko.Test/TransactionProcessorTests.cs b/src/Nethermind/Nethermind.Taiko.Test/TransactionProcessorTests.cs index 5c69b7240f8..4272e260745 100644 --- a/src/Nethermind/Nethermind.Taiko.Test/TransactionProcessorTests.cs +++ b/src/Nethermind/Nethermind.Taiko.Test/TransactionProcessorTests.cs @@ -51,7 +51,7 @@ public void Setup() _stateProvider.CommitTree(0); EthereumCodeInfoRepository codeInfoRepository = new(_stateProvider); - VirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); + EthereumVirtualMachine virtualMachine = new(new TestBlockhashProvider(_specProvider), _specProvider, LimboLogs.Instance); _transactionProcessor = new TaikoTransactionProcessor(BlobBaseFeeCalculator.Instance, _specProvider, _stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); } @@ -62,10 +62,10 @@ public void TearDown() } [TestCaseSource(nameof(FeesDistributionTests))] - public void Fees_distributed_correctly(byte basefeeSharingPctg, UInt256 goesToTreasury, UInt256 goesToBeneficiary, ulong gasPrice) + public void Fees_distributed_correctly(byte basefeeSharingPct, UInt256 goesToTreasury, UInt256 goesToBeneficiary, ulong gasPrice) { long gasLimit = 100000; - Address benefeciaryAddress = TestItem.AddressC; + Address beneficiaryAddress = TestItem.AddressC; Transaction tx = Build.A.Transaction .WithValue(1) @@ -74,12 +74,12 @@ public void Fees_distributed_correctly(byte basefeeSharingPctg, UInt256 goesToTr .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyA).TestObject; var extraData = new byte[32]; - extraData[31] = basefeeSharingPctg; + extraData[31] = basefeeSharingPct; Block block = Build.A.Block.WithNumber(1).WithTransactions(tx) .WithBaseFeePerGas(gasPrice) .WithExtraData(extraData) - .WithBeneficiary(benefeciaryAddress).WithGasLimit(gasLimit).TestObject; + .WithBeneficiary(beneficiaryAddress).WithGasLimit(gasLimit).TestObject; _transactionProcessor!.SetBlockExecutionContext(new BlockExecutionContext(block.Header, _specProvider.GetSpec(block.Header))); _transactionProcessor!.Execute(tx, NullTxTracer.Instance); @@ -87,7 +87,7 @@ public void Fees_distributed_correctly(byte basefeeSharingPctg, UInt256 goesToTr Assert.Multiple(() => { Assert.That(_stateProvider!.GetBalance(_spec.FeeCollector!), Is.EqualTo(goesToTreasury)); - Assert.That(_stateProvider.GetBalance(benefeciaryAddress), Is.EqualTo(goesToBeneficiary)); + Assert.That(_stateProvider.GetBalance(beneficiaryAddress), Is.EqualTo(goesToBeneficiary)); }); } @@ -95,8 +95,8 @@ public static IEnumerable FeesDistributionTests { get { - static object[] Typed(int basefeeSharingPctg, ulong goesToTreasury, ulong goesToBeneficiary, ulong gasPrice) - => [(byte)basefeeSharingPctg, (UInt256)goesToTreasury, (UInt256)goesToBeneficiary, gasPrice]; + static object[] Typed(int basefeeSharingPct, ulong goesToTreasury, ulong goesToBeneficiary, ulong gasPrice) + => [(byte)basefeeSharingPct, (UInt256)goesToTreasury, (UInt256)goesToBeneficiary, gasPrice]; yield return new TestCaseData(Typed(0, 21000, 0, 1)) { TestName = "All goes to treasury" }; yield return new TestCaseData(Typed(100, 0, 21000, 1)) { TestName = "All goes to beneficiary" }; @@ -176,7 +176,7 @@ public void Check_fees_with_fee_collector_destroy_coinbase_taiko(bool isOntakeEn { _spec.FeeCollector = TestItem.AddressC; _spec.IsOntakeEnabled = isOntakeEnabled; - byte defaultBasefeeSharingPctg = 25; + byte defaultBaseFeeSharingPct = 25; _stateProvider!.CreateAccount(TestItem.AddressB, 100.Ether()); @@ -194,7 +194,7 @@ public void Check_fees_with_fee_collector_destroy_coinbase_taiko(bool isOntakeEn .SignedAndResolved(_ethereumEcdsa, TestItem.PrivateKeyB).TestObject; var extraData = new byte[32]; - extraData[31] = defaultBasefeeSharingPctg; + extraData[31] = defaultBaseFeeSharingPct; Block block = Build.A.Block.WithNumber(1) .WithBeneficiary(SelfDestructAddress) @@ -220,7 +220,7 @@ public void Check_fees_with_fee_collector_destroy_coinbase_taiko(bool isOntakeEn UInt256 expectedBaseFees = tracer.BurntFees; if (isOntakeEnabled) { - expectedBaseFees -= expectedBaseFees * defaultBasefeeSharingPctg / 100; + expectedBaseFees -= expectedBaseFees * defaultBaseFeeSharingPct / 100; } receivedBaseFees.Should().Be(expectedBaseFees, "Burnt fees should be paid to treasury"); diff --git a/src/Nethermind/Nethermind.Taiko.Test/TxPoolContentListsTests.cs b/src/Nethermind/Nethermind.Taiko.Test/TxPoolContentListsTests.cs index 03d22dcb609..0db2219b124 100644 --- a/src/Nethermind/Nethermind.Taiko.Test/TxPoolContentListsTests.cs +++ b/src/Nethermind/Nethermind.Taiko.Test/TxPoolContentListsTests.cs @@ -22,7 +22,6 @@ using Nethermind.Blockchain; using Nethermind.Merge.Plugin.Data; using Nethermind.Merge.Plugin.Handlers; -using Nethermind.Consensus.Processing; using Nethermind.Facade.Eth.RpcTransaction; using Nethermind.Serialization.Rlp; @@ -70,33 +69,7 @@ public int[][] Test_TxLists_AreConstructed( IShareableTxProcessorSource shareableTxProcessor = Substitute.For(); shareableTxProcessor.Build(Arg.Any()).Returns(scope); - TaikoEngineRpcModule taikoAuthRpcModule = new( - Substitute.For>(), - Substitute.For>(), - Substitute.For>(), - Substitute.For>(), - Substitute.For>(), - Substitute.For>(), - Substitute.For>(), - Substitute.For(), - Substitute.For, IEnumerable>>(), - Substitute.For(), - Substitute.For>(), - Substitute.For, IEnumerable>>(), - Substitute.For>>(), - Substitute.For?>>(), - Substitute.For>>(), - Substitute.For?>>(), - Substitute.For(), - Substitute.For(), - null!, - Substitute.For(), - txPool, - blockFinder, - shareableTxProcessor, - TxDecoder.Instance, - Substitute.For() - ); + TaikoEngineRpcModule taikoAuthRpcModule = CreateRpcModule(txPool, blockFinder, shareableTxProcessor); ResultWrapper result = taikoAuthRpcModule.taikoAuth_txPoolContent( Address.Zero, @@ -159,4 +132,84 @@ static object[] MakeTestData(Dictionary txs, int[] localAccounts, ul }; } } + + [TestCase(10, 0)] + [TestCase(0, 1)] + public void MaxGasLimitRatio_FiltersHighGasLimitTransactions(int maxGasLimitRatio, int expectedTxCount) + { + Transaction tx = Build.A.Transaction + .WithType(TxType.EIP1559) + .WithMaxFeePerGas(100) + .WithGasLimit(2_100_000) // 100x the actual gas used for this transaction + .WithNonce(1) + .SignedAndResolved() + .TestObject; + + ITxPool txPool = Substitute.For(); + txPool.GetPendingTransactionsBySender().Returns(new Dictionary + { + { tx.SenderAddress!, [tx] } + }); + + IBlockFinder blockFinder = Substitute.For(); + Block block = Build.A.Block.WithHeader(Build.A.BlockHeader.WithGasLimit(30_000_000).TestObject).TestObject; + blockFinder.Head.Returns(block); + + ITransactionProcessor transactionProcessor = Substitute.For(); + transactionProcessor.Execute(Arg.Any(), Arg.Any()) + .Returns(call => + { + call.Arg().SpentGas = 21_000; + return TransactionResult.Ok; + }); + + IReadOnlyTxProcessingScope scope = Substitute.For(); + scope.TransactionProcessor.Returns(transactionProcessor); + + IShareableTxProcessorSource shareableTxProcessor = Substitute.For(); + shareableTxProcessor.Build(Arg.Any()).Returns(scope); + + TaikoEngineRpcModule rpcModule = CreateRpcModule(txPool, blockFinder, shareableTxProcessor, + new Config.SurgeConfig { MaxGasLimitRatio = maxGasLimitRatio }); + + ResultWrapper result = rpcModule.taikoAuth_txPoolContent( + Address.Zero, 1, 30_000_000, 100_000, null, 10); + + Assert.That(result.Result, Is.EqualTo(Result.Success)); + int totalTxCount = result.Data?.Sum(list => list.TxList.Length) ?? 0; + Assert.That(totalTxCount, Is.EqualTo(expectedTxCount)); + } + + private static TaikoEngineRpcModule CreateRpcModule( + ITxPool txPool, + IBlockFinder blockFinder, + IShareableTxProcessorSource shareableTxProcessor, + Config.ISurgeConfig? surgeConfig = null) => new( + Substitute.For>(), + Substitute.For>(), + Substitute.For>(), + Substitute.For>(), + Substitute.For>(), + Substitute.For>(), + Substitute.For>(), + Substitute.For(), + Substitute.For, IEnumerable>>(), + Substitute.For(), + Substitute.For>(), + Substitute.For, IEnumerable>>(), + Substitute.For>>(), + Substitute.For?>>(), + Substitute.For>>(), + Substitute.For?>>(), + Substitute.For(), + Substitute.For(), + null!, + Substitute.For(), + txPool, + blockFinder, + shareableTxProcessor, + TxDecoder.Instance, + Substitute.For(), + surgeConfig ?? new Config.SurgeConfig() + ); } diff --git a/src/Nethermind/Nethermind.Taiko/BlockMetadata.cs b/src/Nethermind/Nethermind.Taiko/BlockMetadata.cs index 30bb9d95baf..aa0e44fe736 100644 --- a/src/Nethermind/Nethermind.Taiko/BlockMetadata.cs +++ b/src/Nethermind/Nethermind.Taiko/BlockMetadata.cs @@ -4,6 +4,7 @@ using System.Text.Json.Serialization; using Nethermind.Core; using Nethermind.Core.Crypto; +using Nethermind.Int256; using Nethermind.Serialization.Json; namespace Nethermind.Taiko; @@ -17,6 +18,8 @@ public class BlockMetadata public required Hash256 MixHash { get; set; } + public UInt256? BatchID { get; set; } + public required byte[] TxList { get; set; } [JsonConverter(typeof(Base64Converter))] diff --git a/src/Nethermind/Nethermind.Taiko/Config/ISurgeConfig.cs b/src/Nethermind/Nethermind.Taiko/Config/ISurgeConfig.cs index a326d9e1356..73ce005b672 100644 --- a/src/Nethermind/Nethermind.Taiko/Config/ISurgeConfig.cs +++ b/src/Nethermind/Nethermind.Taiko/Config/ISurgeConfig.cs @@ -45,4 +45,10 @@ public interface ISurgeConfig : IConfig [ConfigItem(Description = "Maximum time in seconds to use cached gas price estimates before forcing a refresh.", DefaultValue = "12")] int GasPriceRefreshTimeoutSeconds { get; set; } + + [ConfigItem(Description = "Filter transactions exceeding the max allowed ratio of gas limit to the actual gas used (e.g. 1, 2 etc.). Set to 0 to disable.", DefaultValue = "0")] + int MaxGasLimitRatio { get; set; } + + [ConfigItem(Description = "Enable TDX attestation support.", DefaultValue = "false")] + bool TdxEnabled { get; set; } } diff --git a/src/Nethermind/Nethermind.Taiko/Config/SurgeConfig.cs b/src/Nethermind/Nethermind.Taiko/Config/SurgeConfig.cs index f9946e8abc0..ee48b4d0d8d 100644 --- a/src/Nethermind/Nethermind.Taiko/Config/SurgeConfig.cs +++ b/src/Nethermind/Nethermind.Taiko/Config/SurgeConfig.cs @@ -18,4 +18,6 @@ public class SurgeConfig : ISurgeConfig public int SharingPercentage { get; set; } = 75; public int BoostBaseFeePercentage { get; set; } = 5; public int GasPriceRefreshTimeoutSeconds { get; set; } = 12; + public int MaxGasLimitRatio { get; set; } = 0; + public bool TdxEnabled { get; set; } = false; } diff --git a/src/Nethermind/Nethermind.Taiko/IL1OriginStore.cs b/src/Nethermind/Nethermind.Taiko/IL1OriginStore.cs index 63a3f0b0c07..b847f650e07 100644 --- a/src/Nethermind/Nethermind.Taiko/IL1OriginStore.cs +++ b/src/Nethermind/Nethermind.Taiko/IL1OriginStore.cs @@ -12,4 +12,7 @@ public interface IL1OriginStore UInt256? ReadHeadL1Origin(); void WriteHeadL1Origin(UInt256 blockId); + + UInt256? ReadBatchToLastBlockID(UInt256 batchId); + void WriteBatchToLastBlockID(UInt256 batchId, UInt256 blockId); } diff --git a/src/Nethermind/Nethermind.Taiko/L1Origin.cs b/src/Nethermind/Nethermind.Taiko/L1Origin.cs index 4f9a64aba20..ac0d048e3a9 100644 --- a/src/Nethermind/Nethermind.Taiko/L1Origin.cs +++ b/src/Nethermind/Nethermind.Taiko/L1Origin.cs @@ -6,18 +6,26 @@ namespace Nethermind.Taiko; -public class L1Origin(UInt256 blockId, ValueHash256? l2BlockHash, long l1BlockHeight, ValueHash256 l1BlockHash, int[]? buildPayloadArgsId) +public class L1Origin(UInt256 blockId, + ValueHash256? l2BlockHash, + long? l1BlockHeight, + ValueHash256 l1BlockHash, + int[]? buildPayloadArgsId, + bool isForcedInclusion = false, + int[]? signature = null) { public UInt256 BlockId { get; set; } = blockId; public ValueHash256? L2BlockHash { get; set; } = l2BlockHash; - public long L1BlockHeight { get; set; } = l1BlockHeight; + public long? L1BlockHeight { get; set; } = l1BlockHeight; public ValueHash256 L1BlockHash { get; set; } = l1BlockHash; - // Taiko uses int-like serializer + // Taiko uses int-like serializer (Go's default encoding for byte arrays) public int[]? BuildPayloadArgsId { get; set; } = buildPayloadArgsId; + public bool IsForcedInclusion { get; set; } = isForcedInclusion; + public int[]? Signature { get; set; } = signature; /// /// IsPreconfBlock returns true if the L1Origin is for a preconfirmation block. /// - public bool IsPreconfBlock => L1BlockHeight == 0; + public bool IsPreconfBlock => L1BlockHeight == 0 || L1BlockHeight == null; } diff --git a/src/Nethermind/Nethermind.Taiko/L1OriginDecoder.cs b/src/Nethermind/Nethermind.Taiko/L1OriginDecoder.cs index 4d5fb87948a..9e57c56f940 100644 --- a/src/Nethermind/Nethermind.Taiko/L1OriginDecoder.cs +++ b/src/Nethermind/Nethermind.Taiko/L1OriginDecoder.cs @@ -11,6 +11,7 @@ namespace Nethermind.Taiko; public sealed class L1OriginDecoder : RlpStreamDecoder { const int BuildPayloadArgsIdLength = 8; + internal const int SignatureLength = 65; protected override L1Origin DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpBehaviors = RlpBehaviors.None) { @@ -19,11 +20,21 @@ protected override L1Origin DecodeInternal(RlpStream rlpStream, RlpBehaviors rlp UInt256 blockId = rlpStream.DecodeUInt256(); Hash256? l2BlockHash = rlpStream.DecodeKeccak(); - var l1BlockHeight = rlpStream.DecodeLong(); + long? l1BlockHeight = rlpStream.DecodeLong(); Hash256 l1BlockHash = rlpStream.DecodeKeccak() ?? throw new RlpException("L1BlockHash is null"); - int[]? buildPayloadArgsId = itemsCount == 4 ? null : Array.ConvertAll(rlpStream.DecodeByteArray(), Convert.ToInt32); - return new(blockId, l2BlockHash, l1BlockHeight, l1BlockHash, buildPayloadArgsId); + int[]? buildPayloadArgsId = null; + + if (itemsCount >= 5) + { + byte[] buildPayloadBytes = rlpStream.DecodeByteArray(); + buildPayloadArgsId = buildPayloadBytes.Length > 0 ? Array.ConvertAll(buildPayloadBytes, Convert.ToInt32) : null; + } + + bool isForcedInclusion = itemsCount >= 6 && rlpStream.DecodeBool(); + int[]? signature = itemsCount >= 7 ? Array.ConvertAll(rlpStream.DecodeByteArray(), Convert.ToInt32) : null; + + return new(blockId, l2BlockHash, l1BlockHeight, l1BlockHash, buildPayloadArgsId, isForcedInclusion, signature); } public Rlp Encode(L1Origin? item, RlpBehaviors rlpBehaviors = RlpBehaviors.None) @@ -42,27 +53,68 @@ public override void Encode(RlpStream stream, L1Origin item, RlpBehaviors rlpBeh stream.Encode(item.BlockId); stream.Encode(item.L2BlockHash); - stream.Encode(item.L1BlockHeight); + stream.Encode(item.L1BlockHeight ?? 0); stream.Encode(item.L1BlockHash); + + // If all optional remaining fields are missing, nothing to encode + if (item.BuildPayloadArgsId is null && !item.IsForcedInclusion && item.Signature is null) + return; + + // Encode buildPayloadArgsId, even if empty, to maintain field order if (item.BuildPayloadArgsId is not null) { if (item.BuildPayloadArgsId.Length is not BuildPayloadArgsIdLength) { throw new RlpException($"{nameof(item.BuildPayloadArgsId)} should be exactly {BuildPayloadArgsIdLength}"); } - stream.Encode(Array.ConvertAll(item.BuildPayloadArgsId, Convert.ToByte)); } + else + { + stream.Encode(Array.Empty()); + } + + // If neither IsForcedInclusion nor Signature are present, return + if (!item.IsForcedInclusion && item.Signature is null) + return; + + stream.Encode(item.IsForcedInclusion); + + if (item.Signature is not null) + { + if (item.Signature.Length != SignatureLength) + { + throw new RlpException($"{nameof(item.Signature)} should be exactly {SignatureLength}"); + } + + stream.Encode(Array.ConvertAll(item.Signature, Convert.ToByte)); + } } public override int GetLength(L1Origin item, RlpBehaviors rlpBehaviors) { + int buildPayloadLength = 0; + if (item.BuildPayloadArgsId is not null || item.IsForcedInclusion || item.Signature is not null) + { + buildPayloadLength = item.BuildPayloadArgsId is null + ? Rlp.LengthOf(Array.Empty()) + : Rlp.LengthOfByteString(BuildPayloadArgsIdLength, 0); + } + + int isForcedInclusionLength = 0; + if (item.IsForcedInclusion || item.Signature is not null) + { + isForcedInclusionLength = Rlp.LengthOf(item.IsForcedInclusion); + } + return Rlp.LengthOfSequence( Rlp.LengthOf(item.BlockId) + Rlp.LengthOf(item.L2BlockHash) - + Rlp.LengthOf(item.L1BlockHeight) + + Rlp.LengthOf(item.L1BlockHeight ?? 0) + Rlp.LengthOf(item.L1BlockHash) - + (item.BuildPayloadArgsId is null ? 0 : Rlp.LengthOfByteString(BuildPayloadArgsIdLength, 0)) + + buildPayloadLength + + isForcedInclusionLength + + (item.Signature is null ? 0 : Rlp.LengthOfByteString(SignatureLength, 0)) ); } } diff --git a/src/Nethermind/Nethermind.Taiko/L1OriginStore.cs b/src/Nethermind/Nethermind.Taiko/L1OriginStore.cs index 2ab8778f43f..f468c057853 100644 --- a/src/Nethermind/Nethermind.Taiko/L1OriginStore.cs +++ b/src/Nethermind/Nethermind.Taiko/L1OriginStore.cs @@ -16,20 +16,37 @@ public class L1OriginStore([KeyFilter(L1OriginStore.L1OriginDbName)] IDb db, IRl { public const string L1OriginDbName = "L1Origin"; private const int UInt256BytesLength = 32; - private static readonly byte[] L1OriginHeadKey = UInt256.MaxValue.ToBigEndian(); + private const int KeyBytesLength = UInt256BytesLength + 1; + private const byte L1OriginPrefix = 0x00; + private const byte BatchToBlockPrefix = 0x01; + private const byte L1OriginHeadPrefix = 0xFF; + private static readonly byte[] L1OriginHeadKey = [L1OriginHeadPrefix]; + + private static void CreateL1OriginKey(UInt256 blockId, Span keyBytes) + { + keyBytes[0] = L1OriginPrefix; + blockId.ToBigEndian(keyBytes[1..]); + } + + private static void CreateBatchToBlockKey(UInt256 batchId, Span keyBytes) + { + keyBytes[0] = BatchToBlockPrefix; + batchId.ToBigEndian(keyBytes[1..]); + } public L1Origin? ReadL1Origin(UInt256 blockId) { - Span keyBytes = stackalloc byte[UInt256BytesLength]; - blockId.ToBigEndian(keyBytes); + Span keyBytes = stackalloc byte[KeyBytesLength]; + CreateL1OriginKey(blockId, keyBytes); - return db.Get(new ValueHash256(keyBytes), decoder); + byte[]? data = db.Get(keyBytes); + return data is null ? null : decoder.Decode(new RlpStream(data)); } public void WriteL1Origin(UInt256 blockId, L1Origin l1Origin) { - Span key = stackalloc byte[UInt256BytesLength]; - blockId.ToBigEndian(key); + Span key = stackalloc byte[KeyBytesLength]; + CreateL1OriginKey(blockId, key); int encodedL1OriginLength = decoder.GetLength(l1Origin, RlpBehaviors.None); byte[] buffer = ArrayPool.Shared.Rent(encodedL1OriginLength); @@ -38,7 +55,7 @@ public void WriteL1Origin(UInt256 blockId, L1Origin l1Origin) { RlpStream stream = new(buffer); decoder.Encode(stream, l1Origin); - db.Set(new ValueHash256(key), buffer.AsSpan(0, encodedL1OriginLength)); + db.PutSpan(key, buffer.AsSpan(0, encodedL1OriginLength)); } finally { @@ -60,6 +77,29 @@ public void WriteHeadL1Origin(UInt256 blockId) Span blockIdBytes = stackalloc byte[UInt256BytesLength]; blockId.ToBigEndian(blockIdBytes); - db.Set(new(L1OriginHeadKey.AsSpan()), blockIdBytes); + db.PutSpan(L1OriginHeadKey, blockIdBytes); + } + + public UInt256? ReadBatchToLastBlockID(UInt256 batchId) + { + Span keyBytes = stackalloc byte[KeyBytesLength]; + CreateBatchToBlockKey(batchId, keyBytes); + + return db.Get(keyBytes) switch + { + null => null, + byte[] bytes => new UInt256(bytes, isBigEndian: true) + }; + } + + public void WriteBatchToLastBlockID(UInt256 batchId, UInt256 blockId) + { + Span key = stackalloc byte[KeyBytesLength]; + CreateBatchToBlockKey(batchId, key); + + Span blockIdBytes = stackalloc byte[UInt256BytesLength]; + blockId.ToBigEndian(blockIdBytes); + + db.PutSpan(key, blockIdBytes); } } diff --git a/src/Nethermind/Nethermind.Taiko/Rpc/ITaikoEngineRpcModule.cs b/src/Nethermind/Nethermind.Taiko/Rpc/ITaikoEngineRpcModule.cs index 9bb243d5810..e1294830da6 100644 --- a/src/Nethermind/Nethermind.Taiko/Rpc/ITaikoEngineRpcModule.cs +++ b/src/Nethermind/Nethermind.Taiko/Rpc/ITaikoEngineRpcModule.cs @@ -77,4 +77,30 @@ Task> engine_newPayloadV3(TaikoExecutionPayloadV3 IsSharable = true, IsImplemented = true)] ResultWrapper taikoAuth_updateL1Origin(L1Origin l1Origin); + + [JsonRpcMethod( + Description = "Sets the mapping from batch ID to the last block ID in this batch.", + IsSharable = true, + IsImplemented = true)] + ResultWrapper taikoAuth_setBatchToLastBlock(UInt256 batchId, UInt256 blockId); + + [JsonRpcMethod( + Description = "Sets the L1 origin signature for the given block ID.", + IsSharable = true, + IsImplemented = true)] + ResultWrapper taikoAuth_setL1OriginSignature(UInt256 blockId, int[] signature); + + /// + /// Clears txpool state (hash cache, account cache, pending transactions) after a chain reorg. + /// This is specifically designed for Taiko integration tests where the chain is reset to a base block. + /// After a reorg, stale txpool caches would reject transaction resubmissions with "already known" or "nonce too low". + /// Pending transactions must also be cleared because tests resubmit transactions with the same hash/nonce, + /// which would be rejected as "ReplacementNotAllowed" if they remain in the pool. + /// + [JsonRpcMethod( + Description = "Clears txpool state after chain reorg for testing/debugging purposes. " + + "Returns true on success.", + IsSharable = true, + IsImplemented = true)] + ResultWrapper taikoDebug_clearTxPoolForReorg(); } diff --git a/src/Nethermind/Nethermind.Taiko/Rpc/ITaikoExtendedEthRpcModule.cs b/src/Nethermind/Nethermind.Taiko/Rpc/ITaikoExtendedEthRpcModule.cs index 77d856d2d10..1c02cf2645e 100644 --- a/src/Nethermind/Nethermind.Taiko/Rpc/ITaikoExtendedEthRpcModule.cs +++ b/src/Nethermind/Nethermind.Taiko/Rpc/ITaikoExtendedEthRpcModule.cs @@ -28,4 +28,16 @@ public interface ITaikoExtendedEthRpcModule : IRpcModule IsSharable = true, IsImplemented = true)] Task> taiko_getSyncMode(); + + [JsonRpcMethod( + Description = "Returns the L1 origin of the last block for the given batch.", + IsSharable = true, + IsImplemented = true)] + Task> taiko_lastL1OriginByBatchID(UInt256 batchId); + + [JsonRpcMethod( + Description = "Returns the ID of the last block for the given batch.", + IsSharable = true, + IsImplemented = true)] + Task> taiko_lastBlockIDByBatchID(UInt256 batchId); } diff --git a/src/Nethermind/Nethermind.Taiko/Rpc/PreBuiltTxList.cs b/src/Nethermind/Nethermind.Taiko/Rpc/PreBuiltTxList.cs index dd1ce740099..5d4a2dfdf5e 100644 --- a/src/Nethermind/Nethermind.Taiko/Rpc/PreBuiltTxList.cs +++ b/src/Nethermind/Nethermind.Taiko/Rpc/PreBuiltTxList.cs @@ -9,11 +9,14 @@ namespace Nethermind.Taiko.Rpc; public sealed class PreBuiltTxList(TransactionForRpc[] transactions, ulong estimatedGasUsed, ulong bytesLength) { + [JsonPropertyName("TxList")] public TransactionForRpc[] TxList { get; } = transactions; + [JsonPropertyName("EstimatedGasUsed")] [JsonConverter(typeof(ULongRawJsonConverter))] public ulong EstimatedGasUsed { get; } = estimatedGasUsed; + [JsonPropertyName("BytesLength")] [JsonConverter(typeof(ULongRawJsonConverter))] public ulong BytesLength { get; } = bytesLength; } diff --git a/src/Nethermind/Nethermind.Taiko/Rpc/SurgeGasPriceOracle.cs b/src/Nethermind/Nethermind.Taiko/Rpc/SurgeGasPriceOracle.cs index 691a1123374..00319c63307 100644 --- a/src/Nethermind/Nethermind.Taiko/Rpc/SurgeGasPriceOracle.cs +++ b/src/Nethermind/Nethermind.Taiko/Rpc/SurgeGasPriceOracle.cs @@ -110,9 +110,16 @@ public override async ValueTask GetGasPriceEstimate() Math.Max(averageGasUsage, _surgeConfig.L2GasPerL2Batch); // Adjust the gas price estimate with the config values. - UInt256 adjustedGasPriceEstimate = gasPriceEstimate + gasPriceEstimate * (UInt256)_surgeConfig.BoostBaseFeePercentage / 100; - adjustedGasPriceEstimate = adjustedGasPriceEstimate * 100 / (UInt256)_surgeConfig.SharingPercentage; - + UInt256 adjustedGasPriceEstimate; + if (_surgeConfig.SharingPercentage == 0) + { + adjustedGasPriceEstimate = default; + } + else + { + adjustedGasPriceEstimate = gasPriceEstimate + gasPriceEstimate * (UInt256)_surgeConfig.BoostBaseFeePercentage / 100; + adjustedGasPriceEstimate = adjustedGasPriceEstimate * 100 / (UInt256)_surgeConfig.SharingPercentage; + } // Update the cache and timestamp _gasPriceEstimation.Set(headBlockHash, adjustedGasPriceEstimate); _lastGasPriceCalculation = DateTime.UtcNow; diff --git a/src/Nethermind/Nethermind.Taiko/Rpc/TaikoEngineRpcModule.cs b/src/Nethermind/Nethermind.Taiko/Rpc/TaikoEngineRpcModule.cs index c281f7b6ba5..87ddbfffc31 100644 --- a/src/Nethermind/Nethermind.Taiko/Rpc/TaikoEngineRpcModule.cs +++ b/src/Nethermind/Nethermind.Taiko/Rpc/TaikoEngineRpcModule.cs @@ -11,7 +11,6 @@ using Nethermind.Api; using Nethermind.Blockchain; using Nethermind.Blockchain.Find; -using Nethermind.Consensus.Processing; using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; @@ -31,6 +30,7 @@ using Nethermind.Serialization.Rlp; using Nethermind.TxPool; using Nethermind.Evm.State; +using Nethermind.Taiko.Config; namespace Nethermind.Taiko.Rpc; @@ -47,7 +47,7 @@ public class TaikoEngineRpcModule(IAsyncHandler getPa IHandler transitionConfigurationHandler, IHandler, IEnumerable> capabilitiesHandler, IAsyncHandler> getBlobsHandler, - IAsyncHandler?> getBlobsHandlerV2, + IAsyncHandler?> getBlobsHandlerV2, IAsyncHandler> getBALsByHashV1Handler, IAsyncHandler<(long, long), IEnumerable?> getBALsByRangeV1Handler, IEngineRequestsTracker engineRequestsTracker, @@ -58,7 +58,8 @@ public class TaikoEngineRpcModule(IAsyncHandler getPa IBlockFinder blockFinder, IShareableTxProcessorSource txProcessorSource, IRlpStreamDecoder txDecoder, - IL1OriginStore l1OriginStore) : + IL1OriginStore l1OriginStore, + ISurgeConfig surgeConfig) : EngineRpcModule(getPayloadHandlerV1, getPayloadHandlerV2, getPayloadHandlerV3, @@ -117,7 +118,9 @@ public Task> engine_newPayloadV3(TaikoExecutionPa public ResultWrapper taikoAuth_txPoolContentWithMinTip(Address beneficiary, UInt256 baseFee, ulong blockMaxGasLimit, ulong maxBytesPerTxList, Address[]? localAccounts, int maxTransactionsLists, ulong minTip) { - IEnumerable> pendingTxs = txPool.GetPendingTransactionsBySender(); + // Fetch all the pending transactions except transactions from the GoldenTouchAccount + IEnumerable> pendingTxs = txPool.GetPendingTransactionsBySender() + .Where(txs => !txs.Key.Value.Equals(TaikoBlockValidator.GoldenTouchAccount)); if (localAccounts is not null) { @@ -213,6 +216,14 @@ void CommitAndDisposeBatch(Batch batch) while (i < txSource.Length && txSource[i].SenderAddress == tx.SenderAddress) i++; continue; } + + // For Surge, filter out any transaction with very high gas limit + if (surgeConfig.MaxGasLimitRatio > 0 && tx.GasLimit > tx.SpentGas * surgeConfig.MaxGasLimitRatio) + { + worldState.Restore(snapshot); + while (i < txSource.Length && txSource[i].SenderAddress == tx.SenderAddress) i++; + continue; + } } catch { @@ -352,4 +363,31 @@ public ResultWrapper taikoAuth_updateL1Origin(L1Origin l1Origin) l1OriginStore.WriteL1Origin(l1Origin.BlockId, l1Origin); return ResultWrapper.Success(l1Origin); } + + public ResultWrapper taikoAuth_setBatchToLastBlock(UInt256 batchId, UInt256 blockId) + { + l1OriginStore.WriteBatchToLastBlockID(batchId, blockId); + return ResultWrapper.Success(batchId); + } + + public ResultWrapper taikoAuth_setL1OriginSignature(UInt256 blockId, int[] signature) + { + L1Origin? l1Origin = l1OriginStore.ReadL1Origin(blockId); + if (l1Origin is null) + { + return ResultWrapper.Fail($"L1 origin not found for block ID {blockId}"); + } + + l1Origin.Signature = signature; + l1OriginStore.WriteL1Origin(blockId, l1Origin); + + return ResultWrapper.Success(l1Origin); + } + + /// + public ResultWrapper taikoDebug_clearTxPoolForReorg() + { + txPool.ResetTxPoolState(); + return ResultWrapper.Success(true); + } } diff --git a/src/Nethermind/Nethermind.Taiko/Rpc/TaikoExtendedEthModule.cs b/src/Nethermind/Nethermind.Taiko/Rpc/TaikoExtendedEthModule.cs index d1348b4a265..db9b0c5f275 100644 --- a/src/Nethermind/Nethermind.Taiko/Rpc/TaikoExtendedEthModule.cs +++ b/src/Nethermind/Nethermind.Taiko/Rpc/TaikoExtendedEthModule.cs @@ -1,18 +1,24 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Threading.Tasks; +using Nethermind.Blockchain.Find; using Nethermind.Blockchain.Synchronization; +using Nethermind.Core; using Nethermind.Int256; using Nethermind.JsonRpc; +using static Nethermind.Taiko.TaikoBlockValidator; namespace Nethermind.Taiko.Rpc; public class TaikoExtendedEthModule( ISyncConfig syncConfig, - IL1OriginStore l1OriginStore) : ITaikoExtendedEthRpcModule + IL1OriginStore l1OriginStore, + IBlockFinder blockFinder) : ITaikoExtendedEthRpcModule { - private static readonly ResultWrapper NotFound = ResultWrapper.Fail("not found"); + private static readonly ResultWrapper L1OriginNotFound = ResultWrapper.Fail("not found"); + private static readonly ResultWrapper BlockIdNotFound = ResultWrapper.Fail("not found"); public Task> taiko_getSyncMode() => ResultWrapper.Success(syncConfig switch { @@ -25,18 +31,89 @@ public class TaikoExtendedEthModule( UInt256? head = l1OriginStore.ReadHeadL1Origin(); if (head is null) { - return NotFound; + return L1OriginNotFound; } L1Origin? origin = l1OriginStore.ReadL1Origin(head.Value); - return origin is null ? NotFound : ResultWrapper.Success(origin); + return origin is null ? L1OriginNotFound : ResultWrapper.Success(origin); } public Task> taiko_l1OriginByID(UInt256 blockId) { L1Origin? origin = l1OriginStore.ReadL1Origin(blockId); - return origin is null ? NotFound : ResultWrapper.Success(origin); + return origin is null ? L1OriginNotFound : ResultWrapper.Success(origin); + } + + public Task> taiko_lastL1OriginByBatchID(UInt256 batchId) + { + UInt256? blockId = l1OriginStore.ReadBatchToLastBlockID(batchId); + if (blockId is null) + { + blockId = GetLastBlockByBatchId(batchId); + if (blockId is null) + { + return L1OriginNotFound; + } + } + + L1Origin? origin = l1OriginStore.ReadL1Origin(blockId.Value); + + return origin is null ? L1OriginNotFound : ResultWrapper.Success(origin); + } + + public Task> taiko_lastBlockIDByBatchID(UInt256 batchId) + { + UInt256? blockId = l1OriginStore.ReadBatchToLastBlockID(batchId); + if (blockId is null) + { + blockId = GetLastBlockByBatchId(batchId); + if (blockId is null) + { + return BlockIdNotFound; + } + } + + return ResultWrapper.Success(blockId); + } + + /// + /// Traverses the blockchain backwards to find the last Shasta block of the given batch ID. + /// + /// The Shasta batch identifier for which to find the last corresponding block. + private UInt256? GetLastBlockByBatchId(UInt256 batchId) + { + Block? currentBlock = blockFinder.Head; + + while (currentBlock is not null && + currentBlock.Transactions.Length > 0 && + HasAnchorV4Prefix(currentBlock.Transactions[0].Data)) + { + if (currentBlock.Number == 0) + { + break; + } + + UInt256? proposalId = currentBlock.Header.DecodeShastaProposalID(); + if (proposalId is null) + { + return null; + } + + if (proposalId.Value == batchId) + { + return (UInt256)currentBlock.Number; + } + + currentBlock = blockFinder.FindBlock(currentBlock.Number - 1); + } + + return null; + } + + private static bool HasAnchorV4Prefix(ReadOnlyMemory data) + { + return data.Length >= 4 && AnchorV4Selector.AsSpan().SequenceEqual(data.Span[..4]); } } diff --git a/src/Nethermind/Nethermind.Taiko/Rpc/TaikoForkchoiceUpdatedHandler.cs b/src/Nethermind/Nethermind.Taiko/Rpc/TaikoForkchoiceUpdatedHandler.cs index e03907f6a88..f8e6e0cef02 100644 --- a/src/Nethermind/Nethermind.Taiko/Rpc/TaikoForkchoiceUpdatedHandler.cs +++ b/src/Nethermind/Nethermind.Taiko/Rpc/TaikoForkchoiceUpdatedHandler.cs @@ -90,4 +90,16 @@ protected override bool IsPayloadAttributesTimestampValid(Block newHeadBlock, Fo return blockHeader; } + + protected override bool TryGetBranch(Block newHeadBlock, out Block[] blocks) + { + // Allow resetting to any block already on the main chain (including genesis) + if (_blockTree.IsMainChain(newHeadBlock.Header)) + { + blocks = [newHeadBlock]; + return true; + } + + return base.TryGetBranch(newHeadBlock, out blocks); + } } diff --git a/src/Nethermind/Nethermind.Taiko/TaikoBlockValidator.cs b/src/Nethermind/Nethermind.Taiko/TaikoBlockValidator.cs index 6fb161f7848..60526286547 100644 --- a/src/Nethermind/Nethermind.Taiko/TaikoBlockValidator.cs +++ b/src/Nethermind/Nethermind.Taiko/TaikoBlockValidator.cs @@ -24,11 +24,13 @@ public class TaikoBlockValidator( private static readonly byte[] AnchorSelector = Keccak.Compute("anchor(bytes32,bytes32,uint64,uint32)").Bytes[..4].ToArray(); private static readonly byte[] AnchorV2Selector = Keccak.Compute("anchorV2(uint64,bytes32,uint32,(uint8,uint8,uint32,uint64,uint32))").Bytes[..4].ToArray(); private static readonly byte[] AnchorV3Selector = Keccak.Compute("anchorV3(uint64,bytes32,uint32,(uint8,uint8,uint32,uint64,uint32),bytes32[])").Bytes[..4].ToArray(); + public static readonly byte[] AnchorV4Selector = Keccak.Compute("anchorV4((uint48,bytes32,bytes32))").Bytes[..4].ToArray(); + public static readonly Address GoldenTouchAccount = new("0x0000777735367b36bC9B61C50022d9D0700dB4Ec"); private const long AnchorGasLimit = 250_000; - private const long AnchorV3GasLimit = 1_000_000; + private const long AnchorV3V4GasLimit = 1_000_000; protected override bool ValidateEip4844Fields(Block block, IReleaseSpec spec, ref string? error) => true; // No blob transactions are expected, covered by ValidateTransactions also @@ -62,12 +64,9 @@ private bool ValidateAnchorTransaction(Transaction tx, Block block, ITaikoReleas return false; } - if (tx.Data.Length == 0 - || (!AnchorSelector.AsSpan().SequenceEqual(tx.Data.Span[..4]) - && !AnchorV2Selector.AsSpan().SequenceEqual(tx.Data.Span[..4]) - && !AnchorV3Selector.AsSpan().SequenceEqual(tx.Data.Span[..4]))) + if (tx.Data.Length < 4 || !IsValidAnchorSelector(tx.Data.Span[..4], spec)) { - errorMessage = "Anchor transaction must have valid selector"; + errorMessage = "Anchor transaction must have valid selector for the current fork"; return false; } @@ -77,7 +76,7 @@ private bool ValidateAnchorTransaction(Transaction tx, Block block, ITaikoReleas return false; } - if (tx.GasLimit != (spec.IsPacayaEnabled ? AnchorV3GasLimit : AnchorGasLimit)) + if (tx.GasLimit != (spec.IsPacayaEnabled || spec.IsShastaEnabled ? AnchorV3V4GasLimit : AnchorGasLimit)) { errorMessage = "Anchor transaction must have correct gas limit"; return false; @@ -89,7 +88,7 @@ private bool ValidateAnchorTransaction(Transaction tx, Block block, ITaikoReleas return false; } - // We dont set the tx.SenderAddress here, as it will stop the rest of the transactions in the block + // We don't set the tx.SenderAddress here, as it will stop the rest of the transactions in the block // from getting their sender address recovered Address? senderAddress = tx.SenderAddress ?? ecdsa.RecoverAddress(tx); @@ -108,4 +107,16 @@ private bool ValidateAnchorTransaction(Transaction tx, Block block, ITaikoReleas errorMessage = null; return true; } + + private static bool IsValidAnchorSelector(ReadOnlySpan selector, ITaikoReleaseSpec spec) + { + if (spec.IsShastaEnabled) + return AnchorV4Selector.AsSpan().SequenceEqual(selector); + + if (spec.IsPacayaEnabled) + return AnchorV3Selector.AsSpan().SequenceEqual(selector); + + return AnchorSelector.AsSpan().SequenceEqual(selector) + || AnchorV2Selector.AsSpan().SequenceEqual(selector); + } } diff --git a/src/Nethermind/Nethermind.Taiko/TaikoHeaderHelper.cs b/src/Nethermind/Nethermind.Taiko/TaikoHeaderHelper.cs index 76801ec8841..8b4a63f9e99 100644 --- a/src/Nethermind/Nethermind.Taiko/TaikoHeaderHelper.cs +++ b/src/Nethermind/Nethermind.Taiko/TaikoHeaderHelper.cs @@ -3,10 +3,44 @@ using System; using Nethermind.Core; +using Nethermind.Int256; namespace Nethermind.Taiko; +/// +/// Helper methods for decoding Taiko block header extraData. +/// Shasta extraData layout: [basefeeSharingPctg (1 byte)][proposalId (6 bytes)] +/// internal static class TaikoHeaderHelper { - public static byte? DecodeOntakeExtraData(this BlockHeader header) => header.ExtraData is { Length: >= 32 } ? Math.Min(header.ExtraData[31], (byte)100) : null; + public const int ShastaExtraDataBasefeeSharingPctgIndex = 0; + public const int ShastaExtraDataProposalIDIndex = 1; + public const int ShastaExtraDataProposalIDLength = 6; + public const int ShastaExtraDataLen = 1 + ShastaExtraDataProposalIDLength; + + /// + /// Decodes Ontake/Pacaya extraData to get basefeeSharingPctg (last byte, capped at 100). + /// + public static byte? DecodeOntakeExtraData(this BlockHeader header) => + header.ExtraData is { Length: >= 32 } ? Math.Min(header.ExtraData[31], (byte)100) : null; + + /// + /// Decodes Shasta extraData to get basefeeSharingPctg (first byte). + /// + public static byte? DecodeShastaBasefeeSharingPctg(this BlockHeader header) => + header.ExtraData is { Length: < ShastaExtraDataLen } ? null : header.ExtraData[ShastaExtraDataBasefeeSharingPctgIndex]; + + /// + /// Decodes Shasta extraData to get proposalId (bytes 1-6). + /// + public static UInt256? DecodeShastaProposalID(this BlockHeader header) + { + if (header.ExtraData is null || header.ExtraData.Length < ShastaExtraDataLen) + { + return null; + } + + ReadOnlySpan proposalIdBytes = header.ExtraData.AsSpan(ShastaExtraDataProposalIDIndex, ShastaExtraDataProposalIDLength); + return new UInt256(proposalIdBytes, true); + } } diff --git a/src/Nethermind/Nethermind.Taiko/TaikoHeaderValidator.cs b/src/Nethermind/Nethermind.Taiko/TaikoHeaderValidator.cs index 0236e250e18..72e481345eb 100644 --- a/src/Nethermind/Nethermind.Taiko/TaikoHeaderValidator.cs +++ b/src/Nethermind/Nethermind.Taiko/TaikoHeaderValidator.cs @@ -1,13 +1,17 @@ // SPDX-FileCopyrightText: 2024 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using Nethermind.Blockchain; using Nethermind.Consensus; using Nethermind.Consensus.Validators; using Nethermind.Core; +using Nethermind.Core.Crypto; using Nethermind.Core.Messages; using Nethermind.Core.Specs; +using Nethermind.Int256; using Nethermind.Logging; +using Nethermind.Taiko.TaikoSpec; namespace Nethermind.Taiko; @@ -15,26 +19,227 @@ public class TaikoHeaderValidator( IBlockTree? blockTree, ISealValidator? sealValidator, ISpecProvider? specProvider, - ILogManager? logManager) : HeaderValidator(blockTree, sealValidator, specProvider, logManager) + IL1OriginStore l1OriginStore, + ITimestamper? timestamper, + ILogManager? logManager) + : HeaderValidator(blockTree, sealValidator, specProvider, logManager) { + private readonly IL1OriginStore _l1OriginStore = l1OriginStore ?? throw new ArgumentNullException(nameof(l1OriginStore)); + private readonly ITimestamper _timestamper = timestamper ?? Timestamper.Default; + + // EIP-4396 calculation parameters. + private const ulong BlockTimeTarget = 2; + private const ulong MaxGasTargetPercentage = 95; + + private static readonly UInt256 ShastaInitialBaseFee = 25_000_000; + private static readonly UInt256 MinBaseFeeShasta = 5_000_000; + private static readonly UInt256 MaxBaseFeeShasta = 1_000_000_000; + protected override bool ValidateGasLimitRange(BlockHeader header, BlockHeader parent, IReleaseSpec spec, ref string? error) => true; + protected override bool Validate(BlockHeader header, BlockHeader? parent, bool isUncle, out string? error) + { + if (header.UnclesHash != Keccak.OfAnEmptySequenceRlp) + { + error = "Uncles must be empty"; + if (_logger.IsWarn) _logger.Warn($"Invalid block header ({header.Hash}) - uncles not empty"); + return false; + } + + if (header.WithdrawalsRoot is null) + { + error = "WithdrawalsRoot is missing"; + if (_logger.IsWarn) _logger.Warn($"Invalid block header ({header.Hash}) - withdrawals root is null"); + return false; + } + + return base.Validate(header, parent, isUncle, out error); + } + + protected override bool ValidateExtraData(BlockHeader header, IReleaseSpec spec, bool isUncle, ref string? error) + { + var taikoSpec = (ITaikoReleaseSpec)spec; + + if (taikoSpec.IsShastaEnabled && header.ExtraData is { Length: < TaikoHeaderHelper.ShastaExtraDataLen }) + { + error = $"ExtraData must be at least {TaikoHeaderHelper.ShastaExtraDataLen} bytes for Shasta, but got {header.ExtraData.Length}"; + if (_logger.IsWarn) _logger.Warn($"Invalid block header ({header.Hash}) - {error}"); + return false; + } + + return base.ValidateExtraData(header, spec, isUncle, ref error); + } + protected override bool Validate1559(BlockHeader header, BlockHeader parent, IReleaseSpec spec, ref string? error) { - return !header.BaseFeePerGas.IsZero; + if (header.BaseFeePerGas.IsZero) + { + error = "BaseFee cannot be zero"; + return false; + } + + var taikoSpec = (ITaikoReleaseSpec)spec; + if (taikoSpec.IsShastaEnabled) + { + return ValidateEip4396Header(header, parent, spec, ref error); + } + + return true; } - protected override bool ValidateTimestamp(BlockHeader header, BlockHeader parent, ref string? error) + private bool ValidateEip4396Header(BlockHeader header, BlockHeader parent, IReleaseSpec spec, ref string? error) { - if (header.Timestamp < parent.Timestamp) + // Get parent block time (time difference between parent and grandparent) + ulong parentBlockTime = 0; + if (header.Number > 1) + { + BlockHeader? grandParent = _blockTree?.FindHeader(parent.ParentHash!, BlockTreeLookupOptions.None, blockNumber: parent.Number - 1); + if (grandParent is null) + { + error = $"Ancestor block not found for parent {parent.ParentHash}"; + if (_logger.IsWarn) _logger.Warn($"Invalid block header ({header.Hash}) - {error}"); + return false; + } + parentBlockTime = parent.Timestamp - grandParent.Timestamp; + } + + // Calculate expected base fee using EIP-4396 + UInt256 expectedBaseFee = CalculateEip4396BaseFee(parent, parentBlockTime, spec); + + if (header.BaseFeePerGas != expectedBaseFee) { - error = BlockErrorMessages.InvalidTimestamp; - if (_logger.IsWarn) _logger.Warn($"Invalid block header ({header.Hash}) - timestamp before parent"); + error = $"Invalid baseFee: have {header.BaseFeePerGas}, want {expectedBaseFee}, " + + $"parentBaseFee {parent.BaseFeePerGas}, parentGasUsed {parent.GasUsed}, parentBlockTime {parentBlockTime}"; + if (_logger.IsWarn) _logger.Warn($"Invalid block header ({header.Hash}) - {error}"); return false; } + return true; } + private static UInt256 CalculateEip4396BaseFee(BlockHeader parent, ulong parentBlockTime, IReleaseSpec spec) + { + // If the parent is genesis, use the initial base fee for the first post-genesis block + if (parent.Number == 0) + { + return ShastaInitialBaseFee; + } + + IEip1559Spec eip1559Spec = spec; + ulong parentGasTarget = (ulong)(parent.GasLimit / eip1559Spec.ElasticityMultiplier); + ulong parentAdjustedGasTarget = Math.Min(parentGasTarget * parentBlockTime / BlockTimeTarget, + (ulong)parent.GasLimit * MaxGasTargetPercentage / 100); + + // If the parent gasUsed is the same as the adjusted target, the baseFee remains unchanged + if ((ulong)parent.GasUsed == parentAdjustedGasTarget) + { + return parent.BaseFeePerGas; + } + + UInt256 baseFee; + UInt256 baseFeeChangeDenominator = eip1559Spec.BaseFeeMaxChangeDenominator; + + if ((ulong)parent.GasUsed > parentAdjustedGasTarget) + { + // If the parent block used more gas than its target, the baseFee should increase + // max(1, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator) + UInt256 feeDelta; + if (parentGasTarget == 0 || baseFeeChangeDenominator.IsZero) + { + feeDelta = 1; + } + else + { + UInt256 gasUsedDelta = (ulong)parent.GasUsed - parentAdjustedGasTarget; + feeDelta = parent.BaseFeePerGas * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator; + if (feeDelta < 1) + { + feeDelta = 1; + } + } + + baseFee = parent.BaseFeePerGas + feeDelta; + } + else + { + // Otherwise if the parent block used less gas than its target, the baseFee should decrease + // max(0, parentBaseFee * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator) + UInt256 feeDelta; + if (parentGasTarget == 0 || baseFeeChangeDenominator.IsZero) + { + feeDelta = 0; + } + else + { + UInt256 gasUsedDelta = parentAdjustedGasTarget - (ulong)parent.GasUsed; + feeDelta = parent.BaseFeePerGas * gasUsedDelta / parentGasTarget / baseFeeChangeDenominator; + } + + baseFee = parent.BaseFeePerGas > feeDelta ? parent.BaseFeePerGas - feeDelta : UInt256.Zero; + } + + // Clamp the base fee to be within min and max limits for Shasta blocks + return ClampEip4396BaseFeeShasta(baseFee); + } + + private static UInt256 ClampEip4396BaseFeeShasta(UInt256 baseFee) + { + if (baseFee < MinBaseFeeShasta) + { + return MinBaseFeeShasta; + } + + return baseFee > MaxBaseFeeShasta ? MaxBaseFeeShasta : baseFee; + } + + protected override bool ValidateTimestamp(BlockHeader header, BlockHeader parent, ref string? error) + { + var taikoSpec = (ITaikoReleaseSpec)_specProvider.GetSpec(header); + + // Shasta fork enforces a strict timestamp increase, while other forks allow equal timestamps + // (multiple L2 blocks per L1 block scenario). + if (taikoSpec.IsShastaEnabled) + { + if (header.Timestamp <= parent.Timestamp) + { + error = BlockErrorMessages.InvalidTimestamp; + + if (_logger.IsWarn) + { + _logger.Warn($"Invalid block header ({header.Hash}) - timestamp must be greater than parent for Shasta"); + } + + return false; + } + } + else + { + if (header.Timestamp < parent.Timestamp) + { + error = BlockErrorMessages.InvalidTimestamp; + if (_logger.IsWarn) _logger.Warn($"Invalid block header ({header.Hash}) - timestamp before parent"); + return false; + } + } + + // If not a preconfirmation block, check that timestamp is not in the future + L1Origin? l1Origin = _l1OriginStore.ReadL1Origin((UInt256)header.Number); + if (l1Origin is null || l1Origin.IsPreconfBlock) + { + return true; + } + + ulong currentTime = _timestamper.UnixTime.Seconds; + if (header.Timestamp <= currentTime) + { + return true; + } + + error = $"Block timestamp {header.Timestamp} is in the future (current time: {currentTime})"; + if (_logger.IsWarn) _logger.Warn($"Invalid block header ({header.Hash}) - {error}"); + return false; + } + protected override bool ValidateTotalDifficulty(BlockHeader header, BlockHeader parent, ref string? error) { if (header.Difficulty != 0 || header.TotalDifficulty != 0 && header.TotalDifficulty != null) diff --git a/src/Nethermind/Nethermind.Taiko/TaikoPayloadPreparationService.cs b/src/Nethermind/Nethermind.Taiko/TaikoPayloadPreparationService.cs index 583156ab26e..fd6a32086e4 100644 --- a/src/Nethermind/Nethermind.Taiko/TaikoPayloadPreparationService.cs +++ b/src/Nethermind/Nethermind.Taiko/TaikoPayloadPreparationService.cs @@ -59,6 +59,12 @@ public class TaikoPayloadPreparationService( if (!l1Origin.IsPreconfBlock) { l1OriginStore.WriteHeadL1Origin(l1Origin.BlockId); + + // Write the batch to block mapping if the batch ID is given. + if (attrs.BlockMetadata?.BatchID is not null) + { + l1OriginStore.WriteBatchToLastBlockID(attrs.BlockMetadata.BatchID.Value, l1Origin.BlockId); + } } // ignore TryAdd failure (it can only happen if payloadId is already in the dictionary) @@ -67,6 +73,21 @@ public class TaikoPayloadPreparationService( (payloadId, existing) => { if (_logger.IsInfo) _logger.Info($"Payload with the same parameters has already started. PayloadId: {payloadId}"); + + // Write L1Origin and HeadL1Origin even if the payload is already in the cache. + L1Origin l1Origin = attrs.L1Origin ?? throw new InvalidOperationException("L1Origin is required"); + l1OriginStore.WriteL1Origin(l1Origin.BlockId, l1Origin); + + if (!l1Origin.IsPreconfBlock) + { + l1OriginStore.WriteHeadL1Origin(l1Origin.BlockId); + + if (attrs.BlockMetadata?.BatchID is not null) + { + l1OriginStore.WriteBatchToLastBlockID(attrs.BlockMetadata.BatchID.Value, l1Origin.BlockId); + } + } + return existing; }); diff --git a/src/Nethermind/Nethermind.Taiko/TaikoPlugin.cs b/src/Nethermind/Nethermind.Taiko/TaikoPlugin.cs index ffab0b121f3..ea38ee57e1c 100644 --- a/src/Nethermind/Nethermind.Taiko/TaikoPlugin.cs +++ b/src/Nethermind/Nethermind.Taiko/TaikoPlugin.cs @@ -36,6 +36,7 @@ using Nethermind.Taiko.BlockTransactionExecutors; using Nethermind.Taiko.Config; using Nethermind.Taiko.Rpc; +using Nethermind.Taiko.Tdx; using Nethermind.Taiko.TaikoSpec; namespace Nethermind.Taiko; @@ -196,6 +197,9 @@ protected override void Load(ContainerBuilder builder) .RegisterSingletonJsonRpcModule() .AddSingleton() + // TDX attestation (enabled with Surge.TdxEnabled) + .AddModule(new TdxModule()) + // Need to set the rlp globally .OnBuild(ctx => { diff --git a/src/Nethermind/Nethermind.Taiko/TaikoSpec/ITaikoReleaseSpec.cs b/src/Nethermind/Nethermind.Taiko/TaikoSpec/ITaikoReleaseSpec.cs index e82cb75209f..14ce81ac0a9 100644 --- a/src/Nethermind/Nethermind.Taiko/TaikoSpec/ITaikoReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Taiko/TaikoSpec/ITaikoReleaseSpec.cs @@ -10,6 +10,7 @@ public interface ITaikoReleaseSpec : IReleaseSpec { public bool IsOntakeEnabled { get; } public bool IsPacayaEnabled { get; } + public bool IsShastaEnabled { get; } public bool UseSurgeGasPriceOracle { get; } public Address TaikoL2Address { get; } } diff --git a/src/Nethermind/Nethermind.Taiko/TaikoSpec/TaikoCancunReleaseSpec.cs b/src/Nethermind/Nethermind.Taiko/TaikoSpec/TaikoCancunReleaseSpec.cs index 47ed268fd4b..240e17776a1 100644 --- a/src/Nethermind/Nethermind.Taiko/TaikoSpec/TaikoCancunReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Taiko/TaikoSpec/TaikoCancunReleaseSpec.cs @@ -8,26 +8,21 @@ namespace Nethermind.Taiko.TaikoSpec; public class TaikoOntakeReleaseSpec : Cancun, ITaikoReleaseSpec { - - public TaikoOntakeReleaseSpec() - { - IsOntakeEnabled = true; - IsPacayaEnabled = false; - } + public TaikoOntakeReleaseSpec() => IsOntakeEnabled = true; public bool IsOntakeEnabled { get; set; } public bool IsPacayaEnabled { get; set; } + public bool IsShastaEnabled { get; set; } public bool UseSurgeGasPriceOracle { get; set; } public Address TaikoL2Address { get; set; } = new("0x1670000000000000000000000000000000010001"); } -public class TaikoPacayaReleaseSpec : TaikoOntakeReleaseSpec, ITaikoReleaseSpec +public class TaikoPacayaReleaseSpec : TaikoOntakeReleaseSpec { + public TaikoPacayaReleaseSpec() => IsPacayaEnabled = true; +} - public TaikoPacayaReleaseSpec() - { - IsOntakeEnabled = true; - IsPacayaEnabled = true; - } - +public class TaikoShastaReleaseSpec : TaikoPacayaReleaseSpec +{ + public TaikoShastaReleaseSpec() => IsShastaEnabled = true; } diff --git a/src/Nethermind/Nethermind.Taiko/TaikoSpec/TaikoChainSpecBasedSpecProvider.cs b/src/Nethermind/Nethermind.Taiko/TaikoSpec/TaikoChainSpecBasedSpecProvider.cs index 56d5e791c49..6e426f06200 100644 --- a/src/Nethermind/Nethermind.Taiko/TaikoSpec/TaikoChainSpecBasedSpecProvider.cs +++ b/src/Nethermind/Nethermind.Taiko/TaikoSpec/TaikoChainSpecBasedSpecProvider.cs @@ -24,6 +24,7 @@ protected override ReleaseSpec CreateReleaseSpec(ChainSpec chainSpec, long relea releaseSpec.IsOntakeEnabled = (chainSpecEngineParameters.OntakeTransition ?? long.MaxValue) <= releaseStartBlock; releaseSpec.IsPacayaEnabled = (chainSpecEngineParameters.PacayaTransition ?? long.MaxValue) <= releaseStartBlock; + releaseSpec.IsShastaEnabled = (chainSpecEngineParameters.ShastaTimestamp ?? ulong.MaxValue) <= releaseStartTimestamp; releaseSpec.UseSurgeGasPriceOracle = chainSpecEngineParameters.UseSurgeGasPriceOracle ?? false; releaseSpec.TaikoL2Address = chainSpecEngineParameters.TaikoL2Address; diff --git a/src/Nethermind/Nethermind.Taiko/TaikoSpec/TaikoChainSpecEngineParameters.cs b/src/Nethermind/Nethermind.Taiko/TaikoSpec/TaikoChainSpecEngineParameters.cs index 127e790b9f0..408b7b48ed4 100644 --- a/src/Nethermind/Nethermind.Taiko/TaikoSpec/TaikoChainSpecEngineParameters.cs +++ b/src/Nethermind/Nethermind.Taiko/TaikoSpec/TaikoChainSpecEngineParameters.cs @@ -13,6 +13,7 @@ public class TaikoChainSpecEngineParameters : IChainSpecEngineParameters public string SealEngineType => Core.SealEngineType.Taiko; public long? OntakeTransition { get; set; } public long? PacayaTransition { get; set; } + public ulong? ShastaTimestamp { get; set; } public bool? UseSurgeGasPriceOracle { get; set; } public Address TaikoL2Address { get; set; } = new("0x1670000000000000000000000000000000010001"); @@ -28,5 +29,10 @@ public void AddTransitions(SortedSet blockNumbers, SortedSet timest { blockNumbers.Add(PacayaTransition.Value); } + + if (ShastaTimestamp is not null) + { + timestamps.Add(ShastaTimestamp.Value); + } } } diff --git a/src/Nethermind/Nethermind.Taiko/TaikoSpec/TaikoReleaseSpec.cs b/src/Nethermind/Nethermind.Taiko/TaikoSpec/TaikoReleaseSpec.cs index 7cbd49af327..db9b697f23e 100644 --- a/src/Nethermind/Nethermind.Taiko/TaikoSpec/TaikoReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Taiko/TaikoSpec/TaikoReleaseSpec.cs @@ -10,6 +10,7 @@ public class TaikoReleaseSpec : ReleaseSpec, ITaikoReleaseSpec { public bool IsOntakeEnabled { get; set; } public bool IsPacayaEnabled { get; set; } + public bool IsShastaEnabled { get; set; } public bool UseSurgeGasPriceOracle { get; set; } public required Address TaikoL2Address { get; set; } } diff --git a/src/Nethermind/Nethermind.Taiko/TaikoTransactionProcessor.cs b/src/Nethermind/Nethermind.Taiko/TaikoTransactionProcessor.cs index b396bfaa51e..fc8e4972038 100644 --- a/src/Nethermind/Nethermind.Taiko/TaikoTransactionProcessor.cs +++ b/src/Nethermind/Nethermind.Taiko/TaikoTransactionProcessor.cs @@ -5,6 +5,7 @@ using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Evm; +using Nethermind.Evm.GasPolicy; using Nethermind.Evm.Tracing; using Nethermind.Evm.TransactionProcessing; using Nethermind.Int256; @@ -21,19 +22,26 @@ public class TaikoTransactionProcessor( IVirtualMachine virtualMachine, ICodeInfoRepository? codeInfoRepository, ILogManager? logManager - ) : TransactionProcessorBase(blobBaseFeeCalculator, specProvider, worldState, virtualMachine, codeInfoRepository, logManager) + ) : EthereumTransactionProcessorBase(blobBaseFeeCalculator, specProvider, worldState, virtualMachine, codeInfoRepository, logManager) { protected override TransactionResult ValidateStatic(Transaction tx, BlockHeader header, IReleaseSpec spec, ExecutionOptions opts, - in IntrinsicGas intrinsicGas) + in IntrinsicGas intrinsicGas) => base.ValidateStatic(tx, header, spec, tx.IsAnchorTx ? opts | ExecutionOptions.SkipValidationAndCommit : opts, in intrinsicGas); protected override TransactionResult BuyGas(Transaction tx, IReleaseSpec spec, ITxTracer tracer, ExecutionOptions opts, - in UInt256 effectiveGasPrice, out UInt256 premiumPerGas, out UInt256 senderReservedGasPayment, out UInt256 blobBaseFee) - => base.BuyGas(tx, spec, tracer, tx.IsAnchorTx ? opts | ExecutionOptions.SkipValidationAndCommit : opts, in effectiveGasPrice, out premiumPerGas, out senderReservedGasPayment, out blobBaseFee); + in UInt256 effectiveGasPrice, out UInt256 premiumPerGas, out UInt256 senderReservedGasPayment, + out UInt256 blobBaseFee) + { + if (tx.IsAnchorTx) + { + premiumPerGas = UInt256.Zero; + senderReservedGasPayment = UInt256.Zero; + blobBaseFee = UInt256.Zero; + return TransactionResult.Ok; + } - protected override GasConsumed Refund(Transaction tx, BlockHeader header, IReleaseSpec spec, ExecutionOptions opts, - in TransactionSubstate substate, in long unspentGas, in UInt256 gasPrice, int codeInsertRefunds, long floorGas) - => base.Refund(tx, header, spec, tx.IsAnchorTx ? opts | ExecutionOptions.SkipValidationAndCommit : opts, substate, unspentGas, gasPrice, codeInsertRefunds, floorGas); + return base.BuyGas(tx, spec, tracer, opts, in effectiveGasPrice, out premiumPerGas, out senderReservedGasPayment, out blobBaseFee); + } protected override void PayFees(Transaction tx, BlockHeader header, IReleaseSpec spec, ITxTracer tracer, in TransactionSubstate substate, long spentGas, in UInt256 premiumPerGas, in UInt256 blobBaseFee, int statusCode) @@ -52,11 +60,12 @@ protected override void PayFees(Transaction tx, BlockHeader header, IReleaseSpec if (!tx.IsAnchorTx && !baseFees.IsZero && spec.FeeCollector is not null) { - if (((ITaikoReleaseSpec)spec).IsOntakeEnabled) + var taikoSpec = (ITaikoReleaseSpec)spec; + if (taikoSpec.IsOntakeEnabled || taikoSpec.IsShastaEnabled) { - byte basefeeSharingPctg = header.DecodeOntakeExtraData() ?? 0; + byte basefeeSharingPct = (taikoSpec.IsShastaEnabled ? header.DecodeShastaBasefeeSharingPctg() : header.DecodeOntakeExtraData()) ?? 0; - UInt256 feeCoinbase = baseFees * basefeeSharingPctg / 100; + UInt256 feeCoinbase = baseFees * basefeeSharingPct / 100; if (statusCode == StatusCode.Failure || gasBeneficiaryNotDestroyed) { @@ -83,4 +92,12 @@ protected override TransactionResult IncrementNonce(Transaction tx, BlockHeader return base.IncrementNonce(tx, header, spec, tracer, opts); } + + protected override void PayRefund(Transaction tx, UInt256 refundAmount, IReleaseSpec spec) + { + if (!tx.IsAnchorTx) + { + base.PayRefund(tx, refundAmount, spec); + } + } } diff --git a/src/Nethermind/Nethermind.Taiko/Tdx/BlockHeaderForRpc.cs b/src/Nethermind/Nethermind.Taiko/Tdx/BlockHeaderForRpc.cs new file mode 100644 index 00000000000..ac5942e1fd4 --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko/Tdx/BlockHeaderForRpc.cs @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Buffers.Binary; +using System.Text.Json.Serialization; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Int256; + +namespace Nethermind.Taiko.Tdx; + +public class BlockHeaderForRpc +{ + public BlockHeaderForRpc() { } + + public BlockHeaderForRpc(BlockHeader header) + { + Hash = header.Hash; + ParentHash = header.ParentHash; + Sha3Uncles = header.UnclesHash; + Miner = header.Beneficiary; + StateRoot = header.StateRoot; + TransactionsRoot = header.TxRoot; + ReceiptsRoot = header.ReceiptsRoot; + LogsBloom = header.Bloom; + Difficulty = header.Difficulty; + Number = header.Number; + GasLimit = header.GasLimit; + GasUsed = header.GasUsed; + Timestamp = (UInt256)header.Timestamp; + ExtraData = header.ExtraData; + MixHash = header.MixHash; + + Nonce = new byte[8]; + BinaryPrimitives.WriteUInt64BigEndian(Nonce, header.Nonce); + + BaseFeePerGas = header.BaseFeePerGas; + WithdrawalsRoot = header.WithdrawalsRoot; + BlobGasUsed = header.BlobGasUsed; + ExcessBlobGas = header.ExcessBlobGas; + ParentBeaconBlockRoot = header.ParentBeaconBlockRoot; + RequestsHash = header.RequestsHash; + } + + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public Hash256? Hash { get; set; } + + public Hash256? ParentHash { get; set; } + public Hash256? Sha3Uncles { get; set; } + public Address? Miner { get; set; } + public Hash256? StateRoot { get; set; } + public Hash256? TransactionsRoot { get; set; } + public Hash256? ReceiptsRoot { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public Bloom? LogsBloom { get; set; } + public UInt256 Difficulty { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.Never)] + public long Number { get; set; } + + public long GasLimit { get; set; } + public long GasUsed { get; set; } + public UInt256 Timestamp { get; set; } + public byte[] ExtraData { get; set; } = []; + public Hash256? MixHash { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public byte[]? Nonce { get; set; } + public UInt256? BaseFeePerGas { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Hash256? WithdrawalsRoot { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public ulong? BlobGasUsed { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public ulong? ExcessBlobGas { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Hash256? ParentBeaconBlockRoot { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Hash256? RequestsHash { get; set; } +} diff --git a/src/Nethermind/Nethermind.Taiko/Tdx/ISurgeTdxConfig.cs b/src/Nethermind/Nethermind.Taiko/Tdx/ISurgeTdxConfig.cs new file mode 100644 index 00000000000..0c86bff9795 --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko/Tdx/ISurgeTdxConfig.cs @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Config; + +namespace Nethermind.Taiko.Tdx; + +public interface ISurgeTdxConfig : IConfig +{ + [ConfigItem(Description = "Path to the tdxs Unix socket.", DefaultValue = "/var/tdxs.sock")] + string SocketPath { get; set; } + + [ConfigItem(Description = "Path to store TDX bootstrap data and keys.", DefaultValue = "~/.config/nethermind/tdx")] + string ConfigPath { get; set; } +} + diff --git a/src/Nethermind/Nethermind.Taiko/Tdx/ITdxRpcModule.cs b/src/Nethermind/Nethermind.Taiko/Tdx/ITdxRpcModule.cs new file mode 100644 index 00000000000..6b1c514c7f5 --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko/Tdx/ITdxRpcModule.cs @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading.Tasks; +using Nethermind.Blockchain.Find; +using Nethermind.JsonRpc; +using Nethermind.JsonRpc.Modules; + +namespace Nethermind.Taiko.Tdx; + +[RpcModule(ModuleType.Eth)] +public interface ITdxRpcModule : IRpcModule +{ + [JsonRpcMethod( + Description = "Returns TDX signed block header for the specified block.", + IsSharable = true, + IsImplemented = true)] + Task> taiko_tdxSignBlockHeader(BlockParameter blockParameter); + + [JsonRpcMethod( + Description = "Returns the TDX guest information for instance registration.", + IsSharable = true, + IsImplemented = true)] + Task> taiko_getTdxGuestInfo(); + + [JsonRpcMethod( + Description = "Bootstraps the TDX service (generates key, gets initial quote).", + IsSharable = false, + IsImplemented = true)] + Task> taiko_tdxBootstrap(); +} + diff --git a/src/Nethermind/Nethermind.Taiko/Tdx/ITdxService.cs b/src/Nethermind/Nethermind.Taiko/Tdx/ITdxService.cs new file mode 100644 index 00000000000..753f1dbde4b --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko/Tdx/ITdxService.cs @@ -0,0 +1,33 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; + +namespace Nethermind.Taiko.Tdx; + +/// +/// Service for generating TDX attestations for blocks. +/// +public interface ITdxService +{ + /// + /// Whether the service is bootstrapped. + /// + bool IsBootstrapped { get; } + + /// + /// Bootstrap the TDX service: generate key, get initial quote. + /// + TdxGuestInfo Bootstrap(); + + /// + /// Get guest info if already bootstrapped. + /// + TdxGuestInfo? GetGuestInfo(); + + /// + /// Generate a TDX signature for the given block header. + /// + /// The block header to sign. + TdxBlockHeaderSignature SignBlockHeader(BlockHeader blockHeader); +} diff --git a/src/Nethermind/Nethermind.Taiko/Tdx/ITdxsClient.cs b/src/Nethermind/Nethermind.Taiko/Tdx/ITdxsClient.cs new file mode 100644 index 00000000000..d05dc3b12a9 --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko/Tdx/ITdxsClient.cs @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json; + +namespace Nethermind.Taiko.Tdx; + +/// +/// Client for communicating with the tdxs daemon via Unix socket. +/// +public interface ITdxsClient +{ + /// + /// Issues a TDX attestation quote. + /// + /// User data to embed in the quote (typically 32 bytes). + /// Random nonce for freshness. + /// The attestation document (quote) bytes. + byte[] Issue(byte[] userData, byte[] nonce); + + /// + /// Gets metadata about the TDX environment. + /// + TdxMetadata GetMetadata(); +} + +public class TdxMetadata +{ + public required string IssuerType { get; init; } + public JsonElement? Metadata { get; init; } +} + diff --git a/src/Nethermind/Nethermind.Taiko/Tdx/SurgeTdxConfig.cs b/src/Nethermind/Nethermind.Taiko/Tdx/SurgeTdxConfig.cs new file mode 100644 index 00000000000..1edaaa04ad2 --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko/Tdx/SurgeTdxConfig.cs @@ -0,0 +1,11 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +namespace Nethermind.Taiko.Tdx; + +public class SurgeTdxConfig : ISurgeTdxConfig +{ + public string SocketPath { get; set; } = "/var/tdxs.sock"; + public string ConfigPath { get; set; } = "~/.config/nethermind/tdx"; +} + diff --git a/src/Nethermind/Nethermind.Taiko/Tdx/TdxAttestation.cs b/src/Nethermind/Nethermind.Taiko/Tdx/TdxAttestation.cs new file mode 100644 index 00000000000..3e5d4b4d659 --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko/Tdx/TdxAttestation.cs @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Text.Json; +using Nethermind.Core.Crypto; + +namespace Nethermind.Taiko.Tdx; + +/// +/// TDX signed block header containing the signature, block hash, state root and header. +/// Signature is over keccak(). +/// +public class TdxBlockHeaderSignature +{ + public required byte[] Signature { get; init; } + public required Hash256 BlockHash { get; init; } + public required Hash256 StateRoot { get; init; } + public required BlockHeaderForRpc Header { get; init; } +} + +/// +/// TDX guest information for instance registration and bootstrap persistence. +/// +public class TdxGuestInfo +{ + public required string IssuerType { get; init; } + public required string PublicKey { get; init; } + public required string Quote { get; init; } + public required string Nonce { get; init; } + public JsonElement? Metadata { get; init; } +} + diff --git a/src/Nethermind/Nethermind.Taiko/Tdx/TdxModule.cs b/src/Nethermind/Nethermind.Taiko/Tdx/TdxModule.cs new file mode 100644 index 00000000000..dcc8f651444 --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko/Tdx/TdxModule.cs @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Autofac; +using Nethermind.Core; +using Nethermind.JsonRpc.Modules; +using Nethermind.Logging; +using Nethermind.Taiko.Config; + +namespace Nethermind.Taiko.Tdx; + +/// +/// Autofac module for TDX attestation services. +/// Services are registered regardless of config; RPC checks config at runtime. +/// +public class TdxModule : Module +{ + protected override void Load(ContainerBuilder builder) + { + base.Load(builder); + + builder.AddSingleton(); + + // Register TDX service - returns NullTdxService when disabled + builder.Register(ctx => + { + ISurgeConfig surgeConfig = ctx.Resolve(); + if (!surgeConfig.TdxEnabled) + return NullTdxService.Instance; + + return new TdxService( + ctx.Resolve(), + ctx.Resolve(), + ctx.Resolve()); + }).SingleInstance(); + + builder.RegisterSingletonJsonRpcModule(); + } +} diff --git a/src/Nethermind/Nethermind.Taiko/Tdx/TdxRpcModule.cs b/src/Nethermind/Nethermind.Taiko/Tdx/TdxRpcModule.cs new file mode 100644 index 00000000000..38de08a61a7 --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko/Tdx/TdxRpcModule.cs @@ -0,0 +1,108 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Threading.Tasks; +using Nethermind.Blockchain; +using Nethermind.Blockchain.Find; +using Nethermind.Core; +using Nethermind.JsonRpc; +using Nethermind.Logging; +using Nethermind.Taiko.Config; + +namespace Nethermind.Taiko.Tdx; + +public class TdxRpcModule( + ISurgeConfig config, + ITdxService tdxService, + IBlockFinder blockFinder, + ILogManager logManager) : ITdxRpcModule +{ + private static readonly ResultWrapper TdxDisabledInfo = + ResultWrapper.Fail("TDX is not enabled. Set Surge.TdxEnabled=true in configuration."); + + private readonly ILogger _logger = logManager.GetClassLogger(); + + public Task> taiko_tdxSignBlockHeader(BlockParameter blockParameter) + { + string? availabilityError = VerifyTdxAvailability(); + if (availabilityError is not null) + return Task.FromResult(ResultWrapper.Fail(availabilityError)); + + BlockHeader? blockHeader = FindBlockHeader(blockParameter); + if (blockHeader is null) + return Task.FromResult(ResultWrapper.Fail("Block not found")); + + try + { + TdxBlockHeaderSignature signature = tdxService.SignBlockHeader(blockHeader); + return Task.FromResult(ResultWrapper.Success(signature)); + } + catch (Exception ex) + { + _logger.Error($"Failed to sign block header: {ex.Message}", ex); + return Task.FromResult(ResultWrapper.Fail($"Signing failed: {ex.Message}")); + } + } + + private string? VerifyTdxAvailability() + { + if (!config.TdxEnabled) + return "TDX is not enabled. Set Surge.TdxEnabled=true in configuration."; + + return !tdxService.IsBootstrapped ? "TDX service not bootstrapped. Call taiko_tdxBootstrap first." : null; + } + + private BlockHeader? FindBlockHeader(BlockParameter blockParameter) + { + // Only allow valid canonical blocks for TDX attestation + BlockHeader? blockHeader; + BlockTreeLookupOptions options = BlockTreeLookupOptions.RequireCanonical + | BlockTreeLookupOptions.TotalDifficultyNotNeeded + | BlockTreeLookupOptions.ExcludeTxHashes; + + if (blockParameter.BlockHash is { } blockHash) + { + blockHeader = blockFinder.FindHeader(blockHash, options); + } + else if (blockParameter.BlockNumber is { } blockNumber) + { + blockHeader = blockFinder.FindHeader(blockNumber, options); + } + else + { + BlockHeader? header = blockFinder.FindHeader(blockParameter); + blockHeader = header is null ? null : blockFinder.FindHeader(header.Hash!, options); + } + + return blockHeader; + } + + public Task> taiko_getTdxGuestInfo() + { + if (!config.TdxEnabled) + return Task.FromResult(TdxDisabledInfo); + + TdxGuestInfo? info = tdxService.GetGuestInfo(); + return info is null + ? Task.FromResult(ResultWrapper.Fail("TDX service not bootstrapped. Call taiko_tdxBootstrap first.")) + : Task.FromResult(ResultWrapper.Success(info)); + } + + public Task> taiko_tdxBootstrap() + { + if (!config.TdxEnabled) + return Task.FromResult(TdxDisabledInfo); + + try + { + TdxGuestInfo info = tdxService.Bootstrap(); + return Task.FromResult(ResultWrapper.Success(info)); + } + catch (Exception ex) + { + _logger.Error($"Failed to bootstrap TDX: {ex.Message}", ex); + return Task.FromResult(ResultWrapper.Fail($"Bootstrap failed: {ex.Message}")); + } + } +} diff --git a/src/Nethermind/Nethermind.Taiko/Tdx/TdxService.cs b/src/Nethermind/Nethermind.Taiko/Tdx/TdxService.cs new file mode 100644 index 00000000000..192cb3ec241 --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko/Tdx/TdxService.cs @@ -0,0 +1,228 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text.Json; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Crypto; +using Nethermind.Logging; +using Nethermind.Serialization.Rlp; + +namespace Nethermind.Taiko.Tdx; + +/// +/// Service for TDX signing operations. +/// +public class TdxService : ITdxService +{ + + private readonly ISurgeTdxConfig _config; + private readonly ITdxsClient _client; + private readonly ILogger _logger; + private readonly Ecdsa _ecdsa = new(); + + private TdxGuestInfo? _guestInfo; + private PrivateKey? _privateKey; + + public TdxService( + ISurgeTdxConfig config, + ITdxsClient client, + ILogManager logManager) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + throw new PlatformNotSupportedException("TDX attestation is only supported on Linux"); + } + + _config = config; + _client = client; + _logger = logManager.GetClassLogger(); + + TryLoadBootstrap(); + } + + public bool IsBootstrapped => _guestInfo is not null && _privateKey is not null; + + public TdxGuestInfo Bootstrap() + { + if (IsBootstrapped) + { + _logger.Info("Already bootstrapped, returning existing data"); + return _guestInfo!; + } + + _logger.Info("Bootstrapping TDX service"); + + // Generate private key + using var keyGenerator = new PrivateKeyGenerator(); + _privateKey = keyGenerator.Generate(); + Address address = _privateKey.Address; + + _logger.Info($"Generated TDX instance address: {address}"); + + // Get TDX quote with address as user data (padded to 32 bytes) + byte[] userData = new byte[32]; + address.Bytes.CopyTo(userData.AsSpan(12)); + + byte[] nonce = new byte[32]; + new CryptoRandom().GenerateRandomBytes(nonce); + + byte[] quote = _client.Issue(userData, nonce); + TdxMetadata metadata = _client.GetMetadata(); + + _guestInfo = new TdxGuestInfo + { + IssuerType = metadata.IssuerType, + PublicKey = address.ToString(), + Quote = Convert.ToHexString(quote).ToLowerInvariant(), + Nonce = Convert.ToHexString(nonce).ToLowerInvariant(), + Metadata = metadata.Metadata + }; + + SaveBootstrap(_guestInfo); + _logger.Info($"TDX bootstrap complete. Quote length: {quote.Length} bytes"); + + return _guestInfo; + } + + public TdxGuestInfo? GetGuestInfo() + { + return _guestInfo; + } + + public TdxBlockHeaderSignature SignBlockHeader(BlockHeader blockHeader) + { + if (!IsBootstrapped) + throw new TdxException("TDX service not bootstrapped"); + + blockHeader.Hash ??= blockHeader.CalculateHash(); + + if (blockHeader.StateRoot is null) + throw new TdxException("Block header state root is null"); + + // Concatenate blockHash and stateRoot, then hash for signing + byte[] dataToHash = new byte[64]; + blockHeader.Hash.Bytes.CopyTo(dataToHash.AsSpan(0, 32)); + blockHeader.StateRoot.Bytes.CopyTo(dataToHash.AsSpan(32, 32)); + Hash256 signedHash = Keccak.Compute(dataToHash); + + Signature signature = _ecdsa.Sign(_privateKey!, signedHash); + + return new TdxBlockHeaderSignature + { + Signature = GetSignatureBytes(signature), + BlockHash = blockHeader.Hash, + StateRoot = blockHeader.StateRoot, + Header = new BlockHeaderForRpc(blockHeader) + }; + } + + /// + /// Get 65-byte signature in format [r:32][s:32][v:1] where v = 27 + recovery_id + /// + private static byte[] GetSignatureBytes(Signature signature) + { + byte[] result = new byte[Signature.Size]; + signature.Bytes.CopyTo(result.AsSpan(0, Signature.Size - 1)); // r + s + result[Signature.Size - 1] = (byte)signature.V; + return result; + } + + private bool TryLoadBootstrap() + { + string path = GetBootstrapPath(); + string keyPath = GetKeyPath(); + + if (!File.Exists(path) || !File.Exists(keyPath)) + return false; + + try + { + string json = File.ReadAllText(path); + _guestInfo = JsonSerializer.Deserialize(json); + + byte[] keyBytes = File.ReadAllBytes(keyPath); + _privateKey = new PrivateKey(keyBytes); + + if (IsBootstrapped) + { + _logger.Info($"Loaded TDX bootstrap data. Address: {_privateKey.Address}"); + return true; + } + + _logger.Warn("Failed to load TDX bootstrap data, invalid data"); + return false; + } + catch (Exception ex) + { + _logger.Warn($"Failed to load bootstrap data: {ex.Message}"); + _guestInfo = null; + _privateKey = null; + } + + return false; + } + + private void SaveBootstrap(TdxGuestInfo data) + { + string dir = GetConfigDir(); + string secretsDir = Path.Combine(dir, "secrets"); + Directory.CreateDirectory(secretsDir); + + // Save bootstrap data + string path = GetBootstrapPath(); + File.WriteAllText(path, JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true })); + + // Save key with 0600 file permissions + string keyPath = GetKeyPath(); + if (OperatingSystem.IsLinux()) + { + using var fs = new FileStream(keyPath, new FileStreamOptions + { + Mode = FileMode.Create, + Access = FileAccess.Write, + Share = FileShare.None, + UnixCreateMode = UnixFileMode.UserRead | UnixFileMode.UserWrite // 0600 + }); + fs.Write(_privateKey!.KeyBytes); + } + else + { + throw new PlatformNotSupportedException("TDX attestation is only supported on Linux"); + } + + _logger.Debug($"Saved TDX key to {keyPath}"); + } + + private string GetConfigDir() + { + string configPath = _config.ConfigPath; + if (configPath.StartsWith("~/")) + { + configPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + configPath[2..]); + } + return configPath; + } + + private string GetBootstrapPath() => Path.Combine(GetConfigDir(), "bootstrap.json"); + private string GetKeyPath() => Path.Combine(GetConfigDir(), "secrets", "priv.key"); +} + +/// +/// Null implementation of ITdxService for when TDX is disabled. +/// +internal sealed class NullTdxService : ITdxService +{ + public static readonly NullTdxService Instance = new(); + private NullTdxService() { } + + public bool IsBootstrapped => false; + public TdxGuestInfo? GetGuestInfo() => null; + public TdxGuestInfo Bootstrap() => throw new TdxException("TDX is not enabled"); + public TdxBlockHeaderSignature SignBlockHeader(BlockHeader blockHeader) => throw new TdxException("TDX is not enabled"); +} diff --git a/src/Nethermind/Nethermind.Taiko/Tdx/TdxsClient.cs b/src/Nethermind/Nethermind.Taiko/Tdx/TdxsClient.cs new file mode 100644 index 00000000000..081e3326219 --- /dev/null +++ b/src/Nethermind/Nethermind.Taiko/Tdx/TdxsClient.cs @@ -0,0 +1,92 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.IO; +using System.Net.Sockets; +using System.Text; +using System.Text.Json; +using Nethermind.Logging; + +namespace Nethermind.Taiko.Tdx; + +/// +/// Client for communicating with the tdxs daemon via Unix socket. +/// Protocol: JSON request/response over Unix socket. +/// +public class TdxsClient(ISurgeTdxConfig config, ILogManager logManager) : ITdxsClient +{ + private readonly ILogger _logger = logManager.GetClassLogger(); + + public byte[] Issue(byte[] userData, byte[] nonce) + { + var request = new + { + method = "issue", + data = new + { + userData = Convert.ToHexString(userData).ToLowerInvariant(), + nonce = Convert.ToHexString(nonce).ToLowerInvariant() + } + }; + + JsonElement response = SendRequest(request); + + if (response.TryGetProperty("error", out JsonElement error) && error.ValueKind != JsonValueKind.Null) + { + throw new TdxException($"Attestation service error: {error.GetString()}"); + } + + if (!response.TryGetProperty("data", out JsonElement data) || + !data.TryGetProperty("document", out JsonElement document)) + { + throw new TdxException("Invalid response: missing document"); + } + + return Convert.FromHexString(document.GetString()!); + } + + public TdxMetadata GetMetadata() + { + var request = new { method = "metadata", data = new { } }; + JsonElement response = SendRequest(request); + + if (response.TryGetProperty("error", out JsonElement error) && error.ValueKind != JsonValueKind.Null) + { + throw new TdxException($"Attestation service error: {error.GetString()}"); + } + + if (!response.TryGetProperty("data", out JsonElement data)) + { + throw new TdxException("Invalid response: missing data"); + } + + return new TdxMetadata + { + IssuerType = data.GetProperty("issuerType").GetString()!, + Metadata = data.TryGetProperty("metadata", out JsonElement meta) ? meta.Clone() : null + }; + } + + private JsonElement SendRequest(object request) + { + string socketPath = config.SocketPath; + + if (!File.Exists(socketPath)) + throw new TdxException($"TDX socket not found at {socketPath}"); + + using var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); + socket.Connect(new UnixDomainSocketEndPoint(socketPath)); + + using var stream = new NetworkStream(socket, ownsSocket: false); + + string requestJson = JsonSerializer.Serialize(request); + socket.Send(Encoding.UTF8.GetBytes(requestJson)); + socket.Shutdown(SocketShutdown.Send); + + return JsonSerializer.Deserialize(stream); + } +} + +public class TdxException(string message, Exception? innerException = null) + : Exception(message, innerException); diff --git a/src/Nethermind/Nethermind.Trie.Benchmark/CacheBenchmark.cs b/src/Nethermind/Nethermind.Trie.Benchmark/CacheBenchmark.cs index 1d17ea69086..ecd63cccdf2 100644 --- a/src/Nethermind/Nethermind.Trie.Benchmark/CacheBenchmark.cs +++ b/src/Nethermind/Nethermind.Trie.Benchmark/CacheBenchmark.cs @@ -1,3 +1,4 @@ +using System; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Jobs; using Nethermind.Core.Caching; @@ -55,7 +56,7 @@ public MemCountingCache Post_init_trie_cache_with_item_400() { MemCountingCache cache = new MemCountingCache(1024 * 1024, string.Empty); - cache.Set(Keccak.Zero, new byte[0]); + cache.Set(Keccak.Zero, Array.Empty()); return cache; } @@ -64,8 +65,8 @@ public MemCountingCache With_2_items_cache_504() { MemCountingCache cache = new MemCountingCache(1024 * 1024, string.Empty); - cache.Set(TestItem.KeccakA, new byte[0]); - cache.Set(TestItem.KeccakB, new byte[0]); + cache.Set(TestItem.KeccakA, Array.Empty()); + cache.Set(TestItem.KeccakB, Array.Empty()); return cache; } @@ -74,9 +75,9 @@ public MemCountingCache With_3_items_cache_608() { MemCountingCache cache = new MemCountingCache(1024 * 1024, string.Empty); - cache.Set(TestItem.KeccakA, new byte[0]); - cache.Set(TestItem.KeccakB, new byte[0]); - cache.Set(TestItem.KeccakC, new byte[0]); + cache.Set(TestItem.KeccakA, Array.Empty()); + cache.Set(TestItem.KeccakB, Array.Empty()); + cache.Set(TestItem.KeccakC, Array.Empty()); return cache; } @@ -85,10 +86,10 @@ public MemCountingCache Post_dictionary_growth_cache_824_and_136_lost() { MemCountingCache cache = new MemCountingCache(1024 * 1024, string.Empty); - cache.Set(TestItem.KeccakA, new byte[0]); - cache.Set(TestItem.KeccakB, new byte[0]); - cache.Set(TestItem.KeccakC, new byte[0]); - cache.Set(TestItem.KeccakD, new byte[0]); + cache.Set(TestItem.KeccakA, Array.Empty()); + cache.Set(TestItem.KeccakB, Array.Empty()); + cache.Set(TestItem.KeccakC, Array.Empty()); + cache.Set(TestItem.KeccakD, Array.Empty()); return cache; } } diff --git a/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs b/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs index 522e9776507..bf35cbb1232 100644 --- a/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/HexPrefixTests.cs @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using System.Linq; using NUnit.Framework; @@ -95,4 +96,378 @@ public void Nibbles_to_bytes_correct_output() byte[] result = Nibbles.ToBytes(nibbles); Assert.That(bytes, Is.EqualTo(result).AsCollection); } + + [Test] + public void GetPathArray_returns_cached_array_for_single_nibble() + { + // Test all valid single nibble values (0-15) + for (byte i = 0; i < 16; i++) + { + byte[] path1 = HexPrefix.GetArray(new byte[] { i }); + byte[] path2 = HexPrefix.GetArray(new byte[] { i }); + + // Should return the same cached instance + Assert.That(ReferenceEquals(path1, path2), Is.True, $"Single nibble {i} should return cached array"); + Assert.That(path1.Length, Is.EqualTo(1)); + Assert.That(path1[0], Is.EqualTo(i)); + } + } + + [Test] + public void GetPathArray_returns_cached_array_for_double_nibble() + { + // Test a sample of double nibble values + for (byte i = 0; i < 16; i++) + { + for (byte j = 0; j < 16; j++) + { + byte[] path1 = HexPrefix.GetArray(new byte[] { i, j }); + byte[] path2 = HexPrefix.GetArray(new byte[] { i, j }); + + // Should return the same cached instance + Assert.That(ReferenceEquals(path1, path2), Is.True, $"Double nibble [{i},{j}] should return cached array"); + Assert.That(path1.Length, Is.EqualTo(2)); + Assert.That(path1[0], Is.EqualTo(i)); + Assert.That(path1[1], Is.EqualTo(j)); + } + } + } + + [Test] + public void GetPathArray_returns_cached_array_for_triple_nibble() + { + // Test a sample of triple nibble values + for (byte i = 0; i < 4; i++) + { + for (byte j = 0; j < 4; j++) + { + for (byte k = 0; k < 4; k++) + { + byte[] path1 = HexPrefix.GetArray(new byte[] { i, j, k }); + byte[] path2 = HexPrefix.GetArray(new byte[] { i, j, k }); + + // Should return the same cached instance + Assert.That(ReferenceEquals(path1, path2), Is.True, $"Triple nibble [{i},{j},{k}] should return cached array"); + Assert.That(path1.Length, Is.EqualTo(3)); + Assert.That(path1[0], Is.EqualTo(i)); + Assert.That(path1[1], Is.EqualTo(j)); + Assert.That(path1[2], Is.EqualTo(k)); + } + } + } + } + + [Test] + public void GetPathArray_allocates_new_array_for_invalid_nibble_values() + { + // Test single nibble with value >= 16 + byte[] path1 = HexPrefix.GetArray(new byte[] { 16 }); + byte[] path2 = HexPrefix.GetArray(new byte[] { 16 }); + Assert.That(ReferenceEquals(path1, path2), Is.False, "Should allocate new array for nibble value >= 16"); + Assert.That(path1[0], Is.EqualTo(16)); + + // Test double nibble with value >= 16 + byte[] path3 = HexPrefix.GetArray(new byte[] { 5, 16 }); + byte[] path4 = HexPrefix.GetArray(new byte[] { 5, 16 }); + Assert.That(ReferenceEquals(path3, path4), Is.False, "Should allocate new array for nibble value >= 16"); + Assert.That(path3[0], Is.EqualTo(5)); + Assert.That(path3[1], Is.EqualTo(16)); + + // Test triple nibble with value >= 16 + byte[] path5 = HexPrefix.GetArray(new byte[] { 3, 7, 20 }); + byte[] path6 = HexPrefix.GetArray(new byte[] { 3, 7, 20 }); + Assert.That(ReferenceEquals(path5, path6), Is.False, "Should allocate new array for nibble value >= 16"); + Assert.That(path5[0], Is.EqualTo(3)); + Assert.That(path5[1], Is.EqualTo(7)); + Assert.That(path5[2], Is.EqualTo(20)); + } + + [Test] + public void GetPathArray_allocates_new_array_for_longer_paths() + { + // Test paths longer than 3 nibbles + byte[] path1 = HexPrefix.GetArray(new byte[] { 1, 2, 3, 4 }); + byte[] path2 = HexPrefix.GetArray(new byte[] { 1, 2, 3, 4 }); + + Assert.That(ReferenceEquals(path1, path2), Is.False, "Should allocate new array for paths longer than 3"); + Assert.That(path1.Length, Is.EqualTo(4)); + Assert.That(path1, Is.EqualTo(new byte[] { 1, 2, 3, 4 }).AsCollection); + } + + [Test] + public void GetPathArray_returns_empty_array_for_empty_path() + { + byte[] path = HexPrefix.GetArray(ReadOnlySpan.Empty); + Assert.That(path.Length, Is.EqualTo(0)); + } + + [Test] + public void PrependNibble_returns_cached_array_for_single_nibble_result() + { + // Prepending to empty array should return cached single nibble + for (byte i = 0; i < 16; i++) + { + byte[] result1 = HexPrefix.PrependNibble(i, []); + byte[] result2 = HexPrefix.PrependNibble(i, []); + + Assert.That(ReferenceEquals(result1, result2), Is.True, $"PrependNibble({i}, []) should return cached array"); + Assert.That(result1.Length, Is.EqualTo(1)); + Assert.That(result1[0], Is.EqualTo(i)); + } + } + + [Test] + public void PrependNibble_returns_cached_array_for_double_nibble_result() + { + // Prepending to single nibble array should return cached double nibble + for (byte i = 0; i < 16; i++) + { + for (byte j = 0; j < 16; j++) + { + byte[] result1 = HexPrefix.PrependNibble(i, new byte[] { j }); + byte[] result2 = HexPrefix.PrependNibble(i, new byte[] { j }); + + Assert.That(ReferenceEquals(result1, result2), Is.True, $"PrependNibble({i}, [{j}]) should return cached array"); + Assert.That(result1.Length, Is.EqualTo(2)); + Assert.That(result1[0], Is.EqualTo(i)); + Assert.That(result1[1], Is.EqualTo(j)); + } + } + } + + [Test] + public void PrependNibble_returns_cached_array_for_triple_nibble_result() + { + // Prepending to double nibble array should return cached triple nibble + for (byte i = 0; i < 4; i++) + { + for (byte j = 0; j < 4; j++) + { + for (byte k = 0; k < 4; k++) + { + byte[] result1 = HexPrefix.PrependNibble(i, new byte[] { j, k }); + byte[] result2 = HexPrefix.PrependNibble(i, new byte[] { j, k }); + + Assert.That(ReferenceEquals(result1, result2), Is.True, $"PrependNibble({i}, [{j},{k}]) should return cached array"); + Assert.That(result1.Length, Is.EqualTo(3)); + Assert.That(result1[0], Is.EqualTo(i)); + Assert.That(result1[1], Is.EqualTo(j)); + Assert.That(result1[2], Is.EqualTo(k)); + } + } + } + } + + [Test] + public void PrependNibble_allocates_new_array_for_invalid_nibble_values() + { + // Test with nibble value >= 16 + byte[] result1 = HexPrefix.PrependNibble(16, []); + byte[] result2 = HexPrefix.PrependNibble(16, []); + Assert.That(ReferenceEquals(result1, result2), Is.False, "Should allocate new array for nibble value >= 16"); + Assert.That(result1.Length, Is.EqualTo(1)); + Assert.That(result1[0], Is.EqualTo(16)); + } + + [Test] + public void PrependNibble_allocates_new_array_for_longer_results() + { + // Prepending to array of length 3+ should allocate new array + byte[] result1 = HexPrefix.PrependNibble(1, new byte[] { 2, 3, 4 }); + byte[] result2 = HexPrefix.PrependNibble(1, new byte[] { 2, 3, 4 }); + + Assert.That(ReferenceEquals(result1, result2), Is.False, "Should allocate new array for length > 3"); + Assert.That(result1.Length, Is.EqualTo(4)); + Assert.That(result1, Is.EqualTo(new byte[] { 1, 2, 3, 4 }).AsCollection); + } + + [Test] + public void ConcatNibbles_returns_empty_array_for_empty_inputs() + { + byte[] result = HexPrefix.ConcatNibbles([], []); + Assert.That(result.Length, Is.EqualTo(0)); + } + + [Test] + public void ConcatNibbles_returns_cached_array_for_single_nibble_result() + { + // Test [1] + [] = [1] + for (byte i = 0; i < 16; i++) + { + byte[] result1 = HexPrefix.ConcatNibbles(new byte[] { i }, []); + byte[] result2 = HexPrefix.ConcatNibbles(new byte[] { i }, []); + Assert.That(ReferenceEquals(result1, result2), Is.True, $"ConcatNibbles([{i}], []) should return cached array"); + Assert.That(result1.Length, Is.EqualTo(1)); + Assert.That(result1[0], Is.EqualTo(i)); + + // Test [] + [1] = [1] + byte[] result3 = HexPrefix.ConcatNibbles([], new byte[] { i }); + byte[] result4 = HexPrefix.ConcatNibbles([], new byte[] { i }); + Assert.That(ReferenceEquals(result3, result4), Is.True, $"ConcatNibbles([], [{i}]) should return cached array"); + Assert.That(result3.Length, Is.EqualTo(1)); + Assert.That(result3[0], Is.EqualTo(i)); + } + } + + [Test] + public void ConcatNibbles_returns_cached_array_for_double_nibble_result() + { + // Test various combinations that result in length 2 + for (byte i = 0; i < 16; i++) + { + for (byte j = 0; j < 16; j++) + { + // Test [i,j] + [] = [i,j] + byte[] result1 = HexPrefix.ConcatNibbles(new byte[] { i, j }, []); + byte[] result2 = HexPrefix.ConcatNibbles(new byte[] { i, j }, []); + Assert.That(ReferenceEquals(result1, result2), Is.True, $"ConcatNibbles([{i},{j}], []) should return cached array"); + Assert.That(result1.Length, Is.EqualTo(2)); + Assert.That(result1[0], Is.EqualTo(i)); + Assert.That(result1[1], Is.EqualTo(j)); + + // Test [i] + [j] = [i,j] + byte[] result3 = HexPrefix.ConcatNibbles(new byte[] { i }, new byte[] { j }); + byte[] result4 = HexPrefix.ConcatNibbles(new byte[] { i }, new byte[] { j }); + Assert.That(ReferenceEquals(result3, result4), Is.True, $"ConcatNibbles([{i}], [{j}]) should return cached array"); + Assert.That(result3.Length, Is.EqualTo(2)); + Assert.That(result3[0], Is.EqualTo(i)); + Assert.That(result3[1], Is.EqualTo(j)); + + // Test [] + [i,j] = [i,j] + byte[] result5 = HexPrefix.ConcatNibbles([], new byte[] { i, j }); + byte[] result6 = HexPrefix.ConcatNibbles([], new byte[] { i, j }); + Assert.That(ReferenceEquals(result5, result6), Is.True, $"ConcatNibbles([], [{i},{j}]) should return cached array"); + Assert.That(result5.Length, Is.EqualTo(2)); + Assert.That(result5[0], Is.EqualTo(i)); + Assert.That(result5[1], Is.EqualTo(j)); + } + } + } + + [Test] + public void ConcatNibbles_returns_cached_array_for_triple_nibble_result() + { + // Test various combinations that result in length 3 + for (byte i = 0; i < 4; i++) + { + for (byte j = 0; j < 4; j++) + { + for (byte k = 0; k < 4; k++) + { + // Test [i,j,k] + [] = [i,j,k] + byte[] result1 = HexPrefix.ConcatNibbles(new byte[] { i, j, k }, []); + byte[] result2 = HexPrefix.ConcatNibbles(new byte[] { i, j, k }, []); + Assert.That(ReferenceEquals(result1, result2), Is.True, $"ConcatNibbles([{i},{j},{k}], []) should return cached array"); + Assert.That(result1.Length, Is.EqualTo(3)); + Assert.That(result1[0], Is.EqualTo(i)); + Assert.That(result1[1], Is.EqualTo(j)); + Assert.That(result1[2], Is.EqualTo(k)); + + // Test [i] + [j,k] = [i,j,k] + byte[] result3 = HexPrefix.ConcatNibbles(new byte[] { i }, new byte[] { j, k }); + byte[] result4 = HexPrefix.ConcatNibbles(new byte[] { i }, new byte[] { j, k }); + Assert.That(ReferenceEquals(result3, result4), Is.True, $"ConcatNibbles([{i}], [{j},{k}]) should return cached array"); + Assert.That(result3.Length, Is.EqualTo(3)); + Assert.That(result3[0], Is.EqualTo(i)); + Assert.That(result3[1], Is.EqualTo(j)); + Assert.That(result3[2], Is.EqualTo(k)); + + // Test [i,j] + [k] = [i,j,k] + byte[] result5 = HexPrefix.ConcatNibbles(new byte[] { i, j }, new byte[] { k }); + byte[] result6 = HexPrefix.ConcatNibbles(new byte[] { i, j }, new byte[] { k }); + Assert.That(ReferenceEquals(result5, result6), Is.True, $"ConcatNibbles([{i},{j}], [{k}]) should return cached array"); + Assert.That(result5.Length, Is.EqualTo(3)); + Assert.That(result5[0], Is.EqualTo(i)); + Assert.That(result5[1], Is.EqualTo(j)); + Assert.That(result5[2], Is.EqualTo(k)); + + // Test [] + [i,j,k] = [i,j,k] + byte[] result7 = HexPrefix.ConcatNibbles([], new byte[] { i, j, k }); + byte[] result8 = HexPrefix.ConcatNibbles([], new byte[] { i, j, k }); + Assert.That(ReferenceEquals(result7, result8), Is.True, $"ConcatNibbles([], [{i},{j},{k}]) should return cached array"); + Assert.That(result7.Length, Is.EqualTo(3)); + Assert.That(result7[0], Is.EqualTo(i)); + Assert.That(result7[1], Is.EqualTo(j)); + Assert.That(result7[2], Is.EqualTo(k)); + } + } + } + } + + [Test] + public void ConcatNibbles_allocates_new_array_for_longer_results() + { + // Test combinations that result in length > 3 + byte[] result1 = HexPrefix.ConcatNibbles(new byte[] { 1, 2 }, new byte[] { 3, 4 }); + byte[] result2 = HexPrefix.ConcatNibbles(new byte[] { 1, 2 }, new byte[] { 3, 4 }); + + Assert.That(ReferenceEquals(result1, result2), Is.False, "Should allocate new array for length > 3"); + Assert.That(result1.Length, Is.EqualTo(4)); + Assert.That(result1, Is.EqualTo(new byte[] { 1, 2, 3, 4 }).AsCollection); + + // Test longer arrays + byte[] result3 = HexPrefix.ConcatNibbles(new byte[] { 1, 2, 3 }, new byte[] { 4, 5 }); + byte[] result4 = HexPrefix.ConcatNibbles(new byte[] { 1, 2, 3 }, new byte[] { 4, 5 }); + + Assert.That(ReferenceEquals(result3, result4), Is.False, "Should allocate new array for length > 3"); + Assert.That(result3.Length, Is.EqualTo(5)); + Assert.That(result3, Is.EqualTo(new byte[] { 1, 2, 3, 4, 5 }).AsCollection); + } + + [Test] + public void ConcatNibbles_preserves_values_correctly() + { + // Test that values are preserved in the correct order + byte[] result = HexPrefix.ConcatNibbles(new byte[] { 15, 14, 13 }, new byte[] { 12, 11, 10 }); + Assert.That(result, Is.EqualTo(new byte[] { 15, 14, 13, 12, 11, 10 }).AsCollection); + } + + [Test] + public void ConcatNibbles_allocates_new_array_for_invalid_nibble_values() + { + // Test single nibble with value >= 16 + byte[] result1 = HexPrefix.ConcatNibbles(new byte[] { 16 }, []); + byte[] result2 = HexPrefix.ConcatNibbles(new byte[] { 16 }, []); + Assert.That(ReferenceEquals(result1, result2), Is.False, "Should allocate new array for nibble value >= 16"); + Assert.That(result1.Length, Is.EqualTo(1)); + Assert.That(result1[0], Is.EqualTo(16)); + + byte[] result3 = HexPrefix.ConcatNibbles([], new byte[] { 16 }); + byte[] result4 = HexPrefix.ConcatNibbles([], new byte[] { 16 }); + Assert.That(ReferenceEquals(result3, result4), Is.False, "Should allocate new array for nibble value >= 16"); + Assert.That(result3.Length, Is.EqualTo(1)); + Assert.That(result3[0], Is.EqualTo(16)); + + // Test double nibble with value >= 16 + byte[] result5 = HexPrefix.ConcatNibbles(new byte[] { 5 }, new byte[] { 16 }); + byte[] result6 = HexPrefix.ConcatNibbles(new byte[] { 5 }, new byte[] { 16 }); + Assert.That(ReferenceEquals(result5, result6), Is.False, "Should allocate new array for nibble value >= 16"); + Assert.That(result5.Length, Is.EqualTo(2)); + Assert.That(result5[0], Is.EqualTo(5)); + Assert.That(result5[1], Is.EqualTo(16)); + + byte[] result7 = HexPrefix.ConcatNibbles(new byte[] { 16, 5 }, []); + byte[] result8 = HexPrefix.ConcatNibbles(new byte[] { 16, 5 }, []); + Assert.That(ReferenceEquals(result7, result8), Is.False, "Should allocate new array for nibble value >= 16"); + Assert.That(result7.Length, Is.EqualTo(2)); + Assert.That(result7[0], Is.EqualTo(16)); + Assert.That(result7[1], Is.EqualTo(5)); + + // Test triple nibble with value >= 16 + byte[] result9 = HexPrefix.ConcatNibbles(new byte[] { 3, 7 }, new byte[] { 20 }); + byte[] result10 = HexPrefix.ConcatNibbles(new byte[] { 3, 7 }, new byte[] { 20 }); + Assert.That(ReferenceEquals(result9, result10), Is.False, "Should allocate new array for nibble value >= 16"); + Assert.That(result9.Length, Is.EqualTo(3)); + Assert.That(result9[0], Is.EqualTo(3)); + Assert.That(result9[1], Is.EqualTo(7)); + Assert.That(result9[2], Is.EqualTo(20)); + + byte[] result11 = HexPrefix.ConcatNibbles(new byte[] { 3 }, new byte[] { 16, 7 }); + byte[] result12 = HexPrefix.ConcatNibbles(new byte[] { 3 }, new byte[] { 16, 7 }); + Assert.That(ReferenceEquals(result11, result12), Is.False, "Should allocate new array for nibble value >= 16"); + Assert.That(result11.Length, Is.EqualTo(3)); + Assert.That(result11[0], Is.EqualTo(3)); + Assert.That(result11[1], Is.EqualTo(16)); + Assert.That(result11[2], Is.EqualTo(7)); + } } diff --git a/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs b/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs index 72caff99b39..51702876eec 100644 --- a/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/NodeStorageTests.cs @@ -107,7 +107,7 @@ public void When_EntryOfDifferentScheme_Should_StillBeAbleToRead() [TestCase(true, 5, "0211111111111111111111111111111111111111111111111111111111111111112222200000000000053333333333333333333333333333333333333333333333333333333333333333")] [TestCase(true, 10, "02111111111111111111111111111111111111111111111111111111111111111122222222220000000a3333333333333333333333333333333333333333333333333333333333333333")] [TestCase(true, 32, "0211111111111111111111111111111111111111111111111111111111111111112222222222222222203333333333333333333333333333333333333333333333333333333333333333")] - public void Test_HalfPathEncodng(bool hasAddress, int pathLength, string expectedKey) + public void Test_HalfPathEncoding(bool hasAddress, int pathLength, string expectedKey) { if (_currentKeyScheme == INodeStorage.KeyScheme.Hash) return; diff --git a/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs b/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs index 29cf8d7767f..ed8c23ee264 100644 --- a/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/OverlayTrieStoreTests.cs @@ -37,36 +37,36 @@ public void TrieStore_OverlayExistingStore() ITrieStore overlayStore = new OverlayTrieStore(readOnlyDbProvider.GetDb(DbNames.State), existingStore.AsReadOnly()); // Modify the overlay tree - PatriciaTree overlayedTree = new PatriciaTree(overlayStore, LimboLogs.Instance); - overlayedTree.RootHash = originalRoot; - overlayedTree.Get(TestItem.Keccaks[0].Bytes).ToArray().Should().BeEquivalentTo(TestItem.Keccaks[0].BytesToArray()); - overlayedTree.Get(TestItem.Keccaks[1].Bytes).ToArray().Should().BeEquivalentTo(TestItem.Keccaks[1].BytesToArray()); - overlayedTree.Set(TestItem.Keccaks[2].Bytes, TestItem.Keccaks[2].BytesToArray()); - overlayedTree.Set(TestItem.Keccaks[3].Bytes, TestItem.Keccaks[3].BytesToArray()); - overlayedTree.Commit(); - Hash256 newRoot = overlayedTree.RootHash; + PatriciaTree overlaidTree = new PatriciaTree(overlayStore, LimboLogs.Instance); + overlaidTree.RootHash = originalRoot; + overlaidTree.Get(TestItem.Keccaks[0].Bytes).ToArray().Should().BeEquivalentTo(TestItem.Keccaks[0].BytesToArray()); + overlaidTree.Get(TestItem.Keccaks[1].Bytes).ToArray().Should().BeEquivalentTo(TestItem.Keccaks[1].BytesToArray()); + overlaidTree.Set(TestItem.Keccaks[2].Bytes, TestItem.Keccaks[2].BytesToArray()); + overlaidTree.Set(TestItem.Keccaks[3].Bytes, TestItem.Keccaks[3].BytesToArray()); + overlaidTree.Commit(); + Hash256 newRoot = overlaidTree.RootHash; // Verify that the db is modified readOnlyDbProvider.GetDb(DbNames.State).GetAllKeys().Count().Should().NotBe(originalKeyCount); // It can read the modified db - overlayedTree = new PatriciaTree(overlayStore, LimboLogs.Instance); - overlayedTree.RootHash = newRoot; - overlayedTree.Get(TestItem.Keccaks[0].Bytes).ToArray().Should().BeEquivalentTo(TestItem.Keccaks[0].BytesToArray()); - overlayedTree.Get(TestItem.Keccaks[1].Bytes).ToArray().Should().BeEquivalentTo(TestItem.Keccaks[1].BytesToArray()); - overlayedTree.Get(TestItem.Keccaks[2].Bytes).ToArray().Should().BeEquivalentTo(TestItem.Keccaks[2].BytesToArray()); - overlayedTree.Get(TestItem.Keccaks[3].Bytes).ToArray().Should().BeEquivalentTo(TestItem.Keccaks[3].BytesToArray()); + overlaidTree = new PatriciaTree(overlayStore, LimboLogs.Instance); + overlaidTree.RootHash = newRoot; + overlaidTree.Get(TestItem.Keccaks[0].Bytes).ToArray().Should().BeEquivalentTo(TestItem.Keccaks[0].BytesToArray()); + overlaidTree.Get(TestItem.Keccaks[1].Bytes).ToArray().Should().BeEquivalentTo(TestItem.Keccaks[1].BytesToArray()); + overlaidTree.Get(TestItem.Keccaks[2].Bytes).ToArray().Should().BeEquivalentTo(TestItem.Keccaks[2].BytesToArray()); + overlaidTree.Get(TestItem.Keccaks[3].Bytes).ToArray().Should().BeEquivalentTo(TestItem.Keccaks[3].BytesToArray()); // Now we clear it readOnlyDbProvider.ClearTempChanges(); - // It should throw because the overlayed keys are now missing. + // It should throw because the overlaid keys are now missing. readOnlyDbProvider.GetDb(DbNames.State).GetAllKeys().Count().Should().Be(originalKeyCount); - overlayedTree = new PatriciaTree(overlayStore, LimboLogs.Instance); + overlaidTree = new PatriciaTree(overlayStore, LimboLogs.Instance); Action act = () => { - overlayedTree.RootHash = newRoot; - overlayedTree.Get(TestItem.Keccaks[0].Bytes).ToArray().Should() + overlaidTree.RootHash = newRoot; + overlaidTree.Get(TestItem.Keccaks[0].Bytes).ToArray().Should() .BeEquivalentTo(TestItem.Keccaks[0].BytesToArray()); }; act.Should().Throw(); // The root is now missing. diff --git a/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs b/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs index 9d372ea1824..a5b93e6758c 100644 --- a/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/Pruning/TreeStoreTests.cs @@ -20,6 +20,7 @@ using Nethermind.Specs; using Nethermind.Evm.State; using Nethermind.State; +using Nethermind.State.Healing; using Nethermind.Trie.Pruning; using NSubstitute; using NUnit.Framework; @@ -972,6 +973,7 @@ public async Task Will_Trigger_ReorgBoundaryEvent_On_Prune() if (i > 4) { + fullTrieStore.WaitForPruning(); Assert.That(() => reorgBoundary, Is.EqualTo(i - 3).After(10000, 100)); } else @@ -1035,8 +1037,7 @@ public void When_SomeKindOfNonResolvedNotInMainWorldState_OnPrune_DoNotDeleteNod }); WorldState worldState = new WorldState( - fullTrieStore, - memDbProvider.CodeDb, + new TrieStoreScopeProvider(fullTrieStore, memDbProvider.CodeDb, _logManager), LimboLogs.Instance); // Simulate some kind of cache access which causes unresolved node to remain. @@ -1057,7 +1058,8 @@ public void When_SomeKindOfNonResolvedNotInMainWorldState_OnPrune_DoNotDeleteNod (Hash256, ValueHash256) SetupStartingState() { - WorldState worldState = new WorldState(new TestRawTrieStore(nodeStorage), memDbProvider.CodeDb, LimboLogs.Instance); + WorldState worldState = new WorldState( + new TrieStoreScopeProvider(new TestRawTrieStore(nodeStorage), memDbProvider.CodeDb, LimboLogs.Instance), LimboLogs.Instance); using var _ = worldState.BeginScope(IWorldState.PreGenesis); worldState.CreateAccountIfNotExists(address, UInt256.One); worldState.Set(new StorageCell(address, slot), TestItem.KeccakB.BytesToArray()); @@ -1220,6 +1222,55 @@ void WriteRandomData(int seed) fullTrieStore.CachedNodesCount.Should().Be(36); } + [Test] + public void Can_Prune_StorageTreeRoot() + { + if (_scheme == INodeStorage.KeyScheme.Hash) Assert.Ignore("Not applicable for hash"); + + MemDb memDb = new(); + TestPruningStrategy testPruningStrategy = new TestPruningStrategy( + shouldPrune: false, + deleteObsoleteKeys: true + ); + + TrieStore fullTrieStore = CreateTrieStore( + kvStore: memDb, + pruningStrategy: testPruningStrategy, + persistenceStrategy: No.Persistence, + pruningConfig: new PruningConfig() + { + PruningBoundary = 4, + PrunePersistedNodePortion = 1.0, + DirtyNodeShardBit = 4, + MaxBufferedCommitCount = 0, + TrackPastKeys = true + }); + + StateTree ptree = new StateTree(fullTrieStore.GetTrieStore(null), LimboLogs.Instance); + + void WriteRandomData(int seed) + { + Hash256 address = Keccak.Compute(seed.ToBigEndianByteArray()); + StorageTree storageTree = new StorageTree(fullTrieStore.GetTrieStore(address), LimboLogs.Instance); + storageTree.Set(Keccak.Compute((seed * 2).ToBigEndianByteArray()).Bytes, Keccak.Compute(seed.ToBigEndianByteArray()).BytesToArray()); + storageTree.Commit(); + + ptree.Set(address, new Account(0, 0, storageTree.RootHash, Keccak.OfAnEmptyString)); + ptree.Commit(); + } + + for (int i = 0; i < 16; i++) + { + using (fullTrieStore.BeginBlockCommit(i)) + { + WriteRandomData(i); + } + fullTrieStore.PersistAndPruneDirtyCache(); + fullTrieStore.PrunePersistedNodes(); + } + fullTrieStore.CachedNodesCount.Should().Be(19); + } + [TestCase(27, 1000, 31, 7)] [TestCase(27, 1000, 2, 2)] public void Will_HaveConsistentState_AfterPrune(int possibleSeed, int totalBlock, int snapshotInterval, int prunePersistedInterval) @@ -1304,7 +1355,7 @@ void VerifyAllTrie() // Persist sometimes testPruningStrategy.ShouldPruneEnabled = i % snapshotInterval == 0; testPruningStrategy.ShouldPrunePersistedEnabled = i % prunePersistedInterval == 0; - fullTrieStore.SyncPruneCheck(); + fullTrieStore.SyncPruneQueue(); testPruningStrategy.ShouldPruneEnabled = false; testPruningStrategy.ShouldPrunePersistedEnabled = false; @@ -1391,7 +1442,7 @@ void WriteRandomData(int seed) Task persistTask = Task.Run(() => { testPruningStrategy.ShouldPruneEnabled = true; - fullTrieStore.SyncPruneCheck(); + fullTrieStore.SyncPruneQueue(); testPruningStrategy.ShouldPruneEnabled = false; }); Thread.Sleep(100); @@ -1423,7 +1474,7 @@ void WriteRandomData(int seed) // Persisted nodes should be from block 12 testPruningStrategy.ShouldPruneEnabled = true; - fullTrieStore.SyncPruneCheck(); + fullTrieStore.SyncPruneQueue(); testPruningStrategy.ShouldPruneEnabled = false; fullTrieStore.LastPersistedBlockNumber.Should().Be(12); @@ -1492,7 +1543,7 @@ void VerifyAllTrieExceptGenesis() } } - // Start from genesis for simplicty + // Start from genesis for simplicity BlockHeader baseBlock = Build.A.BlockHeader.WithStateRoot(Keccak.EmptyTreeHash).TestObject; int blockNum = 100; int lastNRoots = 0; diff --git a/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs b/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs index 7da686fe81d..223282c6604 100644 --- a/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/PruningScenariosTests.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using FluentAssertions; +using FluentAssertions.Extensions; using Nethermind.Core; using Nethermind.Core.Collections; using Nethermind.Core.Crypto; @@ -227,10 +228,10 @@ private PruningContext(TestPruningStrategy pruningStrategy, IPersistenceStrategy _pruningConfig = pruningConfig ?? new PruningConfig() { TrackPastKeys = false }; _finalizedStateProvider = new TestFinalizedStateProvider(_pruningConfig.PruningBoundary); - _trieStore = new TrieStore( - new NodeStorage(_stateDb), _pruningStrategy, _persistenceStrategy, _finalizedStateProvider, _pruningConfig, _logManager); + _trieStore = new TrieStore(new NodeStorage(_stateDb), _pruningStrategy, _persistenceStrategy, _finalizedStateProvider, _pruningConfig, _logManager); _finalizedStateProvider.TrieStore = _trieStore; - _stateProvider = new WorldState(_trieStore, _codeDb, _logManager); + _stateProvider = new WorldState( + new TrieStoreScopeProvider(_trieStore, _codeDb, _logManager), _logManager); _stateReader = new StateReader(_trieStore, _codeDb, _logManager); _worldStateCloser = _stateProvider.BeginScope(IWorldState.PreGenesis); } @@ -428,15 +429,20 @@ public PruningContext CommitAndWaitForPruning() return Commit(waitForPruning: true); } + public PruningContext SyncPruneCheck() + { + _trieStore.SyncPruneQueue(); + return this; + } + public PruningContext DisposeAndRecreate() { _worldStateCloser!.Dispose(); _trieStore.Dispose(); - TestFinalizedStateProvider finalizedStateProvider = new TestFinalizedStateProvider(_pruningConfig.PruningBoundary); _trieStore = new TrieStore(new NodeStorage(_stateDb), _pruningStrategy, _persistenceStrategy, finalizedStateProvider, _pruningConfig, _logManager); - finalizedStateProvider.TrieStore = _trieStore; - _stateProvider = new WorldState(_trieStore, _codeDb, _logManager); + _stateProvider = new WorldState( + new TrieStoreScopeProvider(_trieStore, _codeDb, _logManager), _logManager); _stateReader = new StateReader(_trieStore, _codeDb, _logManager); return this; } @@ -1349,5 +1355,85 @@ public async Task Can_ContinueEvenWhenPruningIsBlocked(int maxBufferedCommit, in } } + + [Test] + public async Task Can_BeginScope_During_Persisted_Node_Pruning() + { + PruningContext ctx = PruningContext.InMemory + .WithPruningConfig((cfg) => + { + cfg.DirtyNodeShardBit = 12; // More shard, deliberately make it slow + }) + .WithMaxDepth(128) + .WithPersistedMemoryLimit(50.KiB()) + .WithPrunePersistedNodeParameter(1, 0.01) + .TurnOffPrune(); + + // Make a big state + for (int i = 0; i < 16_000; i++) + { + ctx.SetAccountBalance(i, (UInt256)i); + } + ctx.Commit(); + + // Make the big state go to pruning boundary + for (int i = 0; i < 128; i++) + { + ctx.Commit(); + } + + ctx + .AssertThatCachedPersistedNodeCountIs(0) + .TurnOnPrune() + .SyncPruneCheck() + .TurnOffPrune() + .AssertThatCachedPersistedNodeCountIs(21745L); + + // Make a different big state + for (int i = 0; i < 16_000; i++) + { + ctx.SetAccountBalance(i, (UInt256)(i + 1)); + } + ctx.Commit(); + + // Move pruning boundary again + for (int i = 0; i < 128 - 1; i++) + { + ctx.Commit(); + } + + // Make the exact same big state as the first try + for (int i = 0; i < 16_000; i++) + { + ctx.SetAccountBalance(i, (UInt256)i); + } + ctx.Commit(); + + ctx.TurnOnPrune(); + + TimeSpan syncPruneCheckTime = TimeSpan.Zero; + Task pruneTime = Task.Run(() => + { + long sw = Stopwatch.GetTimestamp(); + ctx.SyncPruneCheck(); + ctx.TurnOffPrune(); + ctx.AssertThatCachedPersistedNodeCountIs(21747L); + syncPruneCheckTime = Stopwatch.GetElapsedTime(sw); + }); + + long sw = Stopwatch.GetTimestamp(); + for (int i = 0; i < 1000; i++) + { + ctx.ExitScope(); + ctx.EnterScope(); + } + + TimeSpan exitEnterScopeTime = Stopwatch.GetElapsedTime(sw); + + await pruneTime; + + Assert.That(syncPruneCheckTime, Is.LessThan(5.Seconds())); // Does not hang + Assert.That(exitEnterScopeTime, Is.LessThan(syncPruneCheckTime)); // Is not blocked by prune + } } } diff --git a/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs index 20567786340..df8fcf6dac1 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrieNodeResolverWithReadFlagsTests.cs @@ -29,7 +29,7 @@ public void LoadRlp_shouldPassTheFlag() } [Test] - public void LoadRlp_combine_passed_flaeg() + public void LoadRlp_combine_passed_flag() { ReadFlags theFlags = ReadFlags.HintCacheMiss; TestMemDb memDb = new(); diff --git a/src/Nethermind/Nethermind.Trie.Test/TrieNodeTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrieNodeTests.cs index c63615da3a0..3338f249dda 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrieNodeTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrieNodeTests.cs @@ -481,7 +481,7 @@ public void Can_encode_branch_with_nulls() public void Is_child_dirty_on_extension_when_child_is_null_returns_false() { TrieNode node = new(NodeType.Extension); - Assert.That(node.IsChildDirty(0), Is.False); + Assert.That(node.TryGetDirtyChild(0, out TrieNode? childNode), Is.False); } [Test] @@ -489,7 +489,7 @@ public void Is_child_dirty_on_extension_when_child_is_null_node_returns_false() { TrieNode node = new(NodeType.Extension); node.SetChild(0, null); - Assert.That(node.IsChildDirty(0), Is.False); + Assert.That(node.TryGetDirtyChild(0, out TrieNode? dirtyChild), Is.False); } [Test] @@ -498,7 +498,7 @@ public void Is_child_dirty_on_extension_when_child_is_not_dirty_returns_false() TrieNode node = new(NodeType.Extension); TrieNode cleanChild = new(NodeType.Leaf, Keccak.Zero); node.SetChild(0, cleanChild); - Assert.That(node.IsChildDirty(0), Is.False); + Assert.That(node.TryGetDirtyChild(0, out TrieNode? dirtyChild), Is.False); } [Test] @@ -507,7 +507,7 @@ public void Is_child_dirty_on_extension_when_child_is_dirty_returns_true() TrieNode node = new(NodeType.Extension); TrieNode dirtyChild = new(NodeType.Leaf); node.SetChild(0, dirtyChild); - Assert.That(node.IsChildDirty(0), Is.True); + Assert.That(node.TryGetDirtyChild(0, out TrieNode? _), Is.True); } [Test] @@ -797,7 +797,7 @@ public void Extension_child_as_keccak_not_dirty() trieNode.SetChild(0, child); trieNode.PrunePersistedRecursively(1); - trieNode.IsChildDirty(0).Should().Be(false); + trieNode.TryGetDirtyChild(0, out TrieNode? dirtyChild).Should().Be(false); } [TestCase(true)] diff --git a/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs b/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs index 5a4ebb0887d..cc8bbf89371 100644 --- a/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/TrieTests.cs @@ -1119,7 +1119,7 @@ public void Fuzz_accounts_with_storage( } else { - accounts[i] = TestItem.GenerateRandomAccount(); + accounts[i] = TestItem.GenerateRandomAccount(_random); } addresses[i] = TestItem.GetRandomAddress(_random); diff --git a/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs b/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs index 01b56210412..2fd6f53b2b6 100644 --- a/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs +++ b/src/Nethermind/Nethermind.Trie.Test/VisitingTests.cs @@ -73,10 +73,10 @@ public void Visitors_storage(VisitingOptions options, INodeStorage.KeyScheme sch var blockCommit = trieStore.BeginBlockCommit(0); - for (int outi = 0; outi < 64; outi++) + for (int outerIndex = 0; outerIndex < 64; outerIndex++) { ValueHash256 stateKey = default; - stateKey.BytesAsSpan[outi / 2] = (byte)(1 << (4 * (1 - outi % 2))); + stateKey.BytesAsSpan[outerIndex / 2] = (byte)(1 << (4 * (1 - outerIndex % 2))); StorageTree storage = new(trieStore.GetTrieStore(stateKey.ToCommitment()), LimboLogs.Instance); for (int i = 0; i < 64; i++) diff --git a/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs b/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs new file mode 100644 index 00000000000..3b3e4b1b2a4 --- /dev/null +++ b/src/Nethermind/Nethermind.Trie.Test/VisitorProgressTrackerTests.cs @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Threading.Tasks; +using FluentAssertions; +using Nethermind.Logging; +using NUnit.Framework; + +namespace Nethermind.Trie.Test; + +public class VisitorProgressTrackerTests +{ + [Test] + public void OnNodeVisited_TracksProgress_AtLevel0() + { + // Arrange + var tracker = new VisitorProgressTracker("Test", LimboLogs.Instance, reportingInterval: 1000); + + // Act - visit leaf paths with single nibble, each covers 16^3 = 4096 level-3 nodes + // Visit half the keyspace (8 out of 16) = 8 * 4096 = 32768 out of 65536 = 50% + for (int i = 0; i < 8; i++) + { + TreePath path = TreePath.FromNibble(new byte[] { (byte)i }); + tracker.OnNodeVisited(path, isStorage: false, isLeaf: true); + } + + // Assert - should be ~50% progress + double progress = tracker.GetProgress(); + progress.Should().BeApproximately(0.5, 0.01); + } + + [Test] + public void OnNodeVisited_UsesDeepestLevelWithCoverage() + { + // Arrange + var tracker = new VisitorProgressTracker("Test", LimboLogs.Instance, reportingInterval: 100000); + + // Act - visit leaf nodes at 2-nibble depth + // Each leaf at depth 2 covers 16^(3-2+1) = 16^2 = 256 level-3 nodes + // Visit 64 leaves = 64 * 256 = 16384 out of 65536 = 25% + for (int i = 0; i < 64; i++) + { + TreePath path = TreePath.FromNibble(new byte[] { (byte)(i / 16), (byte)(i % 16) }); + tracker.OnNodeVisited(path, isStorage: false, isLeaf: true); + } + + // Assert - should be ~25% progress + double progress = tracker.GetProgress(); + progress.Should().BeApproximately(0.25, 0.01); + } + + [Test] + public void OnNodeVisited_IsThreadSafe() + { + // Arrange + var tracker = new VisitorProgressTracker("Test", LimboLogs.Instance, reportingInterval: 100000); + const int threadCount = 8; + const int nodesPerThread = 1000; + + // Act - visit nodes concurrently + Parallel.For(0, threadCount, threadId => + { + for (int i = 0; i < nodesPerThread; i++) + { + int nibble1 = (threadId * nodesPerThread + i) / 4096 % 16; + int nibble2 = (threadId * nodesPerThread + i) / 256 % 16; + int nibble3 = (threadId * nodesPerThread + i) / 16 % 16; + int nibble4 = (threadId * nodesPerThread + i) % 16; + TreePath path = TreePath.FromNibble(new byte[] { (byte)nibble1, (byte)nibble2, (byte)nibble3, (byte)nibble4 }); + tracker.OnNodeVisited(path); + } + }); + + // Assert - node count should match + tracker.NodeCount.Should().Be(threadCount * nodesPerThread); + } + + [Test] + public void OnNodeVisited_ProgressIncreases_WithinLevel() + { + // Arrange + var tracker = new VisitorProgressTracker("Test", LimboLogs.Instance, reportingInterval: 100000); + + // Act - visit leaf nodes with single nibble paths + // Each covers 16^3 = 4096 level-3 nodes + double lastProgress = 0; + for (int i = 0; i < 16; i++) + { + TreePath path = TreePath.FromNibble(new byte[] { (byte)i }); + tracker.OnNodeVisited(path, isStorage: false, isLeaf: true); + + double progress = tracker.GetProgress(); + progress.Should().BeGreaterThanOrEqualTo(lastProgress); + lastProgress = progress; + } + + // Assert - after visiting all 16 single-nibble leaves, progress should be 100% + tracker.GetProgress().Should().Be(1.0); + } + + [Test] + public void Finish_SetsProgressTo100() + { + // Arrange + var tracker = new VisitorProgressTracker("Test", LimboLogs.Instance, reportingInterval: 100000); + TreePath path = TreePath.FromNibble(new byte[] { 0, 0, 0, 0 }); + tracker.OnNodeVisited(path); + + // Act + tracker.Finish(); + + // Assert - GetProgress still returns actual progress, but logger shows 100% + // (We can't easily test logger output, so just verify Finish doesn't throw) + tracker.NodeCount.Should().Be(1); + } + + [Test] + public void OnNodeVisited_HandlesShortPaths() + { + // Arrange + var tracker = new VisitorProgressTracker("Test", LimboLogs.Instance, reportingInterval: 100000); + + // Act - visit paths with fewer than 4 nibbles + TreePath path1 = TreePath.FromNibble(new byte[] { 0 }); + TreePath path2 = TreePath.FromNibble(new byte[] { 1, 2 }); + TreePath path3 = TreePath.FromNibble(new byte[] { 3, 4, 5 }); + + tracker.OnNodeVisited(path1); + tracker.OnNodeVisited(path2); + tracker.OnNodeVisited(path3); + + // Assert - should not throw and should track nodes + tracker.NodeCount.Should().Be(3); + } + + [Test] + public void OnNodeVisited_HandlesEmptyPath() + { + // Arrange + var tracker = new VisitorProgressTracker("Test", LimboLogs.Instance, reportingInterval: 100000); + + // Act + TreePath path = TreePath.Empty; + tracker.OnNodeVisited(path); + + // Assert + tracker.NodeCount.Should().Be(1); + tracker.GetProgress().Should().Be(0); // Empty path doesn't contribute to progress + } +} diff --git a/src/Nethermind/Nethermind.Trie/HexPrefix.cs b/src/Nethermind/Nethermind.Trie/HexPrefix.cs index 739db8afa83..e7babfa09bc 100644 --- a/src/Nethermind/Nethermind.Trie/HexPrefix.cs +++ b/src/Nethermind/Nethermind.Trie/HexPrefix.cs @@ -6,81 +6,299 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace Nethermind.Trie +namespace Nethermind.Trie; + +public static class HexPrefix { - public static class HexPrefix + private static readonly byte[][] SingleNibblePaths = CreateSingleNibblePaths(); + private static readonly byte[][] DoubleNibblePaths = CreateDoubleNibblePaths(); + private static readonly byte[][] TripleNibblePaths = CreateTripleNibblePaths(); + + public static int ByteLength(byte[] path) => path.Length / 2 + 1; + + public static void CopyToSpan(byte[] path, bool isLeaf, Span output) { - public static int ByteLength(byte[] path) => path.Length / 2 + 1; + ArgumentOutOfRangeException.ThrowIfNotEqual(ByteLength(path), output.Length, nameof(output)); - public static void CopyToSpan(byte[] path, bool isLeaf, Span output) + output[0] = (byte)(isLeaf ? 0x20 : 0x00); + if (path.Length % 2 != 0) { - if (output.Length != ByteLength(path)) throw new ArgumentOutOfRangeException(nameof(output)); + output[0] += (byte)(0x10 + path[0]); + } - output[0] = (byte)(isLeaf ? 0x20 : 0x00); - if (path.Length % 2 != 0) - { - output[0] += (byte)(0x10 + path[0]); - } + for (int i = 0; i < path.Length - 1; i += 2) + { + output[i / 2 + 1] = + path.Length % 2 == 0 + ? (byte)(16 * path[i] + path[i + 1]) + : (byte)(16 * path[i + 1] + path[i + 2]); + } + } - for (int i = 0; i < path.Length - 1; i += 2) - { - output[i / 2 + 1] = - path.Length % 2 == 0 - ? (byte)(16 * path[i] + path[i + 1]) - : (byte)(16 * path[i + 1] + path[i + 2]); - } + public static byte[] ToBytes(byte[] path, bool isLeaf) + { + byte[] output = new byte[path.Length / 2 + 1]; + + CopyToSpan(path, isLeaf, output); + + return output; + } + + public static (byte[] key, bool isLeaf) FromBytes(ReadOnlySpan bytes) + { + bool isEven = (bytes[0] & 16) == 0; + bool isLeaf = bytes[0] >= 32; + int nibblesCount = bytes.Length * 2 - (isEven ? 2 : 1); + // Return cached arrays for small paths + switch (nibblesCount) + { + case 0: + return ([], isLeaf); + case 1: + // !isEven, bytes.Length == 1 + return (Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(SingleNibblePaths), bytes[0] & 0xF), isLeaf); + case 2: + // isEven, bytes.Length == 2 - byte value IS the index + return (Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(DoubleNibblePaths), bytes[1]), isLeaf); + case 3: + // !isEven, bytes.Length == 2 + return (Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(TripleNibblePaths), ((bytes[0] & 0xF) << 8) | bytes[1]), isLeaf); } - public static byte[] ToBytes(byte[] path, bool isLeaf) + // Longer paths - allocate + byte[] path = new byte[nibblesCount]; + Span span = new(path); + if (!isEven) + { + span[0] = (byte)(bytes[0] & 0xF); + span = span[1..]; + } + bytes = bytes[1..]; + Span nibbles = MemoryMarshal.CreateSpan( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + span.Length / 2); + Debug.Assert(nibbles.Length == bytes.Length); + ref byte byteRef = ref MemoryMarshal.GetReference(bytes); + ref ushort lookup16 = ref MemoryMarshal.GetArrayDataReference(Lookup16); + for (int i = 0; i < nibbles.Length; i++) { - byte[] output = new byte[path.Length / 2 + 1]; + nibbles[i] = Unsafe.Add(ref lookup16, Unsafe.Add(ref byteRef, i)); + } + return (path, isLeaf); + } - CopyToSpan(path, isLeaf, output); + private static readonly ushort[] Lookup16 = CreateLookup16(); - return output; + private static ushort[] CreateLookup16() + { + ushort[] result = new ushort[256]; + for (int i = 0; i < 256; i++) + { + result[i] = (ushort)(((i & 0xF) << 8) | ((i & 240) >> 4)); } - public static (byte[] key, bool isLeaf) FromBytes(ReadOnlySpan bytes) + return result; + } + + /// + /// Returns a byte array for the specified nibble path, using cached arrays for short paths (1-3 nibbles) with valid nibble values (0-15) to reduce allocations. + /// + /// The nibble path to convert to a byte array. + /// + /// A byte array representing the nibble path. For paths of length 1-3 with nibble values 0-15, returns a shared cached array that must not be modified. + /// For longer paths or paths with nibble values >= 16, allocates and returns a new array. + /// + /// + /// This optimization takes advantage of the fact that short nibble paths are common and their possible combinations are limited. + /// The returned cached arrays are shared and must not be modified by callers. + /// + public static byte[] GetArray(ReadOnlySpan path) + { + if (path.Length == 0) + { + return []; + } + if (path.Length == 1) + { + uint value = path[0]; + if (value < 16) + { + return Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(SingleNibblePaths), (int)value); + } + } + else if (path.Length == 2) + { + uint v1 = path[1]; + uint v0 = path[0]; + if ((v0 | v1) < 16) + { + return Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(DoubleNibblePaths), (int)((v0 << 4) | v1)); + } + } + else if (path.Length == 3) { - bool isEven = (bytes[0] & 16) == 0; - int nibblesCount = bytes.Length * 2 - (isEven ? 2 : 1); - byte[] path = new byte[nibblesCount]; - Span span = new(path); - if (!isEven) + uint v2 = path[2]; + uint v1 = path[1]; + uint v0 = path[0]; + if ((v0 | v1 | v2) < 16) { - span[0] = (byte)(bytes[0] & 0xF); - span = span[1..]; + return Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(TripleNibblePaths), (int)((v0 << 8) | (v1 << 4) | v2)); } - bool isLeaf = bytes[0] >= 32; - bytes = bytes[1..]; + } + return path.ToArray(); + } - Span nibbles = MemoryMarshal.CreateSpan( - ref Unsafe.As(ref MemoryMarshal.GetReference(span)), - span.Length / 2); + /// + /// Prepends a nibble to an existing nibble array, returning a cached array for small results. + /// + /// The nibble value (0-15) to prepend. + /// The existing nibble array to prepend to. + /// + /// A cached array if the result length is 3 or fewer nibbles; otherwise a newly allocated array. + /// + public static byte[] PrependNibble(byte prefix, byte[] array) + { + switch (array.Length) + { + case 0: + if (prefix < 16) + { + return Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(SingleNibblePaths), prefix); + } + break; + case 1: + { + uint v1 = array[0]; + if ((prefix | v1) < 16) + { + return Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(DoubleNibblePaths), (prefix << 4) | (int)v1); + } + break; + } + case 2: + { + uint v2 = array[1]; + uint v1 = array[0]; + if ((prefix | v1 | v2) < 16) + { + return Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(TripleNibblePaths), (prefix << 8) | (int)((v1 << 4) | v2)); + } + break; + } + } - Debug.Assert(nibbles.Length == bytes.Length); + // Fallback - allocate and concat + byte[] result = new byte[array.Length + 1]; + result[0] = prefix; + array.CopyTo(result, 1); + return result; + } - ref byte byteRef = ref MemoryMarshal.GetReference(bytes); - ref ushort lookup16 = ref MemoryMarshal.GetArrayDataReference(Lookup16); - for (int i = 0; i < nibbles.Length; i++) - { - nibbles[i] = Unsafe.Add(ref lookup16, Unsafe.Add(ref byteRef, i)); - } + /// + /// Concatenates two nibble arrays, returning a cached array for small results. + /// + /// The first nibble array. + /// The second nibble array to append. + /// + /// A cached array if the combined length is 3 or fewer nibbles; otherwise a newly allocated array. + /// + public static byte[] ConcatNibbles(byte[] first, byte[] second) + { + switch (first.Length + second.Length) + { + case 0: + return []; + case 1: + { + byte nibble = first.Length == 1 ? first[0] : second[0]; + if (nibble < 16) + { + return Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(SingleNibblePaths), nibble); + } + break; + } + case 2: + { + (uint v1, uint v2) = first.Length switch + { + 0 => (second[0], second[1]), + 1 => (first[0], second[0]), + _ => (first[0], first[1]) + }; + if ((v1 | v2) < 16) + { + return Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(DoubleNibblePaths), (int)((v1 << 4) | v2)); + } + break; + } + case 3: + { + (uint v1, uint v2, uint v3) = first.Length switch + { + 0 => (second[0], second[1], second[2]), + 1 => (first[0], second[0], second[1]), + 2 => (first[0], first[1], second[0]), + _ => (first[0], first[1], first[2]) + }; + if ((v1 | v2 | v3) < 16) + { + return Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(TripleNibblePaths), (int)((v1 << 8) | (v2 << 4) | v3)); + } + break; + } + } - return (path, isLeaf); + // Fallback - allocate and concat + byte[] result = new byte[first.Length + second.Length]; + first.CopyTo(result, 0); + second.CopyTo(result, first.Length); + return result; + } + + /// + /// Returns a cached single-nibble array for a byte value if it's a valid nibble (0-15); + /// otherwise allocates a new single-element array. + /// + /// The byte value. + /// + /// A cached single-element array if the value is 0-15; otherwise a newly allocated array. + /// + public static byte[] SingleNibble(byte value) + { + if (value < 16) + { + return Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(SingleNibblePaths), value); } + return [value]; + } - private static readonly ushort[] Lookup16 = CreateLookup16("x2"); + private static byte[][] CreateSingleNibblePaths() + { + var paths = new byte[16][]; + for (int i = 0; i < 16; i++) + { + paths[i] = [(byte)i]; + } + return paths; + } - private static ushort[] CreateLookup16(string format) + private static byte[][] CreateDoubleNibblePaths() + { + var paths = new byte[256][]; + for (int i = 0; i < 256; i++) { - ushort[] result = new ushort[256]; - for (int i = 0; i < 256; i++) - { - result[i] = (ushort)(((i & 0xF) << 8) | ((i & 240) >> 4)); - } + paths[i] = [(byte)(i >> 4), (byte)(i & 0xF)]; + } + return paths; + } - return result; + private static byte[][] CreateTripleNibblePaths() + { + var paths = new byte[4096][]; + for (int i = 0; i < 4096; i++) + { + paths[i] = [(byte)(i >> 8), (byte)((i >> 4) & 0xF), (byte)(i & 0xF)]; } + return paths; } } diff --git a/src/Nethermind/Nethermind.Trie/NibbleExtensions.cs b/src/Nethermind/Nethermind.Trie/NibbleExtensions.cs index f56c055057f..97d6ecefdcb 100644 --- a/src/Nethermind/Nethermind.Trie/NibbleExtensions.cs +++ b/src/Nethermind/Nethermind.Trie/NibbleExtensions.cs @@ -35,7 +35,7 @@ public static byte[] BytesToNibbleBytes(ReadOnlySpan bytes) return output; } - public unsafe static void BytesToNibbleBytes(ReadOnlySpan bytes, Span nibbles) + public static void BytesToNibbleBytes(ReadOnlySpan bytes, Span nibbles) { // Ensure the length of the nibbles span is exactly twice the length of the bytes span. if (nibbles.Length != 2 * bytes.Length) @@ -196,6 +196,7 @@ public static byte[] ToBytes(ReadOnlySpan nibbles) return bytes; } + [SkipLocalsInit] public static byte[] CompactToHexEncode(byte[] compactPath) { if (compactPath.Length == 0) diff --git a/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs b/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs new file mode 100644 index 00000000000..75236286b82 --- /dev/null +++ b/src/Nethermind/Nethermind.Trie/NodeStorageCache.cs @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Collections.Concurrent; +using Nethermind.Core.Collections; + +namespace Nethermind.Trie; + +public sealed class NodeStorageCache +{ + private ConcurrentDictionary _cache = new(); + + private volatile bool _enabled = false; + + public bool Enabled + { + get => _enabled; + set => _enabled = value; + } + + public byte[]? GetOrAdd(NodeKey nodeKey, Func tryLoadRlp) + { + if (!_enabled) + { + return tryLoadRlp(nodeKey); + } + return _cache.GetOrAdd(nodeKey, tryLoadRlp); + } + + public bool ClearCaches() + { + return _cache.NoResizeClear(); + } +} diff --git a/src/Nethermind/Nethermind.Trie/PatriciaTree.BulkSet.cs b/src/Nethermind/Nethermind.Trie/PatriciaTree.BulkSet.cs index 127d396c500..07938e21839 100644 --- a/src/Nethermind/Nethermind.Trie/PatriciaTree.BulkSet.cs +++ b/src/Nethermind/Nethermind.Trie/PatriciaTree.BulkSet.cs @@ -27,7 +27,7 @@ public enum Flags DoNotParallelize = 2 } - public readonly struct BulkSetEntry(ValueHash256 path, byte[] value) : IComparable + public readonly struct BulkSetEntry(in ValueHash256 path, byte[] value) : IComparable { public readonly ValueHash256 Path = path; public readonly byte[] Value = value; @@ -261,6 +261,7 @@ private struct Context return node; } + [SkipLocalsInit] private TrieNode? BulkSetOne(Stack traverseStack, in BulkSetEntry entry, ref TreePath path, TrieNode? node) { Span nibble = stackalloc byte[64]; @@ -273,8 +274,7 @@ private struct Context private TrieNode? MakeFakeBranch(ref TreePath currentPath, TrieNode? existingNode) { - byte[] shortenedKey = new byte[existingNode.Key.Length - 1]; - Array.Copy(existingNode.Key, 1, shortenedKey, 0, existingNode.Key.Length - 1); + ReadOnlySpan shortenedKey = existingNode.Key.AsSpan(1, existingNode.Key.Length - 1); int branchIdx = existingNode.Key[0]; @@ -339,7 +339,7 @@ internal static int BucketSort16Large( Span indexes) { // You know, I originally used another buffer to keep track of the entries per nibble. then ChatGPT gave me this. - // I dont know what is worst, that ChatGPT beat me to it, or that it is simpler. + // I don't know what is worse, that ChatGPT beat me to it, or that it is simpler. Span counts = stackalloc int[TrieNode.BranchesCount]; for (int i = 0; i < entries.Length; i++) @@ -451,6 +451,7 @@ internal static int HexarySearchAlreadySortedSmall(Span entries, i return usedMask; } + [SkipLocalsInit] internal static int HexarySearchAlreadySortedLarge( Span entries, int pathIndex, diff --git a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs index 4f04300e3d3..98cbfb2fb76 100644 --- a/src/Nethermind/Nethermind.Trie/PatriciaTree.cs +++ b/src/Nethermind/Nethermind.Trie/PatriciaTree.cs @@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using Nethermind.Core; @@ -141,13 +142,19 @@ public void Commit(bool skipRoot = false, WriteFlags writeFlags = WriteFlags.Non _writeBeforeCommit = 0; - using ICommitter committer = TrieStore.BeginCommit(RootRef, writeFlags); - if (RootRef is not null && RootRef.IsDirty) + TrieNode? newRoot = RootRef; + using (ICommitter committer = TrieStore.BeginCommit(RootRef, writeFlags)) { - TreePath path = TreePath.Empty; - RootRef = Commit(committer, ref path, RootRef, skipSelf: skipRoot, maxLevelForConcurrentCommit: maxLevelForConcurrentCommit); + if (RootRef is not null && RootRef.IsDirty) + { + TreePath path = TreePath.Empty; + newRoot = Commit(committer, ref path, RootRef, skipSelf: skipRoot, maxLevelForConcurrentCommit: maxLevelForConcurrentCommit); + } } + // Need to be after committer dispose so that it can find it in trie store properly + RootRef = newRoot; + // Sometimes RootRef is set to null, so we still need to reset roothash to empty tree hash. SetRootHash(RootRef?.Keccak, true); } @@ -163,73 +170,64 @@ private TrieNode Commit(ICommitter committer, ref TreePath path, TrieNode node, { if (path.Length > maxLevelForConcurrentCommit) { + path.AppendMut(0); for (int i = 0; i < 16; i++) { - if (node.IsChildDirty(i)) + if (node.TryGetDirtyChild(i, out TrieNode? childNode)) { - path.AppendMut(i); - TrieNode childNode = node.GetChildWithChildPath(TrieStore, ref path, i); + path.SetLast(i); TrieNode newChildNode = Commit(committer, ref path, childNode, maxLevelForConcurrentCommit); if (!ReferenceEquals(childNode, newChildNode)) { node[i] = newChildNode; } - path.TruncateOne(); } else { if (_logger.IsTrace) { + path.SetLast(i); Trace(node, ref path, i); } } } + path.TruncateOne(); } else { - Task CreateTaskForPath(TreePath childPath, TrieNode childNode, int idx) => Task.Run(() => - { - TrieNode newChild = Commit(committer, ref childPath, childNode!, maxLevelForConcurrentCommit); - if (!ReferenceEquals(childNode, newChild)) - { - node[idx] = newChild; - } - committer.ReturnConcurrencyQuota(); - }); - ArrayPoolList? childTasks = null; + path.AppendMut(0); for (int i = 0; i < 16; i++) { - if (node.IsChildDirty(i)) + if (node.TryGetDirtyChild(i, out TrieNode childNode)) { + path.SetLast(i); if (i < 15 && committer.TryRequestConcurrentQuota()) { childTasks ??= new ArrayPoolList(15); - TreePath childPath = path.Append(i); - TrieNode childNode = node.GetChildWithChildPath(TrieStore, ref childPath, i); - childTasks.Add(CreateTaskForPath(childPath, childNode, i)); + // path is copied here + childTasks.Add(CreateTaskForPath(committer, node, maxLevelForConcurrentCommit, path, childNode, i)); } else { - path.AppendMut(i); - TrieNode childNode = node.GetChildWithChildPath(TrieStore, ref path, i); TrieNode newChildNode = Commit(committer, ref path, childNode!, maxLevelForConcurrentCommit); if (!ReferenceEquals(childNode, newChildNode)) { node[i] = newChildNode; } - path.TruncateOne(); } } else { if (_logger.IsTrace) { + path.SetLast(i); Trace(node, ref path, i); } } } + path.TruncateOne(); if (childTasks is not null) { @@ -241,13 +239,7 @@ Task CreateTaskForPath(TreePath childPath, TrieNode childNode, int idx) => Task. else if (node.NodeType == NodeType.Extension) { int previousPathLength = node.AppendChildPath(ref path, 0); - TrieNode extensionChild = node.GetChildWithChildPath(TrieStore, ref path, 0); - if (extensionChild is null) - { - ThrowInvalidExtension(); - } - - if (extensionChild.IsDirty) + if (node.TryGetDirtyChild(0, out TrieNode? extensionChild)) { TrieNode newExtensionChild = Commit(committer, ref path, extensionChild, maxLevelForConcurrentCommit); if (!ReferenceEquals(newExtensionChild, extensionChild)) @@ -255,9 +247,15 @@ Task CreateTaskForPath(TreePath childPath, TrieNode childNode, int idx) => Task. node[0] = newExtensionChild; } } - else + else if (_logger.IsTrace) { - if (_logger.IsTrace) TraceExtensionSkip(extensionChild); + extensionChild = node.GetChildWithChildPath(TrieStore, ref path, 0); + if (extensionChild is null) + { + ThrowInvalidExtension(); + } + + TraceExtensionSkip(extensionChild); } path.TruncateMut(previousPathLength); } @@ -285,7 +283,7 @@ Task CreateTaskForPath(TreePath childPath, TrieNode childNode, int idx) => Task. [MethodImpl(MethodImplOptions.NoInlining)] void Trace(TrieNode node, ref TreePath path, int i) { - TrieNode child = node.GetChild(TrieStore, ref path, i); + TrieNode child = node.GetChildWithChildPath(TrieStore, ref path, i); if (child is not null) { _logger.Trace($"Skipping commit of {child}"); @@ -305,6 +303,18 @@ void TraceSkipInlineNode(TrieNode node) } } + private async Task CreateTaskForPath(ICommitter committer, TrieNode node, int maxLevelForConcurrentCommit, TreePath childPath, TrieNode childNode, int idx) + { + // Background task + await Task.Yield(); + TrieNode newChild = Commit(committer, ref childPath, childNode!, maxLevelForConcurrentCommit); + if (!ReferenceEquals(childNode, newChild)) + { + node[idx] = newChild; + } + committer.ReturnConcurrencyQuota(); + } + public void UpdateRootHash(bool canBeParallel = true) { TreePath path = TreePath.Empty; @@ -384,6 +394,7 @@ public virtual ReadOnlySpan Get(ReadOnlySpan rawKey, Hash256? rootHa } } + [SkipLocalsInit] [DebuggerStepThrough] public byte[]? GetNodeByKey(Span rawKey, Hash256? rootHash = null) { @@ -537,7 +548,7 @@ public void Set(ReadOnlySpan rawKey, Rlp? value) { if (node is null) { - node = value.IsNullOrEmpty ? null : TrieNodeFactory.CreateLeaf(remainingKey.ToArray(), value); + node = value.IsNullOrEmpty ? null : TrieNodeFactory.CreateLeaf(remainingKey, value); // End traverse break; @@ -622,17 +633,17 @@ public void Set(ReadOnlySpan rawKey, Rlp? value) else { // Note: could be a leaf at the end of the tree which now have zero length key - theBranch[currentNodeNib] = node.CloneWithChangedKey(node.Key.Slice(commonPrefixLength + 1)); + theBranch[currentNodeNib] = node.CloneWithChangedKey(HexPrefix.GetArray(node.Key.AsSpan(commonPrefixLength + 1))); } // This is the new branch theBranch[remainingKey[commonPrefixLength]] = - TrieNodeFactory.CreateLeaf(remainingKey[(commonPrefixLength + 1)..].ToArray(), value); + TrieNodeFactory.CreateLeaf(remainingKey[(commonPrefixLength + 1)..], value); // Extension in front of the branch node = commonPrefixLength == 0 ? theBranch : - TrieNodeFactory.CreateExtension(remainingKey[..commonPrefixLength].ToArray(), theBranch); + TrieNodeFactory.CreateExtension(remainingKey[..commonPrefixLength], theBranch); break; } @@ -673,7 +684,7 @@ public void Set(ReadOnlySpan rawKey, Rlp? value) if (child.IsExtension || child.IsLeaf) { // Merge current node with child - node = child.CloneWithChangedKey(Bytes.Concat(node.Key, child.Key)); + node = child.CloneWithChangedKey(HexPrefix.ConcatNibbles(node.Key, child.Key)); } else { @@ -717,8 +728,9 @@ internal bool ShouldUpdateChild(TrieNode? parent, TrieNode? oldChild, TrieNode? if (parent is null) return true; if (oldChild is null && newChild is null) return false; if (!ReferenceEquals(oldChild, newChild)) return true; - if (newChild.Keccak is null && parent.Keccak is not null) return true; // So that recalculate root knows to recalculate the parent root. - return false; + // So that recalculate root knows to recalculate the parent root. + // Parent's hash can also be null depending on nesting level - still need to update child, otherwise combine will remain original value + return newChild.Keccak is null; } /// @@ -765,31 +777,35 @@ internal bool ShouldUpdateChild(TrieNode? parent, TrieNode? oldChild, TrieNode? if (onlyChildNode.IsBranch) { - byte[] extensionKey = [(byte)onlyChildIdx]; + byte[] extensionKey = HexPrefix.SingleNibble((byte)onlyChildIdx); if (originalNode is not null && originalNode.IsExtension && Bytes.AreEqual(extensionKey, originalNode.Key)) { + path.AppendMut(onlyChildIdx); TrieNode? originalChild = originalNode.GetChildWithChildPath(TrieStore, ref path, 0); + path.TruncateOne(); if (!ShouldUpdateChild(originalNode, originalChild, onlyChildNode)) { return originalNode; } } - return TrieNodeFactory.CreateExtension([(byte)onlyChildIdx], onlyChildNode); + return TrieNodeFactory.CreateExtension(extensionKey, onlyChildNode); } // 35% // Replace the only child with something with extra key. - byte[] newKey = Bytes.Concat((byte)onlyChildIdx, onlyChildNode.Key); - + byte[] newKey = HexPrefix.PrependNibble((byte)onlyChildIdx, onlyChildNode.Key); if (originalNode is not null) // Only bulkset provide original node { if (originalNode.IsExtension && onlyChildNode.IsExtension) { if (Bytes.AreEqual(newKey, originalNode.Key)) { + int originalLength = path.Length; + path.AppendMut(newKey); TrieNode? originalChild = originalNode.GetChildWithChildPath(TrieStore, ref path, 0); TrieNode? newChild = onlyChildNode.GetChildWithChildPath(TrieStore, ref path, 0); + path.TruncateMut(originalLength); if (!ShouldUpdateChild(originalNode, originalChild, newChild)) { return originalNode; diff --git a/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs b/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs index 0fd1def8239..dc6d9b6d1fa 100644 --- a/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/PreCachedTrieStore.cs @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-3.0-only using System; -using System.Collections.Concurrent; using System.Numerics; using Nethermind.Core; using Nethermind.Core.Crypto; @@ -10,18 +9,17 @@ namespace Nethermind.Trie; -public class PreCachedTrieStore : ITrieStore +public sealed class PreCachedTrieStore : ITrieStore { private readonly ITrieStore _inner; - private readonly ConcurrentDictionary _preBlockCache; + private readonly NodeStorageCache _preBlockCache; private readonly Func _loadRlp; private readonly Func _tryLoadRlp; - public PreCachedTrieStore(ITrieStore inner, - ConcurrentDictionary preBlockCache) + public PreCachedTrieStore(ITrieStore inner, NodeStorageCache cache) { _inner = inner; - _preBlockCache = preBlockCache; + _preBlockCache = cache; // Capture the delegate once for default path to avoid the allocation of the lambda per call _loadRlp = (NodeKey key) => _inner.LoadRlp(key.Address, in key.Path, key.Hash, flags: ReadFlags.None); diff --git a/src/Nethermind/Nethermind.Trie/Pruning/OverlayTrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/OverlayTrieStore.cs index 72dce31c2d2..0e60190a575 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/OverlayTrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/OverlayTrieStore.cs @@ -11,7 +11,7 @@ namespace Nethermind.Trie.Pruning; /// OverlayTrieStore works by reading and writing to the passed in keyValueStore first as if it is an archive node. /// If a node is missing, then it will try to find from the base store. /// On reset the base db provider is expected to clear any diff which causes this overlay trie store to no longer -/// see overlayed keys. +/// see overlaid keys. /// public class OverlayTrieStore(IKeyValueStoreWithBatching keyValueStore, IReadOnlyTrieStore baseStore) : ITrieStore { diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TreePath.cs b/src/Nethermind/Nethermind.Trie/Pruning/TreePath.cs index 9714b92ddbc..5ab32a9a613 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/TreePath.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/TreePath.cs @@ -231,6 +231,7 @@ public void TruncateOne() Length--; } + [SkipLocalsInit] public readonly byte[] ToNibble() { bool odd = Length % 2 == 1; diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs index 8f872496a4b..66c57f32656 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/TrieStore.cs @@ -22,7 +22,7 @@ namespace Nethermind.Trie.Pruning; /// /// Trie store helps to manage trie commits block by block. -/// If persistence and pruning are needed they have a chance to execute their behaviour on commits. +/// If persistence and pruning are needed they have a chance to execute their behavior on commits. /// public sealed class TrieStore : ITrieStore, IPruningTrieStore { @@ -32,6 +32,7 @@ public sealed class TrieStore : ITrieStore, IPruningTrieStore private readonly int _maxDepth; private readonly double _prunePersistedNodePortion; private readonly long _prunePersistedNodeMinimumTarget; + private readonly int _pruneDelayMs; private int _isFirst; @@ -91,6 +92,7 @@ public TrieStore( _maxBufferedCommitCount = pruningConfig.MaxBufferedCommitCount; _pastKeyTrackingEnabled = pruningConfig.TrackPastKeys && nodeStorage.RequirePath; + _pruneDelayMs = pruningConfig.PruneDelayMilliseconds; _deleteOldNodes = _pruningStrategy.DeleteObsoleteKeys && _pastKeyTrackingEnabled; _shardBit = pruningConfig.DirtyNodeShardBit; _shardedDirtyNodeCount = 1 << _shardBit; @@ -230,6 +232,7 @@ private TrieNode CommitAndInsertToDirtyNodes(long blockNumber, Hash256? address, node = _commitBuffer.SaveOrReplaceInDirtyNodesCache(address, ref path, node, blockNumber); else node = SaveOrReplaceInDirtyNodesCache(address, ref path, node, blockNumber); + node.PrunePersistedRecursively(1); IncrementCommittedNodesCount(); } @@ -570,40 +573,106 @@ private TrieStoreState CaptureCurrentState() public void Prune() { - var state = CaptureCurrentState(); + TrieStoreState state = CaptureCurrentState(); if ((_pruningStrategy.ShouldPruneDirtyNode(state) || _pruningStrategy.ShouldPrunePersistedNode(state)) && _pruningTask.IsCompleted) { - _pruningTask = Task.Run(SyncPruneCheck); + _pruningTask = TrySyncPrune(); } } - internal void SyncPruneCheck() + private async Task TrySyncPrune() + { + // Delay for 3 things: + // 1. Move to background thread + // 2. Allow kick off (block processing) some time to finish before starting to prune (prune in block gap) + // 3. If we are n+1 Task passing the .IsCompleted check but not going to be first to pass lock, + // remain uncompleted for a time to prevent other tasks seeing .IsCompleted and also trying to queue. + int pruneDelayMs = _pruneDelayMs; + if (pruneDelayMs <= 0) + { + // Always async + await Task.Yield(); + } + else + { + await Task.Delay(pruneDelayMs); + } + + if (!_pruningLock.TryEnter()) + { + // Do not want to queue on lock behind already ongoing pruning. + return; + } + + // Skip triggering GC while pruning so they don't fight each other causing pruning to take longer + GCScheduler.Instance.SkipNextGC(); + try + { + SyncPruneNonLocked(); + } + finally + { + _pruningLock.Exit(); + } + + TryExitCommitBufferMode(); + } + + // Testing purpose only + internal void SyncPruneQueue() { using (var _ = _pruningLock.EnterScope()) { - if (_pruningStrategy.ShouldPruneDirtyNode(CaptureCurrentState())) - { - PersistAndPruneDirtyCache(); - } + SyncPruneNonLocked(); + } + + TryExitCommitBufferMode(); + } + + private void SyncPruneNonLocked() + { + Debug.Assert(_pruningLock.IsHeldByCurrentThread, "Pruning lock must be held to perform sync prune."); - if (_prunePersistedNodePortion > 0) + if (_pruningStrategy.ShouldPruneDirtyNode(CaptureCurrentState())) + { + PersistAndPruneDirtyCache(); + } + + if (_prunePersistedNodePortion > 0) + { + try { + // When `_pruningLock` is held, the begin commit will check for _toBePersistedBlockNumber in order + // to decide which block to be used as the boundary for the commit buffer. This number was re-set + // to -1 in `PersistAndPruneDirtyCache`. So we need to re-set it here, otherwise `BeginScope` will hang + // until the prune persisted node loop is completed. + _toBePersistedBlockNumber = LastPersistedBlockNumber; + // `PrunePersistedNodes` only work on part of the partition at any one time. With commit buffer, // it is possible that the commit buffer once flushed will immediately trigger another prune, which // mean `PrunePersistedNodes` was not able to re-trigger multiple time, which make the persisted node // cache even bigger which causes longer prune which causes bigger commit buffer, etc. // So we loop it here until `ShouldPrunePersistedNode` return false. - int maxTry = _shardedDirtyNodeCount; - int i = 0; - while (i < maxTry && _pruningStrategy.ShouldPrunePersistedNode(CaptureCurrentState())) + int startingShard = _lastPrunedShardIdx; + while (_lastPrunedShardIdx - startingShard < _shardedDirtyNodeCount && _pruningStrategy.ShouldPrunePersistedNode(CaptureCurrentState())) { PrunePersistedNodes(); - i++; } + + if (!IsInCommitBufferMode && _lastPrunedShardIdx - startingShard >= _shardedDirtyNodeCount && _pruningStrategy.ShouldPrunePersistedNode(CaptureCurrentState())) + { + // A persisted nodes that was recommitted and is still within pruning boundary cannot be pruned. + // This should be rare but can happen, notably in mainnet block 4500000 around there, But this + // does mean that it will keep retrying to prune persisted nodes. The solution is to either increase + // the memory budget or reduce the pruning boundary. + if (_logger.IsWarn) _logger.Warn($"Unable to completely prune persisted nodes. Consider increasing pruning cache limit or reducing pruning boundary"); + } + } + finally + { + _toBePersistedBlockNumber = -1; } } - - TryExitCommitBufferMode(); } internal void PersistAndPruneDirtyCache() @@ -617,7 +686,7 @@ internal void PersistAndPruneDirtyCache() SaveSnapshot(); // Full pruning may set delete obsolete keys to false - PruneCache(dontRemoveNodes: !_pruningStrategy.DeleteObsoleteKeys); + PruneCache(doNotRemoveNodes: !_pruningStrategy.DeleteObsoleteKeys); TimeSpan sw = Stopwatch.GetElapsedTime(start); long ms = (long)sw.TotalMilliseconds; @@ -652,11 +721,11 @@ private void SaveSnapshot() // Full pruning need to visit all node, so can't delete anything. // If more than one candidate set, its a reorg, we can't remove node as persisted node may not be canonical - // For archice node, it is safe to remove canon key from cache as it will just get re-loaded. + // For archive node, it is safe to remove canon key from cache as it will just get re-loaded. _deleteOldNodes && finalizedBlockNumber.HasValue; - if (_logger.IsDebug) _logger.Debug($"Persisting {candidateSets.Count} commitsets. Finalized block number {finalizedBlockNumber}. Should track past keys {shouldTrackPastKey}"); + if (_logger.IsDebug) _logger.Debug($"Persisting {candidateSets.Count} commit sets. Finalized block number {finalizedBlockNumber}. Should track past keys {shouldTrackPastKey}"); if (!finalizedBlockNumber.HasValue) { @@ -739,7 +808,7 @@ private void SaveSnapshot() } else { - // This can happen if the Max-Min of the commitsetqueue is less than pruning boundary + // This can happen if the Max-Min of the commit set queue is less than pruning boundary if (_logger.IsDebug) _logger.Debug($"Block commits are all after finalized block. Min block commit: {_commitSetQueue.MinBlockNumber}, Effective finalized block: {effectiveFinalizedBlockNumber}, Finalized block number: {finalizedBlockNumber}"); } return (candidateSets, null); @@ -765,7 +834,11 @@ private void SaveSnapshot() { // This is a hang. It should recover itself as new finalized block is set. But it will hang if we for some reason // does not process in the finalized branch at all. - if (_logger.IsWarn) _logger.Warn($"Unable to determine finalized state root at block {effectiveFinalizedBlockNumber}. Available state roots {string.Join(", ", commitSetsAtFinalizedBlock.Select(c => c.StateRoot.ToString()).ToArray())}"); + if (_logger.IsWarn) + { + using ArrayPoolListRef roots = commitSetsAtFinalizedBlock.Select(c => c.StateRoot.ToString()); + _logger.Warn($"Unable to determine finalized state root at block {effectiveFinalizedBlockNumber}. Available state roots {string.Join(", ", roots.AsSpan())}"); + } return (candidateSets, null); } @@ -840,7 +913,7 @@ private void PersistedNodeRecorderNoop(TreePath treePath, Hash256 address, TrieN /// This is done after a `SaveSnapshot`. /// /// - private void PruneCache(bool prunePersisted = false, bool dontRemoveNodes = false, bool forceRemovePersistedNodes = false) + private void PruneCache(bool prunePersisted = false, bool doNotRemoveNodes = false, bool forceRemovePersistedNodes = false) { if (_logger.IsDebug) _logger.Debug($"Pruning nodes {DirtyMemoryUsedByDirtyCache / 1.MB()} MB , last persisted block: {LastPersistedBlockNumber} current: {LatestCommittedBlockNumber}."); long start = Stopwatch.GetTimestamp(); @@ -849,25 +922,7 @@ private void PruneCache(bool prunePersisted = false, bool dontRemoveNodes = fals { int closureIndex = index; TrieStoreDirtyNodesCache dirtyNode = _dirtyNodes[closureIndex]; - _dirtyNodesTasks[closureIndex] = Task.Run(() => - { - ConcurrentDictionary? persistedHashes = null; - if (_persistedHashes.Length > 0) - { - persistedHashes = _persistedHashes[closureIndex]; - } - - INodeStorage nodeStorage = _nodeStorage; - if (dontRemoveNodes) nodeStorage = null; - - dirtyNode - .PruneCache( - prunePersisted: prunePersisted, - forceRemovePersistedNodes: forceRemovePersistedNodes, - persistedHashes: persistedHashes, - nodeStorage: nodeStorage); - persistedHashes?.NoResizeClear(); - }); + _dirtyNodesTasks[closureIndex] = CreatePruneDirtyNodeTask(prunePersisted, doNotRemoveNodes, forceRemovePersistedNodes, closureIndex, dirtyNode); } Task.WaitAll(_dirtyNodesTasks); @@ -877,6 +932,28 @@ private void PruneCache(bool prunePersisted = false, bool dontRemoveNodes = fals if (_logger.IsDebug) _logger.Debug($"Finished pruning nodes in {(long)Stopwatch.GetElapsedTime(start).TotalMilliseconds}ms {DirtyMemoryUsedByDirtyCache / 1.MB()} MB, last persisted block: {LastPersistedBlockNumber} current: {LatestCommittedBlockNumber}."); } + private async Task CreatePruneDirtyNodeTask(bool prunePersisted, bool doNotRemoveNodes, bool forceRemovePersistedNodes, int closureIndex, TrieStoreDirtyNodesCache dirtyNode) + { + // Background task + await Task.Yield(); + ConcurrentDictionary? persistedHashes = null; + if (_persistedHashes.Length > 0) + { + persistedHashes = _persistedHashes[closureIndex]; + } + + INodeStorage nodeStorage = _nodeStorage; + if (doNotRemoveNodes) nodeStorage = null; + + dirtyNode + .PruneCache( + prunePersisted: prunePersisted, + forceRemovePersistedNodes: forceRemovePersistedNodes, + persistedHashes: persistedHashes, + nodeStorage: nodeStorage); + persistedHashes?.NoResizeClear(); + } + /// /// Only prune persisted nodes. This method attempt to pick only some shard for pruning. /// @@ -890,19 +967,19 @@ internal void PrunePersistedNodes() int shardCountToPrune = (int)((targetPruneMemory / (double)PersistedMemoryUsedByDirtyCache) * _shardedDirtyNodeCount); shardCountToPrune = Math.Max(1, Math.Min(shardCountToPrune, _shardedDirtyNodeCount)); - if (_logger.IsWarn) _logger.Debug($"Pruning persisted nodes {PersistedMemoryUsedByDirtyCache / 1.MB()} MB, Pruning {shardCountToPrune} shards starting from shard {_lastPrunedShardIdx}"); + if (_logger.IsWarn) _logger.Debug($"Pruning persisted nodes {PersistedMemoryUsedByDirtyCache / 1.MB()} MB, Pruning {shardCountToPrune} shards starting from shard {_lastPrunedShardIdx % _shardedDirtyNodeCount}"); long start = Stopwatch.GetTimestamp(); using ArrayPoolListRef pruneTask = new(shardCountToPrune); for (int i = 0; i < shardCountToPrune; i++) { - TrieStoreDirtyNodesCache dirtyNode = _dirtyNodes[_lastPrunedShardIdx]; + TrieStoreDirtyNodesCache dirtyNode = _dirtyNodes[_lastPrunedShardIdx % _shardedDirtyNodeCount]; pruneTask.Add(Task.Run(() => { dirtyNode.PruneCache(prunePersisted: true); })); - _lastPrunedShardIdx = (_lastPrunedShardIdx + 1) % _shardedDirtyNodeCount; + _lastPrunedShardIdx++; } Task.WaitAll(pruneTask.AsSpan()); @@ -1199,14 +1276,14 @@ private void PersistOnShutdown() (ArrayPoolList candidateSets, long? finalizedBlockNumber) = DetermineCommitSetToPersistInSnapshot(_commitSetQueue.Count); using var _ = candidateSets; - if (LastPersistedBlockNumber == 0 && candidateSets.Count == 0 && _commitSetQueue.TryDequeue(out BlockCommitSet anyCommmitSet)) + if (LastPersistedBlockNumber == 0 && candidateSets.Count == 0 && _commitSetQueue.TryDequeue(out BlockCommitSet anyCommitSet)) { - // No commitset to persist, likely as not enough block was processed to reached prune boundary + // No commit set to persist, likely as not enough block was processed to reached prune boundary // This happens when node is shutdown right after sync. // we need to persist at least something or in case of fresh sync or the best persisted state will not be set - // at all. This come at a risk that this commitset is not canon though. - candidateSets.Add(anyCommmitSet); - if (_logger.IsDebug) _logger.Debug($"Force persisting commitset {anyCommmitSet} on shutdown."); + // at all. This come at a risk that this commit set is not canon though. + candidateSets.Add(anyCommitSet); + if (_logger.IsDebug) _logger.Debug($"Force persisting commit set {anyCommitSet} on shutdown."); } if (_logger.IsDebug) _logger.Debug($"On shutdown persisting {candidateSets.Count} commit sets. Finalized block is {finalizedBlockNumber}."); @@ -1223,7 +1300,7 @@ private void PersistOnShutdown() if (candidateSets.Count == 0) { - if (_logger.IsDebug) _logger.Debug("No commitset to persist at all."); + if (_logger.IsDebug) _logger.Debug("No commit set to persist at all."); } else { @@ -1238,7 +1315,7 @@ public void PersistCache(CancellationToken cancellationToken) long start = Stopwatch.GetTimestamp(); int commitSetCount = 0; - // We persist all sealed Commitset causing PruneCache to almost completely clear the cache. Any new block that + // We persist all sealed commit sets causing PruneCache to almost completely clear the cache. Any new block that // need existing node will have to read back from db causing copy-on-read mechanism to copy the node. CommitSetQueue commitSetQueue = _commitSetQueue; @@ -1270,7 +1347,7 @@ void ClearCommitSetQueue() // All persisted node including recommitted nodes between head and reorg depth must be removed so that // it will be re-persisted or at least re-read in order to be cloned. // This should clear most nodes. For some reason, not all. - PruneCache(prunePersisted: true, dontRemoveNodes: true, forceRemovePersistedNodes: true); + PruneCache(prunePersisted: true, doNotRemoveNodes: true, forceRemovePersistedNodes: true); if (cancellationToken.IsCancellationRequested) return; int totalPersistedCount = 0; @@ -1288,12 +1365,12 @@ void ClearCommitSetQueue() if (cancellationToken.IsCancellationRequested) return; - PruneCache(prunePersisted: true, dontRemoveNodes: true, forceRemovePersistedNodes: true); + PruneCache(prunePersisted: true, doNotRemoveNodes: true, forceRemovePersistedNodes: true); long nodesCount = NodesCount(); if (nodesCount != 0) { - if (_logger.IsWarn) _logger.Warn($"{nodesCount} cache entry remains. {DirtyCachedNodesCount} dirty, total persistec count is {totalPersistedCount}."); + if (_logger.IsWarn) _logger.Warn($"{nodesCount} cache entry remains. {DirtyCachedNodesCount} dirty, total persisted count is {totalPersistedCount}."); } if (_logger.IsInfo) _logger.Info($"Clear cache took {Stopwatch.GetElapsedTime(start)}."); diff --git a/src/Nethermind/Nethermind.Trie/Pruning/TrieStoreDirtyNodesCache.cs b/src/Nethermind/Nethermind.Trie/Pruning/TrieStoreDirtyNodesCache.cs index 98009ab93f1..356f94abde0 100644 --- a/src/Nethermind/Nethermind.Trie/Pruning/TrieStoreDirtyNodesCache.cs +++ b/src/Nethermind/Nethermind.Trie/Pruning/TrieStoreDirtyNodesCache.cs @@ -119,10 +119,12 @@ public bool IsNodeCached(in Key key) return _byKeyObjectCache.ContainsKey(key); } - public readonly struct NodeRecord(TrieNode node, long lastCommit) + public readonly struct NodeRecord(TrieNode node, long lastCommit) : IEquatable { public readonly TrieNode Node = node; public readonly long LastCommit = lastCommit; + + public bool Equals(NodeRecord other) => other.Node == Node && other.LastCommit == LastCommit; } public IEnumerable> AllNodes @@ -205,7 +207,7 @@ private static NodeRecord RecordReplacementLogic(Hash256AsKey keyHash, NodeRecor // This is because although very rare, it is possible that this node is persisted, but its child is not // persisted. This can happen when a path is not replaced with another node, but its child is and hence, // the child is removed, but the parent is not and remain in the cache as persisted node. - // Additionally, it may hold a reference to its child which is marked as persisted eventhough it was + // Additionally, it may hold a reference to its child which is marked as persisted even though it was // deleted from the cached map. node = arg.Node; } @@ -502,13 +504,17 @@ public override string ToString() public bool IsRoot() { - return Path.Length == 0; + return Address is null && Path.Length == 0; } } public void CopyTo(TrieStoreDirtyNodesCache otherCache) { - foreach (var kv in AllNodes) otherCache.GetOrAdd(kv.Key, kv.Value); + foreach (var kv in AllNodes) + { + kv.Value.Node.PrunePersistedRecursively(1); + otherCache.GetOrAdd(kv.Key, kv.Value); + } Clear(); } } diff --git a/src/Nethermind/Nethermind.Trie/TrieNode.cs b/src/Nethermind/Nethermind.Trie/TrieNode.cs index 6f14f86b2f6..d9b2907a12c 100644 --- a/src/Nethermind/Nethermind.Trie/TrieNode.cs +++ b/src/Nethermind/Nethermind.Trie/TrieNode.cs @@ -241,7 +241,7 @@ public bool IsValidWithOneNodeLess } } - return nonEmptyNodes > 2; + return false; } } @@ -641,7 +641,7 @@ static void ThrowNotABranch() } } - public bool IsChildDirty(int i) + public bool TryGetDirtyChild(int i, [NotNullWhen(true)] out TrieNode? dirtyChild) { if (IsExtension) { @@ -651,20 +651,24 @@ public bool IsChildDirty(int i) ref var data = ref _nodeData[i]; if (data is null) { + dirtyChild = null; return false; } if (ReferenceEquals(data, _nullNode)) { + dirtyChild = null; return false; } if (data is Hash256) { + dirtyChild = null; return false; } - return ((TrieNode)data)!.IsDirty; + dirtyChild = (TrieNode)data; + return dirtyChild.IsDirty; } public TrieNode? this[int i] @@ -732,7 +736,7 @@ public int AppendChildPath(ref TreePath currentPath, int childIndex) } // pruning trick so we never store long persisted paths - // Dont unresolve node of path length <= 4. there should be a relatively small number of these, enough to fit + // Don't unresolve nodes with path length <= 4; there should be relatively few and they should fit // in RAM, but they are hit quite a lot, and don't have very good data locality. // That said, in practice, it does nothing notable, except for significantly improving benchmark score. if (child?.IsPersisted == true && childPath.Length > 4 && childPath.Length % 2 == 0) @@ -1146,7 +1150,7 @@ public void PrunePersistedRecursively(int maxLevelsDeep) // else // { // // we assume that the storage root will get resolved during persistence even if not persisted yet - // // if this is not true then the code above that is commented out would be critical to call isntead + // // if this is not true then the code above that is commented out would be critical to call instead // _storageRoot = null; // } } @@ -1496,7 +1500,7 @@ public ref struct ChildIterator(TrieNode node) } // pruning trick so we never store long persisted paths - // Dont unresolve node of path length <= 4. there should be a relatively small number of these, enough to fit + // Don't unresolve nodes with path length <= 4; there should be relatively few and they should fit // in RAM, but they are hit quite a lot, and don't have very good data locality. // That said, in practice, it does nothing notable, except for significantly improving benchmark score. if (child?.IsPersisted == true && childPath.Length > 4 && childPath.Length % 2 == 0) diff --git a/src/Nethermind/Nethermind.Trie/TrieNodeFactory.cs b/src/Nethermind/Nethermind.Trie/TrieNodeFactory.cs index 0469b0b439c..7b44107d633 100644 --- a/src/Nethermind/Nethermind.Trie/TrieNodeFactory.cs +++ b/src/Nethermind/Nethermind.Trie/TrieNodeFactory.cs @@ -1,30 +1,32 @@ // SPDX-FileCopyrightText: 2022 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using Nethermind.Core.Buffers; -namespace Nethermind.Trie +namespace Nethermind.Trie; + +public static class TrieNodeFactory { - public static class TrieNodeFactory + public static TrieNode CreateBranch() { - public static TrieNode CreateBranch() - { - return new(new BranchData()); - } + return new(new BranchData()); + } - public static TrieNode CreateLeaf(byte[] path, SpanSource value) - { - return new(new LeafData(path, value)); - } + public static TrieNode CreateLeaf(ReadOnlySpan path, SpanSource value) + { + byte[] pathArray = HexPrefix.GetArray(path); + return new(new LeafData(pathArray, value)); + } - public static TrieNode CreateExtension(byte[] path) - { - return new(new ExtensionData(path)); - } + public static TrieNode CreateExtension(ReadOnlySpan path, TrieNode child) + { + byte[] pathArray = HexPrefix.GetArray(path); + return new(new ExtensionData(pathArray, child)); + } - public static TrieNode CreateExtension(byte[] path, TrieNode child) - { - return new(new ExtensionData(path, child)); - } + public static TrieNode CreateExtension(byte[] pathArray, TrieNode child) + { + return new(new ExtensionData(pathArray, child)); } } diff --git a/src/Nethermind/Nethermind.Trie/TrieStatsCollector.cs b/src/Nethermind/Nethermind.Trie/TrieStatsCollector.cs index a49faf1175b..747f8df258f 100644 --- a/src/Nethermind/Nethermind.Trie/TrieStatsCollector.cs +++ b/src/Nethermind/Nethermind.Trie/TrieStatsCollector.cs @@ -14,9 +14,9 @@ public class TrieStatsCollector : ITreeVisitor { private readonly ClockCache _existingCodeHash = new ClockCache(1024 * 8); private readonly IKeyValueStore _codeKeyValueStore; - private long _lastAccountNodeCount = 0; private readonly ILogger _logger; + private readonly VisitorProgressTracker _progressTracker; private readonly CancellationToken _cancellationToken; // Combine both `TreePathContextWithStorage` and `OldStyleTrieVisitContext` @@ -66,6 +66,7 @@ public TrieStatsCollector(IKeyValueStore codeKeyValueStore, ILogManager logManag _logger = logManager.GetClassLogger(); ExpectAccounts = expectAccounts; _cancellationToken = cancellationToken; + _progressTracker = new VisitorProgressTracker("Trie Verification", logManager); } public TrieStats Stats { get; } = new(); @@ -93,7 +94,7 @@ public void VisitMissingNode(in Context nodeContext, in ValueHash256 nodeHash) Interlocked.Increment(ref Stats._missingState); } - IncrementLevel(nodeContext); + IncrementLevel(nodeContext, isLeaf: false); } public void VisitBranch(in Context nodeContext, TrieNode node) @@ -111,7 +112,7 @@ public void VisitBranch(in Context nodeContext, TrieNode node) Interlocked.Increment(ref Stats._stateBranchCount); } - IncrementLevel(nodeContext); + IncrementLevel(nodeContext, isLeaf: false); } public void VisitExtension(in Context nodeContext, TrieNode node) @@ -127,18 +128,11 @@ public void VisitExtension(in Context nodeContext, TrieNode node) Interlocked.Increment(ref Stats._stateExtensionCount); } - IncrementLevel(nodeContext); + IncrementLevel(nodeContext, isLeaf: false); } public void VisitLeaf(in Context nodeContext, TrieNode node) { - long lastAccountNodeCount = _lastAccountNodeCount; - long currentNodeCount = Stats.NodesCount; - if (currentNodeCount - lastAccountNodeCount > 1_000_000 && Interlocked.CompareExchange(ref _lastAccountNodeCount, currentNodeCount, lastAccountNodeCount) == lastAccountNodeCount) - { - _logger.Warn($"Collected info from {Stats.NodesCount} nodes. Missing CODE {Stats.MissingCode} STATE {Stats.MissingState} STORAGE {Stats.MissingStorage}"); - } - if (nodeContext.IsStorage) { Interlocked.Add(ref Stats._storageSize, node.FullRlp.Length); @@ -150,7 +144,7 @@ public void VisitLeaf(in Context nodeContext, TrieNode node) Interlocked.Increment(ref Stats._accountCount); } - IncrementLevel(nodeContext); + IncrementLevel(nodeContext, isLeaf: true); } public void VisitAccount(in Context nodeContext, TrieNode node, in AccountStruct account) @@ -183,15 +177,23 @@ public void VisitAccount(in Context nodeContext, TrieNode node, in AccountStruct IncrementLevel(nodeContext, Stats._codeLevels); } - private void IncrementLevel(Context context) + private void IncrementLevel(Context context, bool isLeaf) { long[] levels = context.IsStorage ? Stats._storageLevels : Stats._stateLevels; IncrementLevel(context, levels); + + // Track all nodes for display; only state nodes used for progress calculation + _progressTracker.OnNodeVisited(context.Path, context.IsStorage, isLeaf); } private static void IncrementLevel(Context context, long[] levels) { Interlocked.Increment(ref levels[context.Level]); } + + public void Finish() + { + _progressTracker.Finish(); + } } } diff --git a/src/Nethermind/Nethermind.Trie/VisitorProgressTracker.cs b/src/Nethermind/Nethermind.Trie/VisitorProgressTracker.cs new file mode 100644 index 00000000000..33817dfece0 --- /dev/null +++ b/src/Nethermind/Nethermind.Trie/VisitorProgressTracker.cs @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using System.Globalization; +using System.Threading; +using Nethermind.Core; +using Nethermind.Logging; + +namespace Nethermind.Trie; + +/// +/// Tracks progress of trie traversal operations using path-based estimation. +/// Uses multi-level prefix tracking to estimate completion percentage even when +/// total node count is unknown and traversal is concurrent/out-of-order. +/// +public class VisitorProgressTracker +{ + private const int Level3Depth = 4; // 4 nibbles + private const int MaxNodes = 65536; // 16^4 possible 4-nibble prefixes + + private int _seenCount; // Count of level-3 nodes seen (or estimated from shallow leaves) + + private long _nodeCount; + private long _totalWorkDone; // Total work done (for display, separate from progress calculation) + private readonly DateTime _startTime; + private readonly ProgressLogger _logger; + private readonly string _operationName; + private readonly int _reportingInterval; + + public VisitorProgressTracker( + string operationName, + ILogManager logManager, + int reportingInterval = 100_000) + { + ArgumentNullException.ThrowIfNull(logManager); + + _operationName = operationName; + _logger = new ProgressLogger(operationName, logManager); + _logger.Reset(0, 10000); // Use 10000 for 0.01% precision + _logger.SetFormat(FormatProgress); + _reportingInterval = reportingInterval; + _startTime = DateTime.UtcNow; + } + + private string FormatProgress(ProgressLogger logger) + { + float percentage = Math.Clamp(logger.CurrentValue / 10000f, 0, 1); + long work = Interlocked.Read(ref _totalWorkDone); + string workStr = work >= 1_000_000 ? $"{work / 1_000_000.0:F1}M" : $"{work:N0}"; + return $"{_operationName,-25} {percentage.ToString("P2", CultureInfo.InvariantCulture),8} " + + Progress.GetMeter(percentage, 1) + + $" nodes: {workStr,8}"; + } + + /// + /// Called when a node is visited during traversal. + /// Thread-safe: can be called concurrently from multiple threads. + /// + /// The path to the node (used for progress estimation) + /// True if this is a storage node (tracked in total but not used for progress) + /// True if this is a leaf node (used to estimate coverage at level 3) + public void OnNodeVisited(in TreePath path, bool isStorage = false, bool isLeaf = false) + { + // Always count the work done + Interlocked.Increment(ref _totalWorkDone); + + // Only track state nodes for progress estimation at level 3 + if (!isStorage) + { + if (path.Length == Level3Depth) + { + // Node at exactly level 3 (4 nibbles): count as 1 node + Interlocked.Increment(ref _seenCount); + } + else if (isLeaf && path.Length > 0 && path.Length < Level3Depth) + { + // Leaf at lower depth: estimate how many level-3 nodes it covers + // Each level has 16 children, so a leaf at depth d covers 16^(4-d) level-3 nodes + int coverageDepth = Level3Depth - path.Length; + int estimatedNodes = 1; + for (int i = 0; i < coverageDepth; i++) + { + estimatedNodes *= 16; + } + + // Add estimated coverage + Interlocked.Add(ref _seenCount, estimatedNodes); + } + // Nodes at depth > Level3Depth are ignored for progress calculation + + // Log progress at intervals (based on state nodes only) + if (Interlocked.Increment(ref _nodeCount) % _reportingInterval == 0) + { + LogProgress(); + } + } + } + + private void LogProgress() + { + // Skip logging for first 5 seconds OR until we've seen at least 1% of nodes + // This avoids showing noisy early estimates + double elapsed = (DateTime.UtcNow - _startTime).TotalSeconds; + int seen = _seenCount; + double progress = Math.Min((double)seen / MaxNodes, 1.0); + + if (elapsed < 5.0 && progress < 0.01) + { + return; + } + + long progressValue = (long)(progress * 10000); + + _logger.Update(progressValue); + _logger.LogProgress(); + } + + /// + /// Call when traversal is complete to log final progress. + /// + public void Finish() + { + _logger.Update(10000); + _logger.MarkEnd(); + _logger.LogProgress(); + } + + /// + /// Gets the current estimated progress (0.0 to 1.0). + /// + public double GetProgress() + { + int seen = _seenCount; + return Math.Min((double)seen / MaxNodes, 1.0); + } + + /// + /// Gets the total number of nodes visited. + /// + public long NodeCount => Interlocked.Read(ref _nodeCount); +} diff --git a/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs b/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs index 87990c5ac31..9b09028feda 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/RetryCacheTests.cs @@ -48,8 +48,8 @@ public void Announced_SameResourceDifferentNode_ReturnsEnqueued() AnnounceResult result1 = _cache.Announced(1, Substitute.For()); AnnounceResult result2 = _cache.Announced(1, Substitute.For()); - Assert.That(result1, Is.EqualTo(AnnounceResult.New)); - Assert.That(result2, Is.EqualTo(AnnounceResult.Enqueued)); + Assert.That(result1, Is.EqualTo(AnnounceResult.RequestRequired)); + Assert.That(result2, Is.EqualTo(AnnounceResult.Delayed)); } [Test] @@ -66,7 +66,7 @@ public void Announced_AfterTimeout_ExecutesRetryRequests() } [Test] - public void Announced_MultipleResources_ExecutesAllRetryRequestsExceptInititalOne() + public void Announced_MultipleResources_ExecutesAllRetryRequestsExceptInitialOne() { ITestHandler request1 = Substitute.For(); ITestHandler request2 = Substitute.For(); @@ -91,7 +91,7 @@ public void Received_RemovesResourceFromRetryQueue() _cache.Received(1); AnnounceResult result = _cache.Announced(1, Substitute.For()); - Assert.That(result, Is.EqualTo(AnnounceResult.New)); + Assert.That(result, Is.EqualTo(AnnounceResult.RequestRequired)); } [Test] @@ -109,6 +109,24 @@ public async Task Received_BeforeTimeout_PreventsRetryExecution() request.DidNotReceive().HandleMessage(ResourceRequestMessage.New(1)); } + [Test] + public async Task Clear_cache_after_timeout() + { + Parallel.For(0, 100, (i) => + { + Parallel.For(0, 100, (j) => + { + ITestHandler request = Substitute.For(); + + _cache.Announced(i, request); + }); + }); + + await Task.Delay(Timeout * 2, _cancellationTokenSource.Token); + + Assert.That(_cache.ResourcesInRetryQueue, Is.Zero); + } + [Test] public void RetryExecution_HandlesExceptions() { @@ -143,7 +161,7 @@ public async Task Announced_AfterRetryInProgress_ReturnsNew() await Task.Delay(Timeout, _cancellationTokenSource.Token); - Assert.That(() => _cache.Announced(1, Substitute.For()), Is.EqualTo(AnnounceResult.New).After(Timeout, 100)); + Assert.That(() => _cache.Announced(1, Substitute.For()), Is.EqualTo(AnnounceResult.RequestRequired).After(Timeout, 100)); } [Test] diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs index 3c575ea5f35..8104846f17b 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxBroadcasterTests.cs @@ -739,7 +739,7 @@ public void can_correctly_broadcast_light_transactions_without_wrappers([Values] IChainHeadInfoProvider mockChainHeadInfoProvider = Substitute.For(); mockChainHeadInfoProvider.CurrentProofVersion.Returns(proofVersion); IReleaseSpec spec = Substitute.For(); - spec.BlobProofVersion.Returns(versionMatches ? proofVersion : GetInvalidVersion(proofVersion)); + spec.IsEip7594Enabled.Returns(versionMatches ? proofVersion == ProofVersion.V1 : proofVersion == ProofVersion.V0); SpecDrivenTxGossipPolicy gossipPolicy = new(mockChainHeadInfoProvider); @@ -755,13 +755,6 @@ public void can_correctly_broadcast_light_transactions_without_wrappers([Values] // Assert result.Should().Be(versionMatches, "LightTransaction from blob transaction should be gossiped when proof version matches."); - - // Gets (version + 1) % (version + 1) - so next version round robin - ProofVersion GetInvalidVersion(ProofVersion version) - { - byte mod = (byte)(FastEnum.GetMaxValue() + 1); - return (ProofVersion)((byte)(version + 1) % mod); - } } private (IList expectedTxs, IList expectedHashes) GetTxsAndHashesExpectedToBroadcast(Transaction[] transactions, int expectedCountTotal) diff --git a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs index c432bce991f..6f431b074d4 100644 --- a/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs +++ b/src/Nethermind/Nethermind.TxPool.Test/TxPoolTests.cs @@ -626,7 +626,7 @@ public void should_not_count_txs_with_stale_nonces_when_calculating_cumulative_c } [Test] - public void should_add_tx_if_cost_of_executing_all_txs_in_bucket_exceeds_balance_but_these_with_lower_nonces_doesnt() + public void should_add_tx_if_cost_of_executing_all_txs_in_bucket_exceeds_balance_but_these_with_lower_nonces_do_not() { const int count = 10; const int gasPrice = 10; diff --git a/src/Nethermind/Nethermind.TxPool/Collections/SortedPool.Events.cs b/src/Nethermind/Nethermind.TxPool/Collections/SortedPool.Events.cs index 9e6f0303765..b71516f43ae 100644 --- a/src/Nethermind/Nethermind.TxPool/Collections/SortedPool.Events.cs +++ b/src/Nethermind/Nethermind.TxPool/Collections/SortedPool.Events.cs @@ -7,10 +7,8 @@ namespace Nethermind.TxPool.Collections { public partial class SortedPool { -#pragma warning disable 67 public event EventHandler? Inserted; public event EventHandler? Removed; -#pragma warning restore 67 public class SortedPoolEventArgs(TKey key, TValue value) { diff --git a/src/Nethermind/Nethermind.TxPool/Filters/FeeTooLowFilter.cs b/src/Nethermind/Nethermind.TxPool/Filters/FeeTooLowFilter.cs index 1ab5ab76a3b..dee9638a68f 100644 --- a/src/Nethermind/Nethermind.TxPool/Filters/FeeTooLowFilter.cs +++ b/src/Nethermind/Nethermind.TxPool/Filters/FeeTooLowFilter.cs @@ -48,9 +48,7 @@ public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandl { Metrics.PendingTransactionsTooLowFee++; if (_logger.IsTrace) _logger.Trace($"Skipped adding transaction {tx.ToString(" ")}, too low payable gas price with options {handlingOptions} from {new StackTrace()}"); - return !isLocal ? - AcceptTxResult.FeeTooLow : - AcceptTxResult.FeeTooLow.WithMessage("Affordable gas price is 0"); + return AcceptTxResult.FeeTooLow; } TxDistinctSortedPool relevantPool = (tx.SupportsBlobs ? _blobTxs : _txs); @@ -63,10 +61,7 @@ public AcceptTxResult Accept(Transaction tx, ref TxFilteringState state, TxHandl _logger.Trace($"Skipped adding transaction {tx.ToString(" ")}, too low payable gas price with options {handlingOptions} from {new StackTrace()}"); } - return !isLocal ? - AcceptTxResult.FeeTooLow : - AcceptTxResult.FeeTooLow.WithMessage($"FeePerGas needs to be higher than {lastTx.GasBottleneck.Value} to be added to the TxPool. FeePerGas of rejected tx: {affordableGasPrice}."); - + return AcceptTxResult.FeeTooLow; } return AcceptTxResult.Accepted; diff --git a/src/Nethermind/Nethermind.TxPool/HashCache.cs b/src/Nethermind/Nethermind.TxPool/HashCache.cs index 429bd42a224..8ce7dd5d3fe 100644 --- a/src/Nethermind/Nethermind.TxPool/HashCache.cs +++ b/src/Nethermind/Nethermind.TxPool/HashCache.cs @@ -58,5 +58,14 @@ public void ClearCurrentBlockCache() { _currentBlockCache.Clear(); } + + /// + /// Clears both long-term and current block caches. + /// + public void ClearAll() + { + _longTermCache.Clear(); + _currentBlockCache.Clear(); + } } } diff --git a/src/Nethermind/Nethermind.TxPool/ITxPool.cs b/src/Nethermind/Nethermind.TxPool/ITxPool.cs index bdee1f2f448..4e6b9a0d7f9 100644 --- a/src/Nethermind/Nethermind.TxPool/ITxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/ITxPool.cs @@ -39,7 +39,7 @@ public interface ITxPool void AddPeer(ITxPoolPeer peer); void RemovePeer(PublicKey nodeId); bool ContainsTx(Hash256 hash, TxType txType); - AnnounceResult AnnounceTx(ValueHash256 txhash, IMessageHandler retryHandler); + AnnounceResult NotifyAboutTx(Hash256 txhash, IMessageHandler retryHandler); AcceptTxResult SubmitTx(Transaction tx, TxHandlingOptions handlingOptions); bool RemoveTransaction(Hash256? hash); Transaction? GetBestTx(); @@ -62,5 +62,11 @@ bool TryGetBlobAndProofV1(byte[] blobVersionedHash, public bool AcceptTxWhenNotSynced { get; set; } bool SupportsBlobs { get; } long PendingTransactionsAdded { get; } + + /// + /// Resets txpool state by clearing all caches (hash cache, account cache) + /// and removing all pending transactions. Used for integration testing after chain reorgs. + /// + void ResetTxPoolState(); } } diff --git a/src/Nethermind/Nethermind.TxPool/Nethermind.TxPool.csproj b/src/Nethermind/Nethermind.TxPool/Nethermind.TxPool.csproj index 2f5a3bab0bf..a229c249e60 100644 --- a/src/Nethermind/Nethermind.TxPool/Nethermind.TxPool.csproj +++ b/src/Nethermind/Nethermind.TxPool/Nethermind.TxPool.csproj @@ -6,7 +6,7 @@ - + diff --git a/src/Nethermind/Nethermind.TxPool/NonceManager.cs b/src/Nethermind/Nethermind.TxPool/NonceManager.cs index 074b7687f6c..b69692ce71f 100644 --- a/src/Nethermind/Nethermind.TxPool/NonceManager.cs +++ b/src/Nethermind/Nethermind.TxPool/NonceManager.cs @@ -63,8 +63,9 @@ private void TxAccepted() public NonceLocker TxWithNonceReceived(UInt256 nonce) { + NonceLocker locker = new(_accountLock, TxAccepted); _reservedNonce = nonce; - return new(_accountLock, TxAccepted); + return locker; } private void ReleaseNonces(UInt256 accountNonce) diff --git a/src/Nethermind/Nethermind.TxPool/NullTxPool.cs b/src/Nethermind/Nethermind.TxPool/NullTxPool.cs index 49d519cc02c..9ab7f22ab6b 100644 --- a/src/Nethermind/Nethermind.TxPool/NullTxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/NullTxPool.cs @@ -87,7 +87,7 @@ public bool TryGetBlobAndProofV1(byte[] blobVersionedHash, public UInt256 GetLatestPendingNonce(Address address) => 0; - public AnnounceResult AnnounceTx(ValueHash256 txhash, IMessageHandler retryHandler) => AnnounceResult.New; + public AnnounceResult NotifyAboutTx(Hash256 txhash, IMessageHandler retryHandler) => AnnounceResult.RequestRequired; public event EventHandler NewDiscovered { @@ -113,5 +113,6 @@ public event EventHandler? EvictedPending remove { } } public bool AcceptTxWhenNotSynced { get; set; } + public void ResetTxPoolState() { } } } diff --git a/src/Nethermind/Nethermind.TxPool/RetryCache.cs b/src/Nethermind/Nethermind.TxPool/RetryCache.cs index 6cd69a89cd6..4bd9b40626c 100644 --- a/src/Nethermind/Nethermind.TxPool/RetryCache.cs +++ b/src/Nethermind/Nethermind.TxPool/RetryCache.cs @@ -1,7 +1,8 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Collections.Pooled; +using ConcurrentCollections; +using Microsoft.Extensions.ObjectPool; using Nethermind.Core; using Nethermind.Core.Caching; using Nethermind.Logging; @@ -12,109 +13,203 @@ namespace Nethermind.TxPool; -public class RetryCache +public sealed class RetryCache : IAsyncDisposable where TMessage : INew where TResourceId : struct, IEquatable { private readonly int _timeoutMs; + private readonly CancellationToken _token; private readonly int _checkMs; - - private readonly ConcurrentDictionary>> _retryRequests = new(); + private readonly int _expiringQueueLimit; + private readonly int _maxRetryRequests; + private readonly Task _mainLoopTask; + private static readonly ObjectPool>> _handlerBagsPool = new DefaultObjectPool>>(new ConcurrentHashSetPolicy>()); + private readonly ConcurrentDictionary>> _retryRequests = new(); private readonly ConcurrentQueue<(TResourceId ResourceId, DateTimeOffset ExpiresAfter)> _expiringQueue = new(); + private int _expiringQueueCounter = 0; private readonly ClockKeyCache _requestingResources; private readonly ILogger _logger; + private readonly Func>, IMessageHandler, ConcurrentHashSet>> _announceUpdate; + + internal int ResourcesInRetryQueue => _expiringQueueCounter; - public RetryCache(ILogManager logManager, int timeoutMs = 2500, int requestingCacheSize = 1024, CancellationToken token = default) + public RetryCache(ILogManager logManager, int timeoutMs = 2500, int requestingCacheSize = 1024, int expiringQueueLimit = 10000, int maxRetryRequests = 8, CancellationToken token = default) { _logger = logManager.GetClassLogger(); _timeoutMs = timeoutMs; + _token = token; _checkMs = _timeoutMs / 5; _requestingResources = new(requestingCacheSize); + _expiringQueueLimit = expiringQueueLimit; + _maxRetryRequests = maxRetryRequests; + // Closure capture + _announceUpdate = AnnounceUpdate; - Task.Run(async () => + _mainLoopTask = Task.Run(async () => { PeriodicTimer timer = new(TimeSpan.FromMilliseconds(_checkMs)); while (await timer.WaitForNextTickAsync(token)) { - while (!token.IsCancellationRequested && _expiringQueue.TryPeek(out (TResourceId ResourceId, DateTimeOffset ExpiresAfter) item) && item.ExpiresAfter <= DateTimeOffset.UtcNow) + try { - _expiringQueue.TryDequeue(out item); - - if (_retryRequests.TryRemove(item.ResourceId, out PooledSet>? requests)) + while (!token.IsCancellationRequested && _expiringQueue.TryPeek(out (TResourceId ResourceId, DateTimeOffset ExpiresAfter) item) && item.ExpiresAfter <= DateTimeOffset.UtcNow) { - using (requests) + if (_expiringQueue.TryDequeue(out item)) { - if (requests.Count > 0) - { - _requestingResources.Set(item.ResourceId); - } - - if (_logger.IsTrace) _logger.Trace($"Sending retry requests for {item.ResourceId} after timeout"); - + Interlocked.Decrement(ref _expiringQueueCounter); - foreach (IMessageHandler retryHandler in requests) + if (_retryRequests.TryRemove(item.ResourceId, out ConcurrentHashSet>? requests)) { try { - retryHandler.HandleMessage(TMessage.New(item.ResourceId)); + bool set = false; + + foreach (IMessageHandler retryHandler in requests) + { + if (!set) + { + _requestingResources.Set(item.ResourceId); + set = true; + + if (_logger.IsTrace) _logger.Trace($"Sending retry requests for {item.ResourceId} after timeout"); + } + + try + { + retryHandler.HandleMessage(TMessage.New(item.ResourceId)); + } + catch (Exception ex) + { + if (_logger.IsTrace) _logger.Error($"Failed to send retry request to {retryHandler} for {item.ResourceId}", ex); + } + } } - catch (Exception ex) + finally { - if (_logger.IsTrace) _logger.Error($"Failed to send retry request to {retryHandler} for {item.ResourceId}", ex); + _handlerBagsPool.Return(requests); } } } } } + catch (Exception ex) + { + if (_logger.IsError) _logger.Error($"Unexpected error in {nameof(TResourceId)} retry cache loop", ex); + Clear(); + } } + + if (_logger.IsDebug) _logger.Debug($"{nameof(TResourceId)} retry cache stopped"); }, token); } - public AnnounceResult Announced(TResourceId resourceId, IMessageHandler retryHandler) + public AnnounceResult Announced(in TResourceId resourceId, IMessageHandler handler) { - if (!_requestingResources.Contains(resourceId)) + if (_token.IsCancellationRequested) { - bool added = false; + return AnnounceResult.RequestRequired; + } - _retryRequests.AddOrUpdate(resourceId, (resourceId) => - { - if (_logger.IsTrace) _logger.Trace($"Announced {resourceId} by {retryHandler}: NEW"); + if (_expiringQueueCounter > _expiringQueueLimit) + { + if (_logger.IsDebug) _logger.Warn($"{nameof(TResourceId)} retry queue is full"); - _expiringQueue.Enqueue((resourceId, DateTimeOffset.UtcNow.AddMilliseconds(_timeoutMs))); - added = true; + return AnnounceResult.RequestRequired; + } - return []; - }, (resourceId, dict) => + if (!_requestingResources.Contains(resourceId)) + { + AnnounceResult result = AnnounceResult.Delayed; + _retryRequests.AddOrUpdate(resourceId, (resourceId, retryHandler) => { - if (_logger.IsTrace) _logger.Trace($"Announced {resourceId} by {retryHandler}: UPDATE"); + return AnnounceAdd(resourceId, retryHandler, out result); + }, _announceUpdate, handler); - dict.Add(retryHandler); - return dict; - }); - - return added ? AnnounceResult.New : AnnounceResult.Enqueued; + return result; } - if (_logger.IsTrace) _logger.Trace($"Announced {resourceId} by {retryHandler}, but a retry is in progress already, immidietly firing"); + if (_logger.IsTrace) _logger.Trace($"Announced {resourceId} by {handler}, but a retry is in progress already, immediately firing"); + + return AnnounceResult.RequestRequired; + } + + private ConcurrentHashSet> AnnounceAdd(TResourceId resourceId, IMessageHandler retryHandler, out AnnounceResult result) + { + if (_logger.IsTrace) _logger.Trace($"Announced {resourceId} by {retryHandler}: NEW"); + + _expiringQueue.Enqueue((resourceId, DateTimeOffset.UtcNow.AddMilliseconds(_timeoutMs))); + Interlocked.Increment(ref _expiringQueueCounter); + + result = AnnounceResult.RequestRequired; + + return _handlerBagsPool.Get(); + } + + private ConcurrentHashSet> AnnounceUpdate(TResourceId resourceId, ConcurrentHashSet> requests, IMessageHandler retryHandler) + { + if (_logger.IsTrace) _logger.Trace($"Announced {resourceId} by {retryHandler}: UPDATE"); - return AnnounceResult.New; + if (requests.Count < _maxRetryRequests) + { + requests.Add(retryHandler); + } + + return requests; } - public void Received(TResourceId resourceId) + public void Received(in TResourceId resourceId) { if (_logger.IsTrace) _logger.Trace($"Received {resourceId}"); - _retryRequests.TryRemove(resourceId, out PooledSet>? _); + if (_retryRequests.TryRemove(resourceId, out ConcurrentHashSet>? item)) + { + _handlerBagsPool.Return(item); + } + _requestingResources.Delete(resourceId); } + + private void Clear() + { + _expiringQueueCounter = 0; + _expiringQueue.Clear(); + _requestingResources.Clear(); + + foreach (ConcurrentHashSet> requests in _retryRequests.Values) + { + _handlerBagsPool.Return(requests); + } + + _retryRequests.Clear(); + } + + public async ValueTask DisposeAsync() + { + try + { + await _mainLoopTask; + } + catch (OperationCanceledException) { } + + Clear(); + } } public enum AnnounceResult { - New, - Enqueued, - PendingRequest + RequestRequired, + Delayed } +internal class ConcurrentHashSetPolicy : IPooledObjectPolicy> +{ + public ConcurrentHashSet Create() => []; + + public bool Return(ConcurrentHashSet obj) + { + obj.Clear(); + return true; + } +} diff --git a/src/Nethermind/Nethermind.TxPool/TxPool.cs b/src/Nethermind/Nethermind.TxPool/TxPool.cs index 727ee2cf41a..367fa9c8f97 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPool.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPool.cs @@ -404,11 +404,6 @@ private void RemoveProcessedTransactions(Block block) } } - if (blockTx.Type == TxType.SetCode) - { - eip7702Txs++; - } - bool isKnown = IsKnown(txHash); if (!isKnown) { @@ -487,6 +482,46 @@ public void RemovePeer(PublicKey nodeId) public bool SupportsBlobs { get; } public long PendingTransactionsAdded => Volatile.Read(ref _pendingTransactionsAdded); + /// This is a debug/testing method that clears the entire txpool state. + /// Currently only used in the Taiko integration tests after chain reorgs. + public void ResetTxPoolState() + { + _newHeadLock.EnterWriteLock(); + try + { + // Clear hash cache and account cache + _hashCache.ClearAll(); + _accountCache.Reset(); + + // Also clear all pending transactions + // Get snapshot first to avoid modifying collection while iterating + Transaction[] pendingTxs = _transactions.GetSnapshot(); + foreach (Transaction tx in pendingTxs) + { + RemoveTransaction(tx.Hash); + } + + // Clear blob transactions too + Transaction[] pendingBlobTxs = _blobTransactions.GetSnapshot(); + foreach (Transaction tx in pendingBlobTxs) + { + RemoveTransaction(tx.Hash); + } + + // Update metrics after removal + Metrics.TransactionCount = _transactions.Count; + Metrics.BlobTransactionCount = _blobTransactions.Count; + + // Reset snapshots + _transactionSnapshot = null; + _blobTransactionSnapshot = null; + } + finally + { + _newHeadLock.ExitWriteLock(); + } + } + public AcceptTxResult SubmitTx(Transaction tx, TxHandlingOptions handlingOptions) { bool startBroadcast = _txPoolConfig.PersistentBroadcastEnabled @@ -498,7 +533,6 @@ public AcceptTxResult SubmitTx(Transaction tx, TxHandlingOptions handlingOptions // If local tx allow it to be accepted even when syncing !startBroadcast) { - _retryCache.Received(tx.Hash!); return AcceptTxResult.Syncing; } @@ -582,7 +616,10 @@ private void TryConvertProofVersion(Transaction tx) } } - public AnnounceResult AnnounceTx(ValueHash256 txhash, IMessageHandler retryHandler) => _retryCache.Announced(txhash, retryHandler); + public AnnounceResult NotifyAboutTx(Hash256 hash, IMessageHandler retryHandler) => + (!AcceptTxWhenNotSynced && _headInfo.IsSyncing) || _hashCache.Get(hash) ? + AnnounceResult.Delayed : + _retryCache.Announced(hash, retryHandler); private AcceptTxResult FilterTransactions(Transaction tx, TxHandlingOptions handlingOptions, ref TxFilteringState state) { @@ -963,13 +1000,14 @@ public async ValueTask DisposeAsync() if (_isDisposed) return; _isDisposed = true; _timer?.Dispose(); - _cts.Cancel(); + await _cts.CancelAsync(); TxPoolHeadChanged -= _broadcaster.OnNewHead; _broadcaster.Dispose(); _headInfo.HeadChanged -= OnHeadChange; _headBlocksChannel.Writer.Complete(); _transactions.Removed -= OnRemovedTx; + await _retryCache.DisposeAsync(); await _headProcessing; } diff --git a/src/Nethermind/Nethermind.TxPool/TxPoolSender.cs b/src/Nethermind/Nethermind.TxPool/TxPoolSender.cs index 231156f560f..e201090e4e7 100644 --- a/src/Nethermind/Nethermind.TxPool/TxPoolSender.cs +++ b/src/Nethermind/Nethermind.TxPool/TxPoolSender.cs @@ -22,7 +22,7 @@ public TxPoolSender(ITxPool txPool, ITxSealer sealer, INonceManager nonceManager _txPool = txPool ?? throw new ArgumentNullException(nameof(txPool)); _sealer = sealer ?? throw new ArgumentNullException(nameof(sealer)); _nonceManager = nonceManager ?? throw new ArgumentNullException(nameof(nonceManager)); - _ecdsa = ecdsa ?? throw new ArgumentException(nameof(ecdsa)); + _ecdsa = ecdsa ?? throw new ArgumentNullException(nameof(ecdsa)); } public ValueTask<(Hash256, AcceptTxResult?)> SendTransaction(Transaction tx, TxHandlingOptions txHandlingOptions) diff --git a/src/Nethermind/Nethermind.UI/package-lock.json b/src/Nethermind/Nethermind.UI/package-lock.json index a2cabaa11ed..d357511536c 100644 --- a/src/Nethermind/Nethermind.UI/package-lock.json +++ b/src/Nethermind/Nethermind.UI/package-lock.json @@ -1116,6 +1116,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } diff --git a/src/Nethermind/Nethermind.UI/scripts/app.ts b/src/Nethermind/Nethermind.UI/scripts/app.ts index d802ecb7b10..e1f9605b621 100644 --- a/src/Nethermind/Nethermind.UI/scripts/app.ts +++ b/src/Nethermind/Nethermind.UI/scripts/app.ts @@ -202,7 +202,7 @@ function updateTxPool(txPool: TxPool) { const logWindow = new LogWindow("nodeLog"); const gasInfo = new GasInfo("minGas", "medianGas", "aveGas", "maxGas", "gasLimit", "gasLimitDelta"); -const sse = new EventSource("/data/events"); +const sse = new EventSource("data/events"); sse.addEventListener("log", (e) => logWindow.receivedLog(e)); sse.addEventListener("processed", (e) => gasInfo.parseEvent(e)); @@ -246,7 +246,7 @@ sse.addEventListener("forkChoice", (e) => { const number = parseInt(data.head.number, 16); if (!setActiveBlock && number !== 0) { setActiveBlock = true; - document.getElementById("lastestBlock").classList.remove("not-active"); + document.getElementById("latestBlock").classList.remove("not-active"); setTimeout(resize, 10); } const safe = parseInt(data.safe, 16); diff --git a/src/Nethermind/Nethermind.Wallet/DevKeyStoreWallet.cs b/src/Nethermind/Nethermind.Wallet/DevKeyStoreWallet.cs index bc056c21f84..d0d2743a4a7 100644 --- a/src/Nethermind/Nethermind.Wallet/DevKeyStoreWallet.cs +++ b/src/Nethermind/Nethermind.Wallet/DevKeyStoreWallet.cs @@ -100,7 +100,7 @@ public Signature Sign(Hash256 message, Address address, SecureString passphrase) key = _keyStore.GetKey(address, passphrase).PrivateKey; } - var rs = SpanSecP256k1.SignCompact(message.Bytes, key.KeyBytes, out int v); + var rs = SecP256k1.SignCompact(message.Bytes, key.KeyBytes, out int v); return new Signature(rs, v); } @@ -116,7 +116,7 @@ public Signature Sign(Hash256 message, Address address) throw new SecurityException("Can only sign without passphrase when account is unlocked."); } - var rs = SpanSecP256k1.SignCompact(message.Bytes, key.KeyBytes, out int v); + var rs = SecP256k1.SignCompact(message.Bytes, key.KeyBytes, out int v); return new Signature(rs, v); } } diff --git a/src/Nethermind/Nethermind.Wallet/DevWallet.cs b/src/Nethermind/Nethermind.Wallet/DevWallet.cs index cd941753bc7..e5ca68166a8 100644 --- a/src/Nethermind/Nethermind.Wallet/DevWallet.cs +++ b/src/Nethermind/Nethermind.Wallet/DevWallet.cs @@ -117,7 +117,7 @@ private bool CheckPassword(Address address, SecureString passphrase) public Signature Sign(Hash256 message, Address address) { - var rs = SpanSecP256k1.SignCompact(message.Bytes, _keys[address].KeyBytes, out int v); + var rs = SecP256k1.SignCompact(message.Bytes, _keys[address].KeyBytes, out int v); return new Signature(rs, v); } } diff --git a/src/Nethermind/Nethermind.Wallet/ProtectedKeyStoreWallet.cs b/src/Nethermind/Nethermind.Wallet/ProtectedKeyStoreWallet.cs index c33deb9ee7a..2afe410ddfa 100644 --- a/src/Nethermind/Nethermind.Wallet/ProtectedKeyStoreWallet.cs +++ b/src/Nethermind/Nethermind.Wallet/ProtectedKeyStoreWallet.cs @@ -97,7 +97,7 @@ private Signature SignCore(Hash256 message, Address address, Func ge { var protectedPrivateKey = (ProtectedPrivateKey)_unlockedAccounts.Get(address.ToString()); using PrivateKey key = protectedPrivateKey is not null ? protectedPrivateKey.Unprotect() : getPrivateKeyWhenNotFound(); - var rs = SpanSecP256k1.SignCompact(message.Bytes, key.KeyBytes, out int v); + var rs = SecP256k1.SignCompact(message.Bytes, key.KeyBytes, out int v); return new Signature(rs, v); } } diff --git a/src/Nethermind/Nethermind.Xdc.Test/Contracts/MasternodeVotingContractTests.cs b/src/Nethermind/Nethermind.Xdc.Test/Contracts/MasternodeVotingContractTests.cs index 23c2095a6a7..359111e4158 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/Contracts/MasternodeVotingContractTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/Contracts/MasternodeVotingContractTests.cs @@ -5,6 +5,7 @@ using FluentAssertions; using Nethermind.Abi; using Nethermind.Blockchain; +using Nethermind.Consensus.Processing; using Nethermind.Core; using Nethermind.Core.Crypto; using Nethermind.Core.Extensions; @@ -67,9 +68,18 @@ public void GetCandidatesAndStake_GenesisSetup_CanReadExpectedCandidates() EthereumCodeInfoRepository codeInfoRepository = new(stateProvider); VirtualMachine virtualMachine = new(new TestBlockhashProvider(specProvider), specProvider, LimboLogs.Instance); - TransactionProcessor transactionProcessor = new(BlobBaseFeeCalculator.Instance, specProvider, stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); + EthereumTransactionProcessor transactionProcessor = new(BlobBaseFeeCalculator.Instance, specProvider, stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); - MasternodeVotingContract masterVoting = new(stateProvider, new AbiEncoder(), codeSource, new AutoReadOnlyTxProcessingEnv(transactionProcessor, stateProvider, Substitute.For())); + AutoReadOnlyTxProcessingEnv autoReadOnlyTxProcessingEnv = new AutoReadOnlyTxProcessingEnv(transactionProcessor, stateProvider, Substitute.For()); + + IReadOnlyTxProcessingEnvFactory readOnlyTxProcessingEnvFactory = Substitute.For(); + + readOnlyTxProcessingEnvFactory.Create().Returns(autoReadOnlyTxProcessingEnv); + //EthereumCodeInfoRepository codeInfoRepository = new(stateProvider); + //EthereumVirtualMachine virtualMachine = new(new TestBlockhashProvider(specProvider), specProvider, LimboLogs.Instance); + //EthereumTransactionProcessor transactionProcessor = new(BlobBaseFeeCalculator.Instance, specProvider, stateProvider, virtualMachine, codeInfoRepository, LimboLogs.Instance); + + MasternodeVotingContract masterVoting = new(new AbiEncoder(), codeSource, readOnlyTxProcessingEnvFactory); Address[] candidates = masterVoting.GetCandidates(genesis); candidates.Length.Should().Be(3); diff --git a/src/Nethermind/Nethermind.Xdc.Test/Helpers/TransactionBuilderXdcExtensions.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/TransactionBuilderXdcExtensions.cs new file mode 100644 index 00000000000..0de5fe077c0 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/TransactionBuilderXdcExtensions.cs @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Test.Builders; +using Nethermind.Xdc.Spec; + +namespace Nethermind.Xdc.Test.Helpers; + +public static class TransactionBuilderXdcExtensions +{ + // function sign(uint256 _blockNumber, bytes32 _blockHash) + // selector = 0xe341eaa4 + private static ReadOnlySpan SignSelector => new byte[] { 0xE3, 0x41, 0xEA, 0xA4 }; + + /// Sets 'To' to the XDC block-signer contract from the spec. + public static TransactionBuilder ToBlockSignerContract( + this TransactionBuilder b, IXdcReleaseSpec spec) + => b.To(spec.BlockSignerContract); + + /// + /// Appends ABI-encoded calldata for sign(uint256 _blockNumber, bytes32 _blockHash). + /// Calldata = 4-byte selector + 32-byte big-endian uint + 32-byte bytes32 (68 bytes total). + /// + public static TransactionBuilder WithXdcSigningData( + this TransactionBuilder b, long blockNumber, Hash256 blockHash) + => b.WithData(CreateSigningCalldata(blockNumber, blockHash)); + + private static byte[] CreateSigningCalldata(long blockNumber, Hash256 blockHash) + { + Span data = stackalloc byte[68]; // 4 + 32 + 32 + + // 0..3: selector + SignSelector.CopyTo(data); + + // 4..35: uint256 blockNumber (big-endian, right-aligned in 32 bytes) + var be = BitConverter.GetBytes((ulong)blockNumber); + if (BitConverter.IsLittleEndian) Array.Reverse(be); + // last 8 bytes of that 32 are the ulong + for (int i = 0; i < 8; i++) data[4 + 24 + i] = be[i]; + + // 36..67: bytes32 blockHash + blockHash.Bytes.CopyTo(data.Slice(36, 32)); + + return data.ToArray(); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcModuleTestOverrides.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcModuleTestOverrides.cs index 4adfbfcd4f1..c311814b1f0 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcModuleTestOverrides.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcModuleTestOverrides.cs @@ -10,6 +10,7 @@ using Nethermind.Core.Crypto; using Nethermind.Core.Specs; using Nethermind.Core.Test.Modules; +using Nethermind.Init.Modules; using Nethermind.JsonRpc; using Nethermind.KeyStore; using Nethermind.Logging; @@ -18,6 +19,7 @@ using Nethermind.Serialization.Rlp; using Nethermind.TxPool; using Nethermind.Wallet; +using Nethermind.Xdc.Contracts; using Nethermind.Xdc.Spec; using Nethermind.Xdc.Types; using NSubstitute; @@ -48,8 +50,10 @@ protected override void Load(ContainerBuilder builder) .AddModule(new PseudoNetworkModule()) .AddModule(new TestBlockProcessingModule()) + .AddSingleton() + // add missing components - .AddSingleton() + .AddSingleton() .AddSingleton() // Environments @@ -81,7 +85,7 @@ protected override void Load(ContainerBuilder builder) }); } - internal class RandomPenalizer(ISpecProvider specProvider) : IPenaltyHandler + internal class RandomPenaltyHandler(ISpecProvider specProvider) : IPenaltyHandler { readonly Dictionary _penaltiesCache = new(); public Address[] Penalize(long number, Hash256 currentHash, Address[] candidates, int count = 2) diff --git a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs index 33f5e224e9d..66ccec3426b 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestBlockchain.cs @@ -10,6 +10,7 @@ using Nethermind.Consensus.Comparers; using Nethermind.Consensus.Processing; using Nethermind.Consensus.Producers; +using Nethermind.Consensus.Rewards; using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Core.Test.Blockchain; @@ -55,7 +56,7 @@ public static async Task Create(int blocksToAdd = 3, bool use if (testConfiguration.SuggestGenesisOnStart) { - // The block added event is not waited by genesis, but its needed to wait here so that `AddBlock` wait correctly. + // The block added event is not waited by genesis, but it's needed to wait here so that `AddBlock` waits correctly. Task newBlockWaiter = chain.BlockTree.WaitForNewBlock(chain.CancellationToken); chain.MainProcessingContext.GenesisLoader.Load(); await newBlockWaiter; @@ -173,18 +174,27 @@ protected override ContainerBuilder ConfigureContainer(ContainerBuilder builder, .AddSingleton() .AddSingleton() .AddSingleton() - .AddScoped() + .AddScoped(ctx => + new XdcTestGenesisBuilder( + ctx.Resolve(), + ctx.Resolve(), + ctx.Resolve>().ToArray(), + ctx.Resolve(), + MasterNodeCandidates + ) + ) .AddSingleton() .AddSingleton((ctx) => new CandidateContainer(MasterNodeCandidates)) .AddSingleton(ctx => { var spec = ctx.Resolve(); - var logmanager = ctx.Resolve(); + var logManager = ctx.Resolve(); //Set the first signer to be a non master node to avoid accidental block proposals - return new Signer(spec.ChainId, TestItem.PrivateKeyA, logmanager); + return new Signer(spec.ChainId, TestItem.PrivateKeyA, logManager); }) .AddSingleton((_) => BlockProducer) //.AddSingleton((_) => BlockProducerRunner) + .AddSingleton() .AddSingleton() .AddSingleton(new ProcessExitSource(TestContext.CurrentContext.CancellationToken)) @@ -218,6 +228,7 @@ private IXdcReleaseSpec WrapReleaseSpec(IReleaseSpec spec) xdcSpec.LimitPenaltyEpoch = 2; xdcSpec.MinimumSigningTx = 1; xdcSpec.GasLimitBoundDivisor = 1024; + xdcSpec.BlockSignerContract = new Address("0x0000000000000000000000000000000000000089"); V2ConfigParams[] v2ConfigParams = [ new V2ConfigParams { @@ -263,6 +274,8 @@ private IXdcReleaseSpec WrapReleaseSpec(IReleaseSpec spec) ]; xdcSpec.V2Configs = v2ConfigParams.ToList(); + xdcSpec.ValidateChainId = false; + xdcSpec.Reward = 5000; return xdcSpec; } @@ -300,9 +313,9 @@ protected override IBlockProducerRunner CreateBlockProducerRunner() private class XdcTestGenesisBuilder( ISpecProvider specProvider, IWorldState state, - ISnapshotManager snapshotManager, IGenesisPostProcessor[] postProcessors, - Configuration testConfiguration + Configuration testConfiguration, + List masterNodeCandidates ) : IGenesisBuilder { public Block Build() @@ -311,6 +324,11 @@ public Block Build() state.CreateAccount(TestItem.AddressB, testConfiguration.AccountInitialValue); state.CreateAccount(TestItem.AddressC, testConfiguration.AccountInitialValue); + foreach (PrivateKey candidate in masterNodeCandidates) + { + state.CreateAccount(candidate.Address, testConfiguration.AccountInitialValue); + } + IXdcReleaseSpec? finalSpec = (IXdcReleaseSpec)specProvider.GetFinalSpec(); XdcBlockHeaderBuilder xdcBlockHeaderBuilder = new(); @@ -330,11 +348,15 @@ public Block Build() state.CommitTree(0); genesisBlock.Header.StateRoot = state.StateRoot; genesisBlock.Header.Hash = genesisBlock.Header.CalculateHash(); - snapshotManager.StoreSnapshot(new Types.Snapshot(genesisBlock.Number, genesisBlock.Hash!, finalSpec.GenesisMasterNodes)); return genesisBlock; } } + private class ZeroRewardCalculator : IRewardCalculator + { + public BlockReward[] CalculateRewards(Block block) => Array.Empty(); + } + public void ChangeReleaseSpec(Action reconfigure) { reconfigure((XdcReleaseSpec)SpecProvider.GetXdcSpec((XdcBlockHeader)BlockTree.Head!.Header)); @@ -371,16 +393,12 @@ public override async Task AddBlock(params Transaction[] transactions) { var b = await AddBlockWithoutCommitQc(transactions); CreateAndCommitQC((XdcBlockHeader)b.Header); - return b; } public override async Task AddBlockFromParent(BlockHeader parent, params Transaction[] transactions) { var b = await base.AddBlockFromParent(parent, transactions); - - CheckIfTimeForSnapshot(); - CreateAndCommitQC((XdcBlockHeader)b.Header); return b; @@ -389,22 +407,9 @@ public override async Task AddBlockFromParent(BlockHeader parent, params public async Task AddBlockWithoutCommitQc(params Transaction[] txs) { await base.AddBlock(txs); - - CheckIfTimeForSnapshot(); - return BlockTree.Head!; } - private void CheckIfTimeForSnapshot() - { - var head = (XdcBlockHeader)BlockTree.Head!.Header; - var headSpec = SpecProvider.GetXdcSpec(head, XdcContext.CurrentRound); - if (ISnapshotManager.IsTimeforSnapshot(head.Number, headSpec)) - { - SnapshotManager.StoreSnapshot(new Types.Snapshot(head.Number, head.Hash!, MasterNodeCandidates.Select(k => k.Address).ToArray())); - } - } - public async Task TriggerAndSimulateBlockProposalAndVoting() { await TriggerBlockProposal(); diff --git a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestDepositContract.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestDepositContract.cs new file mode 100644 index 00000000000..2d032653521 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestDepositContract.cs @@ -0,0 +1,34 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Core.Extensions; +using Nethermind.Int256; +using Nethermind.Xdc.Contracts; +using System.Linq; + +namespace Nethermind.Xdc.Test.Helpers; + +internal class XdcTestDepositContract(CandidateContainer candidateContainer) : IMasternodeVotingContract +{ + public Address[] GetCandidatesByStake(BlockHeader blockHeader) + { + //We fake ordering by returning addresses instead of stake in descending order + return candidateContainer.MasternodeCandidates.Select(m => m.Address).OrderByDescending(a => a).ToArray(); + } + + public Address[] GetCandidates(BlockHeader blockHeader) + { + return candidateContainer.MasternodeCandidates.Select(m => m.Address).ToArray(); + } + + public UInt256 GetCandidateStake(BlockHeader blockHeader, Address candidate) + { + return 10_000_000.Ether(); + } + + public Address GetCandidateOwner(BlockHeader blockHeader, Address candidate) + { + throw new System.NotImplementedException(); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestHelper.cs b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestHelper.cs index a6cf48bb438..9b6021e10fa 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestHelper.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/Helpers/XdcTestHelper.cs @@ -31,13 +31,13 @@ public static QuorumCertificate CreateQc(BlockRoundInfo roundInfo, ulong gapNumb return new QuorumCertificate(roundInfo, signatures.ToArray(), gapNumber); } - public static Signature[] CreateVoteSignatures(BlockRoundInfo roundInfo, ulong gapnumber, PrivateKey[] keys) + public static Signature[] CreateVoteSignatures(BlockRoundInfo roundInfo, ulong gapNumber, PrivateKey[] keys) { var encoder = new VoteDecoder(); IEnumerable signatures = keys.Select(k => { var stream = new KeccakRlpStream(); - encoder.Encode(stream, new Vote(roundInfo, gapnumber), RlpBehaviors.ForSealing); + encoder.Encode(stream, new Vote(roundInfo, gapNumber), RlpBehaviors.ForSealing); return ecdsa.Sign(k, stream.GetValueHash()); }).ToArray(); return signatures.ToArray(); diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/HeaderVerificationTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/HeaderVerificationTests.cs index f4226215cca..a228172df53 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/HeaderVerificationTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/HeaderVerificationTests.cs @@ -51,8 +51,8 @@ public void Block_With_Invalid_Qc_Fails() var voteForSign = new Vote(proposedBlockInfo, 1); var validSigners = xdcTestBlockchain.MasterNodeCandidates - .Where(pvkey => invalidRoundBlockParent.ValidatorsAddress!.Value.Contains(pvkey.Address)) - .Select(pvkey => new Signer(0, pvkey, xdcTestBlockchain.LogManager)) + .Where(pvKey => invalidRoundBlockParent.ValidatorsAddress!.Value.Contains(pvKey.Address)) + .Select(pvKey => new Signer(0, pvKey, xdcTestBlockchain.LogManager)) .ToList(); List signatures = []; @@ -73,7 +73,7 @@ public void Block_With_Invalid_Qc_Fails() } [Test] - public async Task Block_With_Illigitimate_Signer_Fails() + public async Task Block_With_Illegitimate_Signer_Fails() { var previousSigner = xdcSigner.Key; @@ -255,8 +255,8 @@ public void Block_With_QcSignature_Below_Threshold_Fails() var proposedBlockInfo = new BlockRoundInfo(invalidQcSignatureBlockParent!.Hash!, invalidQcSignatureBlockParent.ExtraConsensusData!.BlockRound, invalidQcSignatureBlockParent.Number); var voteForSign = new Vote(proposedBlockInfo, 1); var validSigners = xdcTestBlockchain.MasterNodeCandidates - .Where(pvkey => invalidQcSignatureBlockParent.ValidatorsAddress!.Value.Contains(pvkey.Address)) - .Select(pvkey => new Signer(0, pvkey, xdcTestBlockchain.LogManager)) + .Where(pvKey => invalidQcSignatureBlockParent.ValidatorsAddress!.Value.Contains(pvKey.Address)) + .Select(pvKey => new Signer(0, pvKey, xdcTestBlockchain.LogManager)) .ToList(); List signatures = []; diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/MineModuleTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/MineModuleTests.cs index 575d4067ae4..9c451b00b6b 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/MineModuleTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/MineModuleTests.cs @@ -53,7 +53,7 @@ public async Task TestUpdateMultipleMasterNodes() // mine the gap block that should trigger master node update var gapBlock = await blockchain.AddBlock(); - while (!ISnapshotManager.IsTimeforSnapshot(tree.Head!.Header!.Number, spec)) + while (!ISnapshotManager.IsTimeForSnapshot(tree.Head!.Header!.Number, spec)) { gapBlock = await blockchain.AddBlock(); } @@ -61,7 +61,7 @@ public async Task TestUpdateMultipleMasterNodes() var newHead = (XdcBlockHeader)tree.Head!.Header!; Assert.That(newHead.Number, Is.EqualTo(gapBlock.Number)); - var snapshotAfter = blockchain.SnapshotManager.GetSnapshotByGapNumber((ulong)newHead.Number); + var snapshotAfter = blockchain.SnapshotManager.GetSnapshotByGapNumber(newHead.Number); Assert.That(snapshotAfter, Is.Not.Null); Assert.That(snapshotAfter.BlockNumber, Is.EqualTo(gapBlock.Number)); @@ -127,7 +127,7 @@ public async Task TestUpdateMasterNodes() Assert.That(snapshot.NextEpochCandidates.Length, Is.EqualTo(30)); var gapBlock = await blockchain.AddBlock(); - while (!ISnapshotManager.IsTimeforSnapshot(tree.Head!.Header!.Number, spec)) + while (!ISnapshotManager.IsTimeForSnapshot(tree.Head!.Header!.Number, spec)) { gapBlock = await blockchain.AddBlock(); } @@ -136,14 +136,12 @@ public async Task TestUpdateMasterNodes() header = (XdcBlockHeader)gapBlock.Header!; spec = blockchain.SpecProvider.GetXdcSpec(header); - snapshot = blockchain.SnapshotManager.GetSnapshotByGapNumber((ulong)header.Number); + snapshot = blockchain.SnapshotManager.GetSnapshotByGapNumber(header.Number); Assert.That(snapshot, Is.Not.Null); Assert.That(snapshot.BlockNumber, Is.EqualTo(gapBlock.Number)); Assert.That(snapshot.NextEpochCandidates.Length, Is.EqualTo(blockchain.MasterNodeCandidates.Count)); - long tstamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - var epochSwitchBlock = await blockchain.AddBlock(); while (!blockchain.EpochSwitchManager.IsEpochSwitchAtBlock((XdcBlockHeader)tree.Head!.Header!)) { diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs index 86754fa94ee..fe51092ef36 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/ProposedBlockTests.cs @@ -17,7 +17,7 @@ namespace Nethermind.Xdc.Test; internal class ProposedBlockTests { [Test] - public async Task TestShouldSendVoteMsgAndCommitGrandGrandParentBlockAsync() + public async Task TestShouldSendVoteMsgAndCommitGreatGrandparentBlockAsync() { var blockChain = await XdcTestBlockchain.Create(2, true); @@ -32,8 +32,8 @@ public async Task TestShouldSendVoteMsgAndCommitGrandGrandParentBlockAsync() { //If we randomly picked the block proposer we need to remove him with a another voting masternode var extraMaster = switchInfo.Masternodes.First((m) => m != head.Beneficiary && masternodes.Any(x => x.Address != m)); - var extraMasterkey = blockChain.MasterNodeCandidates.First(x => x.Address == extraMaster); - masternodes = [.. masternodes.Where(x => x.Address != head.Beneficiary), extraMasterkey]; + var extraMasterKey = blockChain.MasterNodeCandidates.First(x => x.Address == extraMaster); + masternodes = [.. masternodes.Where(x => x.Address != head.Beneficiary), extraMasterKey]; } BlockRoundInfo votingBlock = new BlockRoundInfo(head.Hash!, blockChain.XdcContext.CurrentRound, head.Number); @@ -106,8 +106,8 @@ public async Task TestProposedBlockMessageHandlerSuccessfullyGenerateVote() { //If we randomly picked the block proposer we need to remove him with a another voting masternode var extraMaster = switchInfo.Masternodes.First((m) => m != head.Beneficiary && masternodes.Any(x => x.Address != m)); - var extraMasterkey = blockChain.MasterNodeCandidates.First(x => x.Address == extraMaster); - masternodes = [.. masternodes.Where(x => x.Address != head.Beneficiary), extraMasterkey]; + var extraMasterKey = blockChain.MasterNodeCandidates.First(x => x.Address == extraMaster); + masternodes = [.. masternodes.Where(x => x.Address != head.Beneficiary), extraMasterKey]; } BlockRoundInfo votingBlock = new BlockRoundInfo(head.Hash!, blockChain.XdcContext.CurrentRound, head.Number); @@ -181,8 +181,8 @@ public async Task TestProposedBlockMessageHandlerNotGenerateVoteIfSignerNotInMNl { //If we randomly picked the block proposer we need to remove him with a another voting masternode var extraMaster = switchInfo.Masternodes.First((m) => m != head.Beneficiary && masternodes.Any(x => x.Address != m)); - var extraMasterkey = blockChain.MasterNodeCandidates.First(x => x.Address == extraMaster); - masternodes = [.. masternodes.Where(x => x.Address != head.Beneficiary), extraMasterkey]; + var extraMasterKey = blockChain.MasterNodeCandidates.First(x => x.Address == extraMaster); + masternodes = [.. masternodes.Where(x => x.Address != head.Beneficiary), extraMasterKey]; } BlockRoundInfo votingBlock = new BlockRoundInfo(head.Hash!, blockChain.XdcContext.CurrentRound, head.Number); diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/RewardTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/RewardTests.cs new file mode 100644 index 00000000000..08aaa236e43 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/RewardTests.cs @@ -0,0 +1,323 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Nethermind.Blockchain; +using Nethermind.Consensus.Rewards; +using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Core.Test.Builders; +using Nethermind.Crypto; +using Nethermind.Int256; +using Nethermind.Xdc.Contracts; +using Nethermind.Xdc.Spec; +using Nethermind.Xdc.Test.Helpers; +using Nethermind.Xdc.Types; +using NSubstitute; +using NUnit.Framework; + +namespace Nethermind.Xdc.Test.ModuleTests; + +public class RewardTests +{ + // Test ported from XDC reward_test : + // https://github.com/XinFinOrg/XDPoSChain/blob/af4178b2c7f9d668d8ba1f3a0244606a20ce303d/consensus/tests/engine_v2_tests/reward_test.go#L18 + [Test] + public async Task TestHookRewardV2() + { + var chain = await XdcTestBlockchain.Create(); + var masternodeVotingContract = Substitute.For(); + var rc = new XdcRewardCalculator( + chain.EpochSwitchManager, + chain.SpecProvider, + chain.BlockTree, + masternodeVotingContract + ); + var head = (XdcBlockHeader)chain.BlockTree.Head!.Header; + IXdcReleaseSpec spec = chain.SpecProvider.GetXdcSpec(head, chain.XdcContext.CurrentRound); + var epochLength = spec.EpochLength; + + // Add blocks up to epochLength (E) + 15 and create a signing tx that will be inserted in the next block + await chain.AddBlocks(epochLength + 15 - 3); + var header915 = chain.BlockTree.Head!.Header as XdcBlockHeader; + Assert.That(header915, Is.Not.Null); + PrivateKey signer915 = GetSignerFromMasternodes(chain, header915, spec); + Address owner = signer915.Address; + masternodeVotingContract.GetCandidateOwner(Arg.Any(), signer915.Address).Returns(owner); + await chain.AddBlock(BuildSigningTx( + spec, + header915.Number, + header915.Hash ?? header915.CalculateHash().ToHash256(), + signer915)); + + // Add blocks up until 3E and evaluate rewards at this checkpoint + // Save block 3E - 15 for second part of the test + await chain.AddBlocks(2 * epochLength - 31); + var header2685 = chain.BlockTree.Head!.Header as XdcBlockHeader; + Assert.That(header2685, Is.Not.Null); + PrivateKey signer2685 = GetSignerFromMasternodes(chain, header2685, spec); + Address owner2 = signer2685.Address; + masternodeVotingContract.GetCandidateOwner(Arg.Any(), signer2685.Address).Returns(owner2); + // Continue adding blocks up until 3E + await chain.AddBlocks(15); + Block block2700 = chain.BlockTree.Head!; + var header2700 = block2700.Header as XdcBlockHeader; + Assert.That(header2700, Is.Not.Null); + + BlockReward[] rewardsAt2700 = rc.CalculateRewards(block2700); + + // Expect exactly 2 entries: one for the masternode owner and one for foundation + Address foundation = spec.FoundationWallet; + foundation.Should().NotBe(Address.Zero); + + Assert.That(rewardsAt2700, Has.Length.EqualTo(2)); + rewardsAt2700.Length.Should().Be(2); + + UInt256 total = rewardsAt2700.Aggregate(UInt256.Zero, (acc, r) => acc + r.Value); + UInt256 ownerReward = rewardsAt2700.Single(r => r.Address == owner).Value; + UInt256 foundationReward = rewardsAt2700.Single(r => r.Address == foundation).Value; + + // Check 90/10 split + Assert.That(foundationReward, Is.EqualTo(total / 10)); + Assert.That(ownerReward, Is.EqualTo(total * 90 / 100)); + + // === Second part of the test: signing hash in a different epoch still counts === + + Transaction signingTx2 = BuildSigningTx( + spec, + header2685.Number, + header2685.Hash!, + signer2685); + + // Place signingTx2 in block 3E + 16 (different epoch than the signed block) + await chain.AddBlocks(15); + await chain.AddBlock(signingTx2); + + // Add blocks up until 4E and check rewards + await chain.AddBlocks(epochLength - 16); + Block block3600 = chain.BlockTree.Head!; + var header3600 = block3600.Header as XdcBlockHeader; + Assert.That(header3600, Is.Not.Null); + BlockReward[] rewardsAt3600 = rc.CalculateRewards(block3600); + + // Same expectations: exactly two outputs with 90/10 split + // Since this only counts signing txs from 2E to 3E, only signingTx2 should get counted + Assert.That(rewardsAt3600, Has.Length.EqualTo(2)); + UInt256 total2 = rewardsAt3600.Aggregate(UInt256.Zero, (acc, r) => acc + r.Value); + UInt256 ownerReward2 = rewardsAt3600.Single(r => r.Address == owner2).Value; + UInt256 foundationReward2 = rewardsAt3600.Single(r => r.Address == foundation).Value; + + Assert.That(foundationReward2, Is.EqualTo(total2 / 10)); + Assert.That(ownerReward2, Is.EqualTo(total2 * 90 / 100)); + + // === Third part of the test: if no signing tx, reward should be empty === + + // Add blocks up to 5E and check rewards + await chain.AddBlocks(epochLength); + Block block4500 = chain.BlockTree.Head!; + BlockReward[] rewardsAt4500 = rc.CalculateRewards(block4500); + // If no valid signing txs were counted for that epoch, expect no rewards. + rewardsAt4500.Should().BeEmpty(); + } + + // Test ported from XDC reward_test : + // https://github.com/XinFinOrg/XDPoSChain/blob/af4178b2c7f9d668d8ba1f3a0244606a20ce303d/consensus/tests/engine_v2_tests/reward_test.go#L99 + [Test] + public async Task TestHookRewardV2SplitReward() + { + var chain = await XdcTestBlockchain.Create(); + var masternodeVotingContract = Substitute.For(); + var rc = new XdcRewardCalculator( + chain.EpochSwitchManager, + chain.SpecProvider, + chain.BlockTree, + masternodeVotingContract + ); + + var head = (XdcBlockHeader)chain.BlockTree.Head!.Header; + IXdcReleaseSpec spec = chain.SpecProvider.GetXdcSpec(head, chain.XdcContext.CurrentRound); + var epochLength = spec.EpochLength; + + // - Insert 1 signing tx for header (E + 15) signed by signerA into block (E + 16) + // - Insert 2 signing txs (one for header (E + 15), one for header (2E - 15)) signed by signerB into block (2E - 1) + // - Verify: rewards at (3E) split 1:2 between A:B with 90/10 owner/foundation + + // Move to block (E + 15) + await chain.AddBlocks(epochLength + 15 - 3); + var header915 = (XdcBlockHeader)chain.BlockTree.Head!.Header; + + EpochSwitchInfo? epochInfo = chain.EpochSwitchManager.GetEpochSwitchInfo(header915); + Assert.That(epochInfo, Is.Not.Null); + PrivateKey[] masternodes = chain.TakeRandomMasterNodes(spec, epochInfo); + PrivateKey signerA = masternodes.First(); + PrivateKey signerB = masternodes.Last(); + Address ownerA = signerA.Address; + Address ownerB = signerB.Address; + masternodeVotingContract.GetCandidateOwner(Arg.Any(), signerA.Address).Returns(ownerA); + masternodeVotingContract.GetCandidateOwner(Arg.Any(), signerB.Address).Returns(ownerB); + + // Insert 1 signing tx for header (E + 15) in block (E + 16) + Transaction txA = BuildSigningTx( + spec, + header915.Number, + header915.Hash ?? header915.CalculateHash().ToHash256(), + signerA); + await chain.AddBlock(txA); // advances to (E + 16) + + // Move to block (2E - 15) to capture that header as well + var currentNumber = chain.BlockTree.Head!.Number; + await chain.AddBlocks(epochLength - 31); + var header1785 = (XdcBlockHeader)chain.BlockTree.Head!.Header; + + // Prepare two signing txs signed by signerB: + // - for header (E + 15) + // - for header (2E - 15) + Transaction txB1 = BuildSigningTx( + spec, + header915.Number, + header915.Hash!, + signerB); + + Transaction txB2 = BuildSigningTx( + spec, + header1785.Number, + header1785.Hash!, + signerB, + 1); + + // Advance to (2E - 2), then add a block with both signerB txs to be at (2E - 1) + await chain.AddBlocks(13); + await chain.AddBlock(txB1, txB2); // now at (2E - 1) + + // Rewards at (3E) should exist with split 1:2 across A:B and 90/10 owner/foundation + await chain.AddBlocks(epochLength + 1); // from (2E - 1) to (3E) + Block block2700 = chain.BlockTree.Head!; + BlockReward[] rewards = rc.CalculateRewards(block2700); + + Address foundation = spec.FoundationWallet; + + // Expect exactly 3 entries: ownerA, ownerB, foundation. + Assert.That(rewards.Length, Is.EqualTo(3)); + + // Calculate exact rewards for each address + UInt256 totalRewards = (UInt256)spec.Reward * Unit.Ether; + UInt256 signerAReward = totalRewards / 3; + UInt256 ownerAReward = signerAReward * 90 / 100; + UInt256 foundationReward = signerAReward / 10; + UInt256 signerBReward = totalRewards * 2 / 3; + UInt256 ownerBReward = signerBReward * 90 / 100; + foundationReward += signerBReward / 10; + + foreach (BlockReward reward in rewards) + { + if (reward.Address == ownerA) Assert.That(reward.Value, Is.EqualTo(ownerAReward)); + if (reward.Address == ownerB) Assert.That(reward.Value, Is.EqualTo(ownerBReward)); + if (reward.Address == foundation) Assert.That(reward.Value, Is.EqualTo(foundationReward)); + } + } + + // Test to check calculated rewards against precalculated values from : + // https://github.com/XinFinOrg/XDPoSChain/blob/af4178b2c7f9d668d8ba1f3a0244606a20ce303d/consensus/tests/engine_v2_tests/reward_test.go#L147 + [Test] + public void RewardCalculator_SplitReward_MatchesRounding() + { + const long epoch = 45, reward = 250; + PrivateKey[] masternodes = XdcTestHelper.GeneratePrivateKeys(2); + PrivateKey signerA = masternodes.First(); + PrivateKey signerB = masternodes.Last(); + var foundationWalletAddr = new Address("0x0000000000000000000000000000000000000068"); + var blockSignerContract = new Address("0x0000000000000000000000000000000000000089"); + + IEpochSwitchManager epochSwitchManager = Substitute.For(); + epochSwitchManager.IsEpochSwitchAtBlock(Arg.Any()) + .Returns(ci => ((XdcBlockHeader)ci.Args()[0]!).Number % epoch == 0); + + IXdcReleaseSpec xdcSpec = Substitute.For(); + xdcSpec.EpochLength.Returns((int)epoch); + xdcSpec.FoundationWallet.Returns(foundationWalletAddr); + xdcSpec.BlockSignerContract.Returns(blockSignerContract); + xdcSpec.Reward.Returns(reward); + xdcSpec.SwitchBlock.Returns(0); + ISpecProvider specProvider = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(xdcSpec); + + IBlockTree tree = Substitute.For(); + long size = 2 * epoch + 1; + var blockHeaders = new XdcBlockHeader[size]; + var blocks = new Block[size]; + for (int i = 0; i <= epoch * 2; i++) + { + blockHeaders[i] = Build.A.XdcBlockHeader() + .WithNumber(i) + .WithValidators(masternodes.Select(m => m.Address).ToArray()) + .WithExtraConsensusData(new ExtraFieldsV2((ulong)i, Build.A.QuorumCertificate().TestObject)) + .TestObject; + blocks[i] = new Block(blockHeaders[i]); + } + + // SignerA signs blocks 15 and 30 + // SignerB signs blocks 15 + var txsBlock16 = new List(); + txsBlock16.Add(BuildSigningTx(xdcSpec, 15, blockHeaders[15].Hash!, signerA, 1)); + txsBlock16.Add(BuildSigningTx(xdcSpec, 15, blockHeaders[15].Hash!, signerB, 2)); + var txsBlock31 = new List(); + txsBlock31.Add(BuildSigningTx(xdcSpec, 30, blockHeaders[30].Hash!, signerA, 3)); + blocks[16] = new Block(blockHeaders[16], new BlockBody(txsBlock16.ToArray(), null, null)); + blocks[31] = new Block(blockHeaders[31], new BlockBody(txsBlock31.ToArray(), null, null)); + tree.FindHeader(Arg.Any(), Arg.Any()) + .Returns(ci => blockHeaders[(long)ci.Args()[1]]); + tree.FindBlock(Arg.Any()) + .Returns(ci => blocks[(long)ci.Args()[0]]); + + IMasternodeVotingContract votingContract = Substitute.For(); + votingContract.GetCandidateOwner(Arg.Any(), Arg.Any
()) + .Returns(ci => ci.Arg
()); + + var rewardCalculator = new XdcRewardCalculator(epochSwitchManager, specProvider, tree, votingContract); + BlockReward[] rewards = rewardCalculator.CalculateRewards(blocks.Last()); + + // Expect ownerA, ownerB, and foundation + Assert.That(rewards, Has.Length.EqualTo(3)); + + // Expected values from XDC repo: + // A gets 2/3 of total, then 90% owner, 10% foundation (flooring at each integer division step) + // B gets 1/3 of total, then 90% owner, 10% foundation + var aOwnerExpected = UInt256.Parse("149999999999999999999"); + var aFoundExpected = UInt256.Parse("16666666666666666666"); + var bOwnerExpected = UInt256.Parse("74999999999999999999"); + var bFoundExpected = UInt256.Parse("8333333333333333333"); + + UInt256 aOwnerReward = rewards.Single(r => r.Address == signerA.Address).Value; + UInt256 bOwnerReward = rewards.Single(r => r.Address == signerB.Address).Value; + UInt256 foundationReward = rewards.Single(r => r.Address == foundationWalletAddr).Value; + + Assert.That(foundationReward, Is.EqualTo(aFoundExpected + bFoundExpected)); + Assert.That(aOwnerReward, Is.EqualTo(aOwnerExpected)); + Assert.That(bOwnerReward, Is.EqualTo(bOwnerExpected)); + + } + + + private static Transaction BuildSigningTx(IXdcReleaseSpec spec, long blockNumber, Hash256 blockHash, PrivateKey signer, long nonce = 0) + { + return Build.A.Transaction + .WithChainId(0) + .WithNonce((UInt256)nonce) + .WithGasLimit(200000) + .WithXdcSigningData(blockNumber, blockHash) + .ToBlockSignerContract(spec) + .SignedAndResolved(signer) + .TestObject; + } + + private static PrivateKey GetSignerFromMasternodes(XdcTestBlockchain chain, XdcBlockHeader header, IXdcReleaseSpec spec) + { + EpochSwitchInfo? epochInfo = chain.EpochSwitchManager.GetEpochSwitchInfo(header); + Assert.That(epochInfo, Is.Not.Null); + return chain.TakeRandomMasterNodes(spec, epochInfo).First(); + } +} diff --git a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs index b1f4b45b86e..02a8317d50f 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/ModuleTests/XdcReorgModuleTests.cs @@ -48,24 +48,24 @@ public async Task BuildAValidForkOnFinalizedBlockAndAssertForkBecomesCanonical() newHeadWaitHandle.SetResult(); }; - XdcBlockHeader forkparent = finalizedBlock; + XdcBlockHeader forkParent = finalizedBlock; for (int i = 0; i < 3; i++) { //Build a fork on finalized block, which should result in fork becoming new head - forkparent = (XdcBlockHeader)(await blockChain.AddBlockFromParent(forkparent)).Header; + forkParent = (XdcBlockHeader)(await blockChain.AddBlockFromParent(forkParent)).Header; } - if (blockChain.BlockTree.Head!.Hash != forkparent.Hash) + if (blockChain.BlockTree.Head!.Hash != forkParent.Hash) { //Wait for new head await Task.WhenAny(newHeadWaitHandle.Task, Task.Delay(5_000)); } - blockChain.BlockTree.Head!.Hash.Should().Be(forkparent.Hash!); + blockChain.BlockTree.Head!.Hash.Should().Be(forkParent.Hash!); //The new fork head should commit it's grandparent as finalized - blockChain.XdcContext.HighestCommitBlock.Hash.Should().Be(blockChain.BlockTree.FindHeader(forkparent.ParentHash!)!.ParentHash!); + blockChain.XdcContext.HighestCommitBlock.Hash.Should().Be(blockChain.BlockTree.FindHeader(forkParent.ParentHash!)!.ParentHash!); //Our lock QC should be parent of the fork head - blockChain.XdcContext.LockQC!.ProposedBlockInfo.Hash.Should().Be(forkparent.ParentHash!); + blockChain.XdcContext.LockQC!.ProposedBlockInfo.Hash.Should().Be(forkParent.ParentHash!); } [TestCase(5)] diff --git a/src/Nethermind/Nethermind.Xdc.Test/SnapshotDecoderTests.cs b/src/Nethermind/Nethermind.Xdc.Test/SnapshotDecoderTests.cs index 873b55a067f..6c4702d670f 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/SnapshotDecoderTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/SnapshotDecoderTests.cs @@ -21,7 +21,7 @@ public class SnapshotDecoderTests ]; [Test, TestCaseSource(nameof(Snapshots))] - public void RoundTrip_valuedecoder(Snapshot original) + public void RoundTrip_ValueDecoder(Snapshot original) { SnapshotDecoder encoder = new(); RlpStream rlpStream = new(encoder.GetLength(original, RlpBehaviors.None)); diff --git a/src/Nethermind/Nethermind.Xdc.Test/SnapshotManagerTests.cs b/src/Nethermind/Nethermind.Xdc.Test/SnapshotManagerTests.cs index 2061e9efa5d..4a8516e2dde 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/SnapshotManagerTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/SnapshotManagerTests.cs @@ -4,8 +4,10 @@ using FluentAssertions; using Nethermind.Blockchain; using Nethermind.Core; +using Nethermind.Core.Specs; using Nethermind.Core.Test.Builders; using Nethermind.Db; +using Nethermind.Xdc.Contracts; using Nethermind.Xdc.Spec; using Nethermind.Xdc.Types; using NSubstitute; @@ -29,16 +31,16 @@ public void Setup() _snapshotDb = new MemDb(); - IPenaltyHandler penaltyHandler = NSubstitute.Substitute.For(); + IPenaltyHandler penaltyHandler = Substitute.For(); _blockTree = Substitute.For(); - _snapshotManager = new SnapshotManager(_snapshotDb, _blockTree, penaltyHandler); + _snapshotManager = new SnapshotManager(_snapshotDb, _blockTree, penaltyHandler, Substitute.For(), Substitute.For()); } [Test] public void GetSnapshot_ShouldReturnNullForNonExistentSnapshot() { // Act - var result = _snapshotManager.GetSnapshotByBlockNumber(0, _xdcReleaseSpec); + Snapshot? result = _snapshotManager.GetSnapshotByBlockNumber(0, _xdcReleaseSpec); // Assert result.Should().BeNull(); @@ -55,7 +57,7 @@ public void GetSnapshot_ShouldRetrieveFromIfFound() _blockTree.FindHeader(gapBlock).Returns(header); // Act - var result = _snapshotManager.GetSnapshotByGapNumber(gapBlock); + Snapshot? result = _snapshotManager.GetSnapshotByGapNumber(gapBlock); // assert that it was retrieved from cache result.Should().BeEquivalentTo(snapshot); @@ -65,7 +67,7 @@ public void GetSnapshot_ShouldRetrieveFromIfFound() public void GetSnapshot_ShouldReturnNullForEmptyDb() { // Act - var result = _snapshotManager.GetSnapshotByBlockNumber(0, _xdcReleaseSpec); + Snapshot? result = _snapshotManager.GetSnapshotByBlockNumber(0, _xdcReleaseSpec); // Assert result.Should().BeNull(); } @@ -81,7 +83,7 @@ public void GetSnapshot_ShouldRetrieveFromDbIfNotInCache() _blockTree.FindHeader(gapBlock).Returns(header); // Act - var saved = _snapshotManager.GetSnapshotByGapNumber(gapBlock); + Snapshot? saved = _snapshotManager.GetSnapshotByGapNumber(gapBlock); // Assert saved.Should().BeEquivalentTo(snapshot); @@ -98,7 +100,7 @@ public void StoreSnapshot_ShouldStoreSnapshotInDb() // Act _snapshotManager.StoreSnapshot(snapshot); - var fromDb = _snapshotManager.GetSnapshotByGapNumber(gapBlock); + Snapshot? fromDb = _snapshotManager.GetSnapshotByGapNumber(gapBlock); // Assert fromDb.Should().BeEquivalentTo(snapshot); @@ -113,7 +115,7 @@ public void GetSnapshot_ShouldReturnSnapshotIfExists() var snapshot1 = new Snapshot(gapBlock1, header.Hash!, [Address.FromNumber(1)]); _snapshotManager.StoreSnapshot(snapshot1); _blockTree.FindHeader(gapBlock1).Returns(header); - var result = _snapshotManager.GetSnapshotByGapNumber(gapBlock1); + Snapshot? result = _snapshotManager.GetSnapshotByGapNumber(gapBlock1); // assert that it was retrieved from db result.Should().BeEquivalentTo(snapshot1); @@ -146,9 +148,30 @@ public void GetSnapshot_DifferentBlockNumbers_ReturnsSnapshotFromCorrectGapNumbe var snapshot = new Snapshot(expectedGapNumber, header.Hash!, [Address.FromNumber(1)]); _snapshotManager.StoreSnapshot(snapshot); _blockTree.FindHeader(expectedGapNumber).Returns(header); - var result = _snapshotManager.GetSnapshotByBlockNumber(blockNumber, _xdcReleaseSpec); + Snapshot? result = _snapshotManager.GetSnapshotByBlockNumber(blockNumber, _xdcReleaseSpec); // assert that it was retrieved from db result.Should().BeEquivalentTo(snapshot); } + + [TestCase(450)] + [TestCase(1350)] + public void NewHeadBlock_(int gapNumber) + { + IXdcReleaseSpec releaseSpec = Substitute.For(); + releaseSpec.EpochLength.Returns(900); + releaseSpec.Gap.Returns(450); + IBlockTree blockTree = Substitute.For(); + ISpecProvider specProvider = Substitute.For(); + specProvider.GetSpec(Arg.Any()).Returns(releaseSpec); + SnapshotManager snapshotManager = new SnapshotManager(new MemDb(), blockTree, Substitute.For(), Substitute.For(), specProvider); + + XdcBlockHeader header = Build.A.XdcBlockHeader() + .WithGeneratedExtraConsensusData(1) + .WithNumber(gapNumber).TestObject; + blockTree.FindHeader(Arg.Any()).Returns(header); + + blockTree.NewHeadBlock += Raise.EventWith(new BlockEventArgs(new Block(header))); + snapshotManager.GetSnapshotByGapNumber(header.Number)!.HeaderHash.Should().Be(header.Hash!); + } } diff --git a/src/Nethermind/Nethermind.Xdc.Test/TimeoutCertificateManagerTests.cs b/src/Nethermind/Nethermind.Xdc.Test/TimeoutCertificateManagerTests.cs index 9de3ee988df..8901c83aca3 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/TimeoutCertificateManagerTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/TimeoutCertificateManagerTests.cs @@ -43,7 +43,7 @@ public void VerifyTC_SnapshotMissing_ReturnsFalse() { var tc = new TimeoutCertificate(1, Array.Empty(), 0); ISnapshotManager snapshotManager = Substitute.For(); - snapshotManager.GetSnapshotByGapNumber(Arg.Any()) + snapshotManager.GetSnapshotByGapNumber(Arg.Any()) .Returns((Snapshot?)null); IBlockTree blockTree = Substitute.For(); XdcBlockHeader header = Build.A.XdcBlockHeader().TestObject; @@ -69,7 +69,7 @@ public void VerifyTC_EmptyCandidates_ReturnsFalse() { var tc = new TimeoutCertificate(1, Array.Empty(), 0); ISnapshotManager snapshotManager = Substitute.For(); - snapshotManager.GetSnapshotByGapNumber(Arg.Any()) + snapshotManager.GetSnapshotByGapNumber(Arg.Any()) .Returns(new Snapshot(0, Hash256.Zero, Array.Empty
())); IBlockTree blockTree = Substitute.For(); XdcBlockHeader header = Build.A.XdcBlockHeader().TestObject; @@ -120,7 +120,7 @@ public void VerifyTCWithDifferentParameters_ReturnsExpected(TimeoutCertificate t { Address[] masternodes = masternodesList.ToArray(); ISnapshotManager snapshotManager = Substitute.For(); - snapshotManager.GetSnapshotByGapNumber(Arg.Any()) + snapshotManager.GetSnapshotByGapNumber(Arg.Any()) .Returns(new Snapshot(0, Hash256.Zero, masternodes)); IEpochSwitchManager epochSwitchManager = Substitute.For(); diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcSealValidatorTests.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcSealValidatorTests.cs index fd9c0783f99..0cc13b7f80c 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/XdcSealValidatorTests.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcSealValidatorTests.cs @@ -68,10 +68,10 @@ public static IEnumerable InvalidSignatureCases() yield return new TestCaseData(header, new byte[0]); yield return new TestCaseData(header, new byte[65]); yield return new TestCaseData(header, new byte[66]); - byte[] extralongSignature = new byte[66]; + byte[] extraLongSignature = new byte[66]; var keyASig = new EthereumEcdsa(0).Sign(TestItem.PrivateKeyA, header).BytesWithRecovery; - keyASig.CopyTo(extralongSignature, 0); - yield return new TestCaseData(header, extralongSignature); + keyASig.CopyTo(extraLongSignature, 0); + yield return new TestCaseData(header, extraLongSignature); var keyBSig = new EthereumEcdsa(0).Sign(TestItem.PrivateKeyB, header).BytesWithRecovery; yield return new TestCaseData(header, keyBSig); } diff --git a/src/Nethermind/Nethermind.Xdc.Test/XdcTestExtentions.cs b/src/Nethermind/Nethermind.Xdc.Test/XdcTestExtentions.cs index 63f221747bb..4d13e7fdfa0 100644 --- a/src/Nethermind/Nethermind.Xdc.Test/XdcTestExtentions.cs +++ b/src/Nethermind/Nethermind.Xdc.Test/XdcTestExtentions.cs @@ -5,7 +5,7 @@ namespace Nethermind.Core.Test.Builders; -public static class BuildExtentions +public static class BuildExtensions { public static XdcBlockHeaderBuilder XdcBlockHeader(this Build build) { diff --git a/src/Nethermind/Nethermind.Xdc/Contracts/CandidateStake.cs b/src/Nethermind/Nethermind.Xdc/Contracts/CandidateStake.cs new file mode 100644 index 00000000000..3ab5ef5e656 --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/Contracts/CandidateStake.cs @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Core; +using Nethermind.Int256; + +namespace Nethermind.Xdc.Contracts; + +internal struct CandidateStake +{ + public Address Address; + public UInt256 Stake; +} diff --git a/src/Nethermind/Nethermind.Xdc/Contracts/IMasternodeVotingContract.cs b/src/Nethermind/Nethermind.Xdc/Contracts/IMasternodeVotingContract.cs index bb1945b2780..2af469cf419 100644 --- a/src/Nethermind/Nethermind.Xdc/Contracts/IMasternodeVotingContract.cs +++ b/src/Nethermind/Nethermind.Xdc/Contracts/IMasternodeVotingContract.cs @@ -3,11 +3,14 @@ using Nethermind.Core; using Nethermind.Int256; +using System; namespace Nethermind.Xdc.Contracts; -internal interface IMasternodeVotingContract +public interface IMasternodeVotingContract { + Address[] GetCandidatesByStake(BlockHeader blockHeader); Address[] GetCandidates(BlockHeader blockHeader); UInt256 GetCandidateStake(BlockHeader blockHeader, Address candidate); + Address GetCandidateOwner(BlockHeader blockHeader, Address candidate); } diff --git a/src/Nethermind/Nethermind.Xdc/Contracts/MasternodeVotingContract.cs b/src/Nethermind/Nethermind.Xdc/Contracts/MasternodeVotingContract.cs index 8d3f7e6038f..e3d0c87e358 100644 --- a/src/Nethermind/Nethermind.Xdc/Contracts/MasternodeVotingContract.cs +++ b/src/Nethermind/Nethermind.Xdc/Contracts/MasternodeVotingContract.cs @@ -5,30 +5,30 @@ using Nethermind.Blockchain; using Nethermind.Blockchain.Contracts; using Nethermind.Blockchain.Contracts.Json; +using Nethermind.Consensus.Processing; using Nethermind.Core; +using Nethermind.Core.Collections; using Nethermind.Core.Crypto; -using Nethermind.Core.Specs; using Nethermind.Evm.State; using Nethermind.Int256; using Nethermind.State; +using Nethermind.Xdc.Contracts; using System; +using System.Linq; namespace Nethermind.Xdc.Contracts; internal class MasternodeVotingContract : Contract, IMasternodeVotingContract { - private readonly IWorldState _worldState; - private IConstantContract _constant; + private readonly IReadOnlyTxProcessingEnvFactory readOnlyTxProcessingEnvFactory; public MasternodeVotingContract( - IWorldState worldState, IAbiEncoder abiEncoder, Address contractAddress, - IReadOnlyTxProcessorSource readOnlyTxProcessorSource) + IReadOnlyTxProcessingEnvFactory readOnlyTxProcessingEnvFactory) : base(abiEncoder, contractAddress ?? throw new ArgumentNullException(nameof(contractAddress)), CreateAbiDefinition()) { - _constant = GetConstant(readOnlyTxProcessorSource); - _worldState = worldState; + this.readOnlyTxProcessingEnvFactory = readOnlyTxProcessingEnvFactory; } private static AbiDefinition CreateAbiDefinition() @@ -40,17 +40,30 @@ private static AbiDefinition CreateAbiDefinition() public UInt256 GetCandidateStake(BlockHeader blockHeader, Address candidate) { CallInfo callInfo = new CallInfo(blockHeader, "getCandidateCap", Address.SystemUser, candidate); - object[] result = _constant.Call(callInfo); + IConstantContract constant = GetConstant(readOnlyTxProcessingEnvFactory.Create()); + object[] result = constant.Call(callInfo); if (result.Length != 1) throw new InvalidOperationException("Expected 'getCandidateCap' to return exactly one result."); return (UInt256)result[0]!; } + public Address GetCandidateOwner(BlockHeader blockHeader, Address candidate) + { + CallInfo callInfo = new CallInfo(blockHeader, "getCandidateOwner", Address.SystemUser, candidate); + IConstantContract constant = GetConstant(readOnlyTxProcessingEnvFactory.Create()); + object[] result = constant.Call(callInfo); + if (result.Length != 1) + throw new InvalidOperationException("Expected 'getCandidateOwner' to return exactly one result."); + + return (Address)result[0]!; + } + public Address[] GetCandidates(BlockHeader blockHeader) { CallInfo callInfo = new CallInfo(blockHeader, "getCandidates", Address.SystemUser); - object[] result = _constant.Call(callInfo); + IConstantContract constant = GetConstant(readOnlyTxProcessingEnvFactory.Create()); + object[] result = constant.Call(callInfo); return (Address[])result[0]!; } @@ -63,24 +76,57 @@ public Address[] GetCandidatesFromState(BlockHeader header) { CandidateContractSlots variableSlot = CandidateContractSlots.Candidates; Span input = [(byte)variableSlot]; - var slot = new UInt256(Keccak.Compute(input).Bytes); - using IDisposable state = _worldState.BeginScope(header); - ReadOnlySpan storageCell = _worldState.Get(new StorageCell(ContractAddress, slot)); - UInt256 length = new UInt256(storageCell); + UInt256 slot = new UInt256(Keccak.Compute(input).Bytes); + IReadOnlyTxProcessorSource txProcessorSource = readOnlyTxProcessingEnvFactory.Create(); + using IReadOnlyTxProcessingScope source = txProcessorSource.Build(header); + IWorldState worldState = source.WorldState; + ReadOnlySpan storageCell = worldState.Get(new StorageCell(ContractAddress, slot)); + var length = new UInt256(storageCell); Address[] candidates = new Address[(ulong)length]; for (int i = 0; i < length; i++) { UInt256 key = CalculateArrayKey(slot, (ulong)i, 1); - candidates[i] = new Address(_worldState.Get(new StorageCell(ContractAddress, key))); + candidates[i] = new Address(worldState.Get(new StorageCell(ContractAddress, key))); } return candidates; } - private static UInt256 CalculateArrayKey(UInt256 slot, ulong index, ulong size) + private UInt256 CalculateArrayKey(UInt256 slot, ulong index, ulong size) { return slot + new UInt256(index * size); } + /// + /// Returns an array of masternode candidates sorted by stake + /// + /// + /// + public Address[] GetCandidatesByStake(BlockHeader blockHeader) + { + Address[] candidates = GetCandidates(blockHeader); + + using var candidatesAndStake = new ArrayPoolList(candidates.Length); + foreach (Address candidate in candidates) + { + if (candidate == Address.Zero) + continue; + + candidatesAndStake.Add(new CandidateStake() + { + Address = candidate, + Stake = GetCandidateStake(blockHeader, candidate) + }); + } + candidatesAndStake.Sort((x, y) => y.Stake.CompareTo(x.Stake)); + + Address[] sortedCandidates = new Address[candidatesAndStake.Count]; + for (int i = 0; i < candidatesAndStake.Count; i++) + { + sortedCandidates[i] = candidatesAndStake[i].Address; + } + return sortedCandidates; + } + private enum CandidateContractSlots : byte { WithdrawsState, diff --git a/src/Nethermind/Nethermind.Xdc/EpochSwitchManager.cs b/src/Nethermind/Nethermind.Xdc/EpochSwitchManager.cs index b4eca2644d6..a62fd148c25 100644 --- a/src/Nethermind/Nethermind.Xdc/EpochSwitchManager.cs +++ b/src/Nethermind/Nethermind.Xdc/EpochSwitchManager.cs @@ -135,17 +135,17 @@ public bool IsEpochSwitchAtRound(ulong currentRound, XdcBlockHeader parent) Address[] penalties = header.PenaltiesAddress.Value.ToArray(); Address[] candidates = snap.NextEpochCandidates; - var stanbyNodes = new Address[0]; + var standbyNodes = Array.Empty
(); if (masterNodes.Length != candidates.Length) { - stanbyNodes = candidates + standbyNodes = candidates .Except(masterNodes) .Except(penalties) .ToArray(); } - epochSwitchInfo = new EpochSwitchInfo(masterNodes, stanbyNodes, penalties, new BlockRoundInfo(header.Hash, header.ExtraConsensusData?.BlockRound ?? 0, header.Number)); + epochSwitchInfo = new EpochSwitchInfo(masterNodes, standbyNodes, penalties, new BlockRoundInfo(header.Hash, header.ExtraConsensusData?.BlockRound ?? 0, header.Number)); if (header.ExtraConsensusData?.QuorumCert is not null) { diff --git a/src/Nethermind/Nethermind.Xdc/ISnapshotManager.cs b/src/Nethermind/Nethermind.Xdc/ISnapshotManager.cs index 3ec43a6b740..cd41816d6a4 100644 --- a/src/Nethermind/Nethermind.Xdc/ISnapshotManager.cs +++ b/src/Nethermind/Nethermind.Xdc/ISnapshotManager.cs @@ -10,13 +10,13 @@ namespace Nethermind.Xdc; public interface ISnapshotManager { - static bool IsTimeforSnapshot(long blockNumber, IXdcReleaseSpec spec) + static bool IsTimeForSnapshot(long blockNumber, IXdcReleaseSpec spec) { if (blockNumber == spec.SwitchBlock) return true; return blockNumber % spec.EpochLength == spec.EpochLength - spec.Gap; } - Snapshot? GetSnapshotByGapNumber(ulong gapNumber); + Snapshot? GetSnapshotByGapNumber(long gapNumber); Snapshot? GetSnapshotByBlockNumber(long blockNumber, IXdcReleaseSpec spec); void StoreSnapshot(Snapshot snapshot); (Address[] Masternodes, Address[] PenalizedNodes) CalculateNextEpochMasternodes(long blockNumber, Hash256 parentHash, IXdcReleaseSpec spec); diff --git a/src/Nethermind/Nethermind.Xdc/IXdcHeaderStore.cs b/src/Nethermind/Nethermind.Xdc/IXdcHeaderStore.cs index 2c9e16de375..1720c7cf22d 100644 --- a/src/Nethermind/Nethermind.Xdc/IXdcHeaderStore.cs +++ b/src/Nethermind/Nethermind.Xdc/IXdcHeaderStore.cs @@ -5,14 +5,13 @@ using Nethermind.Core; using Nethermind.Core.Crypto; using System.Collections.Generic; -using System.Linq; namespace Nethermind.Xdc; internal interface IXdcHeaderStore : IHeaderStore { void Insert(XdcBlockHeader header) => ((IHeaderStore)this).Insert(header); - void BulkInsert(IReadOnlyList headers) => BulkInsert(headers.Cast().ToList()); + void BulkInsert(IReadOnlyList headers) => ((IHeaderStore)this).BulkInsert(headers); new XdcBlockHeader? Get(Hash256 blockHash, bool shouldCache, long? blockNumber = null) => ((IHeaderStore)this).Get(blockHash, shouldCache, blockNumber) as XdcBlockHeader; void Cache(XdcBlockHeader header) => ((IHeaderStore)this).Cache(header); diff --git a/src/Nethermind/Nethermind.Xdc/QuorumCertificateManager.cs b/src/Nethermind/Nethermind.Xdc/QuorumCertificateManager.cs index 7f33e7dd5d1..16ce9cdf913 100644 --- a/src/Nethermind/Nethermind.Xdc/QuorumCertificateManager.cs +++ b/src/Nethermind/Nethermind.Xdc/QuorumCertificateManager.cs @@ -100,7 +100,7 @@ private bool CommitBlock(IBlockTree chain, XdcBlockHeader proposedBlockHeader, u if (parentHeader.ExtraConsensusData is null) { - error = $"Block {parentHeader.ToString(BlockHeader.Format.FullHashAndNumber)} does not have required consensus data! Chain migth be corrupt!"; + error = $"Block {parentHeader.ToString(BlockHeader.Format.FullHashAndNumber)} does not have required consensus data! Chain might be corrupt!"; return false; } diff --git a/src/Nethermind/Nethermind.Xdc/RLP/TimeoutDecoder.cs b/src/Nethermind/Nethermind.Xdc/RLP/TimeoutDecoder.cs index 546b5b2c2d3..762b6749d6b 100644 --- a/src/Nethermind/Nethermind.Xdc/RLP/TimeoutDecoder.cs +++ b/src/Nethermind/Nethermind.Xdc/RLP/TimeoutDecoder.cs @@ -49,7 +49,7 @@ protected override Timeout DecodeInternal(RlpStream rlpStream, RlpBehaviors rlpB if ((rlpBehaviors & RlpBehaviors.ForSealing) != RlpBehaviors.ForSealing) { if (rlpStream.PeekNextRlpLength() != Signature.Size) - throw new RlpException($"Invalid signature length in {nameof(Vote)}"); + throw new RlpException($"Invalid signature length in {nameof(Timeout)}"); signature = new(rlpStream.DecodeByteArray()); } diff --git a/src/Nethermind/Nethermind.Xdc/SnapshotManager.cs b/src/Nethermind/Nethermind.Xdc/SnapshotManager.cs index 330c884936f..05ff2b96392 100644 --- a/src/Nethermind/Nethermind.Xdc/SnapshotManager.cs +++ b/src/Nethermind/Nethermind.Xdc/SnapshotManager.cs @@ -5,8 +5,10 @@ using Nethermind.Core; using Nethermind.Core.Caching; using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; using Nethermind.Db; using Nethermind.Serialization.Rlp; +using Nethermind.Xdc.Contracts; using Nethermind.Xdc.RLP; using Nethermind.Xdc.Spec; using Nethermind.Xdc.Types; @@ -15,14 +17,29 @@ namespace Nethermind.Xdc; -internal class SnapshotManager(IDb snapshotDb, IBlockTree blockTree, IPenaltyHandler penaltyHandler) : ISnapshotManager +internal class SnapshotManager : ISnapshotManager { private readonly LruCache _snapshotCache = new(128, 128, "XDC Snapshot cache"); private readonly SnapshotDecoder _snapshotDecoder = new(); + private readonly IDb snapshotDb; + private readonly IBlockTree blockTree; + private readonly IMasternodeVotingContract votingContract; + private readonly ISpecProvider specProvider; + private readonly IPenaltyHandler penaltyHandler; - public Snapshot? GetSnapshotByGapNumber(ulong gapNumber) + public SnapshotManager(IDb snapshotDb, IBlockTree blockTree, IPenaltyHandler penaltyHandler, IMasternodeVotingContract votingContract, ISpecProvider specProvider) + { + blockTree.NewHeadBlock += OnNewHeadBlock; + this.snapshotDb = snapshotDb; + this.blockTree = blockTree; + this.votingContract = votingContract; + this.specProvider = specProvider; + this.penaltyHandler = penaltyHandler; + } + + public Snapshot? GetSnapshotByGapNumber(long gapNumber) { var gapBlockHeader = blockTree.FindHeader((long)gapNumber) as XdcBlockHeader; @@ -42,7 +59,7 @@ internal class SnapshotManager(IDb snapshotDb, IBlockTree blockTree, IPenaltyHan if (value.IsEmpty) return null; - var decoded = _snapshotDecoder.Decode(value); + Snapshot decoded = _snapshotDecoder.Decode(value); snapshot = decoded; _snapshotCache.Set(gapBlockHeader.Hash, snapshot); return snapshot; @@ -51,7 +68,7 @@ internal class SnapshotManager(IDb snapshotDb, IBlockTree blockTree, IPenaltyHan public Snapshot? GetSnapshotByBlockNumber(long blockNumber, IXdcReleaseSpec spec) { var gapBlockNum = Math.Max(0, blockNumber - blockNumber % spec.EpochLength - spec.Gap); - return GetSnapshotByGapNumber((ulong)gapBlockNum); + return GetSnapshotByGapNumber(gapBlockNum); } public void StoreSnapshot(Snapshot snapshot) @@ -61,20 +78,21 @@ public void StoreSnapshot(Snapshot snapshot) if (snapshotDb.KeyExists(key)) return; - var rlpEncodedSnapshot = _snapshotDecoder.Encode(snapshot); + Rlp rlpEncodedSnapshot = _snapshotDecoder.Encode(snapshot); snapshotDb.Set(key, rlpEncodedSnapshot.Bytes); + _snapshotCache.Set(snapshot.HeaderHash, snapshot); } public (Address[] Masternodes, Address[] PenalizedNodes) CalculateNextEpochMasternodes(long blockNumber, Hash256 parentHash, IXdcReleaseSpec spec) { int maxMasternodes = spec.MaxMasternodes; - var previousSnapshot = GetSnapshotByBlockNumber(blockNumber, spec); + Snapshot previousSnapshot = GetSnapshotByBlockNumber(blockNumber, spec); if (previousSnapshot is null) throw new InvalidOperationException($"No snapshot found for header #{blockNumber}"); - var candidates = previousSnapshot.NextEpochCandidates; + Address[] candidates = previousSnapshot.NextEpochCandidates; if (blockNumber == spec.SwitchBlock + 1) { @@ -96,4 +114,31 @@ public void StoreSnapshot(Snapshot snapshot) return (candidates, penalties); } + + private void OnNewHeadBlock(object? sender, BlockEventArgs e) + { + UpdateMasterNodes((XdcBlockHeader)e.Block.Header); + } + + private void UpdateMasterNodes(XdcBlockHeader header) + { + ulong round; + if (header.IsGenesis) + round = 0; + else + round = header.ExtraConsensusData.BlockRound; + // Could consider dropping the round parameter here, since the consensus parameters used here should not change + IXdcReleaseSpec spec = specProvider.GetXdcSpec(header, round); + if (!ISnapshotManager.IsTimeForSnapshot(header.Number, spec)) + return; + + Address[] candidates; + if (header.IsGenesis) + candidates = spec.GenesisMasterNodes; + else + candidates = votingContract.GetCandidatesByStake(header); + + Snapshot snapshot = new(header.Number, header.Hash, candidates); + StoreSnapshot(snapshot); + } } diff --git a/src/Nethermind/Nethermind.Xdc/Spec/XdcChainSpecBasedSpecProvider.cs b/src/Nethermind/Nethermind.Xdc/Spec/XdcChainSpecBasedSpecProvider.cs index bf343c85384..7552193e9a7 100644 --- a/src/Nethermind/Nethermind.Xdc/Spec/XdcChainSpecBasedSpecProvider.cs +++ b/src/Nethermind/Nethermind.Xdc/Spec/XdcChainSpecBasedSpecProvider.cs @@ -22,6 +22,10 @@ protected override ReleaseSpec CreateReleaseSpec(ChainSpec chainSpec, long relea releaseSpec.SwitchEpoch = chainSpecEngineParameters.SwitchEpoch; releaseSpec.SwitchBlock = chainSpecEngineParameters.SwitchBlock; releaseSpec.V2Configs = chainSpecEngineParameters.V2Configs; + releaseSpec.FoundationWallet = chainSpecEngineParameters.FoundationWalletAddr; + releaseSpec.Reward = chainSpecEngineParameters.Reward; + releaseSpec.MasternodeVotingContract = chainSpecEngineParameters.MasternodeVotingContract; + releaseSpec.BlockSignerContract = chainSpecEngineParameters.BlockSignerContract; releaseSpec.ApplyV2Config(0); diff --git a/src/Nethermind/Nethermind.Xdc/Spec/XdcChainSpecEngineParameters.cs b/src/Nethermind/Nethermind.Xdc/Spec/XdcChainSpecEngineParameters.cs index a0b51affa27..cefacaa8c16 100644 --- a/src/Nethermind/Nethermind.Xdc/Spec/XdcChainSpecEngineParameters.cs +++ b/src/Nethermind/Nethermind.Xdc/Spec/XdcChainSpecEngineParameters.cs @@ -21,6 +21,9 @@ public class XdcChainSpecEngineParameters : IChainSpecEngineParameters public int SwitchEpoch { get; set; } public long SwitchBlock { get; set; } + public Address MasternodeVotingContract { get; set; } + public Address BlockSignerContract { get; set; } + private List _v2Configs = new(); public List V2Configs diff --git a/src/Nethermind/Nethermind.Xdc/Spec/XdcReleaseSpec.cs b/src/Nethermind/Nethermind.Xdc/Spec/XdcReleaseSpec.cs index bf0b7453dff..2c0f616fa8e 100644 --- a/src/Nethermind/Nethermind.Xdc/Spec/XdcReleaseSpec.cs +++ b/src/Nethermind/Nethermind.Xdc/Spec/XdcReleaseSpec.cs @@ -12,6 +12,7 @@ public class XdcReleaseSpec : ReleaseSpec, IXdcReleaseSpec { public int EpochLength { get; set; } public int Gap { get; set; } + public long Reward { get; set; } public int SwitchEpoch { get; set; } public long SwitchBlock { get; set; } public int MaxMasternodes { get; set; } // v2 max masternodes @@ -31,6 +32,9 @@ public class XdcReleaseSpec : ReleaseSpec, IXdcReleaseSpec public List V2Configs { get; set; } = new List(); public Address[] GenesisMasterNodes { get; set; } + public Address FoundationWallet { get; set; } + public Address MasternodeVotingContract { get; set; } + public Address BlockSignerContract { get; set; } public void ApplyV2Config(ulong round) { @@ -79,6 +83,7 @@ public interface IXdcReleaseSpec : IReleaseSpec { public int EpochLength { get; } public int Gap { get; } + public long Reward { get; } public int SwitchEpoch { get; set; } public long SwitchBlock { get; set; } public int MaxMasternodes { get; set; } // v2 max masternodes @@ -97,5 +102,9 @@ public interface IXdcReleaseSpec : IReleaseSpec public int MinimumSigningTx { get; set; } // Signing txs that a node needs to produce to get out of penalty, after `LimitPenaltyEpoch` public List V2Configs { get; set; } Address[] GenesisMasterNodes { get; set; } + Address FoundationWallet { get; set; } + Address MasternodeVotingContract { get; set; } + Address BlockSignerContract { get; set; } + public void ApplyV2Config(ulong round); } diff --git a/src/Nethermind/Nethermind.Xdc/TimeoutCertificateManager.cs b/src/Nethermind/Nethermind.Xdc/TimeoutCertificateManager.cs index ed06c548673..83f6224378d 100644 --- a/src/Nethermind/Nethermind.Xdc/TimeoutCertificateManager.cs +++ b/src/Nethermind/Nethermind.Xdc/TimeoutCertificateManager.cs @@ -104,7 +104,7 @@ public bool VerifyTimeoutCertificate(TimeoutCertificate timeoutCertificate, out if (timeoutCertificate is null) throw new ArgumentNullException(nameof(timeoutCertificate)); if (timeoutCertificate.Signatures is null) throw new ArgumentNullException(nameof(timeoutCertificate.Signatures)); - Snapshot snapshot = _snapshotManager.GetSnapshotByGapNumber(timeoutCertificate.GapNumber); + Snapshot snapshot = _snapshotManager.GetSnapshotByGapNumber((long)timeoutCertificate.GapNumber); if (snapshot is null) { errorMessage = $"Failed to get snapshot using gap number {timeoutCertificate.GapNumber}"; @@ -196,7 +196,7 @@ public Task OnReceiveTimeout(Timeout timeout) internal bool FilterTimeout(Timeout timeout) { if (timeout.Round < _consensusContext.CurrentRound) return false; - Snapshot snapshot = _snapshotManager.GetSnapshotByGapNumber(timeout.GapNumber); + Snapshot snapshot = _snapshotManager.GetSnapshotByGapNumber((long)timeout.GapNumber); if (snapshot is null || snapshot.NextEpochCandidates.Length == 0) return false; // Verify msg signature diff --git a/src/Nethermind/Nethermind.Xdc/VotesManager.cs b/src/Nethermind/Nethermind.Xdc/VotesManager.cs index 7e6f2cccbbd..5687caed3be 100644 --- a/src/Nethermind/Nethermind.Xdc/VotesManager.cs +++ b/src/Nethermind/Nethermind.Xdc/VotesManager.cs @@ -97,20 +97,22 @@ public Task HandleVote(Vote vote) //Unknown epoch switch info, cannot process vote return Task.CompletedTask; } - if (epochInfo.Masternodes.Length == 0) + int masternodeCount = epochInfo.Masternodes.Length; + if (masternodeCount == 0) { throw new InvalidOperationException($"Epoch has empty master node list for {vote.ProposedBlockInfo.Hash}"); } double certThreshold = _specProvider.GetXdcSpec(proposedHeader, vote.ProposedBlockInfo.Round).CertThreshold; - bool thresholdReached = roundVotes.Count >= epochInfo.Masternodes.Length * certThreshold; + double requiredVotes = masternodeCount * certThreshold; + bool thresholdReached = roundVotes.Count >= requiredVotes; if (thresholdReached) { if (!vote.ProposedBlockInfo.ValidateBlockInfo(proposedHeader)) return Task.CompletedTask; Signature[] validSignatures = GetValidSignatures(roundVotes, epochInfo.Masternodes); - if (validSignatures.Length < epochInfo.Masternodes.Length * certThreshold) + if (validSignatures.Length < requiredVotes) return Task.CompletedTask; // At this point, the QC should be processed for this *round*. @@ -185,7 +187,7 @@ internal bool FilterVote(Vote vote) { if (vote.ProposedBlockInfo.Round < _ctx.CurrentRound) return false; - Snapshot snapshot = _snapshotManager.GetSnapshotByGapNumber(vote.GapNumber); + Snapshot snapshot = _snapshotManager.GetSnapshotByGapNumber((long)vote.GapNumber); if (snapshot is null) return false; // Verify message signature vote.Signer ??= _ethereumEcdsa.RecoverVoteSigner(vote); diff --git a/src/Nethermind/Nethermind.Xdc/XdcBlockTree.cs b/src/Nethermind/Nethermind.Xdc/XdcBlockTree.cs index ba0b7d5a1f3..674addedda0 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcBlockTree.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcBlockTree.cs @@ -12,6 +12,8 @@ using Nethermind.Db.Blooms; using Nethermind.Logging; using Nethermind.State.Repositories; +using Nethermind.Xdc.Contracts; +using Nethermind.Xdc.Types; namespace Nethermind.Xdc; @@ -40,7 +42,7 @@ public XdcBlockTree( protected override AddBlockResult Suggest(Block? block, BlockHeader header, BlockTreeSuggestOptions options = BlockTreeSuggestOptions.ShouldProcess) { - Types.BlockRoundInfo finalizedBlockInfo = _xdcConsensus.HighestCommitBlock; + BlockRoundInfo finalizedBlockInfo = _xdcConsensus.HighestCommitBlock; if (finalizedBlockInfo is null) return base.Suggest(block, header, options); if (finalizedBlockInfo.Hash == header.Hash) @@ -54,7 +56,7 @@ protected override AddBlockResult Suggest(Block? block, BlockHeader header, Bloc } if (header.Number - finalizedBlockInfo.BlockNumber > MaxSearchDepth) { - //Theoretically very deep reorgs could happen, if the chain doesnt finalize for a long time + //Theoretically very deep reorgs could happen, if the chain doesn't finalize for a long time //TODO Maybe this needs to be revisited later Logger.Warn($"Deep reorg past {MaxSearchDepth} blocks detected! Rejecting block {header.ToString(BlockHeader.Format.Full)}"); return AddBlockResult.InvalidBlock; diff --git a/src/Nethermind/Nethermind.Xdc/XdcModule.cs b/src/Nethermind/Nethermind.Xdc/XdcModule.cs index 0676b10e9b7..ac8b642b78c 100644 --- a/src/Nethermind/Nethermind.Xdc/XdcModule.cs +++ b/src/Nethermind/Nethermind.Xdc/XdcModule.cs @@ -1,20 +1,28 @@ // SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only +using System; using Autofac; using Autofac.Features.AttributeFilters; +using Nethermind.Abi; using Nethermind.Blockchain; using Nethermind.Blockchain.Blocks; using Nethermind.Blockchain.Headers; using Nethermind.Consensus; using Nethermind.Consensus.Processing; +using Nethermind.Consensus.Rewards; using Nethermind.Consensus.Validators; using Nethermind.Core; using Nethermind.Core.Specs; using Nethermind.Db; +using Nethermind.Evm; +using Nethermind.Evm.State; using Nethermind.Init.Modules; using Nethermind.Specs.ChainSpecStyle; +using Nethermind.State; +using Nethermind.Xdc.Contracts; using Nethermind.Xdc.Spec; +using Nethermind.Logging; namespace Nethermind.Xdc; @@ -41,12 +49,21 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddSingleton() + // Sys contracts + //TODO this might not be wired correctly + .AddSingleton< + IMasternodeVotingContract, + IAbiEncoder, + ISpecProvider, + IReadOnlyTxProcessingEnvFactory>(CreateVotingContract) + // sealer .AddSingleton() // penalty handler // reward handler + .AddSingleton() // forensics handler @@ -62,16 +79,24 @@ protected override void Load(ContainerBuilder builder) .AddSingleton() .AddSingleton() .AddDatabase(SnapshotDbName) - .AddSingleton(CreateSnapshotManager) + .AddSingleton(CreateSnapshotManager) .AddSingleton() .AddSingleton() .AddSingleton() ; } - private ISnapshotManager CreateSnapshotManager([KeyFilter(SnapshotDbName)] IDb db, IBlockTree blockTree, IPenaltyHandler penaltyHandler) + private ISnapshotManager CreateSnapshotManager([KeyFilter(SnapshotDbName)] IDb db, IBlockTree blockTree, IPenaltyHandler penaltyHandler, IMasternodeVotingContract votingContract, ISpecProvider specProvider) { - return new SnapshotManager(db, blockTree, penaltyHandler); + return new SnapshotManager(db, blockTree, penaltyHandler, votingContract, specProvider); } + private IMasternodeVotingContract CreateVotingContract( + IAbiEncoder abiEncoder, + ISpecProvider specProvider, + IReadOnlyTxProcessingEnvFactory readOnlyTxProcessingEnv) + { + IXdcReleaseSpec spec = (XdcReleaseSpec)specProvider.GetFinalSpec(); + return new MasternodeVotingContract(abiEncoder, spec.MasternodeVotingContract, readOnlyTxProcessingEnv); + } } diff --git a/src/Nethermind/Nethermind.Xdc/XdcRewardCalculator.cs b/src/Nethermind/Nethermind.Xdc/XdcRewardCalculator.cs new file mode 100644 index 00000000000..18e41e0fd0d --- /dev/null +++ b/src/Nethermind/Nethermind.Xdc/XdcRewardCalculator.cs @@ -0,0 +1,247 @@ +// SPDX-FileCopyrightText: 2025 Demerzel Solutions Limited +// SPDX-License-Identifier: LGPL-3.0-only + +using Nethermind.Blockchain; +using Nethermind.Consensus.Rewards; +using Nethermind.Core; +using Nethermind.Core.Caching; +using Nethermind.Core.Crypto; +using Nethermind.Core.Specs; +using Nethermind.Int256; +using Nethermind.Xdc.Spec; +using System; +using System.Collections.Generic; +using System.Linq; +using Nethermind.Crypto; +using Nethermind.Xdc.Contracts; + +namespace Nethermind.Xdc +{ + /// + /// Reward model (current mainnet): + /// - Rewards are paid only at epoch checkpoints (number % EpochLength == 0). + /// - For now we **ignore** TIPUpgradeReward behavior because on mainnet + /// the upgrade activation is set far in the future (effectively “not active”). + /// When TIPUpgradeReward activates, protector/observer beneficiaries must be added. + /// - Current split implemented here: 90% to masternode owner, 10% to foundation. + /// + public class XdcRewardCalculator( + IEpochSwitchManager epochSwitchManager, + ISpecProvider specProvider, + IBlockTree blockTree, + IMasternodeVotingContract masternodeVotingContract) : IRewardCalculator + { + private LruCache _signingTxsCache = new(9000, "XDC Signing Txs Cache"); + private const long BlocksPerYear = 15768000; + // XDC rule: signing transactions are sampled/merged every N blocks (N=15 on XDC). + // Only block numbers that are multiples of MergeSignRange are considered when tallying signers. + private const long MergeSignRange = 15; + private static readonly EthereumEcdsa _ethereumEcdsa = new(0); + + /// + /// Calculates block rewards according to XDPoS consensus rules. + /// + /// For XDPoS, rewards are only distributed at epoch checkpoints (blocks where number % 900 == 0). + /// At these checkpoints, rewards are calculated based on masternode signature counts during + /// the previous epoch and distributed according to the 90/10 split model. + /// + /// The block to calculate rewards for + /// Array of BlockReward objects for all reward recipients + public BlockReward[] CalculateRewards(Block block) + { + if (block is null) + throw new ArgumentNullException(nameof(block)); + if (block.Header is not XdcBlockHeader xdcHeader) + throw new InvalidOperationException("Only supports XDC headers"); + + // Rewards in XDC are calculated only if it's an epoch switch block + if (!epochSwitchManager.IsEpochSwitchAtBlock(xdcHeader)) return Array.Empty(); + + var number = xdcHeader.Number; + IXdcReleaseSpec spec = specProvider.GetXdcSpec(xdcHeader, xdcHeader.ExtraConsensusData.BlockRound); + if (number == spec.SwitchBlock + 1) return Array.Empty(); + + Address foundationWalletAddr = spec.FoundationWallet; + if (foundationWalletAddr == Address.Zero) throw new InvalidOperationException("Foundation wallet address cannot be empty"); + + var (signers, count) = GetSigningTxCount(number, xdcHeader, spec); + + UInt256 chainReward = (UInt256)spec.Reward * Unit.Ether; + Dictionary rewardSigners = CalculateRewardForSigners(chainReward, signers, count); + + UInt256 totalFoundationWalletReward = UInt256.Zero; + var rewards = new List(); + foreach (var (signer, reward) in rewardSigners) + { + (BlockReward holderReward, UInt256 foundationWalletReward) = DistributeRewards(signer, reward, xdcHeader); + totalFoundationWalletReward += foundationWalletReward; + rewards.Add(holderReward); + } + if (totalFoundationWalletReward > UInt256.Zero) rewards.Add(new BlockReward(foundationWalletAddr, totalFoundationWalletReward)); + return rewards.ToArray(); + } + + private (Dictionary Signers, long Count) GetSigningTxCount(long number, XdcBlockHeader header, IXdcReleaseSpec spec) + { + var signers = new Dictionary(); + if (number == 0) return (signers, 0); + + long signEpochCount = 1, rewardEpochCount = 2, epochCount = 0, endBlockNumber = 0, startBlockNumber = 0, signingCount = 0; + var blockNumberToHash = new Dictionary(); + var hashToSigningAddress = new Dictionary>(); + var masternodes = new HashSet
(); + + XdcBlockHeader h = header; + for (long i = number - 1; i >= 0; i--) + { + Hash256 parentHash = h.ParentHash; + h = blockTree.FindHeader(parentHash!, i) as XdcBlockHeader; + if (h == null) throw new InvalidOperationException($"Header with hash {parentHash} not found"); + if (epochSwitchManager.IsEpochSwitchAtBlock(h) && i != spec.SwitchBlock + 1) + { + epochCount++; + if (epochCount == signEpochCount) endBlockNumber = i; + if (epochCount == rewardEpochCount) + { + startBlockNumber = i + 1; + // Get masternodes from epoch switch header + masternodes = new HashSet
(h.ValidatorsAddress!); + // TIPUpgradeReward path (protector/observer selection) is currently ignored, + // because on mainnet the upgrade height is set to an effectively unreachable block. + // If/when that changes, we must compute protector/observer sets here. + break; + } + } + + blockNumberToHash[i] = h.Hash; + if (!_signingTxsCache.TryGet(h.Hash, out Transaction[] signingTxs)) + { + Block? block = blockTree.FindBlock(i); + if (block == null) throw new InvalidOperationException($"Block with number {i} not found"); + Transaction[] txs = block.Transactions; + signingTxs = CacheSigningTxs(h.Hash!, txs, spec); + } + + foreach (Transaction tx in signingTxs) + { + Hash256 blockHash = ExtractBlockHashFromSigningTxData(tx.Data); + tx.SenderAddress ??= _ethereumEcdsa.RecoverAddress(tx); + if (!hashToSigningAddress.ContainsKey(blockHash)) + hashToSigningAddress[blockHash] = new HashSet
(); + hashToSigningAddress[blockHash].Add(tx.SenderAddress); + } + } + + // Only blocks at heights that are multiples of MergeSignRange are considered. + // Calculate start >= startBlockNumber so that start % MergeSignRange == 0 + long start = ((startBlockNumber + MergeSignRange - 1) / MergeSignRange) * MergeSignRange; + for (long i = start; i < endBlockNumber; i += MergeSignRange) + { + if (!blockNumberToHash.TryGetValue(i, out var blockHash)) continue; + if (!hashToSigningAddress.TryGetValue(blockHash, out var addresses)) continue; + foreach (Address addr in addresses) + { + if (!masternodes.Contains(addr)) continue; + if (!signers.ContainsKey(addr)) signers[addr] = 0; + signers[addr] += 1; + signingCount++; + } + } + return (signers, signingCount); + } + + private Transaction[] CacheSigningTxs(Hash256 hash, Transaction[] txs, IXdcReleaseSpec spec) + { + Transaction[] signingTxs = txs.Where(t => IsSigningTransaction(t, spec)).ToArray(); + _signingTxsCache.Set(hash, signingTxs); + return signingTxs; + } + + // Signing transaction ABI (Solidity): + // function sign(uint256 _blockNumber, bytes32 _blockHash) + // Calldata = 4-byte selector + 32-byte big-endian uint + 32-byte bytes32 = 68 bytes total. + private bool IsSigningTransaction(Transaction tx, IXdcReleaseSpec spec) + { + if (tx.To is null || tx.To != spec.BlockSignerContract) return false; + if (tx.Data.Length != 68) return false; + + return ExtractSelectorFromSigningTxData(tx.Data) == "0xe341eaa4"; + } + + private String ExtractSelectorFromSigningTxData(ReadOnlyMemory data) + { + ReadOnlySpan span = data.Span; + if (span.Length != 68) + throw new ArgumentException("Signing tx calldata must be exactly 68 bytes (4 + 32 + 32).", nameof(data)); + + // 0..3: selector + ReadOnlySpan selBytes = span.Slice(0, 4); + return "0x" + Convert.ToHexString(selBytes).ToLowerInvariant(); + } + + private Hash256 ExtractBlockHashFromSigningTxData(ReadOnlyMemory data) + { + ReadOnlySpan span = data.Span; + if (span.Length != 68) + throw new ArgumentException("Signing tx calldata must be exactly 68 bytes (4 + 32 + 32).", nameof(data)); + + // 36..67: bytes32 blockHash + ReadOnlySpan hashBytes = span.Slice(36, 32); + return new Hash256(hashBytes); + } + + private Dictionary CalculateRewardForSigners(UInt256 totalReward, + Dictionary signers, long totalSigningCount) + { + var rewardSigners = new Dictionary(); + foreach (var (signer, count) in signers) + { + UInt256 reward = CalculateProportionalReward(count, totalSigningCount, totalReward); + rewardSigners.Add(signer, reward); + } + return rewardSigners; + } + + /// + /// Calculates a proportional reward based on the number of signatures. + /// Uses UInt256 arithmetic to maintain precision with large Wei values. + /// + /// Formula: (signatureCount / totalSignatures) * totalReward + /// + private UInt256 CalculateProportionalReward( + long signatureCount, + long totalSignatures, + UInt256 totalReward) + { + if (signatureCount <= 0 || totalSignatures <= 0) + { + return UInt256.Zero; + } + + // Convert to UInt256 for precision + var signatures = (UInt256)signatureCount; + var total = (UInt256)totalSignatures; + + // Calculate: (signatures * totalReward) / total + // Order of operations matters to maintain precision + UInt256 numerator = signatures * totalReward; + UInt256 reward = numerator / total; + + return reward; + } + + private (BlockReward HolderReward, UInt256 FoundationWalletReward) DistributeRewards( + Address masternodeAddress, UInt256 reward, XdcBlockHeader header) + { + Address owner = masternodeVotingContract.GetCandidateOwner(header, masternodeAddress); + + // 90% of the reward goes to the masternode + UInt256 masterReward = reward * 90 / 100; + + // 10% of the reward goes to the foundation wallet + UInt256 foundationReward = reward / 10; + + return (new BlockReward(owner, masterReward), foundationReward); + } + } +} diff --git a/src/Nethermind/Nethermind.slnx b/src/Nethermind/Nethermind.slnx index e73865e7af3..c64f1a920b7 100644 --- a/src/Nethermind/Nethermind.slnx +++ b/src/Nethermind/Nethermind.slnx @@ -33,6 +33,7 @@ + diff --git a/tools/DocGen/ConfigGenerator.cs b/tools/DocGen/ConfigGenerator.cs index 8810540e3da..414b3d4c2ba 100644 --- a/tools/DocGen/ConfigGenerator.cs +++ b/tools/DocGen/ConfigGenerator.cs @@ -68,7 +68,6 @@ internal static void Generate(string path) writeStream.Close(); File.Move(tempFileName, fileName, true); - File.Delete(tempFileName); AnsiConsole.MarkupLine($"[green]Updated[/] {fileName}"); } @@ -97,12 +96,15 @@ private static void WriteMarkdown(StreamWriter file, Type configType) foreach (var prop in props) { - var itemAttr = prop.GetCustomAttribute(); + var configAttr = prop.GetCustomAttribute(); - if (itemAttr?.HiddenFromDocs ?? true) + if (configAttr?.HiddenFromDocs ?? true) continue; - var description = itemAttr.Description.Replace("\n", "\n ").TrimEnd(' '); + var description = configAttr.Description.Replace("\n", "\n ").TrimEnd(' '); + var cliAlias = string.IsNullOrWhiteSpace(configAttr.CliOptionAlias) + ? $"{moduleName}-{prop.Name}" + : configAttr.CliOptionAlias; (string value, string cliValue) = GetValue(prop); file.Write($$""" @@ -111,7 +113,7 @@ private static void WriteMarkdown(StreamWriter file, Type configType) ``` - --{{moduleName.ToLowerInvariant()}}-{{prop.Name.ToLowerInvariant()}} {{cliValue}} + --{{cliAlias.ToLowerInvariant()}} {{cliValue}} --{{moduleName}}.{{prop.Name}} {{cliValue}} ``` @@ -136,7 +138,7 @@ private static void WriteMarkdown(StreamWriter file, Type configType) var startsFromNewLine = WriteAllowedValues(file, prop.PropertyType) || description.EndsWith('\n'); - WriteDefaultValue(file, itemAttr, startsFromNewLine); + WriteDefaultValue(file, configAttr, startsFromNewLine); file.WriteLine(); file.WriteLine(); diff --git a/tools/DocGen/DBSizeGenerator.cs b/tools/DocGen/DBSizeGenerator.cs index db8c77ecc54..6987321701e 100644 --- a/tools/DocGen/DBSizeGenerator.cs +++ b/tools/DocGen/DBSizeGenerator.cs @@ -97,7 +97,6 @@ private static void GenerateFile(string docsPath, string dbSizeSourcePath, IList writeStream.Close(); File.Move(tempFileName, fileName, true); - File.Delete(tempFileName); AnsiConsole.MarkupLine($"[green]Updated[/] {fileName}"); } diff --git a/tools/DocGen/MetricsGenerator.cs b/tools/DocGen/MetricsGenerator.cs index 697b0381772..72b19253585 100644 --- a/tools/DocGen/MetricsGenerator.cs +++ b/tools/DocGen/MetricsGenerator.cs @@ -70,7 +70,6 @@ internal static void Generate(string path) writeStream.Close(); File.Move(tempFileName, fileName, true); - File.Delete(tempFileName); AnsiConsole.MarkupLine($"[green]Updated[/] {fileName}"); } diff --git a/tools/Evm/T8n/T8nBlockHashProvider.cs b/tools/Evm/T8n/T8nBlockHashProvider.cs index f8f2e80d7dc..41befa0d10b 100644 --- a/tools/Evm/T8n/T8nBlockHashProvider.cs +++ b/tools/Evm/T8n/T8nBlockHashProvider.cs @@ -10,15 +10,17 @@ namespace Evm.T8n; -public class T8nBlockHashProvider(Dictionary blockHashes) : IBlockhashProvider +public class T8nBlockHashProvider(Dictionary blockHashes) : IBlockhashProvider { public Hash256? GetBlockhash(BlockHeader currentBlock, long number, IReleaseSpec? spec) { long current = currentBlock.Number; - return number >= current || number < current - Math.Min(current, BlockhashProvider.MaxDepth) - ? null - : blockHashes.GetValueOrDefault(number, null) ?? - throw new T8nException($"BlockHash for block {number} not provided", + if (number >= current || number < current - Math.Min(current, BlockhashProvider.MaxDepth)) + return null; + + return blockHashes.TryGetValue(number, out Hash256? hash) + ? hash + : throw new T8nException($"BlockHash for block {number} not provided", T8nErrorCodes.ErrorMissingBlockhash); } diff --git a/tools/Evm/T8n/T8nExecutor.cs b/tools/Evm/T8n/T8nExecutor.cs index 140c962a31a..15081c34a07 100644 --- a/tools/Evm/T8n/T8nExecutor.cs +++ b/tools/Evm/T8n/T8nExecutor.cs @@ -39,11 +39,11 @@ public static T8nExecutionResult Execute(T8nCommandArguments arguments) EthereumCodeInfoRepository codeInfoRepository = new(stateProvider); IBlockhashProvider blockhashProvider = ConstructBlockHashProvider(test); - IVirtualMachine virtualMachine = new VirtualMachine( + IVirtualMachine virtualMachine = new EthereumVirtualMachine( blockhashProvider, test.SpecProvider, _logManager); - TransactionProcessor transactionProcessor = new( + EthereumTransactionProcessor transactionProcessor = new( BlobBaseFeeCalculator.Instance, test.SpecProvider, stateProvider, @@ -52,7 +52,7 @@ public static T8nExecutionResult Execute(T8nCommandArguments arguments) _logManager); stateProvider.CreateAccount(test.CurrentCoinbase, 0); - GeneralStateTestBase.InitializeTestState(test.Alloc, test.CurrentCoinbase, stateProvider, test.SpecProvider); + GeneralStateTestBase.InitializeTestState(test.Alloc, stateProvider, test.SpecProvider); Block block = test.ConstructBlock(); var withdrawalProcessor = new WithdrawalProcessor(stateProvider, _logManager); @@ -136,7 +136,7 @@ public static T8nExecutionResult Execute(T8nCommandArguments arguments) } private static IBlockhashProvider ConstructBlockHashProvider(T8nTest test) => - new T8nBlockHashProvider(test.BlockHashes.ToDictionary, long, Hash256?>(kvp => long.Parse(kvp.Key), kvp => kvp.Value)); + new T8nBlockHashProvider(test.BlockHashes.ToDictionary(kvp => long.Parse(kvp.Key), kvp => kvp.Value)); private static void ApplyRewards(Block block, IWorldState stateProvider, IReleaseSpec spec, ISpecProvider specProvider) { diff --git a/tools/Evm/T8n/T8nInputReader.cs b/tools/Evm/T8n/T8nInputReader.cs index bf89abf1963..33634083dd2 100644 --- a/tools/Evm/T8n/T8nInputReader.cs +++ b/tools/Evm/T8n/T8nInputReader.cs @@ -63,7 +63,7 @@ private static T LoadDataFromFile(string filePath, string description) } catch (FileNotFoundException e) { - throw new T8nException(e, "failed reading {filePath} file: {description}", T8nErrorCodes.ErrorIO); + throw new T8nException(e, $"failed reading {filePath} file: {description}", T8nErrorCodes.ErrorIO); } catch (JsonException e) { diff --git a/tools/Evm/T8n/T8nValidator.cs b/tools/Evm/T8n/T8nValidator.cs index 4f0b18f5016..b66d85fe9a2 100644 --- a/tools/Evm/T8n/T8nValidator.cs +++ b/tools/Evm/T8n/T8nValidator.cs @@ -68,7 +68,7 @@ private static void ApplyMergeChecks(EnvJson env, ISpecProvider specProvider) if (env.CurrentRandom is null) throw new T8nException("post-merge requires currentRandom to be defined in env", T8nErrorCodes.ErrorConfig); - if (env.CurrentDifficulty?.IsZero ?? false) + if (env.CurrentDifficulty is not null && !env.CurrentDifficulty.Value.IsZero) throw new T8nException("post-merge difficulty must be zero (or omitted) in env", T8nErrorCodes.ErrorConfig); return; diff --git a/tools/HiveConsensusWorkflowGenerator/Program.cs b/tools/HiveConsensusWorkflowGenerator/Program.cs index 1a13e2c1c26..5ed026fc728 100644 --- a/tools/HiveConsensusWorkflowGenerator/Program.cs +++ b/tools/HiveConsensusWorkflowGenerator/Program.cs @@ -27,11 +27,12 @@ static void Main(string[] args) if (groupedTestNames.Count == MaxJobsCount) { - testsList = new List(groupedTestNames.First().Value); - size = groupedTestNames.First().Key; + var smallestGroup = groupedTestNames.First(); + testsList = new List(smallestGroup.Value); + size = smallestGroup.Key; testsList.Add(test.Key); size += test.Value; - groupedTestNames.Remove(groupedTestNames.First().Key); + groupedTestNames.Remove(smallestGroup.Key); } else { diff --git a/tools/Kute/Nethermind.Tools.Kute/Config.cs b/tools/Kute/Nethermind.Tools.Kute/Config.cs index 2fc32cdc038..7298564cd93 100644 --- a/tools/Kute/Nethermind.Tools.Kute/Config.cs +++ b/tools/Kute/Nethermind.Tools.Kute/Config.cs @@ -89,7 +89,7 @@ public static class Config public static Option UnwrapBatch { get; } = new("--unwrapBatch", "-u") { - Description = "Batch requests will be unwraped to single requests", + Description = "Batch requests will be unwrapped to single requests", }; public static Option> Labels { get; } = new("--labels", "-l") diff --git a/tools/Kute/Nethermind.Tools.Kute/Metrics/PrometheusPushGatewayMetricsReporter.cs b/tools/Kute/Nethermind.Tools.Kute/Metrics/PrometheusPushGatewayMetricsReporter.cs index fedfcc96ea3..d5c9985bddd 100644 --- a/tools/Kute/Nethermind.Tools.Kute/Metrics/PrometheusPushGatewayMetricsReporter.cs +++ b/tools/Kute/Nethermind.Tools.Kute/Metrics/PrometheusPushGatewayMetricsReporter.cs @@ -97,17 +97,23 @@ public Task Ignored(CancellationToken token = default) public Task Batch(JsonRpc.Request.Batch batch, TimeSpan elapsed, CancellationToken token = default) { - _batchDuration - .WithLabels(batch.Id) - .Observe(elapsed.TotalSeconds); + if (batch.Id is not null) + { + _batchDuration + .WithLabels(batch.Id) + .Observe(elapsed.TotalSeconds); + } return Task.CompletedTask; } public Task Single(JsonRpc.Request.Single single, TimeSpan elapsed, CancellationToken token = default) { - _singleDuration - .WithLabels(single.Id, single.MethodName) - .Observe(elapsed.TotalSeconds); + if (single.Id is not null && single.MethodName is not null) + { + _singleDuration + .WithLabels(single.Id, single.MethodName) + .Observe(elapsed.TotalSeconds); + } return Task.CompletedTask; } diff --git a/tools/Kute/README.md b/tools/Kute/README.md index 87697ed9a27..19c97151c98 100644 --- a/tools/Kute/README.md +++ b/tools/Kute/README.md @@ -1,6 +1,6 @@ # Kute -Kute - /kjuːt/ - is a benchmarking tool developed at Nethermind to simulate an Ethereum Consensus Layer, expected to be used together with the Nethermind Client. The tool sends JSON-RPC messages to the Client and measures its performance. +Kute - is a benchmarking tool developed at Nethermind to simulate an Ethereum Consensus Layer, expected to be used together with the Nethermind Client. The tool sends JSON-RPC messages to the Client and measures its performance. ## Prerequisites diff --git a/tools/SchemaGenerator/SchemaGenerator.csproj b/tools/SchemaGenerator/SchemaGenerator.csproj index d86b000abd9..5ba04fe94ec 100644 --- a/tools/SchemaGenerator/SchemaGenerator.csproj +++ b/tools/SchemaGenerator/SchemaGenerator.csproj @@ -13,7 +13,6 @@ - diff --git a/tools/SchemaGenerator/SchemaGenerator.slnx b/tools/SchemaGenerator/SchemaGenerator.slnx index f8247a54fa9..db27dc75b1a 100644 --- a/tools/SchemaGenerator/SchemaGenerator.slnx +++ b/tools/SchemaGenerator/SchemaGenerator.slnx @@ -2,7 +2,6 @@ - diff --git a/tools/SendBlobs/BlobSender.cs b/tools/SendBlobs/BlobSender.cs index aaad1d6ab34..71ec7dad2de 100644 --- a/tools/SendBlobs/BlobSender.cs +++ b/tools/SendBlobs/BlobSender.cs @@ -51,7 +51,7 @@ public BlobSender(string rpcUrl, ILogManager logManager) // 7 = max fee per blob gas = max value // 9 = 1st proof removed // 10 = 1st commitment removed - // 11 = max fee per blob gas = max value / blobgasperblob + 1 + // 11 = max fee per blob gas = max value / blobGasPerBlob + 1 // 14 = 100 blobs // 15 = 1000 blobs public async Task SendRandomBlobs( @@ -96,7 +96,7 @@ public async Task SendRandomBlobs( int signerIndex = -1; - ulong excessBlobs = (ulong)blobTxCounts.Sum(btxc => btxc.blobCount) / 2; + ulong excessBlobs = (ulong)blobTxCounts.Sum(bc => bc.blobCount) / 2; foreach ((int txCount, int blobCount, string @break) txs in blobTxCounts) { @@ -204,9 +204,18 @@ public async Task SendData( bool waitForInclusion, IReleaseSpec spec) { - data = data - .Select((s, i) => i % 32 != 0 ? [s] : (s < 0x73 ? new byte[] { s } : [(byte)(32), s])) - .SelectMany(b => b).ToArray(); + int capacity = data.Length + data.Length / 32; // at most one extra byte per 32-byte chunk + List normalized = new(capacity); + for (int i = 0; i < data.Length; i++) + { + byte value = data[i]; + if (i % 32 == 0 && value >= 0x73) + { + normalized.Add(32); + } + normalized.Add(value); + } + data = normalized.ToArray(); if (waitForInclusion) { @@ -277,8 +286,8 @@ public async Task SendData( if (defaultMaxPriorityFeePerGas is null) { - string? maxPriorityFeePerGasRes = await _rpcClient.Post("eth_maxPriorityFeePerGas") ?? "1"; - result.maxPriorityFeePerGas = HexConvert.ToUInt256(maxPriorityFeePerGasRes); + string? maxPriorityFeePerGasRes = await _rpcClient.Post("eth_maxPriorityFeePerGas") ?? "0x1"; + result.maxPriorityFeePerGas = UInt256.Parse(maxPriorityFeePerGasRes); } else { @@ -362,8 +371,6 @@ private async static Task WaitForBlobInclusion(IJsonRpcClient rpcClient, Hash256 if (txHash is not null && blockResult.Transactions.Contains(txHash)) { - string? receipt = await rpcClient.Post("eth_getTransactionByHash", txHash.ToString(), true); - Console.WriteLine($"Found blob transaction in block {blockResult.Number}"); return; } diff --git a/tools/SendBlobs/FundsDistributor.cs b/tools/SendBlobs/FundsDistributor.cs index fe87fd76b3b..d813a5e44f7 100644 --- a/tools/SendBlobs/FundsDistributor.cs +++ b/tools/SendBlobs/FundsDistributor.cs @@ -36,7 +36,7 @@ public FundsDistributor(IJsonRpcClient rpcClient, ulong chainId, string? keyFile /// /// containing all the executed tx hashes. /// - public async Task> DitributeFunds(Signer distributeFrom, uint keysToMake, UInt256 maxFee, UInt256 maxPriorityFee) + public async Task> DistributeFunds(Signer distributeFrom, uint keysToMake, UInt256 maxFee, UInt256 maxPriorityFee) { if (keysToMake == 0) throw new ArgumentException("keysToMake must be greater than zero.", nameof(keysToMake)); @@ -48,15 +48,15 @@ public async Task> DitributeFunds(Signer distributeFrom, uin if (nonceString is null) throw new AccountException($"Unable to get nonce for {distributeFrom.Address}"); - string? gasPriceRes = await _rpcClient.Post("eth_gasPrice") ?? "1"; - UInt256 gasPrice = HexConvert.ToUInt256(gasPriceRes); + string? gasPriceRes = await _rpcClient.Post("eth_gasPrice") ?? "0x1"; + UInt256 gasPrice = UInt256.Parse(gasPriceRes); string? maxPriorityFeePerGasRes; UInt256 maxPriorityFeePerGas = maxPriorityFee; if (maxPriorityFee == 0) { - maxPriorityFeePerGasRes = await _rpcClient.Post("eth_maxPriorityFeePerGas") ?? "1"; - maxPriorityFeePerGas = HexConvert.ToUInt256(maxPriorityFeePerGasRes); + maxPriorityFeePerGasRes = await _rpcClient.Post("eth_maxPriorityFeePerGas") ?? "0x1"; + maxPriorityFeePerGas = UInt256.Parse(maxPriorityFeePerGasRes); } UInt256 balance = new UInt256(Bytes.FromHexString(balanceString)); @@ -100,11 +100,11 @@ public async Task> DitributeFunds(Signer distributeFrom, uin { if (maxFee == 0) { - gasPriceRes = await _rpcClient.Post("eth_gasPrice") ?? "1"; - gasPrice = HexConvert.ToUInt256(gasPriceRes); + gasPriceRes = await _rpcClient.Post("eth_gasPrice") ?? "0x1"; + gasPrice = UInt256.Parse(gasPriceRes); - maxPriorityFeePerGasRes = await _rpcClient.Post("eth_maxPriorityFeePerGas") ?? "1"; - maxPriorityFeePerGas = HexConvert.ToUInt256(maxPriorityFeePerGasRes); + maxPriorityFeePerGasRes = await _rpcClient.Post("eth_maxPriorityFeePerGas") ?? "0x1"; + maxPriorityFeePerGas = UInt256.Parse(maxPriorityFeePerGasRes); } Transaction tx = CreateTx(_chainId, @@ -163,14 +163,14 @@ public async Task> ReclaimFunds(Address beneficiary, UInt256 ulong nonce = HexConvert.ToUInt64(nonceString); - string? gasPriceRes = await _rpcClient.Post("eth_gasPrice") ?? "1"; - UInt256 gasPrice = HexConvert.ToUInt256(gasPriceRes); + string? gasPriceRes = await _rpcClient.Post("eth_gasPrice") ?? "0x1"; + UInt256 gasPrice = UInt256.Parse(gasPriceRes); UInt256 maxPriorityFeePerGas = maxPriorityFee; if (maxPriorityFee == 0) { - string? maxPriorityFeePerGasRes = await _rpcClient.Post("eth_maxPriorityFeePerGas") ?? "1"; - maxPriorityFeePerGas = HexConvert.ToUInt256(maxPriorityFeePerGasRes); + string? maxPriorityFeePerGasRes = await _rpcClient.Post("eth_maxPriorityFeePerGas") ?? "0x1"; + maxPriorityFeePerGas = UInt256.Parse(maxPriorityFeePerGasRes); } UInt256 approxGasFee = (gasPrice + maxPriorityFeePerGas) * GasCostOf.Transaction; diff --git a/tools/SendBlobs/HexConvert.cs b/tools/SendBlobs/HexConvert.cs index d68014b229c..d125a6153f9 100644 --- a/tools/SendBlobs/HexConvert.cs +++ b/tools/SendBlobs/HexConvert.cs @@ -1,16 +1,9 @@ // SPDX-FileCopyrightText: 2023 Demerzel Solutions Limited // SPDX-License-Identifier: LGPL-3.0-only -using Nethermind.Int256; - namespace SendBlobs; internal static class HexConvert { - public static UInt256 ToUInt256(string s) - { - return (UInt256)ToUInt64(s); - } - public static ulong ToUInt64(string s) { return Convert.ToUInt64(s, s.StartsWith("0x") ? 16 : 10); diff --git a/tools/SendBlobs/SetupCli.cs b/tools/SendBlobs/SetupCli.cs index 4b68a8557ec..99b00d5e260 100644 --- a/tools/SendBlobs/SetupCli.cs +++ b/tools/SendBlobs/SetupCli.cs @@ -201,15 +201,14 @@ public static void SetupDistributeCommand(Command root) parseResult.GetValue(rpcUrlOption)!, SimpleConsoleLogManager.Instance.GetClassLogger()); - string? chainIdString = await rpcClient.Post("eth_chainId") ?? "1"; - ulong chainId = HexConvert.ToUInt64(chainIdString); + ulong chainId = await GetChainIdAsync(rpcClient); Signer signer = new(chainId, new PrivateKey(parseResult.GetValue(privateKeyOption)!), SimpleConsoleLogManager.Instance); FundsDistributor distributor = new( rpcClient, chainId, parseResult.GetValue(keyFileOption), SimpleConsoleLogManager.Instance); - IEnumerable hashes = await distributor.DitributeFunds( + await distributor.DistributeFunds( signer, parseResult.GetValue(keyNumberOption), parseResult.GetValue(maxFeeOption), @@ -254,6 +253,7 @@ public static void SetupReclaimCommand(Command root) }; command.Add(rpcUrlOption); + command.Add(receiverOption); command.Add(keyFileOption); command.Add(maxPriorityFeeGasOption); command.Add(maxFeeOption); @@ -263,11 +263,10 @@ public static void SetupReclaimCommand(Command root) parseResult.GetValue(rpcUrlOption)!, SimpleConsoleLogManager.Instance.GetClassLogger()); - string? chainIdString = await rpcClient.Post("eth_chainId") ?? "1"; - ulong chainId = HexConvert.ToUInt64(chainIdString); + ulong chainId = await GetChainIdAsync(rpcClient); FundsDistributor distributor = new(rpcClient, chainId, parseResult.GetValue(keyFileOption), SimpleConsoleLogManager.Instance); - IEnumerable hashes = await distributor.ReclaimFunds( + await distributor.ReclaimFunds( new(parseResult.GetValue(receiverOption)!), parseResult.GetValue(maxFeeOption), parseResult.GetValue(maxPriorityFeeGasOption)); @@ -275,6 +274,12 @@ public static void SetupReclaimCommand(Command root) root.Add(command); } + private static async Task GetChainIdAsync(IJsonRpcClient rpcClient) + { + string? chainIdString = await rpcClient.Post("eth_chainId") ?? "1"; + return HexConvert.ToUInt64(chainIdString); + } + public static IJsonRpcClient InitRpcClient(string rpcUrl, ILogger logger) => new BasicJsonRpcClient( new Uri(rpcUrl), diff --git a/tools/StatelessExecution/SetupCli.cs b/tools/StatelessExecution/SetupCli.cs index 631fd0f893f..0ac89458954 100644 --- a/tools/StatelessExecution/SetupCli.cs +++ b/tools/StatelessExecution/SetupCli.cs @@ -110,10 +110,6 @@ private static (Witness witness, Block block, BlockHeader parent) ReadData(strin MixHash = suggestedBlockForRpc.MixHash, BaseFeePerGas = suggestedBlockForRpc.BaseFeePerGas!.Value, WithdrawalsRoot = suggestedBlockForRpc.WithdrawalsRoot, - ParentBeaconBlockRoot = suggestedBlockForRpc.ParentBeaconBlockRoot, - RequestsHash = suggestedBlockForRpc.RequestsHash, - BlobGasUsed = suggestedBlockForRpc.BlobGasUsed, - ExcessBlobGas = suggestedBlockForRpc.ExcessBlobGas, Hash = suggestedBlockForRpc.Hash, };